Merge "MagnificationProcessor uses MagnificationConfig to control the specified magnifier"
diff --git a/GAME_MANAGER_OWNERS b/GAME_MANAGER_OWNERS
new file mode 100644
index 0000000..502a9e36
--- /dev/null
+++ b/GAME_MANAGER_OWNERS
@@ -0,0 +1,2 @@
+lpy@google.com
+timvp@google.com
diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
index 8a6c60f..1cd5d96 100644
--- a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
@@ -138,6 +138,21 @@
     }
 
     @Test
+    public void testCreate_RandomText_NoStyled_Balanced_Hyphenation_Fast() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT);
+            state.resumeTiming();
+
+            StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH)
+                    .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL_FAST)
+                    .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED)
+                    .build();
+        }
+    }
+
+    @Test
     public void testCreate_RandomText_Styled_Greedy_NoHyphenation() {
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 59f602c..af4053f 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -1576,7 +1576,7 @@
                     int err;
                     do {
                         err = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &spec, nullptr);
-                    } while (err<0 && errno == EINTR);
+                    } while (err == EINTR);
                 }
 
                 checkExit();
diff --git a/core/api/current.txt b/core/api/current.txt
index 676c4b4..fdbc48a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1204,6 +1204,7 @@
     field public static final int requiredSplitTypes;
     field public static final int requiresFadingEdge = 16843685; // 0x10103a5
     field public static final int requiresSmallestWidthDp = 16843620; // 0x1010364
+    field public static final int resetEnabledSettingsOnAppDataCleared;
     field public static final int resizeClip = 16843983; // 0x10104cf
     field public static final int resizeMode = 16843619; // 0x1010363
     field public static final int resizeable = 16843405; // 0x101028d
@@ -9647,6 +9648,7 @@
     method public java.util.Map<android.os.ParcelUuid,byte[]> getServiceData();
     method @NonNull public java.util.List<android.os.ParcelUuid> getServiceSolicitationUuids();
     method public java.util.List<android.os.ParcelUuid> getServiceUuids();
+    method @NonNull public java.util.List<android.bluetooth.le.TransportDiscoveryData> getTransportDiscoveryData();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.le.AdvertiseData> CREATOR;
   }
@@ -9657,6 +9659,7 @@
     method public android.bluetooth.le.AdvertiseData.Builder addServiceData(android.os.ParcelUuid, byte[]);
     method @NonNull public android.bluetooth.le.AdvertiseData.Builder addServiceSolicitationUuid(@NonNull android.os.ParcelUuid);
     method public android.bluetooth.le.AdvertiseData.Builder addServiceUuid(android.os.ParcelUuid);
+    method @NonNull public android.bluetooth.le.AdvertiseData.Builder addTransportDiscoveryData(@NonNull android.bluetooth.le.TransportDiscoveryData);
     method public android.bluetooth.le.AdvertiseData build();
     method public android.bluetooth.le.AdvertiseData.Builder setIncludeDeviceName(boolean);
     method public android.bluetooth.le.AdvertiseData.Builder setIncludeTxPowerLevel(boolean);
@@ -9916,6 +9919,31 @@
     method public android.bluetooth.le.ScanSettings.Builder setScanMode(int);
   }
 
+  public final class TransportBlock implements android.os.Parcelable {
+    ctor public TransportBlock(int, int, int, @Nullable byte[]);
+    method public int describeContents();
+    method public int getOrgId();
+    method public int getTdsFlags();
+    method @Nullable public byte[] getTransportData();
+    method public int getTransportDataLength();
+    method @Nullable public byte[] toByteArray();
+    method public int totalBytes();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.le.TransportBlock> CREATOR;
+  }
+
+  public final class TransportDiscoveryData implements android.os.Parcelable {
+    ctor public TransportDiscoveryData(int, @NonNull java.util.List<android.bluetooth.le.TransportBlock>);
+    ctor public TransportDiscoveryData(@NonNull byte[]);
+    method public int describeContents();
+    method @NonNull public java.util.List<android.bluetooth.le.TransportBlock> getTransportBlocks();
+    method public int getTransportDataType();
+    method @Nullable public byte[] toByteArray();
+    method public int totalBytes();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.le.TransportDiscoveryData> CREATOR;
+  }
+
 }
 
 package android.companion {
@@ -17295,8 +17323,12 @@
     method @NonNull public android.graphics.text.MeasuredText.Builder appendReplacementRun(@NonNull android.graphics.Paint, @IntRange(from=0) int, @FloatRange(from=0) @Px float);
     method @NonNull public android.graphics.text.MeasuredText.Builder appendStyleRun(@NonNull android.graphics.Paint, @IntRange(from=0) int, boolean);
     method @NonNull public android.graphics.text.MeasuredText build();
-    method @NonNull public android.graphics.text.MeasuredText.Builder setComputeHyphenation(boolean);
+    method @Deprecated @NonNull public android.graphics.text.MeasuredText.Builder setComputeHyphenation(boolean);
+    method @NonNull public android.graphics.text.MeasuredText.Builder setComputeHyphenation(int);
     method @NonNull public android.graphics.text.MeasuredText.Builder setComputeLayout(boolean);
+    field public static final int HYPHENATION_MODE_FAST = 2; // 0x2
+    field public static final int HYPHENATION_MODE_NONE = 0; // 0x0
+    field public static final int HYPHENATION_MODE_NORMAL = 1; // 0x1
   }
 
   public final class PositionedGlyphs {
@@ -27628,6 +27660,7 @@
     field public static final String CATEGORY_PAYMENT = "payment";
     field public static final String EXTRA_CATEGORY = "category";
     field public static final String EXTRA_SERVICE_COMPONENT = "component";
+    field public static final String EXTRA_USERID = "android.nfc.cardemulation.extra.USERID";
     field public static final int SELECTION_MODE_ALWAYS_ASK = 1; // 0x1
     field public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2; // 0x2
     field public static final int SELECTION_MODE_PREFER_DEFAULT = 0; // 0x0
@@ -40667,7 +40700,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telecom.PhoneAccountHandle> getCallCapablePhoneAccounts();
     method public String getDefaultDialerPackage();
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telecom.PhoneAccountHandle getDefaultOutgoingPhoneAccount(String);
-    method @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_SMS, android.Manifest.permission.READ_PHONE_NUMBERS}, conditional=true) public String getLine1Number(android.telecom.PhoneAccountHandle);
+    method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_SMS, android.Manifest.permission.READ_PHONE_NUMBERS}, conditional=true) public String getLine1Number(android.telecom.PhoneAccountHandle);
     method public android.telecom.PhoneAccount getPhoneAccount(android.telecom.PhoneAccountHandle);
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telecom.PhoneAccountHandle> getSelfManagedPhoneAccounts();
     method public android.telecom.PhoneAccountHandle getSimCallManager();
@@ -42735,7 +42768,7 @@
     method @Nullable public String getMccString();
     method @Deprecated public int getMnc();
     method @Nullable public String getMncString();
-    method public String getNumber();
+    method @Deprecated public String getNumber();
     method public int getPortIndex();
     method public int getSimSlotIndex();
     method public int getSubscriptionId();
@@ -42990,7 +43023,7 @@
     method public String getIccAuthentication(int, int, String);
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getImei();
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getImei(int);
-    method @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_SMS, android.Manifest.permission.READ_PHONE_NUMBERS}) public String getLine1Number();
+    method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_SMS, android.Manifest.permission.READ_PHONE_NUMBERS}) public String getLine1Number();
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public String getManualNetworkSelectionPlmn();
     method @Nullable public String getManufacturerCode();
     method @Nullable public String getManufacturerCode(int);
@@ -43075,7 +43108,7 @@
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabledForReason(int, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setForbiddenPlmns(@NonNull java.util.List<java.lang.String>);
-    method public boolean setLine1NumberForDisplay(String, String);
+    method @Deprecated public boolean setLine1NumberForDisplay(String, String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setNetworkSelectionModeAutomatic();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setNetworkSelectionModeManual(String, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setNetworkSelectionModeManual(@NonNull String, boolean, int);
@@ -44545,8 +44578,10 @@
     field public static final int DIR_LEFT_TO_RIGHT = 1; // 0x1
     field public static final int DIR_RIGHT_TO_LEFT = -1; // 0xffffffff
     field public static final int HYPHENATION_FREQUENCY_FULL = 2; // 0x2
+    field public static final int HYPHENATION_FREQUENCY_FULL_FAST = 4; // 0x4
     field public static final int HYPHENATION_FREQUENCY_NONE = 0; // 0x0
     field public static final int HYPHENATION_FREQUENCY_NORMAL = 1; // 0x1
+    field public static final int HYPHENATION_FREQUENCY_NORMAL_FAST = 3; // 0x3
     field public static final int JUSTIFICATION_MODE_INTER_WORD = 1; // 0x1
     field public static final int JUSTIFICATION_MODE_NONE = 0; // 0x0
   }
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 29a4453..6d342db 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -961,20 +961,20 @@
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean getBluetoothContactSharingDisabled(@NonNull android.os.UserHandle);
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwner();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public android.content.ComponentName getDeviceOwnerComponentOnAnyUser();
-    method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwnerNameOnAnyUser();
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public String getDeviceOwnerNameOnAnyUser();
     method @Nullable public CharSequence getDeviceOwnerOrganizationName();
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.os.UserHandle getDeviceOwnerUser();
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public java.util.List<java.lang.String> getPermittedAccessibilityServices(int);
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public java.util.List<java.lang.String> getPermittedInputMethodsForCurrentUser();
     method @Nullable public android.content.ComponentName getProfileOwner() throws java.lang.IllegalArgumentException;
-    method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getProfileOwnerNameAsUser(int) throws java.lang.IllegalArgumentException;
-    method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public int getUserProvisioningState();
+    method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public String getProfileOwnerNameAsUser(int) throws java.lang.IllegalArgumentException;
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public int getUserProvisioningState();
     method public boolean isDeviceManaged();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioned();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioningConfigApplied();
-    method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isManagedKiosk();
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public boolean isManagedKiosk();
     method public boolean isSecondaryLockscreenEnabled(@NonNull android.os.UserHandle);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isUnattendedManagedKiosk();
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public boolean isUnattendedManagedKiosk();
     method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long);
     method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long, boolean);
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean packageHasActiveAdmins(String);
@@ -8681,7 +8681,11 @@
   }
 
   public final class NewUserRequest {
+    method @Nullable public String getAccountName();
+    method @Nullable public android.os.PersistableBundle getAccountOptions();
+    method @Nullable public String getAccountType();
     method @Nullable public String getName();
+    method @Nullable public android.graphics.Bitmap getUserIcon();
     method @NonNull public String getUserType();
     method public boolean isAdmin();
     method public boolean isEphemeral();
@@ -8690,9 +8694,13 @@
   public static final class NewUserRequest.Builder {
     ctor public NewUserRequest.Builder();
     method @NonNull public android.os.NewUserRequest build();
+    method @NonNull public android.os.NewUserRequest.Builder setAccountName(@Nullable String);
+    method @NonNull public android.os.NewUserRequest.Builder setAccountOptions(@Nullable android.os.PersistableBundle);
+    method @NonNull public android.os.NewUserRequest.Builder setAccountType(@Nullable String);
     method @NonNull public android.os.NewUserRequest.Builder setAdmin();
     method @NonNull public android.os.NewUserRequest.Builder setEphemeral();
     method @NonNull public android.os.NewUserRequest.Builder setName(@Nullable String);
+    method @NonNull public android.os.NewUserRequest.Builder setUserIcon(@Nullable android.graphics.Bitmap);
     method @NonNull public android.os.NewUserRequest.Builder setUserType(@NonNull String);
   }
 
@@ -8970,6 +8978,7 @@
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean removeUser(@NonNull android.os.UserHandle);
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserIcon(@NonNull android.graphics.Bitmap) throws android.os.UserManager.UserOperationException;
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserName(@Nullable String);
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean someUserHasAccount(@NonNull String, @NonNull String);
     field public static final String ACTION_USER_RESTRICTIONS_CHANGED = "android.os.action.USER_RESTRICTIONS_CHANGED";
     field @Deprecated public static final String DISALLOW_OEM_UNLOCK = "no_oem_unlock";
     field public static final String DISALLOW_RUN_IN_BACKGROUND = "no_run_in_background";
@@ -8981,6 +8990,7 @@
     field public static final int SWITCHABILITY_STATUS_SYSTEM_USER_LOCKED = 4; // 0x4
     field public static final int SWITCHABILITY_STATUS_USER_IN_CALL = 1; // 0x1
     field public static final int SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED = 2; // 0x2
+    field public static final int USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS = 7; // 0x7
     field public static final String USER_TYPE_FULL_GUEST = "android.os.usertype.full.GUEST";
     field public static final String USER_TYPE_FULL_SECONDARY = "android.os.usertype.full.SECONDARY";
     field public static final String USER_TYPE_FULL_SYSTEM = "android.os.usertype.full.SYSTEM";
@@ -13164,6 +13174,7 @@
     method @NonNull public java.util.Set<android.telephony.ims.FeatureTagState> getDeregisteredFeatureTags();
     method @NonNull public java.util.Set<android.telephony.ims.FeatureTagState> getDeregisteringFeatureTags();
     method @NonNull public java.util.Set<java.lang.String> getRegisteredFeatureTags();
+    method @NonNull public java.util.Set<java.lang.String> getRegisteringFeatureTags();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.DelegateRegistrationState> CREATOR;
     field public static final int DEREGISTERED_REASON_NOT_PROVISIONED = 1; // 0x1
@@ -13181,6 +13192,7 @@
     method @NonNull public android.telephony.ims.DelegateRegistrationState.Builder addDeregisteringFeatureTag(@NonNull String, int);
     method @NonNull public android.telephony.ims.DelegateRegistrationState.Builder addRegisteredFeatureTag(@NonNull String);
     method @NonNull public android.telephony.ims.DelegateRegistrationState.Builder addRegisteredFeatureTags(@NonNull java.util.Set<java.lang.String>);
+    method @NonNull public android.telephony.ims.DelegateRegistrationState.Builder addRegisteringFeatureTags(@NonNull java.util.Set<java.lang.String>);
     method @NonNull public android.telephony.ims.DelegateRegistrationState build();
   }
 
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 6bfde9a..af907af 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -6791,7 +6791,7 @@
 
         // We show these sorts of notifications immediately in the absence of
         // any explicit app declaration
-        if (isMediaNotification() || hasMediaSession()
+        if (isMediaNotification()
                     || CATEGORY_CALL.equals(category)
                     || CATEGORY_NAVIGATION.equals(category)
                     || (actions != null && actions.length > 0)) {
@@ -6811,14 +6811,6 @@
     }
 
     /**
-     * @return whether this notification has a media session attached
-     * @hide
-     */
-    public boolean hasMediaSession() {
-        return extras.getParcelable(Notification.EXTRA_MEDIA_SESSION) != null;
-    }
-
-    /**
      * @return the style class of this notification
      * @hide
      */
@@ -6861,18 +6853,20 @@
     }
 
     /**
-     * @return true if this is a media notification
+     * @return true if this is a media style notification with a media session
      *
      * @hide
      */
     public boolean isMediaNotification() {
         Class<? extends Style> style = getNotificationStyle();
-        if (MediaStyle.class.equals(style)) {
-            return true;
-        } else if (DecoratedMediaCustomViewStyle.class.equals(style)) {
-            return true;
-        }
-        return false;
+        boolean isMediaStyle = (MediaStyle.class.equals(style)
+                || DecoratedMediaCustomViewStyle.class.equals(style));
+
+        boolean hasMediaSession = (extras.getParcelable(Notification.EXTRA_MEDIA_SESSION) != null
+                && extras.getParcelable(Notification.EXTRA_MEDIA_SESSION)
+                instanceof MediaSession.Token);
+
+        return isMediaStyle && hasMediaSession;
     }
 
     /**
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index 1da0a74..e099716 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -30,6 +30,8 @@
 per-file SystemServiceRegistry.java = file:/services/core/java/com/android/server/am/OWNERS
 per-file *UserSwitchObserver* = file:/services/core/java/com/android/server/am/OWNERS
 per-file UiAutomation.java = file:/services/accessibility/OWNERS
+per-file GameManager* = file:/GAME_MANAGER_OWNERS
+per-file IGameManager* = file:/GAME_MANAGER_OWNERS
 
 # ActivityThread
 per-file ActivityThread.java = file:/services/core/java/com/android/server/am/OWNERS
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index d2963fb..57b3196 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -684,8 +684,9 @@
      * A String extra holding the time zone {@link android.app.AlarmManager} that the device
      * will be set to.
      *
-     * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
-     * provisioning via an NFC bump.
+     * <p>Use only for device owner provisioning. This extra can be returned by the admin app when
+     * performing the admin-integrated provisioning flow as a result of the {@link
+     * #ACTION_GET_PROVISIONING_MODE} activity.
      */
     public static final String EXTRA_PROVISIONING_TIME_ZONE
         = "android.app.extra.PROVISIONING_TIME_ZONE";
@@ -694,8 +695,9 @@
      * A Long extra holding the wall clock time (in milliseconds) to be set on the device's
      * {@link android.app.AlarmManager}.
      *
-     * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
-     * provisioning via an NFC bump.
+     * <p>Use only for device owner provisioning. This extra can be returned by the admin app when
+     * performing the admin-integrated provisioning flow as a result of the {@link
+     * #ACTION_GET_PROVISIONING_MODE} activity.
      */
     public static final String EXTRA_PROVISIONING_LOCAL_TIME
         = "android.app.extra.PROVISIONING_LOCAL_TIME";
@@ -704,8 +706,9 @@
      * A String extra holding the {@link java.util.Locale} that the device will be set to.
      * Format: xx_yy, where xx is the language code, and yy the country code.
      *
-     * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
-     * provisioning via an NFC bump.
+     * <p>Use only for device owner provisioning. This extra can be returned by the admin app when
+     * performing the admin-integrated provisioning flow as a result of the {@link
+     * #ACTION_GET_PROVISIONING_MODE} activity.
      */
     public static final String EXTRA_PROVISIONING_LOCALE
         = "android.app.extra.PROVISIONING_LOCALE";
@@ -1001,7 +1004,10 @@
      * The default for this extra is {@code false} - by default, the admin of a fully-managed
      * device has the ability to grant sensors-related permissions.
      *
-     * <p>Use only for device owner provisioning.
+     * <p>Use only for device owner provisioning. This extra can be returned by the
+     * admin app when performing the admin-integrated provisioning flow as a result of the
+     * {@link #ACTION_GET_PROVISIONING_MODE} activity.
+     *
      * @see #ACTION_GET_PROVISIONING_MODE
      */
     public static final String EXTRA_PROVISIONING_SENSORS_PERMISSION_GRANT_OPT_OUT =
@@ -1070,6 +1076,9 @@
      *
      * <p>From {@link android.os.Build.VERSION_CODES#N} onwards, this is also supported for an
      * intent with action {@link #ACTION_PROVISION_MANAGED_PROFILE}.
+     *
+     * <p>This extra can also be returned by the admin app when performing the admin-integrated
+     * provisioning flow as a result of the {@link #ACTION_GET_PROVISIONING_MODE} activity.
      */
     public static final String EXTRA_PROVISIONING_SKIP_ENCRYPTION =
              "android.app.extra.PROVISIONING_SKIP_ENCRYPTION";
@@ -1109,8 +1118,9 @@
      *
      * <p> Maximum 3 key-value pairs can be specified. The rest will be ignored.
      *
-     * <p> Use in an intent with action {@link #ACTION_PROVISION_MANAGED_PROFILE} or
-     * {@link #ACTION_PROVISION_MANAGED_DEVICE}
+     * <p> Can be used in an intent with action {@link #ACTION_PROVISION_MANAGED_PROFILE}. This
+     * extra can also be returned by the admin app when performing the admin-integrated
+     * provisioning flow as a result of the {@link #ACTION_GET_PROVISIONING_MODE} activity.
      */
     public static final String EXTRA_PROVISIONING_DISCLAIMERS =
             "android.app.extra.PROVISIONING_DISCLAIMERS";
@@ -7857,7 +7867,7 @@
     @SystemApi
     @RequiresPermission(anyOf = {
             android.Manifest.permission.MANAGE_USERS,
-            android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS,
+            android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS
     })
     public ComponentName getDeviceOwnerComponentOnAnyUser() {
         return getDeviceOwnerComponentInner(/* callingUserOnly =*/ false);
@@ -7970,8 +7980,8 @@
      * Called by the system to find out whether the device is managed by a Device Owner.
      *
      * @return whether the device is managed by a Device Owner.
-     * @throws SecurityException if the caller is not the device owner, does not hold the
-     *         MANAGE_USERS permission and is not the system.
+     * @throws SecurityException if the caller is not the device owner, does not hold
+     *         MANAGE_USERS or MANAGE_PROFILE_AND_DEVICE_OWNERS permissions and is not the system.
      *
      * @hide
      */
@@ -7992,7 +8002,10 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS
+    })
     public String getDeviceOwnerNameOnAnyUser() {
         throwIfParentInstance("getDeviceOwnerNameOnAnyUser");
         if (mService != null) {
@@ -8382,7 +8395,10 @@
      * @throws IllegalArgumentException if the userId is invalid.
      */
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS
+    })
     public @Nullable String getProfileOwnerNameAsUser(int userId) throws IllegalArgumentException {
         throwIfParentInstance("getProfileOwnerNameAsUser");
         if (mService != null) {
@@ -11920,7 +11936,10 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS
+    })
     @UserProvisioningState
     public int getUserProvisioningState() {
         throwIfParentInstance("getUserProvisioningState");
@@ -13429,7 +13448,10 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS
+    })
     public boolean isManagedKiosk() {
         throwIfParentInstance("isManagedKiosk");
         if (mService != null) {
@@ -13458,7 +13480,10 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS
+    })
     public boolean isUnattendedManagedKiosk() {
         throwIfParentInstance("isUnattendedManagedKiosk");
         if (mService != null) {
diff --git a/core/java/android/bluetooth/le/AdvertiseData.java b/core/java/android/bluetooth/le/AdvertiseData.java
index cec6580..fdf62ec 100644
--- a/core/java/android/bluetooth/le/AdvertiseData.java
+++ b/core/java/android/bluetooth/le/AdvertiseData.java
@@ -25,6 +25,7 @@
 import android.util.SparseArray;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -47,6 +48,9 @@
     @NonNull
     private final List<ParcelUuid> mServiceSolicitationUuids;
 
+    @Nullable
+    private final List<TransportDiscoveryData> mTransportDiscoveryData;
+
     private final SparseArray<byte[]> mManufacturerSpecificData;
     private final Map<ParcelUuid, byte[]> mServiceData;
     private final boolean mIncludeTxPowerLevel;
@@ -54,12 +58,14 @@
 
     private AdvertiseData(List<ParcelUuid> serviceUuids,
             List<ParcelUuid> serviceSolicitationUuids,
+            List<TransportDiscoveryData> transportDiscoveryData,
             SparseArray<byte[]> manufacturerData,
             Map<ParcelUuid, byte[]> serviceData,
             boolean includeTxPowerLevel,
             boolean includeDeviceName) {
         mServiceUuids = serviceUuids;
         mServiceSolicitationUuids = serviceSolicitationUuids;
+        mTransportDiscoveryData = transportDiscoveryData;
         mManufacturerSpecificData = manufacturerData;
         mServiceData = serviceData;
         mIncludeTxPowerLevel = includeTxPowerLevel;
@@ -83,6 +89,17 @@
     }
 
     /**
+     * Returns a list of {@link TransportDiscoveryData} within the advertisement.
+     */
+    @NonNull
+    public List<TransportDiscoveryData> getTransportDiscoveryData() {
+        if (mTransportDiscoveryData == null) {
+            return Collections.emptyList();
+        }
+        return mTransportDiscoveryData;
+    }
+
+    /**
      * Returns an array of manufacturer Id and the corresponding manufacturer specific data. The
      * manufacturer id is a non-negative number assigned by Bluetooth SIG.
      */
@@ -116,8 +133,8 @@
      */
     @Override
     public int hashCode() {
-        return Objects.hash(mServiceUuids, mServiceSolicitationUuids, mManufacturerSpecificData,
-                mServiceData, mIncludeDeviceName, mIncludeTxPowerLevel);
+        return Objects.hash(mServiceUuids, mServiceSolicitationUuids, mTransportDiscoveryData,
+                mManufacturerSpecificData, mServiceData, mIncludeDeviceName, mIncludeTxPowerLevel);
     }
 
     /**
@@ -134,6 +151,7 @@
         AdvertiseData other = (AdvertiseData) obj;
         return Objects.equals(mServiceUuids, other.mServiceUuids)
                 && Objects.equals(mServiceSolicitationUuids, other.mServiceSolicitationUuids)
+                && Objects.equals(mTransportDiscoveryData, other.mTransportDiscoveryData)
                 && BluetoothLeUtils.equals(mManufacturerSpecificData,
                     other.mManufacturerSpecificData)
                 && BluetoothLeUtils.equals(mServiceData, other.mServiceData)
@@ -144,7 +162,8 @@
     @Override
     public String toString() {
         return "AdvertiseData [mServiceUuids=" + mServiceUuids + ", mServiceSolicitationUuids="
-                + mServiceSolicitationUuids + ", mManufacturerSpecificData="
+                + mServiceSolicitationUuids + ", mTransportDiscoveryData="
+                + mTransportDiscoveryData + ", mManufacturerSpecificData="
                 + BluetoothLeUtils.toString(mManufacturerSpecificData) + ", mServiceData="
                 + BluetoothLeUtils.toString(mServiceData)
                 + ", mIncludeTxPowerLevel=" + mIncludeTxPowerLevel + ", mIncludeDeviceName="
@@ -162,6 +181,8 @@
         dest.writeTypedArray(mServiceSolicitationUuids.toArray(
                 new ParcelUuid[mServiceSolicitationUuids.size()]), flags);
 
+        dest.writeTypedList(mTransportDiscoveryData);
+
         // mManufacturerSpecificData could not be null.
         dest.writeInt(mManufacturerSpecificData.size());
         for (int i = 0; i < mManufacturerSpecificData.size(); ++i) {
@@ -197,6 +218,12 @@
                         builder.addServiceSolicitationUuid(uuid);
                     }
 
+                    List<TransportDiscoveryData> transportDiscoveryData =
+                            in.createTypedArrayList(TransportDiscoveryData.CREATOR);
+                    for (TransportDiscoveryData tdd : transportDiscoveryData) {
+                        builder.addTransportDiscoveryData(tdd);
+                    }
+
                     int manufacturerSize = in.readInt();
                     for (int i = 0; i < manufacturerSize; ++i) {
                         int manufacturerId = in.readInt();
@@ -223,6 +250,9 @@
         private List<ParcelUuid> mServiceUuids = new ArrayList<ParcelUuid>();
         @NonNull
         private List<ParcelUuid> mServiceSolicitationUuids = new ArrayList<ParcelUuid>();
+        @Nullable
+        private List<TransportDiscoveryData> mTransportDiscoveryData =
+                new ArrayList<TransportDiscoveryData>();
         private SparseArray<byte[]> mManufacturerSpecificData = new SparseArray<byte[]>();
         private Map<ParcelUuid, byte[]> mServiceData = new ArrayMap<ParcelUuid, byte[]>();
         private boolean mIncludeTxPowerLevel;
@@ -256,6 +286,7 @@
             mServiceSolicitationUuids.add(serviceSolicitationUuid);
             return this;
         }
+
         /**
          * Add service data to advertise data.
          *
@@ -274,6 +305,23 @@
         }
 
         /**
+         * Add Transport Discovery Data to advertise data.
+         *
+         * @param transportDiscoveryData Transport Discovery Data, consisting of one or more
+         * Transport Blocks. Transport Discovery Data AD Type Code is already included.
+         * @throws IllegalArgumentException If the {@code transportDiscoveryData} is empty
+         */
+        @NonNull
+        public Builder addTransportDiscoveryData(
+                @NonNull TransportDiscoveryData transportDiscoveryData) {
+            if (transportDiscoveryData == null) {
+                throw new IllegalArgumentException("transportDiscoveryData is null");
+            }
+            mTransportDiscoveryData.add(transportDiscoveryData);
+            return this;
+        }
+
+        /**
          * Add manufacturer specific data.
          * <p>
          * Please refer to the Bluetooth Assigned Numbers document provided by the <a
@@ -319,8 +367,8 @@
          */
         public AdvertiseData build() {
             return new AdvertiseData(mServiceUuids, mServiceSolicitationUuids,
-                    mManufacturerSpecificData, mServiceData, mIncludeTxPowerLevel,
-                    mIncludeDeviceName);
+                    mTransportDiscoveryData, mManufacturerSpecificData, mServiceData,
+                    mIncludeTxPowerLevel, mIncludeDeviceName);
         }
     }
 }
diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
index 5802974..b9f8a57 100644
--- a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
+++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
@@ -567,6 +567,9 @@
                         + num128BitUuids * BluetoothUuid.UUID_BYTES_128_BIT;
             }
         }
+        for (TransportDiscoveryData transportDiscoveryData : data.getTransportDiscoveryData()) {
+            size += OVERHEAD_BYTES_PER_FIELD + transportDiscoveryData.totalBytes();
+        }
         for (ParcelUuid uuid : data.getServiceData().keySet()) {
             int uuidLen = BluetoothUuid.uuidToBytes(uuid).length;
             size += OVERHEAD_BYTES_PER_FIELD + uuidLen
diff --git a/core/java/android/bluetooth/le/TransportBlock.java b/core/java/android/bluetooth/le/TransportBlock.java
new file mode 100644
index 0000000..b388bed
--- /dev/null
+++ b/core/java/android/bluetooth/le/TransportBlock.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2014 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.bluetooth.le;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+
+/**
+ * Wrapper for Transport Discovery Data Transport Blocks.
+ * This class represents a Transport Block from a Transport Discovery Data.
+ *
+ * @see TransportDiscoveryData
+ * @see AdvertiseData
+ */
+public final class TransportBlock implements Parcelable {
+    private static final String TAG = "TransportBlock";
+    private final int mOrgId;
+    private final int mTdsFlags;
+    private final int mTransportDataLength;
+    private final byte[] mTransportData;
+
+    /**
+     * Creates an instance of TransportBlock from raw data.
+     *
+     * @param orgId the Organization ID
+     * @param tdsFlags the TDS flags
+     * @param transportDataLength the total length of the Transport Data
+     * @param transportData the Transport Data
+     */
+    public TransportBlock(int orgId, int tdsFlags, int transportDataLength,
+            @Nullable byte[] transportData) {
+        mOrgId = orgId;
+        mTdsFlags = tdsFlags;
+        mTransportDataLength = transportDataLength;
+        mTransportData = transportData;
+    }
+
+    private TransportBlock(Parcel in) {
+        mOrgId = in.readInt();
+        mTdsFlags = in.readInt();
+        mTransportDataLength = in.readInt();
+        mTransportData = new byte[mTransportDataLength];
+        in.readByteArray(mTransportData);
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mOrgId);
+        dest.writeInt(mTdsFlags);
+        dest.writeInt(mTransportDataLength);
+        dest.writeByteArray(mTransportData);
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Creator<TransportBlock> CREATOR = new Creator<TransportBlock>() {
+        @Override
+        public TransportBlock createFromParcel(Parcel in) {
+            return new TransportBlock(in);
+        }
+
+        @Override
+        public TransportBlock[] newArray(int size) {
+            return new TransportBlock[size];
+        }
+    };
+
+    /**
+     * Gets the Organization ID of the Transport Block which corresponds to one of the
+     * the Bluetooth SIG Assigned Numbers.
+     */
+    public int getOrgId() {
+        return mOrgId;
+    }
+
+    /**
+     * Gets the TDS flags of the Transport Block which represents the role of the device and
+     * information about its state and supported features.
+     */
+    public int getTdsFlags() {
+        return mTdsFlags;
+    }
+
+    /**
+     * Gets the total number of octets in the Transport Data field in this Transport Block.
+     */
+    public int getTransportDataLength() {
+        return mTransportDataLength;
+    }
+
+    /**
+     * Gets the Transport Data of the Transport Block which contains organization-specific data.
+     */
+    @Nullable
+    public byte[] getTransportData() {
+        return mTransportData;
+    }
+
+    /**
+     * Converts this TransportBlock to byte array
+     *
+     * @return byte array representation of this Transport Block or null if the conversion failed
+     */
+    @Nullable
+    public byte[] toByteArray() {
+        try {
+            ByteBuffer buffer = ByteBuffer.allocate(totalBytes());
+            buffer.put((byte) mOrgId);
+            buffer.put((byte) mTdsFlags);
+            buffer.put((byte) mTransportDataLength);
+            if (mTransportData != null) {
+                buffer.put(mTransportData);
+            }
+            return buffer.array();
+        } catch (BufferOverflowException e) {
+            Log.e(TAG, "Error converting to byte array: " + e.toString());
+            return null;
+        }
+    }
+
+    /**
+     * @return total byte count of this TransportBlock
+     */
+    public int totalBytes() {
+        // 3 uint8 + byte[] length
+        int size = 3 + mTransportDataLength;
+        return size;
+    }
+}
diff --git a/core/java/android/bluetooth/le/TransportDiscoveryData.java b/core/java/android/bluetooth/le/TransportDiscoveryData.java
new file mode 100644
index 0000000..c8e97f9
--- /dev/null
+++ b/core/java/android/bluetooth/le/TransportDiscoveryData.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2014 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.bluetooth.le;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.nio.BufferOverflowException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Wrapper for Transport Discovery Data AD Type.
+ * This class contains the Transport Discovery Data AD Type Code as well as
+ * a list of potential Transport Blocks.
+ *
+ * @see AdvertiseData
+ */
+public final class TransportDiscoveryData implements Parcelable {
+    private static final String TAG = "TransportDiscoveryData";
+    private final int mTransportDataType;
+    private final List<TransportBlock> mTransportBlocks;
+
+    /**
+     * Creates a TransportDiscoveryData instance.
+     *
+     * @param transportDataType the Transport Discovery Data AD Type
+     * @param transportBlocks the list of Transport Blocks
+     */
+    public TransportDiscoveryData(int transportDataType,
+            @NonNull List<TransportBlock> transportBlocks) {
+        mTransportDataType = transportDataType;
+        mTransportBlocks = transportBlocks;
+    }
+
+    /**
+     * Creates a TransportDiscoveryData instance from byte arrays.
+     *
+     * Uses the transport discovery data bytes and parses them into an usable class.
+     *
+     * @param transportDiscoveryData the raw discovery data
+     */
+    public TransportDiscoveryData(@NonNull byte[] transportDiscoveryData) {
+        ByteBuffer byteBuffer = ByteBuffer.wrap(transportDiscoveryData);
+        mTransportBlocks = new ArrayList();
+        if (byteBuffer.remaining() > 0) {
+            mTransportDataType = byteBuffer.get();
+        } else {
+            mTransportDataType = -1;
+        }
+        try {
+            while (byteBuffer.remaining() > 0) {
+                int orgId = byteBuffer.get();
+                int tdsFlags = byteBuffer.get();
+                int transportDataLength = byteBuffer.get();
+                byte[] transportData = new byte[transportDataLength];
+                byteBuffer.get(transportData, 0, transportDataLength);
+                mTransportBlocks.add(new TransportBlock(orgId, tdsFlags,
+                        transportDataLength, transportData));
+            }
+        } catch (BufferUnderflowException e) {
+            Log.e(TAG, "Error while parsing data: " + e.toString());
+        }
+    }
+
+    private TransportDiscoveryData(Parcel in) {
+        mTransportDataType = in.readInt();
+        mTransportBlocks = in.createTypedArrayList(TransportBlock.CREATOR);
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mTransportDataType);
+        dest.writeTypedList(mTransportBlocks);
+    }
+
+    public static final @NonNull Creator<TransportDiscoveryData> CREATOR =
+            new Creator<TransportDiscoveryData>() {
+                @Override
+                public TransportDiscoveryData createFromParcel(Parcel in) {
+                    return new TransportDiscoveryData(in);
+                }
+
+                @Override
+                public TransportDiscoveryData[] newArray(int size) {
+                    return new TransportDiscoveryData[size];
+                }
+    };
+
+    /**
+     * Gets the transport data type.
+     */
+    public int getTransportDataType() {
+        return mTransportDataType;
+    }
+
+    /**
+     * @return the list of {@link TransportBlock} in this TransportDiscoveryData
+     *         or an empty list if there are no Transport Blocks
+     */
+    @NonNull
+    public List<TransportBlock> getTransportBlocks() {
+        if (mTransportBlocks == null) {
+            return Collections.emptyList();
+        }
+        return mTransportBlocks;
+    }
+
+    /**
+     * Converts this TransportDiscoveryData to byte array
+     *
+     * @return byte array representation of this Transport Discovery Data or null if the
+     *         conversion failed
+     */
+    @Nullable
+    public byte[] toByteArray() {
+        try {
+            ByteBuffer buffer = ByteBuffer.allocate(totalBytes());
+            buffer.put((byte) mTransportDataType);
+            for (TransportBlock transportBlock : getTransportBlocks()) {
+                buffer.put(transportBlock.toByteArray());
+            }
+            return buffer.array();
+        } catch (BufferOverflowException e) {
+            Log.e(TAG, "Error converting to byte array: " + e.toString());
+            return null;
+        }
+    }
+
+    /**
+     * @return total byte count of this TransportDataDiscovery
+     */
+    public int totalBytes() {
+        int size = 1; // Counting Transport Data Type here.
+        for (TransportBlock transportBlock : getTransportBlocks()) {
+            size += transportBlock.totalBytes();
+        }
+        return size;
+    }
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index d627103..25d1d53 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5529,6 +5529,12 @@
     public static final String STATS_COMPANION_SERVICE = "statscompanion";
 
     /**
+     * Service to assist statsd in logging atoms from bootstrap atoms.
+     * @hide
+     */
+    public static final String STATS_BOOTSTRAP_ATOM_SERVICE = "statsbootstrap";
+
+    /**
      * Use with {@link #getSystemService(String)} to retrieve an {@link android.app.StatsManager}.
      * @hide
      */
@@ -6538,30 +6544,24 @@
             @NonNull Configuration overrideConfiguration);
 
     /**
-     * Return a new Context object for the current Context but whose resources
-     * are adjusted to match the metrics of the given Display.  Each call to this method
-     * returns a new instance of a Context object; Context objects are not
-     * shared, however common state (ClassLoader, other Resources for the
-     * same configuration) may be so the Context itself can be fairly lightweight.
-     *
-     * To obtain an instance of a {@link WindowManager} (see {@link #getSystemService(String)}) that
-     * is configured to show windows on the given display call
-     * {@link #createWindowContext(int, Bundle)} on the returned display Context or use an
-     * {@link android.app.Activity}.
-     *
+     * Returns a new <code>Context</code> object from the current context but with resources
+     * adjusted to match the metrics of <code>display</code>. Each call to this method
+     * returns a new instance of a context object. Context objects are not shared; however,
+     * common state (such as the {@link ClassLoader} and other resources for the same
+     * configuration) can be shared, so the <code>Context</code> itself is lightweight.
      * <p>
-     * Note that invoking #createDisplayContext(Display) from an UI context is not regarded
-     * as an UI context. In other words, it is not suggested to access UI components (such as
-     * obtain a {@link WindowManager} by {@link #getSystemService(String)})
-     * from the context created from #createDisplayContext(Display).
-     * </p>
+     * To obtain an instance of {@link WindowManager} configured to show windows on the given
+     * display, call {@link #createWindowContext(int, Bundle)} on the returned display context,
+     * then call {@link #getSystemService(String)} or {@link #getSystemService(Class)} on the
+     * returned window context.
+     * <p>
+     * <b>Note:</b> The context returned by <code>createDisplayContext(Display)</code> is not a UI
+     * context. Do not access UI components or obtain a {@link WindowManager} from the context
+     * created by <code>createDisplayContext(Display)</code>.
      *
-     * @param display A {@link Display} object specifying the display for whose metrics the
-     * Context's resources should be tailored.
+     * @param display The display to which the current context's resources are adjusted.
      *
-     * @return A {@link Context} for the display.
-     *
-     * @see #getSystemService(String)
+     * @return A context for the display.
      */
     @DisplayContext
     public abstract Context createDisplayContext(@NonNull Display display);
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index fccb8a7..2b8681a 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1564,15 +1564,6 @@
     public static final String ACTION_POWER_USAGE_SUMMARY = "android.intent.action.POWER_USAGE_SUMMARY";
 
     /**
-     * Activity Action: Show one-handed mode settings to the user.
-     * <p>Input: Nothing.
-     * <p>Output: Nothing.
-     * @hide
-     */
-    public static final String ACTION_ONE_HANDED_SETTINGS =
-            "android.intent.action.ONE_HANDED_SETTINGS";
-
-    /**
      * Activity Action: Setup wizard action provided for OTA provisioning to determine if it needs
      * to run.
      * <p>Input: Nothing.
@@ -5531,8 +5522,8 @@
 
     /**
      * A boolean extra, when used with {@link #ACTION_VIEW_PERMISSION_USAGE_FOR_PERIOD},
-     * that specifies whether the system displayed attribution information in the
-     * permission usage system UI for the chosen entry.
+     * that specifies whether the permission usage system UI is showing attribution information
+     * for the chosen entry.
      *
      * <p> The extra can only be true if application has specified attributionsAreUserVisible
      * in its manifest. </p>
@@ -6424,6 +6415,7 @@
             FLAG_RECEIVER_FROM_SHELL,
             FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS,
             FLAG_RECEIVER_OFFLOAD,
+            FLAG_RECEIVER_OFFLOAD_FOREGROUND,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Flags {}
@@ -6469,6 +6461,7 @@
             FLAG_RECEIVER_FROM_SHELL,
             FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS,
             FLAG_RECEIVER_OFFLOAD,
+            FLAG_RECEIVER_OFFLOAD_FOREGROUND,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface MutableFlags {}
@@ -6923,6 +6916,14 @@
      */
     public static final int FLAG_RECEIVER_OFFLOAD = 0x80000000;
     /**
+    /**
+     * If set, when sending a broadcast the recipient will run on the system dedicated queue.
+     *
+     * @hide
+     */
+    public static final int FLAG_RECEIVER_OFFLOAD_FOREGROUND = 0x00000800;
+
+    /**
      * If this is an ordered broadcast, don't allow receivers to abort the broadcast.
      * They can still propagate results through to later receivers, but they can not prevent
      * later receivers from seeing the broadcast.
diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java
index 14d69cc..056f99f 100644
--- a/core/java/android/content/pm/parsing/ParsingPackage.java
+++ b/core/java/android/content/pm/parsing/ParsingPackage.java
@@ -362,6 +362,9 @@
 
     ParsingPackage setAttributionsAreUserVisible(boolean attributionsAreUserVisible);
 
+    ParsingPackage setResetEnabledSettingsOnAppDataCleared(
+            boolean resetEnabledSettingsOnAppDataCleared);
+
     // TODO(b/135203078): This class no longer has access to ParsedPackage, find a replacement
     //  for moving to the next step
     @CallSuper
diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
index edddf40..f07f382 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
@@ -517,6 +517,7 @@
         private static final long DISALLOW_PROFILING = 1L << 45;
         private static final long REQUEST_FOREGROUND_SERVICE_EXEMPTION = 1L << 46;
         private static final long ATTRIBUTIONS_ARE_USER_VISIBLE = 1L << 47;
+        private static final long RESET_ENABLED_SETTINGS_ON_APP_DATA_CLEARED = 1L << 48;
     }
 
     private ParsingPackageImpl setBoolean(@Booleans.Values long flag, boolean value) {
@@ -2216,6 +2217,11 @@
     }
 
     @Override
+    public boolean isResetEnabledSettingsOnAppDataCleared() {
+        return getBoolean(Booleans.RESET_ENABLED_SETTINGS_ON_APP_DATA_CLEARED);
+    }
+
+    @Override
     public ParsingPackageImpl setBaseRevisionCode(int value) {
         baseRevisionCode = value;
         return this;
@@ -2771,4 +2777,12 @@
         setBoolean(Booleans.ATTRIBUTIONS_ARE_USER_VISIBLE, attributionsAreUserVisible);
         return this;
     }
+
+    @Override
+    public ParsingPackage setResetEnabledSettingsOnAppDataCleared(
+            boolean resetEnabledSettingsOnAppDataCleared) {
+        setBoolean(Booleans.RESET_ENABLED_SETTINGS_ON_APP_DATA_CLEARED,
+                resetEnabledSettingsOnAppDataCleared);
+        return this;
+    }
 }
diff --git a/core/java/android/content/pm/parsing/ParsingPackageRead.java b/core/java/android/content/pm/parsing/ParsingPackageRead.java
index 4a249bb..2933f95 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageRead.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageRead.java
@@ -297,4 +297,12 @@
      * @see R.styleable#AndroidManifestService_visibleToInstantApps
      */
     boolean isVisibleToInstantApps();
+
+    /**
+     * Whether the enabled settings of components in the application should be reset to the default,
+     * when the application's user data is cleared.
+     *
+     * @see R.styleable#AndroidManifestApplication_resetEnabledSettingsOnAppDataCleared
+     */
+    boolean isResetEnabledSettingsOnAppDataCleared();
 }
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index e3a5de5..d2ac8739 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -2288,6 +2288,9 @@
                 .setVmSafeMode(bool(false, R.styleable.AndroidManifestApplication_vmSafeMode, sa))
                 .setAutoRevokePermissions(anInt(R.styleable.AndroidManifestApplication_autoRevokePermissions, sa))
                 .setAttributionsAreUserVisible(bool(false, R.styleable.AndroidManifestApplication_attributionsAreUserVisible, sa))
+                .setResetEnabledSettingsOnAppDataCleared(bool(false,
+                    R.styleable.AndroidManifestApplication_resetEnabledSettingsOnAppDataCleared,
+                    sa))
                 // targetSdkVersion gated
                 .setAllowAudioPlaybackCapture(bool(targetSdk >= Build.VERSION_CODES.Q, R.styleable.AndroidManifestApplication_allowAudioPlaybackCapture, sa))
                 .setBaseHardwareAccelerated(bool(targetSdk >= Build.VERSION_CODES.ICE_CREAM_SANDWICH, R.styleable.AndroidManifestApplication_hardwareAccelerated, sa))
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 96a18dc..3c8b6e9 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1323,7 +1323,7 @@
      * <p>Maximum flashlight brightness level.</p>
      * <p>If this value is greater than 1, then the device supports controlling the
      * flashlight brightness level via
-     * {android.hardware.camera2.CameraManager#setTorchStrengthLevel}.
+     * {android.hardware.camera2.CameraManager#turnOnTorchWithStrengthLevel}.
      * If this value is equal to 1, flashlight brightness control is not supported.
      * The value for this key will be null for devices with no flash unit.</p>
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
@@ -1335,7 +1335,7 @@
 
     /**
      * <p>Default flashlight brightness level to be set via
-     * {android.hardware.camera2.CameraManager#setTorchStrengthLevel}.</p>
+     * {android.hardware.camera2.CameraManager#turnOnTorchWithStrengthLevel}.</p>
      * <p>If flash unit is available this will be greater than or equal to 1 and less
      * or equal to <code>{@link CameraCharacteristics#FLASH_INFO_STRENGTH_MAXIMUM_LEVEL android.flash.info.strengthMaximumLevel}</code>.</p>
      * <p>Setting flashlight brightness above the default level
diff --git a/core/java/android/net/nsd/INsdManager.aidl b/core/java/android/net/nsd/INsdManager.aidl
index e9e8935..89e9cdb 100644
--- a/core/java/android/net/nsd/INsdManager.aidl
+++ b/core/java/android/net/nsd/INsdManager.aidl
@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2012, The Android Open Source Project
+ * Copyright (c) 2021, The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,16 +16,15 @@
 
 package android.net.nsd;
 
+import android.net.nsd.INsdManagerCallback;
+import android.net.nsd.INsdServiceConnector;
 import android.os.Messenger;
 
 /**
- * Interface that NsdService implements
+ * Interface that NsdService implements to connect NsdManager clients.
  *
  * {@hide}
  */
-interface INsdManager
-{
-    @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
-    Messenger getMessenger();
-    void setEnabled(boolean enable);
+interface INsdManager {
+    INsdServiceConnector connect(INsdManagerCallback cb);
 }
diff --git a/core/java/android/net/nsd/INsdManagerCallback.aidl b/core/java/android/net/nsd/INsdManagerCallback.aidl
new file mode 100644
index 0000000..1a262ec
--- /dev/null
+++ b/core/java/android/net/nsd/INsdManagerCallback.aidl
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.nsd;
+
+import android.os.Messenger;
+import android.net.nsd.NsdServiceInfo;
+
+/**
+ * Callbacks from NsdService to NsdManager
+ * @hide
+ */
+oneway interface INsdManagerCallback {
+    void onDiscoverServicesStarted(int listenerKey, in NsdServiceInfo info);
+    void onDiscoverServicesFailed(int listenerKey, int error);
+    void onServiceFound(int listenerKey, in NsdServiceInfo info);
+    void onServiceLost(int listenerKey, in NsdServiceInfo info);
+    void onStopDiscoveryFailed(int listenerKey, int error);
+    void onStopDiscoverySucceeded(int listenerKey);
+    void onRegisterServiceFailed(int listenerKey, int error);
+    void onRegisterServiceSucceeded(int listenerKey, in NsdServiceInfo info);
+    void onUnregisterServiceFailed(int listenerKey, int error);
+    void onUnregisterServiceSucceeded(int listenerKey);
+    void onResolveServiceFailed(int listenerKey, int error);
+    void onResolveServiceSucceeded(int listenerKey, in NsdServiceInfo info);
+}
diff --git a/core/java/android/net/nsd/INsdServiceConnector.aidl b/core/java/android/net/nsd/INsdServiceConnector.aidl
new file mode 100644
index 0000000..b06ae55
--- /dev/null
+++ b/core/java/android/net/nsd/INsdServiceConnector.aidl
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.nsd;
+
+import android.net.nsd.INsdManagerCallback;
+import android.net.nsd.NsdServiceInfo;
+import android.os.Messenger;
+
+/**
+ * Interface that NsdService implements for each NsdManager client.
+ *
+ * {@hide}
+ */
+interface INsdServiceConnector {
+    void registerService(int listenerKey, in NsdServiceInfo serviceInfo);
+    void unregisterService(int listenerKey);
+    void discoverServices(int listenerKey, in NsdServiceInfo serviceInfo);
+    void stopDiscovery(int listenerKey);
+    void resolveService(int listenerKey, in NsdServiceInfo serviceInfo);
+    void startDaemon();
+}
\ No newline at end of file
diff --git a/core/java/android/net/nsd/NsdManager.java b/core/java/android/net/nsd/NsdManager.java
index ae8d010..6c597e2 100644
--- a/core/java/android/net/nsd/NsdManager.java
+++ b/core/java/android/net/nsd/NsdManager.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -31,17 +31,13 @@
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
-import android.os.Messenger;
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.AsyncChannel;
 import com.android.internal.util.Protocol;
 
-import java.util.concurrent.CountDownLatch;
-
 /**
  * The Network Service Discovery Manager class provides the API to discover services
  * on a network. As an example, if device A and device B are connected over a Wi-Fi
@@ -234,6 +230,11 @@
     /** @hide */
     public static final int NATIVE_DAEMON_EVENT                     = BASE + 26;
 
+    /** @hide */
+    public static final int REGISTER_CLIENT                         = BASE + 27;
+    /** @hide */
+    public static final int UNREGISTER_CLIENT                       = BASE + 28;
+
     /** Dns based service discovery protocol */
     public static final int PROTOCOL_DNS_SD = 0x0001;
 
@@ -274,7 +275,7 @@
 
     private static final int FIRST_LISTENER_KEY = 1;
 
-    private final INsdManager mService;
+    private final INsdServiceConnector mService;
     private final Context mContext;
 
     private int mListenerKey = FIRST_LISTENER_KEY;
@@ -282,9 +283,7 @@
     private final SparseArray<NsdServiceInfo> mServiceMap = new SparseArray<>();
     private final Object mMapLock = new Object();
 
-    private final AsyncChannel mAsyncChannel = new AsyncChannel();
-    private ServiceHandler mHandler;
-    private final CountDownLatch mConnected = new CountDownLatch(1);
+    private final ServiceHandler mHandler;
 
     /**
      * Create a new Nsd instance. Applications use
@@ -295,18 +294,108 @@
      * is a system private class.
      */
     public NsdManager(Context context, INsdManager service) {
-        mService = service;
         mContext = context;
-        init();
+
+        HandlerThread t = new HandlerThread("NsdManager");
+        t.start();
+        mHandler = new ServiceHandler(t.getLooper());
+
+        try {
+            mService = service.connect(new NsdCallbackImpl(mHandler));
+        } catch (RemoteException e) {
+            throw new RuntimeException("Failed to connect to NsdService");
+        }
+
+        // Only proactively start the daemon if the target SDK < S, otherwise the internal service
+        // would automatically start/stop the native daemon as needed.
+        if (!CompatChanges.isChangeEnabled(RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)) {
+            try {
+                mService.startDaemon();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to proactively start daemon");
+                // Continue: the daemon can still be started on-demand later
+            }
+        }
     }
 
-    /**
-     * @hide
-     */
-    @VisibleForTesting
-    public void disconnect() {
-        mAsyncChannel.disconnect();
-        mHandler.getLooper().quitSafely();
+    private static class NsdCallbackImpl extends INsdManagerCallback.Stub {
+        private final Handler mServHandler;
+
+        NsdCallbackImpl(Handler serviceHandler) {
+            mServHandler = serviceHandler;
+        }
+
+        private void sendInfo(int message, int listenerKey, NsdServiceInfo info) {
+            mServHandler.sendMessage(mServHandler.obtainMessage(message, 0, listenerKey, info));
+        }
+
+        private void sendError(int message, int listenerKey, int error) {
+            mServHandler.sendMessage(mServHandler.obtainMessage(message, error, listenerKey));
+        }
+
+        private void sendNoArg(int message, int listenerKey) {
+            mServHandler.sendMessage(mServHandler.obtainMessage(message, 0, listenerKey));
+        }
+
+        @Override
+        public void onDiscoverServicesStarted(int listenerKey, NsdServiceInfo info) {
+            sendInfo(DISCOVER_SERVICES_STARTED, listenerKey, info);
+        }
+
+        @Override
+        public void onDiscoverServicesFailed(int listenerKey, int error) {
+            sendError(DISCOVER_SERVICES_FAILED, listenerKey, error);
+        }
+
+        @Override
+        public void onServiceFound(int listenerKey, NsdServiceInfo info) {
+            sendInfo(SERVICE_FOUND, listenerKey, info);
+        }
+
+        @Override
+        public void onServiceLost(int listenerKey, NsdServiceInfo info) {
+            sendInfo(SERVICE_LOST, listenerKey, info);
+        }
+
+        @Override
+        public void onStopDiscoveryFailed(int listenerKey, int error) {
+            sendError(STOP_DISCOVERY_FAILED, listenerKey, error);
+        }
+
+        @Override
+        public void onStopDiscoverySucceeded(int listenerKey) {
+            sendNoArg(STOP_DISCOVERY_SUCCEEDED, listenerKey);
+        }
+
+        @Override
+        public void onRegisterServiceFailed(int listenerKey, int error) {
+            sendError(REGISTER_SERVICE_FAILED, listenerKey, error);
+        }
+
+        @Override
+        public void onRegisterServiceSucceeded(int listenerKey, NsdServiceInfo info) {
+            sendInfo(REGISTER_SERVICE_SUCCEEDED, listenerKey, info);
+        }
+
+        @Override
+        public void onUnregisterServiceFailed(int listenerKey, int error) {
+            sendError(UNREGISTER_SERVICE_FAILED, listenerKey, error);
+        }
+
+        @Override
+        public void onUnregisterServiceSucceeded(int listenerKey) {
+            sendNoArg(UNREGISTER_SERVICE_SUCCEEDED, listenerKey);
+        }
+
+        @Override
+        public void onResolveServiceFailed(int listenerKey, int error) {
+            sendError(RESOLVE_SERVICE_FAILED, listenerKey, error);
+        }
+
+        @Override
+        public void onResolveServiceSucceeded(int listenerKey, NsdServiceInfo info) {
+            sendInfo(RESOLVE_SERVICE_SUCCEEDED, listenerKey, info);
+        }
     }
 
     /**
@@ -376,19 +465,6 @@
         public void handleMessage(Message message) {
             final int what = message.what;
             final int key = message.arg2;
-            switch (what) {
-                case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
-                    mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
-                    return;
-                case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
-                    mConnected.countDown();
-                    return;
-                case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
-                    Log.e(TAG, "Channel lost");
-                    return;
-                default:
-                    break;
-            }
             final Object listener;
             final NsdServiceInfo ns;
             synchronized (mMapLock) {
@@ -504,36 +580,6 @@
     }
 
     /**
-     * Initialize AsyncChannel
-     */
-    private void init() {
-        final Messenger messenger = getMessenger();
-        if (messenger == null) {
-            fatal("Failed to obtain service Messenger");
-        }
-        HandlerThread t = new HandlerThread("NsdManager");
-        t.start();
-        mHandler = new ServiceHandler(t.getLooper());
-        mAsyncChannel.connect(mContext, mHandler, messenger);
-        try {
-            mConnected.await();
-        } catch (InterruptedException e) {
-            fatal("Interrupted wait at init");
-        }
-        if (CompatChanges.isChangeEnabled(RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)) {
-            return;
-        }
-        // Only proactively start the daemon if the target SDK < S, otherwise the internal service
-        // would automatically start/stop the native daemon as needed.
-        mAsyncChannel.sendMessage(DAEMON_STARTUP);
-    }
-
-    private static void fatal(String msg) {
-        Log.e(TAG, msg);
-        throw new RuntimeException(msg);
-    }
-
-    /**
      * Register a service to be discovered by other services.
      *
      * <p> The function call immediately returns after sending a request to register service
@@ -556,7 +602,11 @@
         checkServiceInfo(serviceInfo);
         checkProtocol(protocolType);
         int key = putListener(listener, serviceInfo);
-        mAsyncChannel.sendMessage(REGISTER_SERVICE, 0, key, serviceInfo);
+        try {
+            mService.registerService(key, serviceInfo);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -574,7 +624,11 @@
      */
     public void unregisterService(RegistrationListener listener) {
         int id = getListenerKey(listener);
-        mAsyncChannel.sendMessage(UNREGISTER_SERVICE, 0, id);
+        try {
+            mService.unregisterService(id);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -613,7 +667,11 @@
         s.setServiceType(serviceType);
 
         int key = putListener(listener, s);
-        mAsyncChannel.sendMessage(DISCOVER_SERVICES, 0, key, s);
+        try {
+            mService.discoverServices(key, s);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -634,7 +692,11 @@
      */
     public void stopServiceDiscovery(DiscoveryListener listener) {
         int id = getListenerKey(listener);
-        mAsyncChannel.sendMessage(STOP_DISCOVERY, 0, id);
+        try {
+            mService.stopDiscovery(id);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -649,29 +711,10 @@
     public void resolveService(NsdServiceInfo serviceInfo, ResolveListener listener) {
         checkServiceInfo(serviceInfo);
         int key = putListener(listener, serviceInfo);
-        mAsyncChannel.sendMessage(RESOLVE_SERVICE, 0, key, serviceInfo);
-    }
-
-    /** Internal use only @hide */
-    public void setEnabled(boolean enabled) {
         try {
-            mService.setEnabled(enabled);
+            mService.resolveService(key, serviceInfo);
         } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Get a reference to NsdService handler. This is used to establish
-     * an AsyncChannel communication with the service
-     *
-     * @return Messenger pointing to the NsdService handler
-     */
-    private Messenger getMessenger() {
-        try {
-            return mService.getMessenger();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
+            e.rethrowFromSystemServer();
         }
     }
 
diff --git a/core/java/android/net/nsd/NsdServiceInfo.aidl b/core/java/android/net/nsd/NsdServiceInfo.aidl
new file mode 100644
index 0000000..657bdd1
--- /dev/null
+++ b/core/java/android/net/nsd/NsdServiceInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.nsd;
+
+@JavaOnlyStableParcelable parcelable NsdServiceInfo;
\ No newline at end of file
diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
index 0af322e..0954013 100644
--- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -528,6 +528,7 @@
     public String toString() {
         StringBuilder out = new StringBuilder("ApduService: ");
         out.append(getComponent());
+        out.append(", UID: " + mUid);
         out.append(", description: " + mDescription);
         out.append(", Static AID Groups: ");
         for (AidGroup aidGroup : mStaticAidGroups.values()) {
@@ -546,7 +547,8 @@
         if (!(o instanceof ApduServiceInfo)) return false;
         ApduServiceInfo thatService = (ApduServiceInfo) o;
 
-        return thatService.getComponent().equals(this.getComponent());
+        return thatService.getComponent().equals(this.getComponent())
+                && thatService.getUid() == this.getUid();
     }
 
     @Override
@@ -619,8 +621,9 @@
     };
 
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("    " + getComponent() +
-                " (Description: " + getDescription() + ")");
+        pw.println("    " + getComponent()
+                + " (Description: " + getDescription() + ")"
+                + " (UID: " + getUid() + ")");
         if (mOnHost) {
             pw.println("    On Host Service");
         } else {
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java
index d498535..0a9fe90 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -30,6 +30,7 @@
 import android.nfc.INfcCardEmulation;
 import android.nfc.NfcAdapter;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 import android.util.Log;
@@ -83,6 +84,13 @@
     public static final String EXTRA_SERVICE_COMPONENT = "component";
 
     /**
+     * The caller userId extra for {@link #ACTION_CHANGE_DEFAULT}.
+     *
+     * @see #ACTION_CHANGE_DEFAULT
+     */
+    public static final String EXTRA_USERID = "android.nfc.cardemulation.extra.USERID";
+
+    /**
      * Category used for NFC payment services.
      */
     public static final String CATEGORY_PAYMENT = "payment";
@@ -269,8 +277,8 @@
         if (CATEGORY_PAYMENT.equals(category)) {
             boolean preferForeground = false;
             try {
-                preferForeground = Settings.Secure.getInt(mContext.getContentResolver(),
-                        Settings.Secure.NFC_PAYMENT_FOREGROUND) != 0;
+                preferForeground = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                        Settings.Secure.NFC_PAYMENT_FOREGROUND, UserHandle.myUserId()) != 0;
             } catch (SettingNotFoundException e) {
             }
             return preferForeground;
@@ -829,6 +837,28 @@
     /**
      * @hide
      */
+    public boolean setDefaultForNextTap(int userId, ComponentName service) {
+        try {
+            return sService.setDefaultForNextTap(userId, service);
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+            try {
+                return sService.setDefaultForNextTap(userId, service);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return false;
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
     public List<ApduServiceInfo> getServices(String category) {
         try {
             return sService.getServices(mContext.getUserId(), category);
@@ -849,6 +879,28 @@
     }
 
     /**
+     * @hide
+     */
+    public List<ApduServiceInfo> getServices(String category, int userId) {
+        try {
+            return sService.getServices(userId, category);
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return null;
+            }
+            try {
+                return sService.getServices(userId, category);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return null;
+            }
+        }
+    }
+
+    /**
      * A valid AID according to ISO/IEC 7816-4:
      * <ul>
      * <li>Has >= 5 bytes and <=16 bytes (>=10 hex chars and <= 32 hex chars)
diff --git a/core/java/android/os/IStatsBootstrapAtomService.aidl b/core/java/android/os/IStatsBootstrapAtomService.aidl
new file mode 100644
index 0000000..9d1df67
--- /dev/null
+++ b/core/java/android/os/IStatsBootstrapAtomService.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.os.StatsBootstrapAtom;
+
+/**
+ * IBootstrapAtomService interface exposes an interface for processes that launch in the
+ * bootstrap namespace to push atoms to statsd.
+ *
+ * @hide
+ */
+oneway interface IStatsBootstrapAtomService {
+    /**
+     * Push an atom to StatsBootstrapAtomService, which will forward it to statsd.
+     *
+     * @param atom - parcelled representation of the atom to log.
+     *
+     * Errors are reported as service specific errors.
+     */
+    void reportBootstrapAtom(in StatsBootstrapAtom atom);
+}
\ No newline at end of file
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index b839706..50ca9ff 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -20,6 +20,7 @@
 import android.os.Bundle;
 import android.os.IUserRestrictionsListener;
 import android.os.PersistableBundle;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.content.pm.UserInfo;
 import android.content.IntentSender;
@@ -91,6 +92,9 @@
     boolean markGuestForDeletion(int userId);
     UserInfo findCurrentGuestUser();
     boolean isQuietModeEnabled(int userId);
+    UserHandle createUserWithAttributes(in String userName, in String userType, int flags,
+            in Bitmap userIcon,
+            in String accountName, in String accountType, in PersistableBundle accountOptions);
     void setSeedAccountData(int userId, in String accountName,
             in String accountType, in PersistableBundle accountOptions, boolean persist);
     String getSeedAccountName(int userId);
@@ -98,6 +102,7 @@
     PersistableBundle getSeedAccountOptions(int userId);
     void clearSeedAccountData(int userId);
     boolean someUserHasSeedAccount(in String accountName, in String accountType);
+    boolean someUserHasAccount(in String accountName, in String accountType);
     boolean isProfile(int userId);
     boolean isManagedProfile(int userId);
     boolean isCloneProfile(int userId);
diff --git a/core/java/android/os/NewUserRequest.java b/core/java/android/os/NewUserRequest.java
index 2ebc01f..b0e1f91 100644
--- a/core/java/android/os/NewUserRequest.java
+++ b/core/java/android/os/NewUserRequest.java
@@ -17,7 +17,11 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
+import android.content.pm.UserInfo;
+import android.graphics.Bitmap;
+import android.text.TextUtils;
 
 /**
  * Contains necessary information to create user using
@@ -26,6 +30,7 @@
  * @hide
  */
 @SystemApi
+@SuppressLint("PackageLayering")
 public final class NewUserRequest {
     @Nullable
     private final String mName;
@@ -33,16 +38,24 @@
     private final boolean mEphemeral;
     @NonNull
     private final String mUserType;
+    private final Bitmap mUserIcon;
+    private final String mAccountName;
+    private final String mAccountType;
+    private final PersistableBundle mAccountOptions;
 
     private NewUserRequest(Builder builder) {
         mName = builder.mName;
         mAdmin = builder.mAdmin;
         mEphemeral = builder.mEphemeral;
         mUserType = builder.mUserType;
+        mUserIcon = builder.mUserIcon;
+        mAccountName = builder.mAccountName;
+        mAccountType = builder.mAccountType;
+        mAccountOptions = builder.mAccountOptions;
     }
 
     /**
-     * Gets the user name.
+     * Returns the name of the user.
      */
     @Nullable
     public String getName() {
@@ -50,7 +63,7 @@
     }
 
     /**
-     * Is user Ephemenral?
+     * Returns whether the user is ephemeral.
      *
      * <p> Ephemeral user will be removed after leaving the foreground.
      */
@@ -59,7 +72,7 @@
     }
 
     /**
-     * Is user Admin?
+     * Returns whether the user is an admin.
      *
      * <p> Admin user is with administrative privileges and such user can create and
      * delete users.
@@ -69,7 +82,17 @@
     }
 
     /**
-     * Gets user type.
+     * Returns the calculated flags for user creation.
+     */
+    int getFlags() {
+        int flags = 0;
+        if (isAdmin()) flags |= UserInfo.FLAG_ADMIN;
+        if (isEphemeral()) flags |= UserInfo.FLAG_EPHEMERAL;
+        return flags;
+    }
+
+    /**
+     * Returns the user type.
      *
      * <p> Supported types are {@link UserManager.USER_TYPE_FULL_SECONDARY} and
      * {@link USER_TYPE_FULL_GUEST}
@@ -79,25 +102,71 @@
         return mUserType;
     }
 
+    /**
+     * Returns the user icon.
+     */
+    @Nullable
+    public Bitmap getUserIcon() {
+        return mUserIcon;
+    }
+
+    /**
+     * Returns the account name.
+     */
+    @Nullable
+    public String getAccountName() {
+        return mAccountName;
+    }
+
+    /**
+     * Returns the account type.
+     */
+    @Nullable
+    public String getAccountType() {
+        return mAccountType;
+    }
+
+    /**
+     * Returns the account options.
+     */
+    @SuppressLint("NullableCollection")
+    @Nullable
+    public PersistableBundle getAccountOptions() {
+        return mAccountOptions;
+    }
+
     @Override
     public String toString() {
-        return String.format(
-                "NewUserRequest- UserName:%s, userType:%s, IsAdmin:%s, IsEphemeral:%s.", mName,
-                mUserType, mAdmin, mEphemeral);
+        return "NewUserRequest{"
+                + "mName='" + mName + '\''
+                + ", mAdmin=" + mAdmin
+                + ", mEphemeral=" + mEphemeral
+                + ", mUserType='" + mUserType + '\''
+                + ", mAccountName='" + mAccountName + '\''
+                + ", mAccountType='" + mAccountType + '\''
+                + ", mAccountOptions=" + mAccountOptions
+                + '}';
     }
 
     /**
      * Builder for building {@link NewUserRequest}
      */
+    @SuppressLint("PackageLayering")
     public static final class Builder {
 
         private String mName;
         private boolean mAdmin;
         private boolean mEphemeral;
         private String mUserType = UserManager.USER_TYPE_FULL_SECONDARY;
+        private Bitmap mUserIcon;
+        private String mAccountName;
+        private String mAccountType;
+        private PersistableBundle mAccountOptions;
 
         /**
          * Sets user name.
+         *
+         * @return This object for method chaining.
          */
         @NonNull
         public Builder setName(@Nullable String name) {
@@ -110,6 +179,8 @@
          *
          * <p> Admin user is with administrative privileges and such user can create
          * and delete users.
+         *
+         * @return This object for method chaining.
          */
         @NonNull
         public Builder setAdmin() {
@@ -121,6 +192,8 @@
          * Sets user as ephemeral.
          *
          * <p> Ephemeral user will be removed after leaving the foreground.
+         *
+         * @return This object for method chaining.
          */
         @NonNull
         public Builder setEphemeral() {
@@ -134,6 +207,8 @@
          * Supported types are {@link UserManager.USER_TYPE_FULL_SECONDARY} and
          * {@link UserManager.USER_TYPE_FULL_GUEST}. Default value is
          * {@link UserManager.USER_TYPE_FULL_SECONDARY}.
+         *
+         * @return This object for method chaining.
          */
         @NonNull
         public Builder setUserType(@NonNull String type) {
@@ -142,6 +217,54 @@
         }
 
         /**
+         * Sets user icon.
+         *
+         * @return This object for method chaining.
+         */
+        @NonNull
+        public Builder setUserIcon(@Nullable Bitmap userIcon) {
+            mUserIcon = userIcon;
+            return this;
+        }
+
+        /**
+         * Sets account name that will be used by the setup wizard to initialize the user.
+         *
+         * @see android.accounts.Account
+         * @return This object for method chaining.
+         */
+        @NonNull
+        public Builder setAccountName(@Nullable String accountName) {
+            mAccountName = accountName;
+            return this;
+        }
+
+        /**
+         * Sets account type for the account to be created. This is required if the account name
+         * is not null. This will be used by the setup wizard to initialize the user.
+         *
+         * @see android.accounts.Account
+         * @return This object for method chaining.
+         */
+        @NonNull
+        public Builder setAccountType(@Nullable String accountType) {
+            mAccountType = accountType;
+            return this;
+        }
+
+        /**
+         * Sets account options that can contain account-specific extra information
+         * to be used by setup wizard to initialize the account for the user.
+         *
+         * @return This object for method chaining.
+         */
+        @NonNull
+        public Builder setAccountOptions(@Nullable PersistableBundle accountOptions) {
+            mAccountOptions = accountOptions;
+            return this;
+        }
+
+        /**
          * Builds {@link NewUserRequest}
          *
          * @throws IllegalStateException if builder is configured with incompatible properties and
@@ -165,6 +288,11 @@
                     && mUserType != UserManager.USER_TYPE_FULL_GUEST) {
                 throw new IllegalStateException("Unsupported user type: " + mUserType);
             }
+
+            if (TextUtils.isEmpty(mAccountName) != TextUtils.isEmpty(mAccountType)) {
+                throw new IllegalStateException(
+                        "Account name and account type should be provided together.");
+            }
         }
     }
 }
diff --git a/core/java/android/os/StatsBootstrapAtom.aidl b/core/java/android/os/StatsBootstrapAtom.aidl
new file mode 100644
index 0000000..47500af
--- /dev/null
+++ b/core/java/android/os/StatsBootstrapAtom.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os;
+
+import android.os.StatsBootstrapAtomValue;
+
+/*
+ * Generic encapsulation of an atom for bootstrap processes to log.
+ *
+ * @hide
+ */
+parcelable StatsBootstrapAtom {
+    /*
+     * Atom ID. Must be between 1 - 10,000.
+     */
+    int atomId;
+    /*
+     * Vector of fields in the order of the atom definition.
+     */
+    StatsBootstrapAtomValue[] values;
+ }
\ No newline at end of file
diff --git a/core/java/android/os/StatsBootstrapAtomValue.aidl b/core/java/android/os/StatsBootstrapAtomValue.aidl
new file mode 100644
index 0000000..a90dfa4
--- /dev/null
+++ b/core/java/android/os/StatsBootstrapAtomValue.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os;
+/*
+ * Supported field types.
+ *
+ * @hide
+ */
+union StatsBootstrapAtomValue {
+    boolean boolValue;
+    int intValue;
+    long longValue;
+    float floatValue;
+    String stringValue;
+    byte[] bytesValue;
+}
\ No newline at end of file
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 94375c0..cf4ce9b 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -70,6 +70,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -1661,6 +1662,14 @@
     public static final int USER_OPERATION_ERROR_MAX_USERS = 6;
 
     /**
+     * Indicates user operation failed because a user with that account already exists.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS = 7;
+
+    /**
      * Result returned from various user operations.
      *
      * @hide
@@ -1673,7 +1682,8 @@
             USER_OPERATION_ERROR_MAX_RUNNING_USERS,
             USER_OPERATION_ERROR_CURRENT_USER,
             USER_OPERATION_ERROR_LOW_STORAGE,
-            USER_OPERATION_ERROR_MAX_USERS
+            USER_OPERATION_ERROR_MAX_USERS,
+            USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS
     })
     public @interface UserOperationResult {}
 
@@ -3159,26 +3169,24 @@
     @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
             Manifest.permission.CREATE_USERS})
     public @NonNull NewUserResponse createUser(@NonNull NewUserRequest newUserRequest) {
-        UserInfo user = null;
-        int operationResult = USER_OPERATION_ERROR_UNKNOWN;
         try {
-            user = createUser(newUserRequest.getName(), newUserRequest.getUserType(),
-                    determineFlagsForUserCreation(newUserRequest));
-        } catch (UserOperationException e) {
-            Log.w(TAG, "Exception while creating user " + newUserRequest, e);
-            operationResult = e.getUserOperationResult();
-        }
-        if (user == null) {
-            return new NewUserResponse(null, operationResult);
-        }
-        return new NewUserResponse(user.getUserHandle(), USER_OPERATION_SUCCESS);
-    }
+            final UserHandle userHandle = mService.createUserWithAttributes(
+                    newUserRequest.getName(),
+                    newUserRequest.getUserType(),
+                    newUserRequest.getFlags(),
+                    newUserRequest.getUserIcon(),
+                    newUserRequest.getAccountName(),
+                    newUserRequest.getAccountType(),
+                    newUserRequest.getAccountOptions());
 
-    private int determineFlagsForUserCreation(NewUserRequest newUserRequest) {
-        int flags = 0;
-        if (newUserRequest.isAdmin()) flags |= UserInfo.FLAG_ADMIN;
-        if (newUserRequest.isEphemeral()) flags |= UserInfo.FLAG_EPHEMERAL;
-        return flags;
+            return new NewUserResponse(userHandle, USER_OPERATION_SUCCESS);
+
+        } catch (ServiceSpecificException e) {
+            Log.w(TAG, "Exception while creating user " + newUserRequest, e);
+            return new NewUserResponse(null, e.errorCode);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -4913,12 +4921,12 @@
     }
 
     /**
-     * @hide
      * Checks if any uninitialized user has the specific seed account name and type.
      *
      * @param accountName The account name to check for
      * @param accountType The account type of the account to check for
      * @return whether the seed account was found
+     * @hide
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
     public boolean someUserHasSeedAccount(String accountName, String accountType) {
@@ -4930,6 +4938,29 @@
     }
 
     /**
+     * Checks if any initialized or uninitialized user has the specific account name and type.
+     *
+     * @param accountName The account name to check for
+     * @param accountType The account type of the account to check for
+     * @return whether the account was found
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS})
+    public boolean someUserHasAccount(
+            @NonNull String accountName, @NonNull String accountType) {
+        Objects.requireNonNull(accountName, "accountName must not be null");
+        Objects.requireNonNull(accountType, "accountType must not be null");
+
+        try {
+            return mService.someUserHasAccount(accountName, accountType);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * @hide
      * User that enforces a restriction.
      *
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 3d59b03..cc95c1f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4537,6 +4537,25 @@
                 "haptic_feedback_intensity";
 
         /**
+         * The intensity of haptic feedback vibrations for interaction with hardware components from
+         * the device, like buttons and sensors, if configurable.
+         *
+         * Not all devices are capable of changing their feedback intensity; on these devices
+         * there will likely be no difference between the various vibration intensities except for
+         * intensity 0 (off) and the rest.
+         *
+         * <b>Values:</b><br/>
+         * 0 - Vibration is disabled<br/>
+         * 1 - Weak vibrations<br/>
+         * 2 - Medium vibrations<br/>
+         * 3 - Strong vibrations
+         * @hide
+         */
+        @Readable
+        public static final String HARDWARE_HAPTIC_FEEDBACK_INTENSITY =
+                "hardware_haptic_feedback_intensity";
+
+        /**
          * Ringer volume. This is used internally, changing this value will not
          * change the volume. See AudioManager.
          *
@@ -16690,12 +16709,6 @@
             public static final String AMBIENT_FORCE_WHEN_DOCKED = "ambient_force_when_docked";
 
             /**
-             * The id of the gesture sensor.
-             * @hide
-             */
-            public static final String AMBIENT_GESTURE_SENSOR_ID = "ambient_gesture_sensor_id";
-
-            /**
              * Whether the ambient low bit mode is enabled.
              * @hide
              */
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 1b38f59..5d84af0 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -3584,6 +3584,23 @@
                 "content://telephony/carriers/enforce_managed");
 
         /**
+         * The {@code content://} style URL for the perferred APN used for internet.
+         *
+         * @hide
+         */
+        public static final Uri PREFERRED_APN_URI = Uri.parse(
+                "content://telephony/carriers/preferapn/subId/");
+
+        /**
+         * The {@code content://} style URL for the perferred APN set id.
+         *
+         * @hide
+         */
+        public static final Uri PREFERRED_APN_SET_URI = Uri.parse(
+                "content://telephony/carriers/preferapnset/subId/");
+
+
+        /**
          * The column name for ENFORCE_MANAGED_URI, indicates whether DPC-owned APNs are enforced.
          * @hide
          */
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 505f400..da3e9b6 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -82,7 +82,9 @@
     /** @hide */
     @IntDef(prefix = { "HYPHENATION_FREQUENCY_" }, value = {
             HYPHENATION_FREQUENCY_NORMAL,
+            HYPHENATION_FREQUENCY_NORMAL_FAST,
             HYPHENATION_FREQUENCY_FULL,
+            HYPHENATION_FREQUENCY_FULL_FAST,
             HYPHENATION_FREQUENCY_NONE
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -95,21 +97,40 @@
      * layout and there is otherwise no valid break. Soft hyphens are ignored and will not be used
      * as suggestions for potential line breaks.
      */
-    public static final int HYPHENATION_FREQUENCY_NONE = LineBreaker.HYPHENATION_FREQUENCY_NONE;
+    public static final int HYPHENATION_FREQUENCY_NONE = 0;
 
     /**
      * Value for hyphenation frequency indicating a light amount of automatic hyphenation, which
      * is a conservative default. Useful for informal cases, such as short sentences or chat
      * messages.
      */
-    public static final int HYPHENATION_FREQUENCY_NORMAL = LineBreaker.HYPHENATION_FREQUENCY_NORMAL;
+    public static final int HYPHENATION_FREQUENCY_NORMAL = 1;
 
     /**
      * Value for hyphenation frequency indicating the full amount of automatic hyphenation, typical
      * in typography. Useful for running text and where it's important to put the maximum amount of
      * text in a screen with limited space.
      */
-    public static final int HYPHENATION_FREQUENCY_FULL = LineBreaker.HYPHENATION_FREQUENCY_FULL;
+    public static final int HYPHENATION_FREQUENCY_FULL = 2;
+
+    /**
+     * Value for hyphenation frequency indicating a light amount of automatic hyphenation with
+     * using faster algorithm.
+     *
+     * This option is useful for informal cases, such as short sentences or chat messages. To make
+     * text rendering faster with hyphenation, this algorithm ignores some hyphen character related
+     * typographic features, e.g. kerning.
+     */
+    public static final int HYPHENATION_FREQUENCY_NORMAL_FAST = 3;
+    /**
+     * Value for hyphenation frequency indicating the full amount of automatic hyphenation with
+     * using faster algorithm.
+     *
+     * This option is useful for running text and where it's important to put the maximum amount of
+     * text in a screen with limited space. To make text rendering faster with hyphenation, this
+     * algorithm ignores some hyphen character related typographic features, e.g. kerning.
+     */
+    public static final int HYPHENATION_FREQUENCY_FULL_FAST = 4;
 
     private static final ParagraphStyle[] NO_PARA_SPANS =
         ArrayUtils.emptyArray(ParagraphStyle.class);
diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java
index 7e41878..6a3c618 100644
--- a/core/java/android/text/MeasuredParagraph.java
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -377,7 +377,7 @@
      * @param start the inclusive start offset of the target region in the text
      * @param end the exclusive end offset of the target region in the text
      * @param textDir the text direction
-     * @param computeHyphenation true if need to compute hyphenation, otherwise false
+     * @param hyphenationMode a hyphenation mode
      * @param computeLayout true if need to compute full layout, otherwise false.
      * @param hint pass if you already have measured paragraph.
      * @param recycle pass existing MeasuredParagraph if you want to recycle it.
@@ -390,7 +390,7 @@
             @IntRange(from = 0) int start,
             @IntRange(from = 0) int end,
             @NonNull TextDirectionHeuristic textDir,
-            boolean computeHyphenation,
+            int hyphenationMode,
             boolean computeLayout,
             @Nullable MeasuredParagraph hint,
             @Nullable MeasuredParagraph recycle) {
@@ -399,7 +399,7 @@
         final MeasuredText.Builder builder;
         if (hint == null) {
             builder = new MeasuredText.Builder(mt.mCopiedBuffer)
-                    .setComputeHyphenation(computeHyphenation)
+                    .setComputeHyphenation(hyphenationMode)
                     .setComputeLayout(computeLayout);
         } else {
             builder = new MeasuredText.Builder(hint.mMeasuredText);
diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java
index 08741d6..152570f 100644
--- a/core/java/android/text/PrecomputedText.java
+++ b/core/java/android/text/PrecomputedText.java
@@ -22,6 +22,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.Rect;
+import android.graphics.text.MeasuredText;
 import android.text.style.MetricAffectingSpan;
 
 import com.android.internal.util.Preconditions;
@@ -395,17 +396,30 @@
         return new PrecomputedText(text, 0, text.length(), params, paraInfo);
     }
 
+    private static boolean isFastHyphenation(int frequency) {
+        return frequency == Layout.HYPHENATION_FREQUENCY_FULL_FAST
+                || frequency == Layout.HYPHENATION_FREQUENCY_NORMAL_FAST;
+    }
+
     private static ParagraphInfo[] createMeasuredParagraphsFromPrecomputedText(
             @NonNull PrecomputedText pct, @NonNull Params params, boolean computeLayout) {
         final boolean needHyphenation = params.getBreakStrategy() != Layout.BREAK_STRATEGY_SIMPLE
                 && params.getHyphenationFrequency() != Layout.HYPHENATION_FREQUENCY_NONE;
+        final int hyphenationMode;
+        if (needHyphenation) {
+            hyphenationMode = isFastHyphenation(params.getHyphenationFrequency())
+                    ? MeasuredText.Builder.HYPHENATION_MODE_FAST :
+                    MeasuredText.Builder.HYPHENATION_MODE_NORMAL;
+        } else {
+            hyphenationMode = MeasuredText.Builder.HYPHENATION_MODE_NONE;
+        }
         ArrayList<ParagraphInfo> result = new ArrayList<>();
         for (int i = 0; i < pct.getParagraphCount(); ++i) {
             final int paraStart = pct.getParagraphStart(i);
             final int paraEnd = pct.getParagraphEnd(i);
             result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout(
                     params.getTextPaint(), pct, paraStart, paraEnd, params.getTextDirection(),
-                    needHyphenation, computeLayout, pct.getMeasuredParagraph(i),
+                    hyphenationMode, computeLayout, pct.getMeasuredParagraph(i),
                     null /* no recycle */)));
         }
         return result.toArray(new ParagraphInfo[result.size()]);
@@ -421,6 +435,14 @@
         Preconditions.checkNotNull(params);
         final boolean needHyphenation = params.getBreakStrategy() != Layout.BREAK_STRATEGY_SIMPLE
                 && params.getHyphenationFrequency() != Layout.HYPHENATION_FREQUENCY_NONE;
+        final int hyphenationMode;
+        if (needHyphenation) {
+            hyphenationMode = isFastHyphenation(params.getHyphenationFrequency())
+                    ? MeasuredText.Builder.HYPHENATION_MODE_FAST :
+                    MeasuredText.Builder.HYPHENATION_MODE_NORMAL;
+        } else {
+            hyphenationMode = MeasuredText.Builder.HYPHENATION_MODE_NONE;
+        }
 
         int paraEnd = 0;
         for (int paraStart = start; paraStart < end; paraStart = paraEnd) {
@@ -435,8 +457,7 @@
 
             result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout(
                     params.getTextPaint(), text, paraStart, paraEnd, params.getTextDirection(),
-                    needHyphenation, computeLayout, null /* no hint */,
-                    null /* no recycle */)));
+                    hyphenationMode, computeLayout, null /* no hint */, null /* no recycle */)));
         }
         return result.toArray(new ParagraphInfo[result.size()]);
     }
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index f99d430..6984e4d 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -591,6 +591,20 @@
         generate(b, b.mIncludePad, b.mIncludePad);
     }
 
+    private static int getBaseHyphenationFrequency(int frequency) {
+        switch (frequency) {
+            case Layout.HYPHENATION_FREQUENCY_FULL:
+            case Layout.HYPHENATION_FREQUENCY_FULL_FAST:
+                return LineBreaker.HYPHENATION_FREQUENCY_FULL;
+            case Layout.HYPHENATION_FREQUENCY_NORMAL:
+            case Layout.HYPHENATION_FREQUENCY_NORMAL_FAST:
+                return LineBreaker.HYPHENATION_FREQUENCY_NORMAL;
+            case Layout.HYPHENATION_FREQUENCY_NONE:
+            default:
+                return LineBreaker.HYPHENATION_FREQUENCY_NONE;
+        }
+    }
+
     /* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
         final CharSequence source = b.mText;
         final int bufStart = b.mStart;
@@ -641,7 +655,7 @@
 
         final LineBreaker lineBreaker = new LineBreaker.Builder()
                 .setBreakStrategy(b.mBreakStrategy)
-                .setHyphenationFrequency(b.mHyphenationFrequency)
+                .setHyphenationFrequency(getBaseHyphenationFrequency(b.mHyphenationFrequency))
                 // TODO: Support more justification mode, e.g. letter spacing, stretching.
                 .setJustificationMode(b.mJustificationMode)
                 .setIndents(indents)
diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java
index ec613ed..c5bc99d 100644
--- a/core/java/android/view/HapticFeedbackConstants.java
+++ b/core/java/android/view/HapticFeedbackConstants.java
@@ -153,11 +153,20 @@
 
     /**
      * Invocation of the voice assistant via hardware button.
+     * This is a private constant.  Feel free to renumber as desired.
      * @hide
      */
     public static final int ASSISTANT_BUTTON = 10002;
 
     /**
+     * The user has performed a long press on the power button hardware that is resulting
+     * in an action being performed.
+     * This is a private constant.  Feel free to renumber as desired.
+     * @hide
+     */
+    public static final int LONG_PRESS_POWER_BUTTON = 10003;
+
+    /**
      * Flag for {@link View#performHapticFeedback(int, int)
      * View.performHapticFeedback(int, int)}: Ignore the setting in the
      * view for whether to perform haptic feedback, do it always.
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 1460cb2..c3a638c 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -295,7 +295,8 @@
     /** Key code constant: Fast Forward media key. */
     public static final int KEYCODE_MEDIA_FAST_FORWARD = 90;
     /** Key code constant: Mute key.
-     * Mutes the microphone, unlike {@link #KEYCODE_VOLUME_MUTE}. */
+     * Mute key for the microphone (unlike {@link #KEYCODE_VOLUME_MUTE}, which is the speaker mute
+     * key). */
     public static final int KEYCODE_MUTE            = 91;
     /** Key code constant: Page Up key. */
     public static final int KEYCODE_PAGE_UP         = 92;
@@ -482,9 +483,10 @@
     /** Key code constant: Numeric keypad ')' key. */
     public static final int KEYCODE_NUMPAD_RIGHT_PAREN = 163;
     /** Key code constant: Volume Mute key.
-     * Mutes the speaker, unlike {@link #KEYCODE_MUTE}.
-     * This key should normally be implemented as a toggle such that the first press
-     * mutes the speaker and the second press restores the original volume. */
+     * Mute key for speaker (unlike {@link #KEYCODE_MUTE}, which is the mute key for the
+     * microphone). This key should normally be implemented as a toggle such that the first press
+     * mutes the speaker and the second press restores the original volume.
+     */
     public static final int KEYCODE_VOLUME_MUTE     = 164;
     /** Key code constant: Info key.
      * Common on TV remotes to show additional information related to what is
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 3b4fcc0..7fe810a 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -129,17 +129,15 @@
 
 /**
  * The interface that apps use to talk to the window manager.
- * </p><p>
- * Each window manager instance is bound to a particular {@link Display}.
- * To obtain a {@link WindowManager} for a different display, use
- * {@link Context#createDisplayContext} to obtain a {@link Context} for that
- * display, then use <code>Context.getSystemService(Context.WINDOW_SERVICE)</code>
- * to get the WindowManager.
- * </p><p>
- * The simplest way to show a window on another display is to create a
- * {@link Presentation}.  The presentation will automatically obtain a
- * {@link WindowManager} and {@link Context} for that display.
- * </p>
+ * <p>
+ * Each window manager instance is bound to a {@link Display}. To obtain the
+ * <code>WindowManager</code> associated with a display,
+ * call {@link Context#createWindowContext(Display, int, Bundle)} to get the display's UI context,
+ * then call {@link Context#getSystemService(String)} or {@link Context#getSystemService(Class)} on
+ * the UI context.
+ * <p>
+ * The simplest way to show a window on a particular display is to create a {@link Presentation},
+ * which automatically obtains a <code>WindowManager</code> and context for the display.
  */
 @SystemService(Context.WINDOW_SERVICE)
 public interface WindowManager extends ViewManager {
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index fd9ad0d..359c382 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -186,6 +186,17 @@
             = "com.android.internal.app.ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP";
 
     /**
+     * Boolean extra added to "unbundled Sharesheet" delegation intents to signal whether the app
+     * prediction service is available. Our query of the service <em>availability</em> depends on
+     * privileges that are only available in the system, even though the service itself would then
+     * be available to the unbundled component. For now, we just include the query result as part of
+     * the handover intent.
+     * TODO: investigate whether the privileged query is necessary to determine the availability.
+     */
+    protected static final String EXTRA_IS_APP_PREDICTION_SERVICE_AVAILABLE =
+            "com.android.internal.app.ChooserActivity.EXTRA_IS_APP_PREDICTION_SERVICE_AVAILABLE";
+
+    /**
      * Transition name for the first image preview.
      * To be used for shared element transition into this activity.
      * @hide
@@ -757,6 +768,11 @@
             delegationIntent.setComponent(delegateActivity);
             delegationIntent.putExtra(Intent.EXTRA_INTENT, getIntent());
             delegationIntent.putExtra(ActivityTaskManager.EXTRA_PERMISSION_TOKEN, permissionToken);
+
+            // Query prediction availability; mIsAppPredictorComponentAvailable isn't initialized.
+            delegationIntent.putExtra(
+                    EXTRA_IS_APP_PREDICTION_SERVICE_AVAILABLE, isAppPredictionServiceAvailable());
+
             delegationIntent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
 
             // Don't close until the delegate finishes, or the token will be invalidated.
@@ -971,7 +987,8 @@
             return false;
         }
 
-        // Check if the app prediction component actually exists on the device.
+        // Check if the app prediction component actually exists on the device. The component is
+        // only visible when this is running in a system activity; otherwise this check will fail.
         Intent intent = new Intent();
         intent.setComponent(appPredictionComponentName);
         if (getPackageManager().resolveService(intent, PackageManager.MATCH_ALL) == null) {
diff --git a/core/java/com/android/internal/os/ClassLoaderFactory.java b/core/java/com/android/internal/os/ClassLoaderFactory.java
index 8b0411d..8f5e97d 100644
--- a/core/java/com/android/internal/os/ClassLoaderFactory.java
+++ b/core/java/com/android/internal/os/ClassLoaderFactory.java
@@ -24,6 +24,7 @@
 import dalvik.system.DexClassLoader;
 import dalvik.system.PathClassLoader;
 
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -100,14 +101,25 @@
     }
 
     /**
-     * Same as {@code createClassLoader} below, but passes a null list of shared
-     * libraries.
+     * Same as {@code createClassLoader} below, but passes a null list of shared libraries. This
+     * method is used only to load platform classes (i.e. those in framework.jar or services.jar),
+     * and MUST NOT be used for loading untrusted classes, especially the app classes. For the
+     * latter case, use the below method which accepts list of shared libraries so that the classes
+     * don't have unlimited access to all shared libraries.
      */
     public static ClassLoader createClassLoader(String dexPath,
             String librarySearchPath, String libraryPermittedPath, ClassLoader parent,
             int targetSdkVersion, boolean isNamespaceShared, String classLoaderName) {
+        // b/205164833: allow framework classes to have access to all public vendor libraries.
+        // This is because those classes are part of the platform and don't have an app manifest
+        // where required libraries can be specified using the <uses-native-library> tag.
+        // Note that this still does not give access to "private" vendor libraries.
+        List<String> nativeSharedLibraries = new ArrayList<>();
+        nativeSharedLibraries.add("ALL");
+
         return createClassLoader(dexPath, librarySearchPath, libraryPermittedPath,
-            parent, targetSdkVersion, isNamespaceShared, classLoaderName, null, null, null);
+            parent, targetSdkVersion, isNamespaceShared, classLoaderName, null,
+            nativeSharedLibraries, null);
     }
 
     /**
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d3ee98a..601280a 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6395,6 +6395,10 @@
                  android:permission="android.permission.BIND_JOB_SERVICE">
         </service>
 
+        <service android:name="com.android.server.compos.IsolatedCompilationJobService"
+                 android:permission="android.permission.BIND_JOB_SERVICE">
+        </service>
+
         <service android:name="com.android.server.PruneInstantAppsJobService"
                  android:permission="android.permission.BIND_JOB_SERVICE" >
         </service>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 1380165..697ec20 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5383,6 +5383,16 @@
             <!-- Standard amount of hyphenation, useful for running text and for
             screens with limited space for text. -->
             <enum name="full" value="2" />
+
+            <!-- Same to hyphenationFrequency="normal" but using faster algorithm for measuring
+            hyphenation break points. To make text rendering faster with hyphenation, this algorithm
+             ignores some hyphen character related typographic features, e.g. kerning. -->
+            <enum name="normalFast" value="3" />
+
+            <!-- Same to hyphenationFrequency="full" but using faster algorithm for measuring
+            hyphenation break points. To make text rendering faster with hyphenation, this algorithm
+             ignores some hyphen character related typographic features, e.g. kerning. -->
+            <enum name="fullFast" value="4" />
         </attr>
         <!-- Specify the type of auto-size. Note that this feature is not supported by EditText,
         works only for TextView. -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 7805d46..94717b1 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1991,6 +1991,12 @@
 
         <!-- Whether attributions provided are meant to be user-visible. -->
         <attr name="attributionsAreUserVisible" format="boolean" />
+
+        <!-- Specifies whether enabled settings of components in the application should be
+             reset to {@link android.content.pm.PackageManager#COMPONENT_ENABLED_STATE_DEFAULT}
+             when the application's user data is cleared. The default value is false.
+        -->
+        <attr name="resetEnabledSettingsOnAppDataCleared" format="boolean" />
     </declare-styleable>
 
     <!-- An attribution is a logical part of an app and is identified by a tag.
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index a2dadb9..dc548b9 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3305,6 +3305,7 @@
     <public name="splitTypes" />
     <public name="canDisplayOnRemoteDevices" />
     <public name="supportedTypes" />
+    <public name="resetEnabledSettingsOnAppDataCleared" />
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01de0000">
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 34c1763..37cf514 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -43,6 +43,7 @@
 import android.graphics.Color;
 import android.graphics.drawable.Icon;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.Spannable;
@@ -545,6 +546,29 @@
         validateColorizedPaletteForColor(Color.BLACK);
     }
 
+    @Test
+    public void testIsMediaNotification_nullSession_returnsFalse() {
+        // Null media session
+        Notification.MediaStyle mediaStyle = new Notification.MediaStyle();
+        Notification notification = new Notification.Builder(mContext, "test id")
+                .setStyle(mediaStyle)
+                .build();
+        assertFalse(notification.isMediaNotification());
+    }
+
+    @Test
+    public void testIsMediaNotification_invalidSession_returnsFalse() {
+        // Extra was set manually to an invalid type
+        Bundle extras = new Bundle();
+        extras.putParcelable(Notification.EXTRA_MEDIA_SESSION, new Intent());
+        Notification.MediaStyle mediaStyle = new Notification.MediaStyle();
+        Notification notification = new Notification.Builder(mContext, "test id")
+                .setStyle(mediaStyle)
+                .addExtras(extras)
+                .build();
+        assertFalse(notification.isMediaNotification());
+    }
+
     public void validateColorizedPaletteForColor(int rawColor) {
         Notification.Colors cDay = new Notification.Colors();
         Notification.Colors cNight = new Notification.Colors();
diff --git a/core/tests/coretests/src/android/text/MeasuredParagraphTest.java b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
index 57bb434..d6a7682 100644
--- a/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
+++ b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
@@ -23,6 +23,7 @@
 
 import android.content.Context;
 import android.graphics.Typeface;
+import android.graphics.text.MeasuredText;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -135,7 +136,8 @@
         MeasuredParagraph mt = null;
 
         mt = MeasuredParagraph.buildForStaticLayout(
-                PAINT, "XXX", 0, 3, LTR, false, false, null /* no hint */, null);
+                PAINT, "XXX", 0, 3, LTR, MeasuredText.Builder.HYPHENATION_MODE_NONE, false,
+                null /* no hint */, null);
         assertNotNull(mt);
         assertNotNull(mt.getChars());
         assertEquals("XXX", charsToString(mt.getChars()));
@@ -150,7 +152,8 @@
 
         // Recycle it
         MeasuredParagraph mt2 = MeasuredParagraph.buildForStaticLayout(
-                PAINT, "_VVV_", 1, 4, RTL, false, false, null /* no hint */, mt);
+                PAINT, "_VVV_", 1, 4, RTL, MeasuredText.Builder.HYPHENATION_MODE_NONE, false,
+                null /* no hint */, mt);
         assertEquals(mt2, mt);
         assertNotNull(mt2.getChars());
         assertEquals("VVV", charsToString(mt.getChars()));
diff --git a/data/etc/com.android.intentresolver.xml b/data/etc/com.android.intentresolver.xml
index 0f1c467..f4e94ad 100644
--- a/data/etc/com.android.intentresolver.xml
+++ b/data/etc/com.android.intentresolver.xml
@@ -18,5 +18,6 @@
     <privapp-permissions package="com.android.intentresolver">
         <permission name="android.permission.INTERACT_ACROSS_USERS"/>
         <permission name="android.permission.MANAGE_USERS"/>
+        <permission name="android.permission.PACKAGE_USAGE_STATS"/>
     </privapp-permissions>
 </permissions>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 8b37805..8ccf02ca 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -961,6 +961,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/RootWindowContainer.java"
     },
+    "-1101551167": {
+      "message": "Auto-PIP allowed, entering PIP mode directly: %s, didAutoPip: %b",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/TaskFragment.java"
+    },
     "-1089874824": {
       "message": "SURFACE SHOW (performLayout): %s",
       "level": "INFO",
@@ -2629,12 +2635,6 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "660908897": {
-      "message": "Auto-PIP allowed, entering PIP mode directly: %s",
-      "level": "DEBUG",
-      "group": "WM_DEBUG_STATES",
-      "at": "com\/android\/server\/wm\/TaskFragment.java"
-    },
     "662572728": {
       "message": "Attempted to add a toast window with bad token %s.  Aborting.",
       "level": "WARN",
diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java
index 31c3d09..df5b3f5 100644
--- a/graphics/java/android/graphics/text/MeasuredText.java
+++ b/graphics/java/android/graphics/text/MeasuredText.java
@@ -17,12 +17,14 @@
 package android.graphics.text;
 
 import android.annotation.FloatRange;
+import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.Px;
 import android.graphics.Paint;
 import android.graphics.Rect;
+import android.util.Log;
 
 import com.android.internal.util.Preconditions;
 
@@ -30,6 +32,9 @@
 
 import libcore.util.NativeAllocationRegistry;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Result of text shaping of the single paragraph string.
  *
@@ -49,6 +54,8 @@
  * </p>
  */
 public class MeasuredText {
+    private static final String TAG = "MeasuredText";
+
     private long mNativePtr;
     private boolean mComputeHyphenation;
     private boolean mComputeLayout;
@@ -179,6 +186,7 @@
         private final @NonNull char[] mText;
         private boolean mComputeHyphenation = false;
         private boolean mComputeLayout = true;
+        private boolean mFastHyphenation = false;
         private int mCurrentOffset = 0;
         private @Nullable MeasuredText mHintMt = null;
 
@@ -275,10 +283,78 @@
          * Even if you pass false to this method, you can still enable automatic hyphenation of
          * LineBreaker but line break computation becomes slower.
          *
+         * @deprecated use setComputeHyphenation(int) instead.
+         *
          * @param computeHyphenation true if you want to use automatic hyphenations.
          */
         public @NonNull Builder setComputeHyphenation(boolean computeHyphenation) {
-            mComputeHyphenation = computeHyphenation;
+            setComputeHyphenation(
+                    computeHyphenation ? HYPHENATION_MODE_NORMAL : HYPHENATION_MODE_NONE);
+            return this;
+        }
+
+        /** @hide */
+        @IntDef(prefix = { "HYPHENATION_MODE_" }, value = {
+                HYPHENATION_MODE_NONE,
+                HYPHENATION_MODE_NORMAL,
+                HYPHENATION_MODE_FAST
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface HyphenationMode {}
+
+        /**
+         * A value for hyphenation calculation mode.
+         *
+         * This value indicates that no hyphenation points are calculated.
+         */
+        public static final int HYPHENATION_MODE_NONE = 0;
+
+        /**
+         * A value for hyphenation calculation mode.
+         *
+         * This value indicates that hyphenation points are calculated.
+         */
+        public static final int HYPHENATION_MODE_NORMAL = 1;
+
+        /**
+         * A value for hyphenation calculation mode.
+         *
+         * This value indicates that hyphenation points are calculated with faster algorithm. This
+         * algorithm measures text width with ignoring the context of hyphen character shaping, e.g.
+         * kerning.
+         */
+        public static final int HYPHENATION_MODE_FAST = 2;
+
+        /**
+         * By passing true to this method, the build method will calculate hyphenation break
+         * points faster with ignoring some typographic features, e.g. kerning.
+         *
+         * {@link #HYPHENATION_MODE_NONE} is by default.
+         *
+         * @see #setComputeHyphenation(boolean)
+         *
+         * @param mode a hyphenation mode.
+         */
+        public @NonNull Builder setComputeHyphenation(@HyphenationMode int mode) {
+            switch (mode) {
+                case HYPHENATION_MODE_NONE:
+                    mComputeHyphenation = false;
+                    mFastHyphenation = false;
+                    break;
+                case HYPHENATION_MODE_NORMAL:
+                    mComputeHyphenation = true;
+                    mFastHyphenation = false;
+                    break;
+                case HYPHENATION_MODE_FAST:
+                    mComputeHyphenation = true;
+                    mFastHyphenation = true;
+                    break;
+                default:
+                    Log.e(TAG, "Unknown hyphenation mode: " + mode);
+                    mComputeHyphenation = false;
+                    mFastHyphenation = false;
+                    break;
+            }
             return this;
         }
 
@@ -319,7 +395,7 @@
             try {
                 long hintPtr = (mHintMt == null) ? 0 : mHintMt.getNativePtr();
                 long ptr = nBuildMeasuredText(mNativePtr, hintPtr, mText, mComputeHyphenation,
-                        mComputeLayout);
+                        mComputeLayout, mFastHyphenation);
                 final MeasuredText res = new MeasuredText(ptr, mText, mComputeHyphenation,
                         mComputeLayout);
                 sRegistry.registerNativeAllocation(res, ptr);
@@ -378,7 +454,8 @@
                 long hintMtPtr,
                 @NonNull char[] text,
                 boolean computeHyphenation,
-                boolean computeLayout);
+                boolean computeLayout,
+                boolean fastHyphenationMode);
 
         private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr);
     }
diff --git a/libs/WindowManager/Shell/res/color/size_compat_background_ripple.xml b/libs/WindowManager/Shell/res/color/size_compat_background_ripple.xml
new file mode 100644
index 0000000..329e5b9
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/size_compat_background_ripple.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@android:color/system_neutral1_500" android:lStar="35" />
+</selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml b/libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml
index 94165a1..22cd384 100644
--- a/libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml
+++ b/libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml
@@ -16,6 +16,6 @@
   -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
-    <solid android:color="@color/size_compat_hint_bubble"/>
+    <solid android:color="@color/size_compat_background"/>
     <corners android:radius="@dimen/size_compat_hint_corner_radius"/>
 </shape>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_hint_point.xml b/libs/WindowManager/Shell/res/drawable/size_compat_hint_point.xml
index a8f0f76..af9063a 100644
--- a/libs/WindowManager/Shell/res/drawable/size_compat_hint_point.xml
+++ b/libs/WindowManager/Shell/res/drawable/size_compat_hint_point.xml
@@ -20,6 +20,6 @@
         android:viewportWidth="10"
         android:viewportHeight="8">
     <path
-        android:fillColor="@color/size_compat_hint_bubble"
+        android:fillColor="@color/size_compat_background"
         android:pathData="M10,0 l-4.1875,6.6875 a1,1 0 0,1 -1.625,0 l-4.1875,-6.6875z"/>
 </vector>
diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
index 3e486df..18caa35 100644
--- a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
+++ b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
@@ -20,16 +20,16 @@
         android:viewportWidth="48"
         android:viewportHeight="48">
     <path
-        android:fillColor="#53534D"
+        android:fillColor="@color/size_compat_background"
         android:pathData="M0,24 a24,24 0 1,0 48,0 a24,24 0 1,0 -48,0" />
     <group
         android:translateX="12"
         android:translateY="12">
         <path
-            android:fillColor="#E4E3DA"
+            android:fillColor="@color/size_compat_text"
             android:pathData="M6,13c0,-1.65 0.67,-3.15 1.76,-4.24L6.34,7.34C4.9,8.79 4,10.79 4,13c0,4.08 3.05,7.44 7,7.93v-2.02C8.17,18.43 6,15.97 6,13z"/>
         <path
-            android:fillColor="#E4E3DA"
+            android:fillColor="@color/size_compat_text"
             android:pathData="M20,13c0,-4.42 -3.58,-8 -8,-8c-0.06,0 -0.12,0.01 -0.18,0.01v0l1.09,-1.09L11.5,2.5L8,6l3.5,3.5l1.41,-1.41l-1.08,-1.08C11.89,7.01 11.95,7 12,7c3.31,0 6,2.69 6,6c0,2.97 -2.17,5.43 -5,5.91v2.02C16.95,20.44 20,17.08 20,13z"/>
     </group>
 </vector>
diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button_ripple.xml b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button_ripple.xml
new file mode 100644
index 0000000..95decff
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button_ripple.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+        android:color="@color/size_compat_background_ripple">
+    <item android:drawable="@drawable/size_compat_restart_button"/>
+</ripple>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml b/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml
index 17347f6..d0e7c42 100644
--- a/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml
+++ b/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml
@@ -40,7 +40,7 @@
                 android:padding="16dp"
                 android:text="@string/restart_button_description"
                 android:textAlignment="viewStart"
-                android:textColor="#E4E3DA"
+                android:textColor="@color/size_compat_text"
                 android:textSize="14sp"/>
 
             <ImageView
diff --git a/libs/WindowManager/Shell/res/layout/size_compat_ui.xml b/libs/WindowManager/Shell/res/layout/size_compat_ui.xml
index 47e76f0..82ebee2 100644
--- a/libs/WindowManager/Shell/res/layout/size_compat_ui.xml
+++ b/libs/WindowManager/Shell/res/layout/size_compat_ui.xml
@@ -30,7 +30,7 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_gravity="center"
-            android:src="@drawable/size_compat_restart_button"
+            android:src="@drawable/size_compat_restart_button_ripple"
             android:background="@android:color/transparent"
             android:contentDescription="@string/restart_button_description"/>
 
diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml
index b25a218..23a2172 100644
--- a/libs/WindowManager/Shell/res/values/colors.xml
+++ b/libs/WindowManager/Shell/res/values/colors.xml
@@ -29,7 +29,10 @@
     <color name="bubbles_light">#FFFFFF</color>
     <color name="bubbles_dark">@color/GM2_grey_800</color>
     <color name="bubbles_icon_tint">@color/GM2_grey_700</color>
-    <color name="size_compat_hint_bubble">#30312B</color>
+
+    <!-- Size Compat Restart Button -->
+    <color name="size_compat_background">@android:color/system_neutral1_800</color>
+    <color name="size_compat_text">@android:color/system_neutral1_50</color>
 
     <!-- GM2 colors -->
     <color name="GM2_grey_200">#E8EAED</color>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
index 7784665..6f4e22f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
@@ -93,6 +93,19 @@
     private boolean mReverseDefaultRotation = false;
     private InsetsState mInsetsState = new InsetsState();
 
+    /**
+     * Different from {@link #equals(Object)}, this method compares the basic geometry properties
+     * of two {@link DisplayLayout} objects including width, height, rotation, density and cutout.
+     * @return {@code true} if the given {@link DisplayLayout} is identical geometry wise.
+     */
+    public boolean isSameGeometry(@NonNull DisplayLayout other) {
+        return mWidth == other.mWidth
+                && mHeight == other.mHeight
+                && mRotation == other.mRotation
+                && mDensityDpi == other.mDensityDpi
+                && Objects.equals(mCutout, other.mCutout);
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
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 625bcee..d07fff3 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
@@ -34,6 +34,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -61,6 +62,8 @@
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
 
+import java.io.PrintWriter;
+
 /**
  * Records and handles layout of splits. Helps to calculate proper bounds when configuration or
  * divide position changes.
@@ -415,6 +418,19 @@
         return bounds.width() > bounds.height();
     }
 
+    /** Reverse the split position. */
+    @SplitPosition
+    public static int reversePosition(@SplitPosition int position) {
+        switch (position) {
+            case SPLIT_POSITION_TOP_OR_LEFT:
+                return SPLIT_POSITION_BOTTOM_OR_RIGHT;
+            case SPLIT_POSITION_BOTTOM_OR_RIGHT:
+                return SPLIT_POSITION_TOP_OR_LEFT;
+            default:
+                return SPLIT_POSITION_UNDEFINED;
+        }
+    }
+
     /**
      * Return if this layout is landscape.
      */
@@ -502,6 +518,13 @@
         }
     }
 
+    /** Dumps the current split bounds recorded in this layout. */
+    public void dump(@NonNull PrintWriter pw, String prefix) {
+        pw.println(prefix + "bounds1=" + mBounds1.toShortString());
+        pw.println(prefix + "dividerBounds=" + mDividerBounds.toShortString());
+        pw.println(prefix + "bounds2=" + mBounds2.toShortString());
+    }
+
     /** Handles layout change event. */
     public interface SplitLayoutHandler {
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index d239e56..9ceed24 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -244,15 +244,24 @@
     // Fullscreen
     //
 
+    // Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
+    @BindsOptionalOf
+    @DynamicOverride
+    abstract FullscreenTaskListener optionalFullscreenTaskListener();
+
     @WMSingleton
     @Provides
     static FullscreenTaskListener provideFullscreenTaskListener(
+            @DynamicOverride Optional<FullscreenTaskListener> fullscreenTaskListener,
             SyncTransactionQueue syncQueue,
             Optional<FullscreenUnfoldController> optionalFullscreenUnfoldController,
-            Optional<RecentTasksController> recentTasksOptional
-    ) {
-        return new FullscreenTaskListener(syncQueue, optionalFullscreenUnfoldController,
-                recentTasksOptional);
+            Optional<RecentTasksController> recentTasksOptional) {
+        if (fullscreenTaskListener.isPresent()) {
+            return fullscreenTaskListener.get();
+        } else {
+            return new FullscreenTaskListener(syncQueue, optionalFullscreenUnfoldController,
+                    recentTasksOptional);
+        }
     }
 
     //
@@ -337,7 +346,7 @@
     @Provides
     static Optional<OneHandedController> providesOneHandedController(
             @DynamicOverride Optional<OneHandedController> oneHandedController) {
-        if (!SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false)) {
+        if (SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false)) {
             return oneHandedController;
         }
         return Optional.empty();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 05111a3..9575b0a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -594,10 +594,10 @@
                         getSurfaceTransactionHelper().scaleAndCrop(tx, leash,
                                 initialSourceValue, bounds, insets);
                         if (shouldApplyCornerRadius()) {
-                            final Rect destinationBounds = new Rect(bounds);
-                            destinationBounds.inset(insets);
+                            final Rect sourceBounds = new Rect(initialContainerRect);
+                            sourceBounds.inset(insets);
                             getSurfaceTransactionHelper().round(tx, leash,
-                                    initialContainerRect, destinationBounds);
+                                    sourceBounds, bounds);
                         }
                     }
                     if (!handlePipTransaction(leash, tx, bounds)) {
@@ -641,11 +641,13 @@
                             y = fraction * (end.bottom - start.top) + start.top;
                         }
                     }
+                    final Rect sourceBounds = new Rect(initialContainerRect);
+                    sourceBounds.inset(insets);
                     getSurfaceTransactionHelper()
                             .rotateAndScaleWithCrop(tx, leash, initialContainerRect, bounds,
                                     insets, degree, x, y, isOutPipDirection,
                                     rotationDelta == ROTATION_270 /* clockwise */)
-                            .round(tx, leash, initialContainerRect, bounds);
+                            .round(tx, leash, sourceBounds, bounds);
                     tx.apply();
                 }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index d18bcfc..79e3444 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -88,7 +88,6 @@
 import com.android.wm.shell.transition.Transitions;
 
 import java.io.PrintWriter;
-import java.util.Objects;
 import java.util.Optional;
 import java.util.function.Consumer;
 
@@ -469,7 +468,7 @@
     }
 
     private void onDisplayChanged(DisplayLayout layout, boolean saveRestoreSnapFraction) {
-        if (Objects.equals(layout, mPipBoundsState.getDisplayLayout())) {
+        if (mPipBoundsState.getDisplayLayout().isSameGeometry(layout)) {
             return;
         }
         Runnable updateDisplayLayout = () -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 00083d9..83390a5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -57,7 +57,7 @@
 public class TvPipController implements PipTransitionController.PipTransitionCallback,
         TvPipMenuController.Delegate, TvPipNotificationController.Delegate {
     private static final String TAG = "TvPipController";
-    static final boolean DEBUG = true;
+    static final boolean DEBUG = false;
 
     private static final int NONEXISTENT_TASK_ID = -1;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 7cf3baf..a006f30 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -111,6 +111,11 @@
         if (taskId1 == taskId2) {
             return;
         }
+        if (mSplitTasks.get(taskId1, INVALID_TASK_ID) == taskId2
+                && mTaskSplitBoundsMap.get(taskId1).equals(splitBounds)) {
+            // If the two tasks are already paired and the bounds are the same, then skip updating
+            return;
+        }
         // Remove any previous pairs
         removeSplitPair(taskId1);
         removeSplitPair(taskId2);
@@ -121,6 +126,7 @@
         mSplitTasks.put(taskId2, taskId1);
         mTaskSplitBoundsMap.put(taskId1, splitBounds);
         mTaskSplitBoundsMap.put(taskId2, splitBounds);
+        notifyRecentTasksChanged();
     }
 
     /**
@@ -133,6 +139,7 @@
             mSplitTasks.delete(pairedTaskId);
             mTaskSplitBoundsMap.remove(taskId);
             mTaskSplitBoundsMap.remove(pairedTaskId);
+            notifyRecentTasksChanged();
         }
     }
 
@@ -217,7 +224,7 @@
             }
 
             final int pairedTaskId = mSplitTasks.get(taskInfo.taskId);
-            if (pairedTaskId != INVALID_TASK_ID) {
+            if (pairedTaskId != INVALID_TASK_ID && rawMapping.contains(pairedTaskId)) {
                 final ActivityManager.RecentTaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId);
                 rawMapping.remove(pairedTaskId);
                 recentTasks.add(new GroupedRecentTaskInfo(taskInfo, pairedTaskInfo,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
index f8c0304..1ba1d22 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
@@ -50,10 +50,6 @@
         wct.setBounds(rootToken, rootBounds).reorder(rootToken, true /* onTop */);
     }
 
-    void addTask(ActivityManager.RunningTaskInfo task, WindowContainerTransaction wct) {
-        wct.reparent(task.token, mRootTaskInfo.token, true /* onTop*/);
-    }
-
     boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) {
         // No matter if the root task is empty or not, moving the root to bottom because it no
         // longer preserves visible child task.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index e86462f..02edaa0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -87,6 +87,12 @@
      */
     void onKeyguardVisibilityChanged(boolean showing);
 
+    /** Called when device waking up finished. */
+    void onFinishedWakingUp();
+
+    /** Called when device going to sleep finished. */
+    void onFinishedGoingToSleep();
+
     /** Get a string representation of a stage type */
     static String stageTypeToString(@StageType int stage) {
         switch (stage) {
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 8af72a8..6b42ed7 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
@@ -24,6 +24,8 @@
 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
 import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
 
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
@@ -182,30 +184,17 @@
     }
 
     public boolean moveToSideStage(int taskId, @SplitPosition int sideStagePosition) {
+        return moveToStage(taskId, STAGE_TYPE_SIDE, sideStagePosition,
+                new WindowContainerTransaction());
+    }
+
+    private boolean moveToStage(int taskId, @SplitScreen.StageType int stageType,
+            @SplitPosition int stagePosition, WindowContainerTransaction wct) {
         final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId);
         if (task == null) {
             throw new IllegalArgumentException("Unknown taskId" + taskId);
         }
-        return moveToSideStage(task, sideStagePosition);
-    }
-
-    public boolean moveToSideStage(int taskId, @SplitPosition int sideStagePosition,
-            WindowContainerTransaction wct) {
-        final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId);
-        if (task == null) {
-            throw new IllegalArgumentException("Unknown taskId" + taskId);
-        }
-        return moveToSideStage(task, sideStagePosition, wct);
-    }
-
-    public boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
-            @SplitPosition int sideStagePosition) {
-        return mStageCoordinator.moveToSideStage(task, sideStagePosition);
-    }
-
-    public boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
-            @SplitPosition int sideStagePosition, WindowContainerTransaction wct) {
-        return mStageCoordinator.moveToSideStage(task, sideStagePosition, wct);
+        return mStageCoordinator.moveToStage(task, stageType, stagePosition, wct);
     }
 
     public boolean removeFromSideStage(int taskId) {
@@ -221,13 +210,14 @@
     }
 
     public void enterSplitScreen(int taskId, boolean leftOrTop) {
-        moveToSideStage(taskId,
-                leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT);
+        enterSplitScreen(taskId, leftOrTop, new WindowContainerTransaction());
     }
 
     public void enterSplitScreen(int taskId, boolean leftOrTop, WindowContainerTransaction wct) {
-        moveToSideStage(taskId,
-                leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
+        final int stageType = isSplitScreenVisible() ? STAGE_TYPE_UNDEFINED : STAGE_TYPE_SIDE;
+        final int stagePosition =
+                leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT;
+        moveToStage(taskId, stageType, stagePosition, wct);
     }
 
     public void exitSplitScreen(int toTopTaskId, @ExitReason int exitReason) {
@@ -242,6 +232,14 @@
         mStageCoordinator.onKeyguardVisibilityChanged(showing);
     }
 
+    public void onFinishedWakingUp() {
+        mStageCoordinator.onFinishedWakingUp();
+    }
+
+    public void onFinishedGoingToSleep() {
+        mStageCoordinator.onFinishedGoingToSleep();
+    }
+
     public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
         mStageCoordinator.exitSplitScreenOnHide(exitSplitScreenOnHide);
     }
@@ -501,6 +499,20 @@
                 SplitScreenController.this.onKeyguardVisibilityChanged(showing);
             });
         }
+
+        @Override
+        public void onFinishedWakingUp() {
+            mMainExecutor.execute(() -> {
+                SplitScreenController.this.onFinishedWakingUp();
+            });
+        }
+
+        @Override
+        public void onFinishedGoingToSleep() {
+            mMainExecutor.execute(() -> {
+                SplitScreenController.this.onFinishedGoingToSleep();
+            });
+        }
     }
 
     /**
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 d494191..a3726d4 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
@@ -155,6 +155,7 @@
     private boolean mShouldUpdateRecents;
     private boolean mExitSplitScreenOnHide;
     private boolean mKeyguardOccluded;
+    private boolean mDeviceSleep;
 
     @SplitScreen.StageType
     private int mDismissTop = NO_DISMISS;
@@ -273,18 +274,31 @@
         return mSideStageListener.mVisible && mMainStageListener.mVisible;
     }
 
-    boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
-            @SplitPosition int sideStagePosition) {
-        final WindowContainerTransaction wct = new WindowContainerTransaction();
-        return moveToSideStage(task, sideStagePosition, wct);
-    }
+    boolean moveToStage(ActivityManager.RunningTaskInfo task, @SplitScreen.StageType int stageType,
+            @SplitPosition int stagePosition, WindowContainerTransaction wct) {
+        StageTaskListener targetStage;
+        int sideStagePosition;
+        if (stageType == STAGE_TYPE_MAIN) {
+            targetStage = mMainStage;
+            sideStagePosition = SplitLayout.reversePosition(stagePosition);
+        } else if (stageType == STAGE_TYPE_SIDE) {
+            targetStage = mSideStage;
+            sideStagePosition = stagePosition;
+        } else {
+            if (mMainStage.isActive()) {
+                // If the split screen is activated, retrieves target stage based on position.
+                targetStage = stagePosition == mSideStagePosition ? mSideStage : mMainStage;
+                sideStagePosition = mSideStagePosition;
+            } else {
+                targetStage = mSideStage;
+                sideStagePosition = stagePosition;
+            }
+        }
 
-    boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
-            @SplitPosition int sideStagePosition, WindowContainerTransaction wct) {
-        final WindowContainerTransaction evictWct = new WindowContainerTransaction();
         setSideStagePosition(sideStagePosition, wct);
-        mSideStage.evictAllChildren(evictWct);
-        mSideStage.addTask(task, wct);
+        final WindowContainerTransaction evictWct = new WindowContainerTransaction();
+        targetStage.evictAllChildren(evictWct);
+        targetStage.addTask(task, wct);
         if (!evictWct.isEmpty()) {
             wct.merge(evictWct, true /* transfer */);
         }
@@ -463,9 +477,7 @@
             case STAGE_TYPE_MAIN: {
                 if (position != SPLIT_POSITION_UNDEFINED) {
                     // Set the side stage opposite of what we want to the main stage.
-                    final int sideStagePosition = position == SPLIT_POSITION_TOP_OR_LEFT
-                            ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
-                    setSideStagePosition(sideStagePosition, wct);
+                    setSideStagePosition(SplitLayout.reversePosition(position), wct);
                 } else {
                     position = getMainStagePosition();
                 }
@@ -489,8 +501,7 @@
 
     @SplitLayout.SplitPosition
     int getMainStagePosition() {
-        return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
-                ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
+        return SplitLayout.reversePosition(mSideStagePosition);
     }
 
     void setSideStagePosition(@SplitPosition int sideStagePosition,
@@ -537,6 +548,17 @@
         }
     }
 
+    void onFinishedWakingUp() {
+        if (mMainStage.isActive()) {
+            exitSplitScreenIfKeyguardOccluded();
+        }
+        mDeviceSleep = false;
+    }
+
+    void onFinishedGoingToSleep() {
+        mDeviceSleep = true;
+    }
+
     void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
         mExitSplitScreenOnHide = exitSplitScreenOnHide;
     }
@@ -565,6 +587,19 @@
         applyExitSplitScreen(childrenToTop, wct, exitReason);
     }
 
+    private void exitSplitScreenIfKeyguardOccluded() {
+        final boolean mainStageVisible = mMainStageListener.mVisible;
+        final boolean oneStageVisible = mainStageVisible ^ mSideStageListener.mVisible;
+        if (mDeviceSleep && mKeyguardOccluded && oneStageVisible) {
+            // Only the stages include show-when-locked activity is visible while keyguard occluded.
+            // Dismiss split because there's show-when-locked activity showing on top of keyguard.
+            // Also make sure the task contains show-when-locked activity remains on top after split
+            // dismissed.
+            final StageTaskListener toTop = mainStageVisible ? mMainStage : mSideStage;
+            exitSplitScreen(toTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
+        }
+    }
+
     private void applyExitSplitScreen(StageTaskListener childrenToTop,
             WindowContainerTransaction wct, @ExitReason int exitReason) {
         mRecentTasks.ifPresent(recentTasks -> {
@@ -780,14 +815,8 @@
             // like the cases keyguard showing or screen off.
                 exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RETURN_HOME);
             }
-        } else if (mKeyguardOccluded) {
-            // At least one of the stages is visible while keyguard occluded. Dismiss split because
-            // there's show-when-locked activity showing on top of keyguard. Also make sure the
-            // task contains show-when-locked activity remains on top after split dismissed.
-            final StageTaskListener toTop =
-                    mainStageVisible ? mMainStage : (sideStageVisible ? mSideStage : null);
-            exitSplitScreen(toTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
         }
+        exitSplitScreenIfKeyguardOccluded();
 
         mSyncQueue.runInSync(t -> {
             // Same above, we only set root tasks and divider leash visibility when both stage
@@ -870,8 +899,7 @@
 
     @Override
     public void onDoubleTappedDivider() {
-        setSideStagePosition(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
-                ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT, null /* wct */);
+        setSideStagePosition(SplitLayout.reversePosition(mSideStagePosition), null /* wct */);
         mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
                 getSideStagePosition(), mSideStage.getTopChildTaskUid(),
                 mSplitLayout.isLandscape());
@@ -1296,11 +1324,16 @@
         pw.println(prefix + TAG + " mDisplayId=" + mDisplayId);
         pw.println(innerPrefix + "mDividerVisible=" + mDividerVisible);
         pw.println(innerPrefix + "MainStage");
+        pw.println(childPrefix + "stagePosition=" + getMainStagePosition());
         pw.println(childPrefix + "isActive=" + mMainStage.isActive());
         mMainStageListener.dump(pw, childPrefix);
         pw.println(innerPrefix + "SideStage");
+        pw.println(childPrefix + "stagePosition=" + getSideStagePosition());
         mSideStageListener.dump(pw, childPrefix);
-        pw.println(innerPrefix + "mSplitLayout=" + mSplitLayout);
+        if (mMainStage.isActive()) {
+            pw.println(innerPrefix + "SplitLayout");
+            mSplitLayout.dump(pw, childPrefix);
+        }
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 62b8638..5b08245 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -293,6 +293,10 @@
         }
     }
 
+    void addTask(ActivityManager.RunningTaskInfo task, WindowContainerTransaction wct) {
+        wct.reparent(task.token, mRootTaskInfo.token, true /* onTop*/);
+    }
+
     void setBounds(Rect bounds, WindowContainerTransaction wct) {
         wct.setBounds(mRootTaskInfo.token, bounds);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/StagedSplitBounds.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/StagedSplitBounds.java
index aadf792..a0c84cc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/StagedSplitBounds.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/StagedSplitBounds.java
@@ -19,6 +19,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.Objects;
+
 /**
  * Container of various information needed to display split screen
  * tasks/leashes/etc in Launcher
@@ -93,6 +95,24 @@
     }
 
     @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof StagedSplitBounds)) {
+            return false;
+        }
+        // Only need to check the base fields (the other fields are derived from these)
+        final StagedSplitBounds other = (StagedSplitBounds) obj;
+        return Objects.equals(leftTopBounds, other.leftTopBounds)
+                && Objects.equals(rightBottomBounds, other.rightBottomBounds)
+                && leftTopTaskId == other.leftTopTaskId
+                && rightBottomTaskId == other.rightBottomTaskId;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(leftTopBounds, rightBottomBounds, leftTopTaskId, rightBottomTaskId);
+    }
+
+    @Override
     public String toString() {
         return "LeftTop: " + leftTopBounds + ", taskId: " + leftTopTaskId + "\n"
                 + "RightBottom: " + rightBottomBounds + ", taskId: " + rightBottomTaskId +  "\n"
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 19a5417..50f6bd7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -28,6 +28,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import static java.lang.Integer.MAX_VALUE;
@@ -83,6 +84,36 @@
     }
 
     @Test
+    public void testAddRemoveSplitNotifyChange() {
+        ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
+        ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
+        setRawList(t1, t2);
+
+        mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, mock(StagedSplitBounds.class));
+        verify(mRecentTasksController).notifyRecentTasksChanged();
+
+        reset(mRecentTasksController);
+        mRecentTasksController.removeSplitPair(t1.taskId);
+        verify(mRecentTasksController).notifyRecentTasksChanged();
+    }
+
+    @Test
+    public void testAddSameSplitBoundsInfoSkipNotifyChange() {
+        ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
+        ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
+        setRawList(t1, t2);
+
+        // Verify only one update if the split info is the same
+        StagedSplitBounds bounds1 = new StagedSplitBounds(new Rect(0, 0, 50, 50),
+                new Rect(50, 50, 100, 100), t1.taskId, t2.taskId);
+        mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, bounds1);
+        StagedSplitBounds bounds2 = new StagedSplitBounds(new Rect(0, 0, 50, 50),
+                new Rect(50, 50, 100, 100), t1.taskId, t2.taskId);
+        mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, bounds2);
+        verify(mRecentTasksController, times(1)).notifyRecentTasksChanged();
+    }
+
+    @Test
     public void testGetRecentTasks() {
         ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
         ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index ad65c04..ef14d84 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -19,11 +19,14 @@
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.view.Display.DEFAULT_DISPLAY;
 
-import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME;
 import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME;
 
+import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
@@ -49,7 +52,6 @@
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.split.SplitLayout;
-import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.transition.Transitions;
 
 import org.junit.Before;
@@ -110,12 +112,39 @@
     }
 
     @Test
-    public void testMoveToSideStage() {
+    public void testMoveToStage() {
         final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
 
-        mStageCoordinator.moveToSideStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT);
+        mStageCoordinator.moveToStage(task, STAGE_TYPE_MAIN, SPLIT_POSITION_BOTTOM_OR_RIGHT,
+                new WindowContainerTransaction());
+        verify(mMainStage).addTask(eq(task), any(WindowContainerTransaction.class));
+        assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getMainStagePosition());
 
+        mStageCoordinator.moveToStage(task, STAGE_TYPE_SIDE, SPLIT_POSITION_BOTTOM_OR_RIGHT,
+                new WindowContainerTransaction());
         verify(mSideStage).addTask(eq(task), any(WindowContainerTransaction.class));
+        assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition());
+    }
+
+    @Test
+    public void testMoveToUndefinedStage() {
+        final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
+
+        // Verify move to undefined stage while split screen not activated moves task to side stage.
+        when(mMainStage.isActive()).thenReturn(false);
+        mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null);
+        mStageCoordinator.moveToStage(task, STAGE_TYPE_UNDEFINED, SPLIT_POSITION_BOTTOM_OR_RIGHT,
+                new WindowContainerTransaction());
+        verify(mSideStage).addTask(eq(task), any(WindowContainerTransaction.class));
+        assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition());
+
+        // Verify move to undefined stage after split screen activated moves task based on position.
+        when(mMainStage.isActive()).thenReturn(true);
+        assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition());
+        mStageCoordinator.moveToStage(task, STAGE_TYPE_UNDEFINED, SPLIT_POSITION_TOP_OR_LEFT,
+                new WindowContainerTransaction());
+        verify(mMainStage).addTask(eq(task), any(WindowContainerTransaction.class));
+        assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition());
     }
 
     @Test
diff --git a/libs/hwui/WebViewFunctorManager.cpp b/libs/hwui/WebViewFunctorManager.cpp
index 5aad821..6fc251d 100644
--- a/libs/hwui/WebViewFunctorManager.cpp
+++ b/libs/hwui/WebViewFunctorManager.cpp
@@ -118,6 +118,24 @@
     }
 }
 
+bool WebViewFunctor::prepareRootSurfaceControl() {
+    if (!Properties::enableWebViewOverlays) return false;
+
+    renderthread::CanvasContext* activeContext = renderthread::CanvasContext::getActiveContext();
+    if (!activeContext) return false;
+
+    ASurfaceControl* rootSurfaceControl = activeContext->getSurfaceControl();
+    if (!rootSurfaceControl) return false;
+
+    int32_t rgid = activeContext->getSurfaceControlGenerationId();
+    if (mParentSurfaceControlGenerationId != rgid) {
+        reparentSurfaceControl(rootSurfaceControl);
+        mParentSurfaceControlGenerationId = rgid;
+    }
+
+    return true;
+}
+
 void WebViewFunctor::drawGl(const DrawGlInfo& drawInfo) {
     ATRACE_NAME("WebViewFunctor::drawGl");
     if (!mHasContext) {
@@ -131,20 +149,8 @@
             .mergeTransaction = currentFunctor.mergeTransaction,
     };
 
-    if (Properties::enableWebViewOverlays && !drawInfo.isLayer) {
-        renderthread::CanvasContext* activeContext =
-                renderthread::CanvasContext::getActiveContext();
-        if (activeContext != nullptr) {
-            ASurfaceControl* rootSurfaceControl = activeContext->getSurfaceControl();
-            if (rootSurfaceControl) {
-                overlayParams.overlaysMode = OverlaysMode::Enabled;
-                int32_t rgid = activeContext->getSurfaceControlGenerationId();
-                if (mParentSurfaceControlGenerationId != rgid) {
-                    reparentSurfaceControl(rootSurfaceControl);
-                    mParentSurfaceControlGenerationId = rgid;
-                }
-            }
-        }
+    if (!drawInfo.isLayer && prepareRootSurfaceControl()) {
+        overlayParams.overlaysMode = OverlaysMode::Enabled;
     }
 
     mCallbacks.gles.draw(mFunctor, mData, drawInfo, overlayParams);
@@ -170,7 +176,10 @@
             .mergeTransaction = currentFunctor.mergeTransaction,
     };
 
-    // TODO, enable surface control once offscreen mode figured out
+    if (!params.is_layer && prepareRootSurfaceControl()) {
+        overlayParams.overlaysMode = OverlaysMode::Enabled;
+    }
+
     mCallbacks.vk.draw(mFunctor, mData, params, overlayParams);
 }
 
diff --git a/libs/hwui/WebViewFunctorManager.h b/libs/hwui/WebViewFunctorManager.h
index f28f310..0a02f2d 100644
--- a/libs/hwui/WebViewFunctorManager.h
+++ b/libs/hwui/WebViewFunctorManager.h
@@ -88,6 +88,7 @@
     }
 
 private:
+    bool prepareRootSurfaceControl();
     void reparentSurfaceControl(ASurfaceControl* parent);
 
 private:
diff --git a/libs/hwui/jni/text/MeasuredText.cpp b/libs/hwui/jni/text/MeasuredText.cpp
index 7793746..bd9bd71 100644
--- a/libs/hwui/jni/text/MeasuredText.cpp
+++ b/libs/hwui/jni/text/MeasuredText.cpp
@@ -80,15 +80,17 @@
 }
 
 // Regular JNI
-static jlong nBuildMeasuredText(JNIEnv* env, jclass /* unused */, jlong builderPtr,
-                                jlong hintPtr, jcharArray javaText, jboolean computeHyphenation,
-                                jboolean computeLayout) {
+static jlong nBuildMeasuredText(JNIEnv* env, jclass /* unused */, jlong builderPtr, jlong hintPtr,
+                                jcharArray javaText, jboolean computeHyphenation,
+                                jboolean computeLayout, jboolean fastHyphenationMode) {
     ScopedCharArrayRO text(env, javaText);
     const minikin::U16StringPiece textBuffer(text.get(), text.size());
 
     // Pass the ownership to Java.
-    return toJLong(toBuilder(builderPtr)->build(textBuffer, computeHyphenation, computeLayout,
-                                                toMeasuredParagraph(hintPtr)).release());
+    return toJLong(toBuilder(builderPtr)
+                           ->build(textBuffer, computeHyphenation, computeLayout,
+                                   fastHyphenationMode, toMeasuredParagraph(hintPtr))
+                           .release());
 }
 
 // Regular JNI
@@ -140,12 +142,12 @@
 }
 
 static const JNINativeMethod gMTBuilderMethods[] = {
-    // MeasuredParagraphBuilder native functions.
-    {"nInitBuilder", "()J", (void*) nInitBuilder},
-    {"nAddStyleRun", "(JJIIZ)V", (void*) nAddStyleRun},
-    {"nAddReplacementRun", "(JJIIF)V", (void*) nAddReplacementRun},
-    {"nBuildMeasuredText", "(JJ[CZZ)J", (void*) nBuildMeasuredText},
-    {"nFreeBuilder", "(J)V", (void*) nFreeBuilder},
+        // MeasuredParagraphBuilder native functions.
+        {"nInitBuilder", "()J", (void*)nInitBuilder},
+        {"nAddStyleRun", "(JJIIZ)V", (void*)nAddStyleRun},
+        {"nAddReplacementRun", "(JJIIF)V", (void*)nAddReplacementRun},
+        {"nBuildMeasuredText", "(JJ[CZZZ)J", (void*)nBuildMeasuredText},
+        {"nFreeBuilder", "(J)V", (void*)nFreeBuilder},
 };
 
 static const JNINativeMethod gMTMethods[] = {
diff --git a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
index 8abf4534..e6ef95b 100644
--- a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
@@ -72,6 +72,7 @@
             .clip_top = mClip.fTop,
             .clip_right = mClip.fRight,
             .clip_bottom = mClip.fBottom,
+            .is_layer = !vulkan_info.fFromSwapchainOrAndroidWindow,
     };
     mat4.getColMajor(&params.transform[0]);
     params.secondary_command_buffer = vulkan_info.fSecondaryCommandBuffer;
diff --git a/libs/hwui/private/hwui/DrawVkInfo.h b/libs/hwui/private/hwui/DrawVkInfo.h
index 4ae0f5a..5c59657 100644
--- a/libs/hwui/private/hwui/DrawVkInfo.h
+++ b/libs/hwui/private/hwui/DrawVkInfo.h
@@ -68,6 +68,9 @@
   int clip_top;
   int clip_right;
   int clip_bottom;
+
+  // Input: Whether destination surface is offscreen surface.
+  bool is_layer;
 };
 
 }  // namespace uirenderer
diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp
index fe9a30a..611a4d9 100644
--- a/libs/hwui/renderthread/VulkanSurface.cpp
+++ b/libs/hwui/renderthread/VulkanSurface.cpp
@@ -426,7 +426,7 @@
     if (bufferInfo->skSurface.get() == nullptr) {
         bufferInfo->skSurface = SkSurface::MakeFromAHardwareBuffer(
                 mGrContext, ANativeWindowBuffer_getHardwareBuffer(bufferInfo->buffer.get()),
-                kTopLeft_GrSurfaceOrigin, mWindowInfo.colorspace, nullptr);
+                kTopLeft_GrSurfaceOrigin, mWindowInfo.colorspace, nullptr, /*from_window=*/true);
         if (bufferInfo->skSurface.get() == nullptr) {
             ALOGE("SkSurface::MakeFromAHardwareBuffer failed");
             mNativeWindow->cancelBuffer(mNativeWindow.get(), buffer,
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 8f04cfb..f43586f 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -17,24 +17,29 @@
 #define LOG_TAG "PointerController"
 //#define LOG_NDEBUG 0
 
-// Log debug messages about pointer updates
-#define DEBUG_POINTER_UPDATES 0
-
 #include "PointerController.h"
-#include "MouseCursorController.h"
 #include "PointerControllerContext.h"
-#include "TouchSpotController.h"
 
-#include <log/log.h>
-
-#include <SkBitmap.h>
 #include <SkBlendMode.h>
 #include <SkCanvas.h>
 #include <SkColor.h>
-#include <SkPaint.h>
 
 namespace android {
 
+namespace {
+
+const ui::Transform kIdentityTransform;
+
+} // namespace
+
+// --- PointerController::DisplayInfoListener ---
+
+void PointerController::DisplayInfoListener::onWindowInfosChanged(
+        const std::vector<android::gui::WindowInfo>&,
+        const std::vector<android::gui::DisplayInfo>& displayInfo) {
+    mPointerController.onDisplayInfosChanged(displayInfo);
+}
+
 // --- PointerController ---
 
 std::shared_ptr<PointerController> PointerController::create(
@@ -63,9 +68,16 @@
 PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy,
                                      const sp<Looper>& looper,
                                      const sp<SpriteController>& spriteController)
-      : mContext(policy, looper, spriteController, *this), mCursorController(mContext) {
+      : mContext(policy, looper, spriteController, *this),
+        mCursorController(mContext),
+        mDisplayInfoListener(new DisplayInfoListener(*this)) {
     std::scoped_lock lock(mLock);
     mLocked.presentation = Presentation::SPOT;
+    SurfaceComposerClient::getDefault()->addWindowInfosListener(mDisplayInfoListener);
+}
+
+PointerController::~PointerController() {
+    SurfaceComposerClient::getDefault()->removeWindowInfosListener(mDisplayInfoListener);
 }
 
 bool PointerController::getBounds(float* outMinX, float* outMinY, float* outMaxX,
@@ -74,7 +86,14 @@
 }
 
 void PointerController::move(float deltaX, float deltaY) {
-    mCursorController.move(deltaX, deltaY);
+    const int32_t displayId = mCursorController.getDisplayId();
+    vec2 transformed;
+    {
+        std::scoped_lock lock(mLock);
+        const auto& transform = getTransformForDisplayLocked(displayId);
+        transformed = transformWithoutTranslation(transform, {deltaX, deltaY});
+    }
+    mCursorController.move(transformed.x, transformed.y);
 }
 
 void PointerController::setButtonState(int32_t buttonState) {
@@ -86,12 +105,26 @@
 }
 
 void PointerController::setPosition(float x, float y) {
-    std::scoped_lock lock(mLock);
-    mCursorController.setPosition(x, y);
+    const int32_t displayId = mCursorController.getDisplayId();
+    vec2 transformed;
+    {
+        std::scoped_lock lock(mLock);
+        const auto& transform = getTransformForDisplayLocked(displayId);
+        transformed = transform.transform(x, y);
+    }
+    mCursorController.setPosition(transformed.x, transformed.y);
 }
 
 void PointerController::getPosition(float* outX, float* outY) const {
+    const int32_t displayId = mCursorController.getDisplayId();
     mCursorController.getPosition(outX, outY);
+    {
+        std::scoped_lock lock(mLock);
+        const auto& transform = getTransformForDisplayLocked(displayId);
+        const auto xy = transform.inverse().transform(*outX, *outY);
+        *outX = xy.x;
+        *outY = xy.y;
+    }
 }
 
 int32_t PointerController::getDisplayId() const {
@@ -130,11 +163,25 @@
 void PointerController::setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
                                  BitSet32 spotIdBits, int32_t displayId) {
     std::scoped_lock lock(mLock);
+    std::array<PointerCoords, MAX_POINTERS> outSpotCoords{};
+    const ui::Transform& transform = getTransformForDisplayLocked(displayId);
+
+    for (BitSet32 idBits(spotIdBits); !idBits.isEmpty();) {
+        const uint32_t index = spotIdToIndex[idBits.clearFirstMarkedBit()];
+
+        const vec2 xy = transform.transform(spotCoords[index].getXYValue());
+        outSpotCoords[index].setAxisValue(AMOTION_EVENT_AXIS_X, xy.x);
+        outSpotCoords[index].setAxisValue(AMOTION_EVENT_AXIS_Y, xy.y);
+
+        float pressure = spotCoords[index].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE);
+        outSpotCoords[index].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pressure);
+    }
+
     auto it = mLocked.spotControllers.find(displayId);
     if (it == mLocked.spotControllers.end()) {
         mLocked.spotControllers.try_emplace(displayId, displayId, mContext);
     }
-    mLocked.spotControllers.at(displayId).setSpots(spotCoords, spotIdToIndex, spotIdBits);
+    mLocked.spotControllers.at(displayId).setSpots(outSpotCoords.data(), spotIdToIndex, spotIdBits);
 }
 
 void PointerController::clearSpots() {
@@ -194,7 +241,7 @@
 
 void PointerController::onDisplayViewportsUpdated(std::vector<DisplayViewport>& viewports) {
     std::unordered_set<int32_t> displayIdSet;
-    for (DisplayViewport viewport : viewports) {
+    for (const DisplayViewport& viewport : viewports) {
         displayIdSet.insert(viewport.displayId);
     }
 
@@ -214,4 +261,17 @@
     }
 }
 
+void PointerController::onDisplayInfosChanged(const std::vector<gui::DisplayInfo>& displayInfo) {
+    std::scoped_lock lock(mLock);
+    mLocked.mDisplayInfos = displayInfo;
+}
+
+const ui::Transform& PointerController::getTransformForDisplayLocked(int displayId) const {
+    const auto& di = mLocked.mDisplayInfos;
+    auto it = std::find_if(di.begin(), di.end(), [displayId](const gui::DisplayInfo& info) {
+        return info.displayId == displayId;
+    });
+    return it != di.end() ? it->transform : kIdentityTransform;
+}
+
 } // namespace android
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 97567ba..796077f 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -47,7 +47,7 @@
             const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
             const sp<SpriteController>& spriteController);
 
-    virtual ~PointerController() = default;
+    ~PointerController() override;
 
     virtual bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
     virtual void move(float deltaX, float deltaY);
@@ -72,6 +72,8 @@
     void reloadPointerResources();
     void onDisplayViewportsUpdated(std::vector<DisplayViewport>& viewports);
 
+    void onDisplayInfosChanged(const std::vector<gui::DisplayInfo>& displayInfos);
+
 private:
     friend PointerControllerContext::LooperCallback;
     friend PointerControllerContext::MessageHandler;
@@ -85,9 +87,23 @@
     struct Locked {
         Presentation presentation;
 
+        std::vector<gui::DisplayInfo> mDisplayInfos;
         std::unordered_map<int32_t /* displayId */, TouchSpotController> spotControllers;
     } mLocked GUARDED_BY(mLock);
 
+    class DisplayInfoListener : public gui::WindowInfosListener {
+    public:
+        explicit DisplayInfoListener(PointerController& pc) : mPointerController(pc){};
+        void onWindowInfosChanged(const std::vector<android::gui::WindowInfo>&,
+                                  const std::vector<android::gui::DisplayInfo>&) override;
+
+    private:
+        PointerController& mPointerController;
+    };
+    sp<DisplayInfoListener> mDisplayInfoListener;
+
+    const ui::Transform& getTransformForDisplayLocked(int displayId) const REQUIRES(mLock);
+
     PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
                       const sp<SpriteController>& spriteController);
     void clearSpotsLocked();
diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp
index da21438..2b809ea 100644
--- a/libs/input/SpriteController.cpp
+++ b/libs/input/SpriteController.cpp
@@ -70,8 +70,8 @@
 }
 
 void SpriteController::invalidateSpriteLocked(const sp<SpriteImpl>& sprite) {
-    bool wasEmpty = mLocked.invalidatedSprites.isEmpty();
-    mLocked.invalidatedSprites.push(sprite);
+    bool wasEmpty = mLocked.invalidatedSprites.empty();
+    mLocked.invalidatedSprites.push_back(sprite);
     if (wasEmpty) {
         if (mLocked.transactionNestingCount != 0) {
             mLocked.deferredSpriteUpdate = true;
@@ -82,8 +82,8 @@
 }
 
 void SpriteController::disposeSurfaceLocked(const sp<SurfaceControl>& surfaceControl) {
-    bool wasEmpty = mLocked.disposedSurfaces.isEmpty();
-    mLocked.disposedSurfaces.push(surfaceControl);
+    bool wasEmpty = mLocked.disposedSurfaces.empty();
+    mLocked.disposedSurfaces.push_back(surfaceControl);
     if (wasEmpty) {
         mLooper->sendMessage(mHandler, Message(MSG_DISPOSE_SURFACES));
     }
@@ -113,7 +113,7 @@
 
         numSprites = mLocked.invalidatedSprites.size();
         for (size_t i = 0; i < numSprites; i++) {
-            const sp<SpriteImpl>& sprite = mLocked.invalidatedSprites.itemAt(i);
+            const sp<SpriteImpl>& sprite = mLocked.invalidatedSprites[i];
 
             updates.push(SpriteUpdate(sprite, sprite->getStateLocked()));
             sprite->resetDirtyLocked();
@@ -305,7 +305,7 @@
 
 void SpriteController::doDisposeSurfaces() {
     // Collect disposed surfaces.
-    Vector<sp<SurfaceControl> > disposedSurfaces;
+    std::vector<sp<SurfaceControl>> disposedSurfaces;
     { // acquire lock
         AutoMutex _l(mLock);
 
@@ -313,6 +313,13 @@
         mLocked.disposedSurfaces.clear();
     } // release lock
 
+    // Remove the parent from all surfaces.
+    SurfaceComposerClient::Transaction t;
+    for (const sp<SurfaceControl>& sc : disposedSurfaces) {
+        t.reparent(sc, nullptr);
+    }
+    t.apply();
+
     // Release the last reference to each surface outside of the lock.
     // We don't want the surfaces to be deleted while we are holding our lock.
     disposedSurfaces.clear();
diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h
index 2a80d95..2e9cb96 100644
--- a/libs/input/SpriteController.h
+++ b/libs/input/SpriteController.h
@@ -251,8 +251,8 @@
     sp<SurfaceComposerClient> mSurfaceComposerClient;
 
     struct Locked {
-        Vector<sp<SpriteImpl> > invalidatedSprites;
-        Vector<sp<SurfaceControl> > disposedSurfaces;
+        std::vector<sp<SpriteImpl>> invalidatedSprites;
+        std::vector<sp<SurfaceControl>> disposedSurfaces;
         uint32_t transactionNestingCount;
         bool deferredSpriteUpdate;
     } mLocked; // guarded by mLock
diff --git a/media/java/android/media/tv/interactive/ITvIAppService.aidl b/media/java/android/media/tv/interactive/ITvIAppService.aidl
index c4f82eb..2f165f0 100644
--- a/media/java/android/media/tv/interactive/ITvIAppService.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppService.aidl
@@ -16,6 +16,7 @@
 
 package android.media.tv.interactive;
 
+import android.media.tv.interactive.ITvIAppServiceCallback;
 import android.media.tv.interactive.ITvIAppSessionCallback;
 
 /**
@@ -24,5 +25,7 @@
  * @hide
  */
 oneway interface ITvIAppService {
+    void registerCallback(in ITvIAppServiceCallback callback);
+    void unregisterCallback(in ITvIAppServiceCallback callback);
     void createSession(in ITvIAppSessionCallback callback, in String iAppServiceId, int type);
 }
\ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/TvIAppService.java b/media/java/android/media/tv/interactive/TvIAppService.java
index 8863729..78b8173 100644
--- a/media/java/android/media/tv/interactive/TvIAppService.java
+++ b/media/java/android/media/tv/interactive/TvIAppService.java
@@ -26,6 +26,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.KeyEvent;
@@ -62,11 +63,26 @@
     public static final String SERVICE_META_DATA = "android.media.tv.interactive.app";
 
     private final Handler mServiceHandler = new ServiceHandler();
+    private final RemoteCallbackList<ITvIAppServiceCallback> mCallbacks =
+            new RemoteCallbackList<>();
 
     /** @hide */
     @Override
     public final IBinder onBind(Intent intent) {
         ITvIAppService.Stub tvIAppServiceBinder = new ITvIAppService.Stub() {
+            @Override
+            public void registerCallback(ITvIAppServiceCallback cb) {
+                if (cb != null) {
+                    mCallbacks.register(cb);
+                }
+            }
+
+            @Override
+            public void unregisterCallback(ITvIAppServiceCallback cb) {
+                if (cb != null) {
+                    mCallbacks.unregister(cb);
+                }
+            }
 
             @Override
             public void createSession(ITvIAppSessionCallback cb, String iAppServiceId, int type) {
@@ -137,7 +153,7 @@
          * Called when the application sets the surface.
          *
          * <p>The TV IApp service should render interactive app UI onto the given surface. When
-         * called with {@code null}, the input service should immediately free any references to the
+         * called with {@code null}, the IApp service should immediately free any references to the
          * currently set surface and stop using it.
          *
          * @param surface The surface to be used for interactive app UI rendering. Can be
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 58d2185..1df1bce 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -160,10 +160,12 @@
     private void registerIntentReceiver(BroadcastReceiver receiver, IntentFilter filter) {
         if (mUserHandle == null) {
             // If userHandle has not been provided, simply call registerReceiver.
-            mContext.registerReceiver(receiver, filter, null, mReceiverHandler);
+            mContext.registerReceiver(receiver, filter, null, mReceiverHandler,
+                    Context.RECEIVER_EXPORTED);
         } else {
             // userHandle was explicitly specified, so need to call multi-user aware API.
-            mContext.registerReceiverAsUser(receiver, mUserHandle, filter, null, mReceiverHandler);
+            mContext.registerReceiverAsUser(receiver, mUserHandle, filter, null, mReceiverHandler,
+                    Context.RECEIVER_EXPORTED);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
index a210e90..8b17be1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
@@ -19,7 +19,6 @@
 import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.AlertDialog;
-import android.app.Dialog;
 import android.app.NotificationManager;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -85,6 +84,7 @@
 
     @VisibleForTesting
     protected Context mContext;
+    private final int mThemeResId;
     @VisibleForTesting
     protected TextView mZenAlarmWarning;
     @VisibleForTesting
@@ -97,10 +97,15 @@
     protected LayoutInflater mLayoutInflater;
 
     public EnableZenModeDialog(Context context) {
-        mContext = context;
+        this(context, 0);
     }
 
-    public Dialog createDialog() {
+    public EnableZenModeDialog(Context context, int themeResId) {
+        mContext = context;
+        mThemeResId = themeResId;
+    }
+
+    public AlertDialog createDialog() {
         mNotificationManager = (NotificationManager) mContext.
                 getSystemService(Context.NOTIFICATION_SERVICE);
         mForeverId =  Condition.newId(mContext).appendPath("forever").build();
@@ -108,7 +113,7 @@
         mUserId = mContext.getUserId();
         mAttached = false;
 
-        final AlertDialog.Builder builder = new AlertDialog.Builder(mContext)
+        final AlertDialog.Builder builder = new AlertDialog.Builder(mContext, mThemeResId)
                 .setTitle(R.string.zen_mode_settings_turn_on_dialog_title)
                 .setNegativeButton(R.string.cancel, null)
                 .setPositiveButton(R.string.zen_mode_enable_dialog_turn_on,
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index 0fe4efe..a944bf5 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -80,6 +80,7 @@
         Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
         Settings.System.RING_VIBRATION_INTENSITY,
         Settings.System.HAPTIC_FEEDBACK_INTENSITY,
+        Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY,
         Settings.System.DISPLAY_COLOR_MODE_VENDOR_HINT, // must precede DISPLAY_COLOR_MODE
         Settings.System.DISPLAY_COLOR_MODE,
         Settings.System.ALARM_ALERT,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index bf62f1d..a10b819 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -262,7 +262,6 @@
         VALIDATORS.put(Global.Wearable.AMBIENT_TOUCH_TO_WAKE, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.Wearable.DECOMPOSABLE_WATCHFACE, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.Wearable.AMBIENT_FORCE_WHEN_DOCKED, BOOLEAN_VALIDATOR);
-        VALIDATORS.put(Global.Wearable.AMBIENT_GESTURE_SENSOR_ID, ANY_INTEGER_VALIDATOR);
         VALIDATORS.put(Global.Wearable.AMBIENT_LOW_BIT_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.Wearable.AMBIENT_PLUGGED_TIMEOUT_MIN, ANY_INTEGER_VALIDATOR);
         VALIDATORS.put(Global.Wearable.AMBIENT_TILT_TO_BRIGHT, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 462c3a5..63acffb 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -120,6 +120,7 @@
         VALIDATORS.put(System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
         VALIDATORS.put(System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
         VALIDATORS.put(System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
+        VALIDATORS.put(System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
         VALIDATORS.put(System.RINGTONE, URI_VALIDATOR);
         VALIDATORS.put(System.NOTIFICATION_SOUND, URI_VALIDATOR);
         VALIDATORS.put(System.ALARM_ALERT, URI_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index aca70f1..ce7517f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -5325,9 +5325,6 @@
                             Settings.Global.Wearable.AMBIENT_FORCE_WHEN_DOCKED,
                             SystemProperties.getBoolean("ro.ambient.force_when_docked", false));
                     initGlobalSettingsDefaultValForWearLocked(
-                            Settings.Global.Wearable.AMBIENT_GESTURE_SENSOR_ID,
-                            SystemProperties.getInt("ro.ambient.gesture_sensor_id", 0));
-                    initGlobalSettingsDefaultValForWearLocked(
                             Settings.Global.Wearable.AMBIENT_LOW_BIT_ENABLED,
                             SystemProperties.getBoolean("ro.ambient.low_bit_enabled", false));
                     initGlobalSettingsDefaultValForWearLocked(
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 324c05e..c9c93c4 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -640,7 +640,6 @@
                     Settings.Global.Wearable.AMBIENT_TILT_TO_BRIGHT,
                     Settings.Global.Wearable.DECOMPOSABLE_WATCHFACE,
                     Settings.Global.Wearable.AMBIENT_FORCE_WHEN_DOCKED,
-                    Settings.Global.Wearable.AMBIENT_GESTURE_SENSOR_ID,
                     Settings.Global.Wearable.AMBIENT_LOW_BIT_ENABLED,
                     Settings.Global.Wearable.AMBIENT_PLUGGED_TIMEOUT_MIN,
                     Settings.Global.Wearable.PAIRED_DEVICE_OS_TYPE,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index f59f099..abd010d 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -589,6 +589,9 @@
     <!-- Permission required to run GtsAssistantTestCases -->
     <uses-permission android:name="android.permission.MANAGE_VOICE_KEYPHRASES" />
 
+    <!-- Permission required for CTS test - SettingsMultiPaneDeepLinkTest -->
+    <uses-permission android:name="android.permission.LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK" />
+
     <application android:label="@string/app_label"
                 android:theme="@android:style/Theme.DeviceDefault.DayNight"
                 android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml b/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml
index 0dec981..991dc63e 100644
--- a/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml
+++ b/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml
@@ -35,7 +35,7 @@
             android:id="@+id/volume_number"
             android:layout_width="@dimen/tv_volume_dialog_bubble_size"
             android:layout_height="@dimen/tv_volume_dialog_bubble_size"
-            android:maxLength="2"
+            android:maxLength="3"
             android:gravity="center"
             android:fontFeatureSettings="tnum"
             android:background="@drawable/tv_volume_dialog_circle"
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index 8bd0f91..0149751 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -95,4 +95,9 @@
      * Sent when screen turned on and ready to use (blocker scrim is hidden)
      */
     void onScreenTurnedOn() = 21;
+
+    /**
+     * Sent when the desired dark intensity of the nav buttons has changed
+     */
+    void onNavButtonsDarkIntensityChanged(float darkIntensity) = 22;
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
index 6154d84..8d98a75 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
@@ -18,7 +18,6 @@
 
 import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
 import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN;
-import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE;
 
 import android.annotation.TargetApi;
 import android.content.Context;
@@ -126,10 +125,9 @@
         final WindowManager windowManager = context.getSystemService(WindowManager.class);
         final Rect bounds = windowManager.getCurrentWindowMetrics().getBounds();
 
-        float originalSmallestWidth = dpiFromPx(Math.min(bounds.width(), bounds.height()),
+        float smallestWidth = dpiFromPx(Math.min(bounds.width(), bounds.height()),
                 context.getResources().getConfiguration().densityDpi);
-        return dpiFromPx(Math.min(bounds.width(), bounds.height()), DENSITY_DEVICE_STABLE)
-                >= TABLET_MIN_DPS && originalSmallestWidth >= TABLET_MIN_DPS;
+        return smallestWidth >= TABLET_MIN_DPS;
     }
 
     public static float dpiFromPx(float size, int densityDpi) {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index 954cf9f..a319b40 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -321,7 +321,7 @@
                     // re-showing it's task).
                     final WindowContainerTransaction wct = new WindowContainerTransaction();
                     final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-                    for (int i = mPausingTasks.size() - 1; i >= 0; ++i) {
+                    for (int i = mPausingTasks.size() - 1; i >= 0; --i) {
                         // reverse order so that index 0 ends up on top
                         wct.reorder(mPausingTasks.get(i), true /* onTop */);
                         t.show(mInfo.getChange(mPausingTasks.get(i)).getLeash());
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index e24f07c..b56d189 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -801,13 +801,16 @@
             mHandler.postDelayed(mRetryFingerprintAuthentication, HAL_ERROR_RETRY_TIMEOUT);
         }
 
+        boolean lockedOutStateChanged = false;
         if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) {
+            lockedOutStateChanged |= !mFingerprintLockedOutPermanent;
             mFingerprintLockedOutPermanent = true;
             requireStrongAuthIfAllLockedOut();
         }
 
         if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT
                 || msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) {
+            lockedOutStateChanged |= !mFingerprintLockedOut;
             mFingerprintLockedOut = true;
             if (isUdfpsEnrolled()) {
                 updateFingerprintListeningState();
@@ -820,9 +823,14 @@
                 cb.onBiometricError(msgId, errString, BiometricSourceType.FINGERPRINT);
             }
         }
+
+        if (lockedOutStateChanged) {
+            notifyLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
+        }
     }
 
     private void handleFingerprintLockoutReset() {
+        boolean changed = mFingerprintLockedOut || mFingerprintLockedOutPermanent;
         mFingerprintLockedOut = false;
         mFingerprintLockedOutPermanent = false;
 
@@ -837,6 +845,10 @@
         } else {
             updateFingerprintListeningState();
         }
+
+        if (changed) {
+            notifyLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
+        }
     }
 
     private void setFingerprintRunningState(int fingerprintRunningState) {
@@ -999,7 +1011,9 @@
             }
         }
 
+        boolean lockedOutStateChanged = false;
         if (msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT) {
+            lockedOutStateChanged = !mFaceLockedOutPermanent;
             mFaceLockedOutPermanent = true;
             requireStrongAuthIfAllLockedOut();
         }
@@ -1011,11 +1025,21 @@
                         BiometricSourceType.FACE);
             }
         }
+
+        if (lockedOutStateChanged) {
+            notifyLockedOutStateChanged(BiometricSourceType.FACE);
+        }
     }
 
     private void handleFaceLockoutReset() {
+        boolean changed = mFaceLockedOutPermanent;
         mFaceLockedOutPermanent = false;
+
         updateFaceListeningState();
+
+        if (changed) {
+            notifyLockedOutStateChanged(BiometricSourceType.FACE);
+        }
     }
 
     private void setFaceRunningState(int faceRunningState) {
@@ -1237,6 +1261,16 @@
         }
     }
 
+    private void notifyLockedOutStateChanged(BiometricSourceType type) {
+        Assert.isMainThread();
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+            if (cb != null) {
+                cb.onLockedOutStateChanged(type);
+            }
+        }
+    }
+
     public boolean isScreenOn() {
         return mScreenOn;
     }
@@ -2454,6 +2488,10 @@
         }
     }
 
+    public boolean isFingerprintLockedOut() {
+        return mFingerprintLockedOut || mFingerprintLockedOutPermanent;
+    }
+
     /**
      * If biometrics hardware is available, not disabled, and user has enrolled templates.
      * This does NOT check if the device is encrypted or in lockdown.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 12431984..8170a81 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -292,6 +292,11 @@
     public void onStrongAuthStateChanged(int userId) { }
 
     /**
+     * When the current user's locked out state changed.
+     */
+    public void onLockedOutStateChanged(BiometricSourceType biometricSourceType) { }
+
+    /**
      * Called when the dream's window state is changed.
      * @param dreaming true if the dream's window has been created and is visible
      */
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index e84024d..33538ec 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -87,7 +87,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.qs.SecureSetting;
+import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.events.PrivacyDotViewController;
 import com.android.systemui.tuner.TunerService;
@@ -155,7 +155,7 @@
     private float mDensity;
     private WindowManager mWindowManager;
     private int mRotation;
-    private SecureSetting mColorInversionSetting;
+    private SettingObserver mColorInversionSetting;
     private DelayableExecutor mExecutor;
     private Handler mHandler;
     private boolean mPendingRotationChange;
@@ -346,7 +346,7 @@
 
             // Watch color inversion and invert the overlay as needed.
             if (mColorInversionSetting == null) {
-                mColorInversionSetting = new SecureSetting(mSecureSettings, mHandler,
+                mColorInversionSetting = new SettingObserver(mSecureSettings, mHandler,
                         Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
                         mUserTracker.getUserId()) {
                     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index 223eb78..8f4d6f6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -255,7 +255,6 @@
     private void maybeShowInputBouncer() {
         if (mShowingUdfpsBouncer && hasUdfpsBouncerShownWithMinTime()) {
             mKeyguardViewManager.showBouncer(true);
-            mKeyguardViewManager.resetAlternateAuth(false);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSource.java b/packages/SystemUI/src/com/android/systemui/communal/CommunalSource.java
index 99c311e..53586f5 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSource.java
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSource.java
@@ -23,6 +23,8 @@
 
 import com.google.common.util.concurrent.ListenableFuture;
 
+import java.util.Optional;
+
 /**
  * {@link CommunalSource} defines an interface for working with a source for communal data. Clients
  * may request a communal surface that can be shown within a {@link android.view.SurfaceView}.
@@ -30,6 +32,28 @@
  */
 public interface CommunalSource {
     /**
+     * {@link Connector} defines an interface for {@link CommunalSource} instances to be generated.
+     */
+    interface Connector {
+        ListenableFuture<Optional<CommunalSource>> connect();
+    }
+
+    /**
+     * The {@link Observer} interface specifies an entity which {@link CommunalSource} listeners
+     * can be informed of changes to the source, which will require updating. Note that this deals
+     * with changes to the source itself, not content which will be updated through the
+     * {@link CommunalSource} interface.
+     */
+    interface Observer {
+        interface Callback {
+            void onSourceChanged();
+        }
+
+        void addCallback(Callback callback);
+        void removeCallback(Callback callback);
+    }
+
+    /**
      * {@link CommunalViewResult} is handed back from {@link #requestCommunalView(Context)} and
      * contains the view to be displayed and its associated controller.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSourcePrimer.java b/packages/SystemUI/src/com/android/systemui/communal/CommunalSourcePrimer.java
new file mode 100644
index 0000000..3c2b79e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSourcePrimer.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.Log;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.Optional;
+
+import javax.inject.Inject;
+
+/**
+ * The {@link CommunalSourcePrimer} is responsible for priming SystemUI with a pre-configured
+ * Communal source. The SystemUI service binds to the component to retrieve the
+ * {@link CommunalSource}. {@link CommunalSourcePrimer} has no effect
+ * if there is no pre-defined value.
+ */
+@SysUISingleton
+public class CommunalSourcePrimer extends CoreStartable {
+    private static final String TAG = "CommunalSourcePrimer";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private final DelayableExecutor mMainExecutor;
+    private final CommunalSourceMonitor mMonitor;
+    private final int mBaseReconnectDelayMs;
+    private final int mMaxReconnectAttempts;
+
+    private int mReconnectAttempts = 0;
+    private Runnable mCurrentReconnectCancelable;
+    private ListenableFuture<Optional<CommunalSource>> mGetSourceFuture;
+
+    private final Optional<CommunalSource.Connector> mConnector;
+    private final Optional<CommunalSource.Observer> mObserver;
+
+    private final Runnable mConnectRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mCurrentReconnectCancelable = null;
+            connect();
+        }
+    };
+
+    @Inject
+    public CommunalSourcePrimer(Context context, @Main Resources resources,
+            DelayableExecutor mainExecutor,
+            CommunalSourceMonitor monitor,
+            Optional<CommunalSource.Connector> connector,
+            Optional<CommunalSource.Observer> observer) {
+        super(context);
+        mMainExecutor = mainExecutor;
+        mMonitor = monitor;
+        mConnector = connector;
+        mObserver = observer;
+
+        mMaxReconnectAttempts = resources.getInteger(
+                R.integer.config_communalSourceMaxReconnectAttempts);
+        mBaseReconnectDelayMs = resources.getInteger(
+                R.integer.config_communalSourceReconnectBaseDelay);
+    }
+
+    @Override
+    public void start() {
+    }
+
+    private void initiateConnectionAttempt() {
+        // Reset attempts
+        mReconnectAttempts = 0;
+        mMonitor.setSource(null);
+
+        // The first attempt is always a direct invocation rather than delayed.
+        connect();
+    }
+
+    private void scheduleConnectionAttempt() {
+        // always clear cancelable if present.
+        if (mCurrentReconnectCancelable != null) {
+            mCurrentReconnectCancelable.run();
+            mCurrentReconnectCancelable = null;
+        }
+
+        if (mReconnectAttempts >= mMaxReconnectAttempts) {
+            if (DEBUG) {
+                Log.d(TAG, "exceeded max connection attempts.");
+            }
+            return;
+        }
+
+        final long reconnectDelayMs =
+                (long) Math.scalb(mBaseReconnectDelayMs, mReconnectAttempts);
+
+        if (DEBUG) {
+            Log.d(TAG,
+                    "scheduling connection attempt in " + reconnectDelayMs + "milliseconds");
+        }
+
+        mCurrentReconnectCancelable = mMainExecutor.executeDelayed(mConnectRunnable,
+                reconnectDelayMs);
+
+        mReconnectAttempts++;
+    }
+
+    @Override
+    protected void onBootCompleted() {
+        if (mObserver.isPresent()) {
+            mObserver.get().addCallback(() -> initiateConnectionAttempt());
+        }
+        initiateConnectionAttempt();
+    }
+
+    private void connect() {
+        if (DEBUG) {
+            Log.d(TAG, "attempting to communal to communal source");
+        }
+
+        if (mGetSourceFuture != null) {
+            if (DEBUG) {
+                Log.d(TAG, "canceling in-flight connection");
+            }
+            mGetSourceFuture.cancel(true);
+        }
+
+        mGetSourceFuture = mConnector.get().connect();
+        mGetSourceFuture.addListener(() -> {
+            try {
+                Optional<CommunalSource> result = mGetSourceFuture.get();
+                if (result.isPresent()) {
+                    final CommunalSource source = result.get();
+                    source.addCallback(() -> initiateConnectionAttempt());
+                    mMonitor.setSource(source);
+                } else {
+                    scheduleConnectionAttempt();
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }, mMainExecutor);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index e97e762..fb7ab60 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -793,7 +793,8 @@
                 return KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN;
             } else if (trust && (strongAuth & SOME_AUTH_REQUIRED_AFTER_USER_REQUEST) != 0) {
                 return KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
-            } else if (any && (strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0) {
+            } else if (any && ((strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0
+                    || mUpdateMonitor.isFingerprintLockedOut())) {
                 return KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT;
             } else if (any && (strongAuth & STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE) != 0) {
                 return KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index e87558e..47ef5e4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -833,12 +833,15 @@
     )
 
     private val comparator =
-            compareByDescending<MediaSortKey> { it.data.isPlaying == true && it.data.isLocalSession }
-                    .thenByDescending { it.data.isPlaying }
-                    .thenByDescending { if (shouldPrioritizeSs) it.isSsMediaRec else !it.isSsMediaRec }
-                    .thenByDescending { !it.data.resumption }
-                    .thenByDescending { it.updateTime }
-                    .thenByDescending { !it.data.isLocalSession }
+            compareByDescending<MediaSortKey> { it.data.isPlaying == true &&
+                        it.data.playbackLocation == MediaData.PLAYBACK_LOCAL }
+                .thenByDescending { it.data.isPlaying == true &&
+                        it.data.playbackLocation == MediaData.PLAYBACK_CAST_LOCAL }
+                .thenByDescending { if (shouldPrioritizeSs) it.isSsMediaRec else !it.isSsMediaRec }
+                .thenByDescending { !it.data.resumption }
+                .thenByDescending { it.data.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE }
+                .thenByDescending { it.updateTime }
+                .thenByDescending { it.data.notificationKey }
 
     private val mediaPlayers = TreeMap<MediaSortKey, MediaControlPanel>(comparator)
     private val mediaData: MutableMap<String, MediaSortKey> = mutableMapOf()
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
index 6f0c887..bda07f4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
@@ -82,9 +82,9 @@
      */
     var resumeAction: Runnable?,
     /**
-     * Local or remote playback
+     * Playback location: one of PLAYBACK_LOCAL, PLAYBACK_CAST_LOCAL, or PLAYBACK_CAST_REMOTE
      */
-    var isLocalSession: Boolean = true,
+    var playbackLocation: Int = PLAYBACK_LOCAL,
     /**
      * Indicates that this player is a resumption player (ie. It only shows a play actions which
      * will start the app and start playing).
@@ -110,7 +110,20 @@
      * Timestamp when this player was last active.
      */
     var lastActive: Long = 0L
-)
+) {
+    companion object {
+        /** Media is playing on the local device */
+        const val PLAYBACK_LOCAL = 0
+        /** Media is cast but originated on the local device */
+        const val PLAYBACK_CAST_LOCAL = 1
+        /** Media is from a remote cast notification */
+        const val PLAYBACK_CAST_REMOTE = 2
+    }
+
+    fun isLocalSession(): Boolean {
+        return playbackLocation == PLAYBACK_LOCAL
+    }
+}
 
 /** State of a media action. */
 data class MediaAction(
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 3631d2f..6e86bef 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -27,6 +27,8 @@
 import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
 import android.graphics.Bitmap
 import android.graphics.Canvas
 import android.graphics.ImageDecoder
@@ -85,15 +87,7 @@
     "INVALID", null, emptyList(), null, 0)
 
 fun isMediaNotification(sbn: StatusBarNotification): Boolean {
-    if (!sbn.notification.hasMediaSession()) {
-        return false
-    }
-    val notificationStyle = sbn.notification.notificationStyle
-    if (Notification.DecoratedMediaCustomViewStyle::class.java.equals(notificationStyle) ||
-            Notification.MediaStyle::class.java.equals(notificationStyle)) {
-        return true
-    }
-    return false
+    return sbn.notification.isMediaNotification()
 }
 
 /**
@@ -153,6 +147,24 @@
     private var smartspaceSession: SmartspaceSession? = null
     private var allowMediaRecommendations = Utils.allowMediaRecommendations(context)
 
+    /**
+     * Check whether this notification is an RCN
+     * TODO(b/204910409) implement new API for explicitly declaring this
+     */
+    private fun isRemoteCastNotification(sbn: StatusBarNotification): Boolean {
+        val pm = context.packageManager
+        try {
+            val info = pm.getApplicationInfo(sbn.packageName, PackageManager.MATCH_SYSTEM_ONLY)
+            if (info.privateFlags and ApplicationInfo.PRIVATE_FLAG_PRIVILEGED != 0) {
+                val extras = sbn.notification.extras
+                if (extras.containsKey(Notification.EXTRA_SUBSTITUTE_APP_NAME)) {
+                    return true
+                }
+            }
+        } catch (e: PackageManager.NameNotFoundException) { }
+        return false
+    }
+
     @Inject
     constructor(
         context: Context,
@@ -442,7 +454,7 @@
         val existed = mediaEntries[key] != null
         backgroundExecutor.execute {
             mediaEntries[key]?.let { mediaData ->
-                if (mediaData.isLocalSession) {
+                if (mediaData.isLocalSession()) {
                     mediaData.token?.let {
                         val mediaController = mediaControllerFactory.create(it)
                         mediaController.transportControls.stop()
@@ -626,8 +638,11 @@
             }
         }
 
-        val isLocalSession = mediaController.playbackInfo?.playbackType ==
-            MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL
+        val playbackLocation =
+                if (isRemoteCastNotification(sbn)) MediaData.PLAYBACK_CAST_REMOTE
+                else if (mediaController.playbackInfo?.playbackType ==
+                    MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL) MediaData.PLAYBACK_LOCAL
+                else MediaData.PLAYBACK_CAST_LOCAL
         val isPlaying = mediaController.playbackState?.let { isPlayingState(it.state) } ?: null
         val lastActive = systemClock.elapsedRealtime()
         foregroundExecutor.execute {
@@ -637,7 +652,7 @@
             onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, bgColor, app,
                     smallIcon, artist, song, artWorkIcon, actionIcons,
                     actionsToShowCollapsed, sbn.packageName, token, notif.contentIntent, null,
-                    active, resumeAction = resumeAction, isLocalSession = isLocalSession,
+                    active, resumeAction = resumeAction, playbackLocation = playbackLocation,
                     notificationKey = key, hasCheckedForResume = hasCheckedForResume,
                     isPlaying = isPlaying, isClearable = sbn.isClearable(),
                     lastActive = lastActive))
@@ -762,7 +777,7 @@
     fun onNotificationRemoved(key: String) {
         Assert.isMainThread()
         val removed = mediaEntries.remove(key)
-        if (useMediaResumption && removed?.resumeAction != null && removed?.isLocalSession) {
+        if (useMediaResumption && removed?.resumeAction != null && removed?.isLocalSession()) {
             Log.d(TAG, "Not removing $key because resumable")
             // Move to resume key (aka package name) if that key doesn't already exist.
             val resumeAction = getResumeMediaAction(removed.resumeAction!!)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
index 608c784..d8095f3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
@@ -193,7 +193,7 @@
                 mediaBrowser = null
             }
             // If we don't have a resume action, check if we haven't already
-            if (data.resumeAction == null && !data.hasCheckedForResume && data.isLocalSession) {
+            if (data.resumeAction == null && !data.hasCheckedForResume && data.isLocalSession()) {
                 // TODO also check for a media button receiver intended for restarting (b/154127084)
                 Log.d(TAG, "Checking for service component for " + data.packageName)
                 val pm = context.packageManager
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 1981269..6da4d48 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -242,7 +242,7 @@
         for (NotificationEntry entry
                 : mNotificationEntryManager.getActiveNotificationsForCurrentUser()) {
             final Notification notification = entry.getSbn().getNotification();
-            if (notification.hasMediaSession()
+            if (notification.isMediaNotification()
                     && TextUtils.equals(entry.getSbn().getPackageName(), mPackageName)) {
                 final Icon icon = notification.getLargeIcon();
                 if (icon == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
new file mode 100644
index 0000000..52103d3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.navigationbar;
+
+import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
+
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.view.accessibility.AccessibilityManager;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.Dumpable;
+import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+
+import dagger.Lazy;
+
+/**
+ * Extracts shared elements between navbar and taskbar delegate to de-dupe logic and help them
+ * experience the joys of friendship.
+ * The events are then passed through
+ *
+ * Currently consolidates
+ * * A11y
+ * * Assistant
+ */
+@SysUISingleton
+public final class NavBarHelper implements
+        AccessibilityButtonModeObserver.ModeChangedListener,
+        OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener,
+        Dumpable {
+    private final AccessibilityManager mAccessibilityManager;
+    private final Lazy<AssistManager> mAssistManagerLazy;
+    private final UserTracker mUserTracker;
+    private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver;
+    private final List<NavbarTaskbarStateUpdater> mA11yEventListeners = new ArrayList<>();
+    private Context mContext;
+    private ContentResolver mContentResolver;
+    private boolean mAssistantAvailable;
+    private boolean mLongPressHomeEnabled;
+    private boolean mAssistantTouchGestureEnabled;
+    private int mNavBarMode;
+
+    private final ContentObserver mAssistContentObserver = new ContentObserver(
+            new Handler(Looper.getMainLooper())) {
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            updateAssitantAvailability();
+        }
+    };
+
+    @Inject
+    public NavBarHelper(AccessibilityManager accessibilityManager,
+            AccessibilityManagerWrapper accessibilityManagerWrapper,
+            AccessibilityButtonModeObserver accessibilityButtonModeObserver,
+            OverviewProxyService overviewProxyService,
+            Lazy<AssistManager> assistManagerLazy,
+            NavigationModeController navigationModeController,
+            UserTracker userTracker,
+            DumpManager dumpManager) {
+        mAccessibilityManager = accessibilityManager;
+        mAssistManagerLazy = assistManagerLazy;
+        mUserTracker = userTracker;
+        accessibilityManagerWrapper.addCallback(
+                accessibilityManager1 -> NavBarHelper.this.dispatchA11yEventUpdate());
+        mAccessibilityButtonModeObserver = accessibilityButtonModeObserver;
+
+        mAccessibilityButtonModeObserver.addListener(this);
+        mNavBarMode = navigationModeController.addListener(this);
+        overviewProxyService.addCallback(this);
+        dumpManager.registerDumpable(this);
+    }
+
+    public void init(Context context) {
+        mContext = context;
+        mContentResolver = mContext.getContentResolver();
+        mContentResolver.registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.ASSISTANT),
+                false /* notifyForDescendants */, mAssistContentObserver, UserHandle.USER_ALL);
+        mContentResolver.registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED),
+                false, mAssistContentObserver, UserHandle.USER_ALL);
+        mContentResolver.registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED),
+                false, mAssistContentObserver, UserHandle.USER_ALL);
+        updateAssitantAvailability();
+    }
+
+    public void destroy() {
+        mContentResolver.unregisterContentObserver(mAssistContentObserver);
+    }
+
+    /**
+     * @param listener Will immediately get callbacks based on current state
+     */
+    public void registerNavTaskStateUpdater(NavbarTaskbarStateUpdater listener) {
+        mA11yEventListeners.add(listener);
+        listener.updateAccessibilityServicesState();
+        listener.updateAssistantAvailable(mAssistantAvailable);
+    }
+
+    public void removeNavTaskStateUpdater(NavbarTaskbarStateUpdater listener) {
+        mA11yEventListeners.remove(listener);
+    }
+
+    private void dispatchA11yEventUpdate() {
+        for (NavbarTaskbarStateUpdater listener : mA11yEventListeners) {
+            listener.updateAccessibilityServicesState();
+        }
+    }
+
+    private void dispatchAssistantEventUpdate(boolean assistantAvailable) {
+        for (NavbarTaskbarStateUpdater listener : mA11yEventListeners) {
+            listener.updateAssistantAvailable(assistantAvailable);
+        }
+    }
+
+    @Override
+    public void onAccessibilityButtonModeChanged(int mode) {
+        dispatchA11yEventUpdate();
+    }
+
+    /**
+     * See {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_CLICKABLE} and
+     * {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE}
+     *
+     * @return the a11y button clickable and long_clickable states, or 0 if there is no
+     *         a11y button in the navbar
+     */
+    public int getA11yButtonState() {
+        // AccessibilityManagerService resolves services for the current user since the local
+        // AccessibilityManager is created from a Context with the INTERACT_ACROSS_USERS permission
+        final List<String> a11yButtonTargets =
+                mAccessibilityManager.getAccessibilityShortcutTargets(
+                        AccessibilityManager.ACCESSIBILITY_BUTTON);
+        final int requestingServices = a11yButtonTargets.size();
+
+        // If accessibility button is floating menu mode, click and long click state should be
+        // disabled.
+        if (mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode()
+                == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU) {
+            return 0;
+        }
+
+        return (requestingServices >= 1 ? SYSUI_STATE_A11Y_BUTTON_CLICKABLE : 0)
+                | (requestingServices >= 2 ? SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE : 0);
+    }
+
+    @Override
+    public void onConnectionChanged(boolean isConnected) {
+        if (isConnected) {
+            updateAssitantAvailability();
+        }
+    }
+
+    private void updateAssitantAvailability() {
+        boolean assistantAvailableForUser = mAssistManagerLazy.get()
+                .getAssistInfoForUser(UserHandle.USER_CURRENT) != null;
+        boolean longPressDefault = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_assistLongPressHomeEnabledDefault);
+        mLongPressHomeEnabled = Settings.Secure.getIntForUser(mContentResolver,
+                Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED, longPressDefault ? 1 : 0,
+                mUserTracker.getUserId()) != 0;
+        boolean gestureDefault = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_assistTouchGestureEnabledDefault);
+        mAssistantTouchGestureEnabled = Settings.Secure.getIntForUser(mContentResolver,
+                Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED, gestureDefault ? 1 : 0,
+                mUserTracker.getUserId()) != 0;
+
+        mAssistantAvailable = assistantAvailableForUser
+                && mAssistantTouchGestureEnabled
+                && QuickStepContract.isGesturalMode(mNavBarMode);
+        dispatchAssistantEventUpdate(mAssistantAvailable);
+    }
+
+    public boolean getLongPressHomeEnabled() {
+        return mLongPressHomeEnabled;
+    }
+
+    @Override
+    public void startAssistant(Bundle bundle) {
+        mAssistManagerLazy.get().startAssist(bundle);
+    }
+
+    @Override
+    public void onNavigationModeChanged(int mode) {
+        mNavBarMode = mode;
+        updateAssitantAvailability();
+    }
+
+    /**
+     * Callbacks will get fired once immediately after registering via
+     * {@link #registerNavTaskStateUpdater(NavbarTaskbarStateUpdater)}
+     */
+    public interface NavbarTaskbarStateUpdater {
+        void updateAccessibilityServicesState();
+        void updateAssistantAvailable(boolean available);
+    }
+
+    @Override
+    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+        pw.println("NavbarTaskbarFriendster");
+        pw.println("  longPressHomeEnabled=" + mLongPressHomeEnabled);
+        pw.println("  mAssistantTouchGestureEnabled=" + mAssistantTouchGestureEnabled);
+        pw.println("  mAssistantAvailable=" + mAssistantAvailable);
+        pw.println("  mNavBarMode=" + mNavBarMode);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index bdacc41..e0da9a0 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -67,22 +67,18 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.res.Configuration;
-import android.database.ContentObserver;
 import android.graphics.Insets;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.inputmethodservice.InputMethodService;
-import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
-import android.provider.Settings;
 import android.telecom.TelecomManager;
 import android.text.TextUtils;
 import android.util.Log;
@@ -200,8 +196,7 @@
     private final Handler mHandler;
     private final NavigationBarOverlayController mNavbarOverlayController;
     private final UiEventLogger mUiEventLogger;
-    private final NavigationBarA11yHelper mNavigationBarA11yHelper;
-    private final UserTracker mUserTracker;
+    private final NavBarHelper mNavBarHelper;
     private final NotificationShadeDepthController mNotificationShadeDepthController;
 
     private Bundle mSavedState;
@@ -213,9 +208,7 @@
     private int mNavigationIconHints = 0;
     private @TransitionMode int mNavigationBarMode;
     private ContentResolver mContentResolver;
-    private boolean mAssistantAvailable;
     private boolean mLongPressHomeEnabled;
-    private boolean mAssistantTouchGestureEnabled;
 
     private int mDisabledFlags1;
     private int mDisabledFlags2;
@@ -309,16 +302,31 @@
         }
     };
 
+    private final NavBarHelper.NavbarTaskbarStateUpdater mNavbarTaskbarStateUpdater =
+            new NavBarHelper.NavbarTaskbarStateUpdater() {
+                @Override
+                public void updateAccessibilityServicesState() {
+                    updateAcessibilityStateFlags();
+                }
+
+                @Override
+                public void updateAssistantAvailable(boolean available) {
+                    // TODO(b/198002034): Content observers currently can still be called back after
+                    //  being unregistered, and in this case we can ignore the change if the nav bar
+                    //  has been destroyed already
+                    if (mNavigationBarView == null) {
+                        return;
+                    }
+                    mLongPressHomeEnabled = mNavBarHelper.getLongPressHomeEnabled();
+                    updateAssistantEntrypoints(available);
+                }
+            };
+
     private final OverviewProxyListener mOverviewProxyListener = new OverviewProxyListener() {
         @Override
         public void onConnectionChanged(boolean isConnected) {
             mNavigationBarView.updateStates();
             updateScreenPinningGestures();
-
-            // Send the assistant availability upon connection
-            if (isConnected) {
-                updateAssistantEntrypoints();
-            }
         }
 
         @Override
@@ -421,20 +429,6 @@
         }
     };
 
-    private final ContentObserver mAssistContentObserver = new ContentObserver(
-            new Handler(Looper.getMainLooper())) {
-        @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            // TODO(b/198002034): Content observers currently can still be called back after being
-            // unregistered, and in this case we can ignore the change if the nav bar has been
-            // destroyed already
-            if (mNavigationBarView == null) {
-                return;
-            }
-            updateAssistantEntrypoints();
-        }
-    };
-
     private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
             new DeviceConfig.OnPropertiesChangedListener() {
                 @Override
@@ -504,7 +498,7 @@
             @Main Handler mainHandler,
             NavigationBarOverlayController navbarOverlayController,
             UiEventLogger uiEventLogger,
-            NavigationBarA11yHelper navigationBarA11yHelper,
+            NavBarHelper navBarHelper,
             UserTracker userTracker,
             LightBarController mainLightBarController,
             LightBarController.Factory lightBarControllerFactory,
@@ -535,8 +529,7 @@
         mHandler = mainHandler;
         mNavbarOverlayController = navbarOverlayController;
         mUiEventLogger = uiEventLogger;
-        mNavigationBarA11yHelper = navigationBarA11yHelper;
-        mUserTracker = userTracker;
+        mNavBarHelper = navBarHelper;
         mNotificationShadeDepthController = notificationShadeDepthController;
         mMainLightBarController = mainLightBarController;
         mLightBarControllerFactory = lightBarControllerFactory;
@@ -568,18 +561,9 @@
         mIsOnDefaultDisplay = mDisplayId == DEFAULT_DISPLAY;
 
         mCommandQueue.addCallback(this);
-        mAssistantAvailable = mAssistManagerLazy.get()
-                .getAssistInfoForUser(UserHandle.USER_CURRENT) != null;
+        mLongPressHomeEnabled = mNavBarHelper.getLongPressHomeEnabled();
         mContentResolver = mContext.getContentResolver();
-        mContentResolver.registerContentObserver(
-                Settings.Secure.getUriFor(Settings.Secure.ASSISTANT),
-                false /* notifyForDescendants */, mAssistContentObserver, UserHandle.USER_ALL);
-        mContentResolver.registerContentObserver(
-                Settings.Secure.getUriFor(Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED),
-                false, mAssistContentObserver, UserHandle.USER_ALL);
-        mContentResolver.registerContentObserver(
-                Settings.Secure.getUriFor(Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED),
-                false, mAssistContentObserver, UserHandle.USER_ALL);
+        mNavBarHelper.init(mContext);
         mAllowForceNavBarHandleOpaque = mContext.getResources().getBoolean(
                 R.bool.allow_force_nav_bar_handle_opaque);
         mForceNavBarHandleOpaque = DeviceConfig.getBoolean(
@@ -593,7 +577,6 @@
         )).filter(duration -> duration != 0);
         DeviceConfig.addOnPropertiesChangedListener(
                 DeviceConfig.NAMESPACE_SYSTEMUI, mHandler::post, mOnPropertiesChangedListener);
-        updateAssistantEntrypoints();
 
         if (savedState != null) {
             mDisabledFlags1 = savedState.getInt(EXTRA_DISABLE_STATE, 0);
@@ -620,8 +603,8 @@
         mWindowManager.removeViewImmediate(mNavigationBarView.getRootView());
         mNavigationModeController.removeListener(this);
 
-        mNavigationBarA11yHelper.removeA11yEventListener(mAccessibilityListener);
-        mContentResolver.unregisterContentObserver(mAssistContentObserver);
+        mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
+        mNavBarHelper.destroy();
         mDeviceProvisionedController.removeCallback(mUserSetupListener);
         mNotificationShadeDepthController.removeListener(mDepthListener);
 
@@ -643,7 +626,7 @@
         mNavigationBarView.setWindowVisible(isNavBarWindowVisible());
         mNavigationBarView.setBehavior(mBehavior);
 
-        mNavigationBarA11yHelper.registerA11yEventListener(mAccessibilityListener);
+        mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
 
         mSplitScreenOptional.ifPresent(mNavigationBarView::registerDockedListener);
         mPipOptional.ifPresent(mNavigationBarView::registerPipExclusionBoundsChangeListener);
@@ -716,7 +699,7 @@
         mHandler.removeCallbacks(mAutoDim);
         mHandler.removeCallbacks(mOnVariableDurationHomeLongClick);
         mHandler.removeCallbacks(mEnableLayoutTransitions);
-        mNavigationBarA11yHelper.removeA11yEventListener(mAccessibilityListener);
+        mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
         mFrame = null;
         mNavigationBarView = null;
         mOrientationHandle = null;
@@ -885,7 +868,6 @@
         pw.println("  mCurrentRotation=" + mCurrentRotation);
         pw.println("  mHomeButtonLongPressDurationMs=" + mHomeButtonLongPressDurationMs);
         pw.println("  mLongPressHomeEnabled=" + mLongPressHomeEnabled);
-        pw.println("  mAssistantTouchGestureEnabled=" + mAssistantTouchGestureEnabled);
         pw.println("  mNavigationBarWindowState="
                 + windowStateToString(mNavigationBarWindowState));
         pw.println("  mNavigationBarMode="
@@ -1175,7 +1157,7 @@
         ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton();
         accessibilityButton.setOnClickListener(this::onAccessibilityClick);
         accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick);
-        updateAccessibilityServicesState();
+        updateAcessibilityStateFlags();
 
         ButtonDispatcher imeSwitcherButton = mNavigationBarView.getImeSwitchButton();
         imeSwitcherButton.setOnClickListener(this::onImeSwitcherClick);
@@ -1400,8 +1382,8 @@
         return true;
     }
 
-    void updateAccessibilityServicesState() {
-        int a11yFlags = mNavigationBarA11yHelper.getA11yButtonState();
+    void updateAcessibilityStateFlags() {
+        int a11yFlags = mNavBarHelper.getA11yButtonState();
 
         if (mNavigationBarView != null) {
             boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
@@ -1413,7 +1395,7 @@
 
     public void updateSystemUiStateFlags(int a11yFlags) {
         if (a11yFlags < 0) {
-            a11yFlags = mNavigationBarA11yHelper.getA11yButtonState();
+            a11yFlags = mNavBarHelper.getA11yButtonState();
         }
         boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
         boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
@@ -1440,24 +1422,10 @@
         }
     }
 
-    private void updateAssistantEntrypoints() {
-        mAssistantAvailable = mAssistManagerLazy.get()
-                .getAssistInfoForUser(UserHandle.USER_CURRENT) != null;
-        boolean longPressDefault = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_assistLongPressHomeEnabledDefault);
-        mLongPressHomeEnabled = Settings.Secure.getIntForUser(mContentResolver,
-                Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED, longPressDefault ? 1 : 0,
-                mUserTracker.getUserId()) != 0;
-        boolean gestureDefault = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_assistTouchGestureEnabledDefault);
-        mAssistantTouchGestureEnabled = Settings.Secure.getIntForUser(mContentResolver,
-                Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED, gestureDefault ? 1 : 0,
-                mUserTracker.getUserId()) != 0;
+    private void updateAssistantEntrypoints(boolean assistantAvailable) {
         if (mOverviewProxyService.getProxy() != null) {
             try {
-                mOverviewProxyService.getProxy().onAssistantAvailable(mAssistantAvailable
-                        && mAssistantTouchGestureEnabled
-                        && QuickStepContract.isGesturalMode(mNavBarMode));
+                mOverviewProxyService.getProxy().onAssistantAvailable(assistantAvailable);
             } catch (RemoteException e) {
                 Log.w(TAG, "Unable to send assistant availability data to launcher");
             }
@@ -1528,8 +1496,6 @@
     @Override
     public void onNavigationModeChanged(int mode) {
         mNavBarMode = mode;
-        // update assistant entry points on system navigation radio button click
-        updateAssistantEntrypoints();
 
         if (!QuickStepContract.isGesturalMode(mode)) {
             // Reset the override alpha
@@ -1568,9 +1534,6 @@
         mNavigationBarView.getBarTransitions().finishAnimations();
     }
 
-    private final NavigationBarA11yHelper.NavA11yEventListener mAccessibilityListener =
-            this::updateAccessibilityServicesState;
-
     private WindowManager.LayoutParams getBarLayoutParams(int rotation) {
         WindowManager.LayoutParams lp = getBarLayoutParamsForRotation(rotation);
         lp.paramsForRotation = new WindowManager.LayoutParams[4];
@@ -1674,7 +1637,7 @@
             }
             if (Intent.ACTION_USER_SWITCHED.equals(action)) {
                 // The accessibility settings may be different for the new user
-                updateAccessibilityServicesState();
+                updateAcessibilityStateFlags();
             }
         }
     };
@@ -1710,7 +1673,7 @@
         private final Handler mMainHandler;
         private final NavigationBarOverlayController mNavbarOverlayController;
         private final UiEventLogger mUiEventLogger;
-        private final NavigationBarA11yHelper mNavigationBarA11yHelper;
+        private final NavBarHelper mNavBarHelper;
         private final UserTracker mUserTracker;
         private final LightBarController mMainLightBarController;
         private final LightBarController.Factory mLightBarControllerFactory;
@@ -1743,7 +1706,7 @@
                 @Main Handler mainHandler,
                 NavigationBarOverlayController navbarOverlayController,
                 UiEventLogger uiEventLogger,
-                NavigationBarA11yHelper navigationBarA11yHelper,
+                NavBarHelper navBarHelper,
                 UserTracker userTracker,
                 LightBarController mainLightBarController,
                 LightBarController.Factory lightBarControllerFactory,
@@ -1773,7 +1736,7 @@
             mMainHandler = mainHandler;
             mNavbarOverlayController = navbarOverlayController;
             mUiEventLogger = uiEventLogger;
-            mNavigationBarA11yHelper = navigationBarA11yHelper;
+            mNavBarHelper = navBarHelper;
             mUserTracker = userTracker;
             mMainLightBarController = mainLightBarController;
             mLightBarControllerFactory = lightBarControllerFactory;
@@ -1794,7 +1757,7 @@
                     mSplitScreenOptional, mRecentsOptional, mStatusBarOptionalLazy,
                     mShadeController, mNotificationRemoteInputManager,
                     mNotificationShadeDepthController, mSystemActions, mMainHandler,
-                    mNavbarOverlayController, mUiEventLogger, mNavigationBarA11yHelper,
+                    mNavbarOverlayController, mUiEventLogger, mNavBarHelper,
                     mUserTracker, mMainLightBarController, mLightBarControllerFactory,
                     mMainAutoHideController, mAutoHideControllerFactory, mTelecomManagerOptional,
                     mInputMethodManager);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarA11yHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarA11yHelper.java
deleted file mode 100644
index 13e6d8b..0000000
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarA11yHelper.java
+++ /dev/null
@@ -1,90 +0,0 @@
-package com.android.systemui.navigationbar;
-
-import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
-
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
-
-import android.view.accessibility.AccessibilityManager;
-
-import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.inject.Inject;
-
-/**
- * Extracts shared elements of a11y necessary between navbar and taskbar delegate
- */
-@SysUISingleton
-public final class NavigationBarA11yHelper implements
-        AccessibilityButtonModeObserver.ModeChangedListener {
-    private final AccessibilityManager mAccessibilityManager;
-    private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver;
-    private final List<NavA11yEventListener> mA11yEventListeners = new ArrayList<>();
-
-    @Inject
-    public NavigationBarA11yHelper(AccessibilityManager accessibilityManager,
-            AccessibilityManagerWrapper accessibilityManagerWrapper,
-            AccessibilityButtonModeObserver accessibilityButtonModeObserver) {
-        mAccessibilityManager = accessibilityManager;
-        accessibilityManagerWrapper.addCallback(
-                accessibilityManager1 -> NavigationBarA11yHelper.this.dispatchEventUpdate());
-        mAccessibilityButtonModeObserver = accessibilityButtonModeObserver;
-
-        mAccessibilityButtonModeObserver.addListener(this);
-    }
-
-    public void registerA11yEventListener(NavA11yEventListener listener) {
-        mA11yEventListeners.add(listener);
-    }
-
-    public void removeA11yEventListener(NavA11yEventListener listener) {
-        mA11yEventListeners.remove(listener);
-    }
-
-    private void dispatchEventUpdate() {
-        for (NavA11yEventListener listener : mA11yEventListeners) {
-            listener.updateAccessibilityServicesState();
-        }
-    }
-
-    @Override
-    public void onAccessibilityButtonModeChanged(int mode) {
-        dispatchEventUpdate();
-    }
-
-    /**
-     * See {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_CLICKABLE} and
-     * {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE}
-     *
-     * @return the a11y button clickable and long_clickable states, or 0 if there is no
-     *         a11y button in the navbar
-     */
-    public int getA11yButtonState() {
-        // AccessibilityManagerService resolves services for the current user since the local
-        // AccessibilityManager is created from a Context with the INTERACT_ACROSS_USERS permission
-        final List<String> a11yButtonTargets =
-                mAccessibilityManager.getAccessibilityShortcutTargets(
-                        AccessibilityManager.ACCESSIBILITY_BUTTON);
-        final int requestingServices = a11yButtonTargets.size();
-
-        // If accessibility button is floating menu mode, click and long click state should be
-        // disabled.
-        if (mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode()
-                == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU) {
-            return 0;
-        }
-
-        return (requestingServices >= 1 ? SYSUI_STATE_A11Y_BUTTON_CLICKABLE : 0)
-                | (requestingServices >= 2 ? SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE : 0);
-    }
-
-    public interface NavA11yEventListener {
-        void updateAccessibilityServicesState();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 3dc79c4..0429c02 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -57,6 +57,7 @@
 import com.android.systemui.statusbar.CommandQueue.Callbacks;
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
+import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import java.io.FileDescriptor;
@@ -100,11 +101,12 @@
             CommandQueue commandQueue,
             @Main Handler mainHandler,
             ConfigurationController configurationController,
-            NavigationBarA11yHelper navigationBarA11yHelper,
+            NavBarHelper navBarHelper,
             TaskbarDelegate taskbarDelegate,
             NavigationBar.Factory navigationBarFactory,
             DumpManager dumpManager,
-            AutoHideController autoHideController) {
+            AutoHideController autoHideController,
+            LightBarController lightBarController) {
         mContext = context;
         mHandler = mainHandler;
         mNavigationBarFactory = navigationBarFactory;
@@ -115,8 +117,8 @@
         mNavMode = navigationModeController.addListener(this);
         mTaskbarDelegate = taskbarDelegate;
         mTaskbarDelegate.setDependencies(commandQueue, overviewProxyService,
-                navigationBarA11yHelper, navigationModeController, sysUiFlagsContainer,
-                dumpManager, autoHideController);
+                navBarHelper, navigationModeController, sysUiFlagsContainer,
+                dumpManager, autoHideController, lightBarController);
         mIsTablet = isTablet(mContext);
         dumpManager.registerDumpable(this);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index d707dbd..3d58a5a 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -43,6 +43,8 @@
 import android.hardware.display.DisplayManager;
 import android.inputmethodservice.InputMethodService;
 import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
 import android.view.Display;
 import android.view.InsetsVisibilities;
 import android.view.View;
@@ -62,6 +64,9 @@
 import com.android.systemui.statusbar.AutoHideUiElement;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.AutoHideController;
+import com.android.systemui.statusbar.phone.BarTransitions;
+import com.android.systemui.statusbar.phone.LightBarController;
+import com.android.systemui.statusbar.phone.LightBarTransitionsController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -73,19 +78,32 @@
 public class TaskbarDelegate implements CommandQueue.Callbacks,
         OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener,
         ComponentCallbacks, Dumpable {
+    private static final String TAG = TaskbarDelegate.class.getSimpleName();
 
     private final EdgeBackGestureHandler mEdgeBackGestureHandler;
-
+    private boolean mInitialized;
     private CommandQueue mCommandQueue;
     private OverviewProxyService mOverviewProxyService;
-    private NavigationBarA11yHelper mNavigationBarA11yHelper;
+    private NavBarHelper mNavBarHelper;
     private NavigationModeController mNavigationModeController;
     private SysUiState mSysUiState;
     private AutoHideController mAutoHideController;
+    private LightBarController mLightBarController;
+    private LightBarTransitionsController mLightBarTransitionsController;
     private int mDisplayId;
     private int mNavigationIconHints;
-    private final NavigationBarA11yHelper.NavA11yEventListener mNavA11yEventListener =
-            this::updateSysuiFlags;
+    private final NavBarHelper.NavbarTaskbarStateUpdater mNavbarTaskbarStateUpdater =
+            new NavBarHelper.NavbarTaskbarStateUpdater() {
+                @Override
+                public void updateAccessibilityServicesState() {
+                    updateSysuiFlags();
+                }
+
+                @Override
+                public void updateAssistantAvailable(boolean available) {
+                    updateAssistantAvailability(available);
+                }
+            };
     private int mDisabledFlags;
     private @WindowVisibleState int mTaskBarWindowState = WINDOW_STATE_SHOWING;
     private @Behavior int mBehavior;
@@ -125,27 +143,56 @@
 
     public void setDependencies(CommandQueue commandQueue,
             OverviewProxyService overviewProxyService,
-            NavigationBarA11yHelper navigationBarA11yHelper,
+            NavBarHelper navBarHelper,
             NavigationModeController navigationModeController,
             SysUiState sysUiState, DumpManager dumpManager,
-            AutoHideController autoHideController) {
+            AutoHideController autoHideController,
+            LightBarController lightBarController) {
         // TODO: adding this in the ctor results in a dagger dependency cycle :(
         mCommandQueue = commandQueue;
         mOverviewProxyService = overviewProxyService;
-        mNavigationBarA11yHelper = navigationBarA11yHelper;
+        mNavBarHelper = navBarHelper;
         mNavigationModeController = navigationModeController;
         mSysUiState = sysUiState;
         dumpManager.registerDumpable(this);
         mAutoHideController = autoHideController;
+        mLightBarController = lightBarController;
+        mLightBarTransitionsController = createLightBarTransitionsController();
+    }
+
+    // Separated into a method to keep setDependencies() clean/readable.
+    private LightBarTransitionsController createLightBarTransitionsController() {
+        return new LightBarTransitionsController(mContext,
+                new LightBarTransitionsController.DarkIntensityApplier() {
+                    @Override
+                    public void applyDarkIntensity(float darkIntensity) {
+                        mOverviewProxyService.onNavButtonsDarkIntensityChanged(darkIntensity);
+                    }
+
+                    @Override
+                    public int getTintAnimationDuration() {
+                        return LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION;
+                    }
+                }, mCommandQueue) {
+            @Override
+            public boolean supportsIconTintForNavMode(int navigationMode) {
+                // Always tint taskbar nav buttons (region sampling handles gesture bar separately).
+                return true;
+            }
+        };
     }
 
     public void init(int displayId) {
+        if (mInitialized) {
+            return;
+        }
         mDisplayId = displayId;
         mCommandQueue.addCallback(this);
         mOverviewProxyService.addCallback(this);
         mEdgeBackGestureHandler.onNavigationModeChanged(
                 mNavigationModeController.addListener(this));
-        mNavigationBarA11yHelper.registerA11yEventListener(mNavA11yEventListener);
+        mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
+        mNavBarHelper.init(mContext);
         mEdgeBackGestureHandler.onNavBarAttached();
         // Initialize component callback
         Display display = mDisplayManager.getDisplay(displayId);
@@ -154,23 +201,32 @@
         // Set initial state for any listeners
         updateSysuiFlags();
         mAutoHideController.setNavigationBar(mAutoHideUiElement);
+        mLightBarController.setNavigationBar(mLightBarTransitionsController);
+        mInitialized = true;
     }
 
     public void destroy() {
+        if (!mInitialized) {
+            return;
+        }
         mCommandQueue.removeCallback(this);
         mOverviewProxyService.removeCallback(this);
         mNavigationModeController.removeListener(this);
-        mNavigationBarA11yHelper.removeA11yEventListener(mNavA11yEventListener);
+        mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
+        mNavBarHelper.destroy();
         mEdgeBackGestureHandler.onNavBarDetached();
         if (mWindowContext != null) {
             mWindowContext.unregisterComponentCallbacks(this);
             mWindowContext = null;
         }
         mAutoHideController.setNavigationBar(null);
+        mLightBarTransitionsController.destroy(mContext);
+        mLightBarController.setNavigationBar(null);
+        mInitialized = false;
     }
 
     private void updateSysuiFlags() {
-        int a11yFlags = mNavigationBarA11yHelper.getA11yButtonState();
+        int a11yFlags = mNavBarHelper.getA11yButtonState();
         boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
         boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
 
@@ -194,6 +250,18 @@
                 .commitUpdate(mDisplayId);
     }
 
+    private void updateAssistantAvailability(boolean assistantAvailable) {
+        if (mOverviewProxyService.getProxy() == null) {
+            return;
+        }
+
+        try {
+            mOverviewProxyService.getProxy().onAssistantAvailable(assistantAvailable);
+        } catch (RemoteException e) {
+            Log.e(TAG, "onAssistantAvailable() failed, available: " + assistantAvailable, e);
+        }
+    }
+
     @Override
     public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
             boolean showImeSwitcher) {
@@ -233,6 +301,10 @@
             AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme, int behavior,
             InsetsVisibilities requestedVisibilities, String packageName) {
         mOverviewProxyService.onSystemBarAttributesChanged(displayId, behavior);
+        if (mLightBarController != null && displayId == mDisplayId) {
+            mLightBarController.onNavigationBarAppearanceChanged(appearance, false/*nbModeChanged*/,
+                    BarTransitions.MODE_TRANSPARENT /*navigationBarMode*/, navbarColorManagedByIme);
+        }
         if (mBehavior != behavior) {
             mBehavior = behavior;
             updateSysuiFlags();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
index 98b9146..fce0c0c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
@@ -17,8 +17,10 @@
 package com.android.systemui.qs
 
 import android.content.Intent
+import android.os.Handler
 import android.os.UserManager
 import android.provider.Settings
+import android.provider.Settings.Global.USER_SWITCHER_ENABLED
 import android.view.View
 import android.widget.Toast
 import androidx.annotation.VisibleForTesting
@@ -35,6 +37,7 @@
 import com.android.systemui.qs.FooterActionsController.ExpansionState.COLLAPSED
 import com.android.systemui.qs.FooterActionsController.ExpansionState.EXPANDED
 import com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.phone.MultiUserSwitchController
 import com.android.systemui.statusbar.phone.SettingsButton
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
@@ -42,6 +45,7 @@
 import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener
 import com.android.systemui.tuner.TunerService
 import com.android.systemui.util.ViewController
+import com.android.systemui.util.settings.GlobalSettings
 import javax.inject.Inject
 import javax.inject.Named
 
@@ -55,6 +59,7 @@
     private val qsPanelController: QSPanelController,
     private val activityStarter: ActivityStarter,
     private val userManager: UserManager,
+    private val userTracker: UserTracker,
     private val userInfoController: UserInfoController,
     private val multiUserSwitchController: MultiUserSwitchController,
     private val deviceProvisionedController: DeviceProvisionedController,
@@ -64,7 +69,9 @@
     private val globalActionsDialog: GlobalActionsDialogLite,
     private val uiEventLogger: UiEventLogger,
     @Named(PM_LITE_ENABLED) private val showPMLiteButton: Boolean,
-    private val buttonsVisibleState: ExpansionState
+    private val buttonsVisibleState: ExpansionState,
+    private val globalSetting: GlobalSettings,
+    private val handler: Handler
 ) : ViewController<FooterActionsView>(view) {
 
     enum class ExpansionState { COLLAPSED, EXPANDED }
@@ -83,6 +90,16 @@
         mView.onUserInfoChanged(picture, isGuestUser)
     }
 
+    private val multiUserSetting =
+            object : SettingObserver(
+                    globalSetting, handler, USER_SWITCHER_ENABLED, userTracker.userId) {
+                override fun handleValueChanged(value: Int, observedChange: Boolean) {
+                    if (observedChange) {
+                        updateView()
+                    }
+                }
+            }
+
     private val onClickListener = View.OnClickListener { v ->
         // Don't do anything until views are unhidden. Don't do anything if the tap looks
         // suspicious.
@@ -182,6 +199,7 @@
             return
         }
         this.listening = listening
+        multiUserSetting.isListening = listening
         if (this.listening) {
             userInfoController.addCallback(onUserInfoChangedListener)
             updateView()
@@ -215,4 +233,4 @@
     }
 
     private fun isTunerEnabled() = tunerService.isTunerEnabled
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt
index f6c89a9..dd4dc87 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt
@@ -16,18 +16,22 @@
 
 package com.android.systemui.qs
 
+import android.os.Handler
 import android.os.UserManager
 import com.android.internal.logging.MetricsLogger
 import com.android.internal.logging.UiEventLogger
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.globalactions.GlobalActionsDialogLite
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.qs.FooterActionsController.ExpansionState
 import com.android.systemui.qs.dagger.QSFlagsModule
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.phone.MultiUserSwitchController
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.statusbar.policy.UserInfoController
 import com.android.systemui.tuner.TunerService
+import com.android.systemui.util.settings.GlobalSettings
 import javax.inject.Inject
 import javax.inject.Named
 
@@ -35,6 +39,7 @@
     private val qsPanelController: QSPanelController,
     private val activityStarter: ActivityStarter,
     private val userManager: UserManager,
+    private val userTracker: UserTracker,
     private val userInfoController: UserInfoController,
     private val multiUserSwitchControllerFactory: MultiUserSwitchController.Factory,
     private val deviceProvisionedController: DeviceProvisionedController,
@@ -43,7 +48,9 @@
     private val tunerService: TunerService,
     private val globalActionsDialog: GlobalActionsDialogLite,
     private val uiEventLogger: UiEventLogger,
-    @Named(QSFlagsModule.PM_LITE_ENABLED) private val showPMLiteButton: Boolean
+    @Named(QSFlagsModule.PM_LITE_ENABLED) private val showPMLiteButton: Boolean,
+    private val globalSettings: GlobalSettings,
+    @Main private val handler: Handler
 ) {
     private lateinit var view: FooterActionsView
     private lateinit var buttonsVisibleState: ExpansionState
@@ -60,8 +67,9 @@
 
     fun build(): FooterActionsController {
         return FooterActionsController(view, qsPanelController, activityStarter, userManager,
-                userInfoController, multiUserSwitchControllerFactory.create(view),
+                userTracker, userInfoController, multiUserSwitchControllerFactory.create(view),
                 deviceProvisionedController, falsingManager, metricsLogger, tunerService,
-                globalActionsDialog, uiEventLogger, showPMLiteButton, buttonsVisibleState)
+                globalActionsDialog, uiEventLogger, showPMLiteButton, buttonsVisibleState,
+                globalSettings, handler)
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/GlobalSetting.java b/packages/SystemUI/src/com/android/systemui/qs/GlobalSetting.java
deleted file mode 100644
index c169df0..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/GlobalSetting.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2014 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.qs;
-
-import android.content.Context;
-import android.database.ContentObserver;
-import android.os.Handler;
-import android.provider.Settings.Global;
-
-import com.android.systemui.statusbar.policy.Listenable;
-
-/** Helper for managing a global setting. **/
-public abstract class GlobalSetting extends ContentObserver implements Listenable {
-    private final Context mContext;
-    private final String mSettingName;
-
-    protected abstract void handleValueChanged(int value);
-
-    public GlobalSetting(Context context, Handler handler, String settingName) {
-        super(handler);
-        mContext = context;
-        mSettingName = settingName;
-    }
-
-    public int getValue() {
-        return Global.getInt(mContext.getContentResolver(), mSettingName, 0);
-    }
-
-    public void setValue(int value) {
-        Global.putInt(mContext.getContentResolver(), mSettingName, value);
-    }
-
-    @Override
-    public void setListening(boolean listening) {
-        if (listening) {
-            mContext.getContentResolver().registerContentObserver(
-                    Global.getUriFor(mSettingName), false, this);
-        } else {
-            mContext.getContentResolver().unregisterContentObserver(this);
-        }
-    }
-
-    @Override
-    public void onChange(boolean selfChange) {
-        handleValueChanged(getValue());
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 71eb4a2..d69deef 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -26,6 +26,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
+import android.util.ArrayMap;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.Gravity;
@@ -103,6 +104,8 @@
     protected LinearLayout mHorizontalContentContainer;
 
     protected QSTileLayout mTileLayout;
+    private float mSquishinessFraction = 1f;
+    private final ArrayMap<View, Integer> mChildrenLayoutTop = new ArrayMap<>();
 
     public QSPanel(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -179,10 +182,26 @@
         if (mTileLayout == null) {
             mTileLayout = (QSTileLayout) LayoutInflater.from(mContext)
                     .inflate(R.layout.qs_paged_tile_layout, this, false);
+            mTileLayout.setSquishinessFraction(mSquishinessFraction);
         }
         return mTileLayout;
     }
 
+    public void setSquishinessFraction(float squishinessFraction) {
+        if (Float.compare(squishinessFraction, mSquishinessFraction) == 0) {
+            return;
+        }
+        mSquishinessFraction = squishinessFraction;
+        if (mTileLayout == null) {
+            return;
+        }
+        mTileLayout.setSquishinessFraction(squishinessFraction);
+        if (getMeasuredWidth() == 0) {
+            return;
+        }
+        updateViewPositions();
+    }
+
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         if (mTileLayout instanceof PagedTileLayout) {
@@ -228,6 +247,39 @@
         setMeasuredDimension(getMeasuredWidth(), height);
     }
 
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        for (int i = 0; i < getChildCount(); i++) {
+            View child = getChildAt(i);
+            mChildrenLayoutTop.put(child, child.getTop());
+        }
+        updateViewPositions();
+    }
+
+    private void updateViewPositions() {
+        if (!(mTileLayout instanceof TileLayout)) {
+            return;
+        }
+        TileLayout layout = (TileLayout) mTileLayout;
+
+        // Adjust view positions based on tile squishing
+        int tileHeightOffset = layout.getTilesHeight() - layout.getHeight();
+
+        boolean move = false;
+        for (int i = 0; i < getChildCount(); i++) {
+            View child = getChildAt(i);
+            if (move) {
+                int top = mChildrenLayoutTop.get(child);
+                child.setLeftTopRightBottom(child.getLeft(), top + tileHeightOffset,
+                        child.getRight(), top + tileHeightOffset + child.getHeight());
+            }
+            if (child == mTileLayout) {
+                move = true;
+            }
+        }
+    }
+
     protected String getDumpableTag() {
         return TAG;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index f7d1b1e..eddc206 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -147,6 +147,10 @@
         return mMediaHost;
     }
 
+    public void setSquishinessFraction(float squishinessFraction) {
+        mView.setSquishinessFraction(squishinessFraction);
+    }
+
     @Override
     protected void onViewAttached() {
         mQsTileRevealController = createTileRevealController();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSquishinessController.kt b/packages/SystemUI/src/com/android/systemui/qs/QSSquishinessController.kt
index c1c146d..c680cb5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSquishinessController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSquishinessController.kt
@@ -1,14 +1,10 @@
 package com.android.systemui.qs
 
-import android.view.ViewGroup
-import com.android.systemui.qs.dagger.QSFragmentModule.QQS_FOOTER
 import com.android.systemui.qs.dagger.QSScope
 import javax.inject.Inject
-import javax.inject.Named
 
 @QSScope
 class QSSquishinessController @Inject constructor(
-    @Named(QQS_FOOTER) private val qqsFooterActionsView: FooterActionsView,
     private val qsAnimator: QSAnimator,
     private val qsPanelController: QSPanelController,
     private val quickQSPanelController: QuickQSPanelController
@@ -33,23 +29,7 @@
      * Change the height of all tiles and repositions their siblings.
      */
     private fun updateSquishiness() {
-        (qsPanelController.tileLayout as QSPanel.QSTileLayout).setSquishinessFraction(squishiness)
-        val tileLayout = quickQSPanelController.tileLayout as TileLayout
-        tileLayout.setSquishinessFraction(squishiness)
-
-        // Calculate how much we should move the footer
-        val tileHeightOffset = tileLayout.height - tileLayout.tilesHeight
-        val footerTopMargin = (qqsFooterActionsView.layoutParams as ViewGroup.MarginLayoutParams)
-                .topMargin
-        val nextTop = tileLayout.bottom - tileHeightOffset + footerTopMargin
-        val amountMoved = nextTop - qqsFooterActionsView.top
-
-        // Move the footer and other siblings (MediaPlayer)
-        (qqsFooterActionsView.parent as ViewGroup?)?.let { parent ->
-            val index = parent.indexOfChild(qqsFooterActionsView)
-            for (i in index until parent.childCount) {
-                parent.getChildAt(i).top += amountMoved
-            }
-        }
+        qsPanelController.setSquishinessFraction(squishiness)
+        quickQSPanelController.setSquishinessFraction(squishiness)
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java b/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
similarity index 63%
rename from packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java
rename to packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
index d2bc38e..6b0abd4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
@@ -21,11 +21,18 @@
 import android.os.Handler;
 
 import com.android.systemui.statusbar.policy.Listenable;
+import com.android.systemui.util.settings.GlobalSettings;
 import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.util.settings.SettingsProxy;
+import com.android.systemui.util.settings.SystemSettings;
 
-/** Helper for managing a secure setting. **/
-public abstract class SecureSetting extends ContentObserver implements Listenable {
-    private final SecureSettings mSecureSettings;
+/**
+ * Helper for managing secure, global, and system settings through use of {@link SettingsProxy},
+ * which is the common superclass of {@link SecureSettings}, {@link GlobalSettings}, and
+ * {@link SystemSettings}.
+ */
+public abstract class SettingObserver extends ContentObserver implements Listenable {
+    private final SettingsProxy mSettingsProxy;
     private final String mSettingName;
     private final int mDefaultValue;
 
@@ -35,19 +42,19 @@
 
     protected abstract void handleValueChanged(int value, boolean observedChange);
 
-    public SecureSetting(SecureSettings secureSettings, Handler handler, String settingName,
+    public SettingObserver(SettingsProxy settingsProxy, Handler handler, String settingName,
             int userId) {
-        this(secureSettings, handler, settingName, userId, 0);
+        this(settingsProxy, handler, settingName, userId, 0);
     }
 
-    public SecureSetting(SecureSettings secureSetting, Handler handler, String settingName) {
-        this(secureSetting, handler, settingName, ActivityManager.getCurrentUser());
+    public SettingObserver(SettingsProxy settingsProxy, Handler handler, String settingName) {
+        this(settingsProxy, handler, settingName, ActivityManager.getCurrentUser());
     }
 
-    public SecureSetting(SecureSettings secureSettings, Handler handler, String settingName,
+    public SettingObserver(SettingsProxy settingsProxy, Handler handler, String settingName,
             int userId, int defaultValue) {
         super(handler);
-        mSecureSettings = secureSettings;
+        mSettingsProxy = settingsProxy;
         mSettingName = settingName;
         mObservedValue = mDefaultValue = defaultValue;
         mUserId = userId;
@@ -57,12 +64,17 @@
         return mListening ? mObservedValue : getValueFromProvider();
     }
 
+    /**
+     * Set the value of the observed setting.
+     *
+     * @param value The new value for the setting.
+     */
     public void setValue(int value) {
-        mSecureSettings.putIntForUser(mSettingName, value, mUserId);
+        mSettingsProxy.putIntForUser(mSettingName, value, mUserId);
     }
 
     private int getValueFromProvider() {
-        return mSecureSettings.getIntForUser(mSettingName, mDefaultValue, mUserId);
+        return mSettingsProxy.getIntForUser(mSettingName, mDefaultValue, mUserId);
     }
 
     @Override
@@ -71,10 +83,10 @@
         mListening = listening;
         if (listening) {
             mObservedValue = getValueFromProvider();
-            mSecureSettings.registerContentObserverForUser(
-                    mSecureSettings.getUriFor(mSettingName), false, this, mUserId);
+            mSettingsProxy.registerContentObserverForUser(
+                    mSettingsProxy.getUriFor(mSettingName), false, this, mUserId);
         } else {
-            mSecureSettings.unregisterContentObserver(this);
+            mSettingsProxy.unregisterContentObserver(this);
             mObservedValue = mDefaultValue;
         }
     }
@@ -87,6 +99,9 @@
         handleValueChanged(value, changed);
     }
 
+    /**
+     * Set user handle for which to observe the setting.
+     */
     public void setUserId(int userId) {
         mUserId = userId;
         if (mListening) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 4bdeb56..09fad30 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -247,7 +247,10 @@
         } else {
             measuredHeight
         }
-        bottom = top + (actualHeight * squishinessFraction).toInt()
+        // Limit how much we affect the height, so we don't have rounding artifacts when the tile
+        // is too short.
+        val constrainedSquishiness = 0.1f + squishinessFraction * 0.9f
+        bottom = top + (actualHeight * constrainedSquishiness).toInt()
         scrollY = (actualHeight - height) / 2
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
index 22cd6f8..5650a17 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -44,10 +44,11 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.GlobalSetting;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.util.settings.GlobalSettings;
 
 import javax.inject.Inject;
 
@@ -56,7 +57,7 @@
 /** Quick settings tile: Airplane mode **/
 public class AirplaneModeTile extends QSTileImpl<BooleanState> {
     private final Icon mIcon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_airplane);
-    private final GlobalSetting mSetting;
+    private final SettingObserver mSetting;
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final Lazy<ConnectivityManager> mLazyConnectivityManager;
 
@@ -73,16 +74,17 @@
             ActivityStarter activityStarter,
             QSLogger qsLogger,
             BroadcastDispatcher broadcastDispatcher,
-            Lazy<ConnectivityManager> lazyConnectivityManager
+            Lazy<ConnectivityManager> lazyConnectivityManager,
+            GlobalSettings globalSettings
     ) {
         super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mBroadcastDispatcher = broadcastDispatcher;
         mLazyConnectivityManager = lazyConnectivityManager;
 
-        mSetting = new GlobalSetting(mContext, mHandler, Global.AIRPLANE_MODE_ON) {
+        mSetting = new SettingObserver(globalSettings, mHandler, Global.AIRPLANE_MODE_ON) {
             @Override
-            protected void handleValueChanged(int value) {
+            protected void handleValueChanged(int value, boolean observedChange) {
                 // mHandler is the background handler so calling this is OK
                 handleRefreshState(value);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
index e3024fa..b8ef312 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
@@ -36,7 +36,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
-import com.android.systemui.qs.SecureSetting;
+import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -49,7 +49,7 @@
 
     private final BatteryController mBatteryController;
     @VisibleForTesting
-    protected final SecureSetting mSetting;
+    protected final SettingObserver mSetting;
 
     private int mLevel;
     private boolean mPowerSave;
@@ -76,7 +76,7 @@
         mBatteryController = batteryController;
         mBatteryController.observe(getLifecycle(), this);
         int currentUser = host.getUserContext().getUserId();
-        mSetting = new SecureSetting(
+        mSetting = new SettingObserver(
                 secureSettings,
                 mHandler,
                 Secure.LOW_POWER_WARNING_ACKNOWLEDGED,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index b729953..d2d2180 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -38,7 +38,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
-import com.android.systemui.qs.SecureSetting;
+import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.settings.UserTracker;
@@ -54,7 +54,7 @@
     private static final String COLOR_INVERSION_PREFERENCE_KEY = "toggle_inversion_preference";
 
     private final Icon mIcon = ResourceIcon.get(drawable.ic_invert_colors);
-    private final SecureSetting mSetting;
+    private final SettingObserver mSetting;
 
     private boolean mListening;
 
@@ -74,7 +74,7 @@
         super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
 
-        mSetting = new SecureSetting(secureSettings, mHandler,
+        mSetting = new SettingObserver(secureSettings, mHandler,
                 Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, userTracker.getUserId()) {
             @Override
             protected void handleValueChanged(int value, boolean observedChange) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index b1cd68e..18b401f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -19,6 +19,7 @@
 import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
 import static android.provider.Settings.Global.ZEN_MODE_OFF;
 
+import android.app.AlertDialog;
 import android.app.Dialog;
 import android.content.Context;
 import android.content.Intent;
@@ -41,7 +42,6 @@
 import android.view.View;
 import android.view.View.OnAttachStateChangeListener;
 import android.view.ViewGroup;
-import android.view.WindowManager;
 import android.widget.Switch;
 import android.widget.Toast;
 
@@ -53,6 +53,7 @@
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.SysUIToast;
+import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
@@ -61,7 +62,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
-import com.android.systemui.qs.SecureSetting;
+import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -83,7 +84,8 @@
     private final ZenModeController mController;
     private final DndDetailAdapter mDetailAdapter;
     private final SharedPreferences mSharedPreferences;
-    private final SecureSetting mSettingZenDuration;
+    private final SettingObserver mSettingZenDuration;
+    private final DialogLaunchAnimator mDialogLaunchAnimator;
 
     private boolean mListening;
     private boolean mShowingDetail;
@@ -100,7 +102,8 @@
             QSLogger qsLogger,
             ZenModeController zenModeController,
             @Main SharedPreferences sharedPreferences,
-            SecureSettings secureSettings
+            SecureSettings secureSettings,
+            DialogLaunchAnimator dialogLaunchAnimator
     ) {
         super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
@@ -108,7 +111,8 @@
         mSharedPreferences = sharedPreferences;
         mDetailAdapter = new DndDetailAdapter();
         mController.observe(getLifecycle(), mZenCallback);
-        mSettingZenDuration = new SecureSetting(secureSettings, mUiHandler,
+        mDialogLaunchAnimator = dialogLaunchAnimator;
+        mSettingZenDuration = new SettingObserver(secureSettings, mUiHandler,
                 Settings.Secure.ZEN_DURATION, getHost().getUserId()) {
             @Override
             protected void handleValueChanged(int value, boolean observedChange) {
@@ -117,8 +121,6 @@
         };
     }
 
-
-
     public static void setVisible(Context context, boolean visible) {
         Prefs.putBoolean(context, Prefs.Key.DND_TILE_VISIBLE, visible);
     }
@@ -187,14 +189,17 @@
             switch (zenDuration) {
                 case Settings.Secure.ZEN_DURATION_PROMPT:
                     mUiHandler.post(() -> {
-                        Dialog mDialog = new EnableZenModeDialog(mContext).createDialog();
-                        mDialog.getWindow().setType(
-                                WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
-                        SystemUIDialog.setShowForAllUsers(mDialog, true);
-                        SystemUIDialog.registerDismissListener(mDialog);
-                        SystemUIDialog.setWindowOnTop(mDialog);
-                        mUiHandler.post(() -> mDialog.show());
-                        mHost.collapsePanels();
+                        Dialog dialog = makeZenModeDialog();
+                        if (view != null) {
+                            final Dialog hostDialog =
+                                    mDialogLaunchAnimator.showFromView(dialog, view, false);
+                            setDialogListeners(dialog, hostDialog);
+                        } else {
+                            // If we are not launching with animator, register default
+                            // dismiss listener
+                            SystemUIDialog.registerDismissListener(dialog);
+                            dialog.show();
+                        }
                     });
                     break;
                 case Settings.Secure.ZEN_DURATION_FOREVER:
@@ -209,6 +214,20 @@
         }
     }
 
+    private Dialog makeZenModeDialog() {
+        AlertDialog dialog = new EnableZenModeDialog(mContext, R.style.Theme_SystemUI_Dialog)
+                .createDialog();
+        SystemUIDialog.applyFlags(dialog);
+        SystemUIDialog.setShowForAllUsers(dialog, true);
+        return dialog;
+    }
+
+    private void setDialogListeners(Dialog zenModeDialog, Dialog hostDialog) {
+        // Zen mode dialog is never hidden.
+        SystemUIDialog.registerDismissListener(zenModeDialog, hostDialog::dismiss);
+        zenModeDialog.setOnCancelListener(dialog -> hostDialog.cancel());
+    }
+
     @Override
     protected void handleSecondaryClick(@Nullable View view) {
         if (mController.isVolumeRestricted()) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
index c8d2cdc..521dbe71 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
@@ -36,7 +36,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
-import com.android.systemui.qs.SecureSetting;
+import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.settings.UserTracker;
@@ -49,7 +49,7 @@
 public class OneHandedModeTile extends QSTileImpl<BooleanState> {
     private final Icon mIcon = ResourceIcon.get(
             com.android.internal.R.drawable.ic_qs_one_handed_mode);
-    private final SecureSetting mSetting;
+    private final SettingObserver mSetting;
 
     @Inject
     public OneHandedModeTile(
@@ -65,7 +65,7 @@
             SecureSettings secureSettings) {
         super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
-        mSetting = new SecureSetting(secureSettings, mHandler,
+        mSetting = new SettingObserver(secureSettings, mHandler,
                 Settings.Secure.ONE_HANDED_MODE_ENABLED, userTracker.getUserId()) {
             @Override
             protected void handleValueChanged(int value, boolean observedChange) {
@@ -105,7 +105,8 @@
 
     @Override
     public Intent getLongClickIntent() {
-        return new Intent(Intent.ACTION_ONE_HANDED_SETTINGS);
+        // TODO(b/201743873) define new intent action ACTION_ONE_HANDED_SETTINGS in Settings.
+        return null;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index fa874b1..3ed7e84 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -986,6 +986,18 @@
         }
     }
 
+    public void onNavButtonsDarkIntensityChanged(float darkIntensity) {
+        try {
+            if (mOverviewProxy != null) {
+                mOverviewProxy.onNavButtonsDarkIntensityChanged(darkIntensity);
+            } else {
+                Log.e(TAG_OPS, "Failed to get overview proxy to update nav buttons dark intensity");
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG_OPS, "Failed to call onNavButtonsDarkIntensityChanged()", e);
+        }
+    }
+
     private void updateEnabledState() {
         final int currentUser = ActivityManagerWrapper.getInstance().getCurrentUserId();
         mIsEnabled = mContext.getPackageManager().resolveServiceAsUser(mQuickStepIntent,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 75b3592..3cecbb7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -533,9 +533,13 @@
      * @param animate {@code true} to show animations.
      */
     public void recomputeDisableFlags(int displayId, boolean animate) {
-        int disabled1 = getDisabled1(displayId);
-        int disabled2 = getDisabled2(displayId);
-        disable(displayId, disabled1, disabled2, animate);
+        // This must update holding the lock otherwise it can clobber the disabled flags set on the
+        // binder thread from the disable() call
+        synchronized (mLock) {
+            int disabled1 = getDisabled1(displayId);
+            int disabled2 = getDisabled2(displayId);
+            disable(displayId, disabled1, disabled2, animate);
+        }
     }
 
     private void setDisabled(int displayId, int disabled1, int disabled2) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 282babc..efe02ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -449,7 +449,8 @@
         NotificationEntry mediaNotification = null;
         MediaController controller = null;
         for (NotificationEntry entry : allNotifications) {
-            if (entry.isMediaNotification()) {
+            Notification notif = entry.getSbn().getNotification();
+            if (notif.isMediaNotification()) {
                 final MediaSession.Token token =
                         entry.getSbn().getNotification().extras.getParcelable(
                                 Notification.EXTRA_MEDIA_SESSION);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/CommunalCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/CommunalCoordinator.java
index 992d898..bd011c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/CommunalCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/CommunalCoordinator.java
@@ -19,6 +19,7 @@
 import androidx.annotation.NonNull;
 
 import com.android.systemui.communal.CommunalStateController;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -26,6 +27,8 @@
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 
+import java.util.concurrent.Executor;
+
 import javax.inject.Inject;
 
 /**
@@ -34,14 +37,17 @@
  */
 @CoordinatorScope
 public class CommunalCoordinator implements Coordinator {
+    final Executor mExecutor;
     final CommunalStateController mCommunalStateController;
     final NotificationEntryManager mNotificationEntryManager;
     final NotificationLockscreenUserManager mNotificationLockscreenUserManager;
 
     @Inject
-    public CommunalCoordinator(NotificationEntryManager notificationEntryManager,
+    public CommunalCoordinator(@Main Executor executor,
+            NotificationEntryManager notificationEntryManager,
             NotificationLockscreenUserManager notificationLockscreenUserManager,
             CommunalStateController communalStateController) {
+        mExecutor = executor;
         mNotificationEntryManager = notificationEntryManager;
         mNotificationLockscreenUserManager = notificationLockscreenUserManager;
         mCommunalStateController = communalStateController;
@@ -57,8 +63,10 @@
     final CommunalStateController.Callback mStateCallback = new CommunalStateController.Callback() {
         @Override
         public void onCommunalViewShowingChanged() {
-            mFilter.invalidateList();
-            mNotificationEntryManager.updateNotifications("Communal mode state changed");
+            mExecutor.execute(() -> {
+                mFilter.invalidateList();
+                mNotificationEntryManager.updateNotifications("Communal mode state changed");
+            });
         }
     };
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
index 0478658..e7ef2ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
@@ -99,7 +99,7 @@
 
     private boolean hasHighPriorityCharacteristics(NotificationEntry entry) {
         return !hasUserSetImportance(entry)
-                && (entry.getSbn().getNotification().hasMediaSession()
+                && (entry.getSbn().getNotification().isMediaNotification()
                 || isPeopleNotification(entry)
                 || isMessagingStyle(entry));
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index d90613f..9f10322 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -3203,11 +3203,8 @@
         return getCurrentBottomRoundness() == 0.0f && getCurrentTopRoundness() == 0.0f;
     }
 
-    //TODO: this logic can't depend on layout if we are recycling!
     public boolean isMediaRow() {
-        return getExpandedContentView() != null
-                && getExpandedContentView().findViewById(
-                com.android.internal.R.id.media_actions) != null;
+        return mEntry.getSbn().getNotification().isMediaNotification();
     }
 
     public boolean isTopLevelChild() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index eb7410c..1038e76 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -468,8 +468,8 @@
         final int height = (view instanceof ExpandableView)
                 ? ((ExpandableView) view).getActualHeight()
                 : view.getHeight();
-        final int rx = (int) ev.getX();
-        final int ry = (int) ev.getY();
+        final int rx = (int) ev.getRawX();
+        final int ry = (int) ev.getRawY();
         int[] temp = new int[2];
         view.getLocationOnScreen(temp);
         final int x = temp[0];
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index fa32620..244103c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -31,7 +31,7 @@
 import com.android.systemui.qs.AutoAddTracker;
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.qs.ReduceBrightColorsController;
-import com.android.systemui.qs.SecureSetting;
+import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.qs.external.CustomTile;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.CastController.CastDevice;
@@ -383,8 +383,8 @@
     };
 
     @VisibleForTesting
-    protected SecureSetting getSecureSettingForKey(String key) {
-        for (SecureSetting s : mAutoAddSettingList) {
+    protected SettingObserver getSecureSettingForKey(String key) {
+        for (SettingObserver s : mAutoAddSettingList) {
             if (Objects.equals(key, s.getKey())) {
                 return s;
             }
@@ -398,7 +398,7 @@
      * When the setting changes to a value different from 0, if the tile has not been auto added
      * before, it will be added and the listener will be stopped.
      */
-    private class AutoAddSetting extends SecureSetting {
+    private class AutoAddSetting extends SettingObserver {
         private final String mSpec;
 
         AutoAddSetting(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 67f51cb..aa3b3e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.hardware.biometrics.BiometricSourceType;
+import android.hardware.fingerprint.FingerprintManager;
 import android.metrics.LogMaker;
 import android.os.Handler;
 import android.os.PowerManager;
@@ -46,9 +47,11 @@
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import java.io.FileDescriptor;
@@ -71,6 +74,7 @@
     private static final long BIOMETRIC_WAKELOCK_TIMEOUT_MS = 15 * 1000;
     private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock:wakelock";
     private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl();
+    private static final int FP_ATTEMPTS_BEFORE_SHOW_BOUNCER = 3;
 
     @IntDef(prefix = { "MODE_" }, value = {
             MODE_NONE,
@@ -167,6 +171,10 @@
 
     private final MetricsLogger mMetricsLogger;
     private final AuthController mAuthController;
+    private final StatusBarStateController mStatusBarStateController;
+
+    private long mLastFpFailureUptimeMillis;
+    private int mNumConsecutiveFpFailures;
 
     private static final class PendingAuthenticated {
         public final int userId;
@@ -209,7 +217,10 @@
         BIOMETRIC_IRIS_FAILURE(403),
 
         @UiEvent(doc = "A biometric event of type iris errored.")
-        BIOMETRIC_IRIS_ERROR(404);
+        BIOMETRIC_IRIS_ERROR(404),
+
+        @UiEvent(doc = "Bouncer was shown as a result of consecutive failed UDFPS attempts.")
+        BIOMETRIC_BOUNCER_SHOWN(916);
 
         private final int mId;
 
@@ -257,7 +268,8 @@
             NotificationMediaManager notificationMediaManager,
             WakefulnessLifecycle wakefulnessLifecycle,
             ScreenLifecycle screenLifecycle,
-            AuthController authController) {
+            AuthController authController,
+            StatusBarStateController statusBarStateController) {
         mContext = context;
         mPowerManager = powerManager;
         mShadeController = shadeController;
@@ -279,6 +291,7 @@
         mKeyguardBypassController.setUnlockController(this);
         mMetricsLogger = metricsLogger;
         mAuthController = authController;
+        mStatusBarStateController = statusBarStateController;
         dumpManager.registerDumpable(getClass().getName(), this);
     }
 
@@ -620,6 +633,22 @@
                 .setType(MetricsEvent.TYPE_FAILURE).setSubtype(toSubtype(biometricSourceType)));
         Optional.ofNullable(BiometricUiEvent.FAILURE_EVENT_BY_SOURCE_TYPE.get(biometricSourceType))
                 .ifPresent(UI_EVENT_LOGGER::log);
+
+        long currUptimeMillis = SystemClock.uptimeMillis();
+        if (currUptimeMillis - mLastFpFailureUptimeMillis < 2000) { // attempt within 2 seconds
+            mNumConsecutiveFpFailures += 1;
+        } else {
+            mNumConsecutiveFpFailures = 1;
+        }
+        mLastFpFailureUptimeMillis = currUptimeMillis;
+
+        if (biometricSourceType.equals(BiometricSourceType.FINGERPRINT)
+                && mUpdateMonitor.isUdfpsSupported()
+                && mNumConsecutiveFpFailures >= FP_ATTEMPTS_BEFORE_SHOW_BOUNCER) {
+            mKeyguardViewController.showBouncer(true);
+            UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN);
+            mNumConsecutiveFpFailures = 0;
+        }
         cleanup();
     }
 
@@ -631,6 +660,16 @@
                 .addTaggedData(MetricsEvent.FIELD_BIOMETRIC_AUTH_ERROR, msgId));
         Optional.ofNullable(BiometricUiEvent.ERROR_EVENT_BY_SOURCE_TYPE.get(biometricSourceType))
                 .ifPresent(UI_EVENT_LOGGER::log);
+
+        // if we're on the shade and we're locked out, immediately show the bouncer
+        if (biometricSourceType == BiometricSourceType.FINGERPRINT
+                && (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT
+                || msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT)
+                && mUpdateMonitor.isUdfpsSupported()
+                && (mStatusBarStateController.getState() == StatusBarState.SHADE
+                    || mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED)) {
+            mKeyguardViewController.showBouncer(true);
+        }
         cleanup();
     }
 
@@ -664,6 +703,8 @@
             mBiometricModeListener.onResetMode();
             mBiometricModeListener.notifyBiometricAuthModeChanged();
         }
+        mNumConsecutiveFpFailures = 0;
+        mLastFpFailureUptimeMillis = 0;
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 353868b..9647486 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -21,6 +21,7 @@
 
 import android.content.Context;
 import android.content.res.ColorStateList;
+import android.hardware.biometrics.BiometricSourceType;
 import android.os.Handler;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -81,6 +82,13 @@
                 public void onStrongAuthStateChanged(int userId) {
                     mBouncerPromptReason = mCallback.getBouncerPromptReason();
                 }
+
+                @Override
+                public void onLockedOutStateChanged(BiometricSourceType type) {
+                    if (type == BiometricSourceType.FINGERPRINT) {
+                        mBouncerPromptReason = mCallback.getBouncerPromptReason();
+                    }
+                }
             };
     private final Runnable mRemoveViewRunnable = this::removeView;
     private final KeyguardBypassController mKeyguardBypassController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index 570b0ca..88ae0db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -37,7 +37,6 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.policy.BatteryController;
 
 import java.io.FileDescriptor;
@@ -251,7 +250,7 @@
 
     private void updateNavigation() {
         if (mNavigationBarController != null
-                && !QuickStepContract.isGesturalMode(mNavigationMode)) {
+                && mNavigationBarController.supportsIconTintForNavMode(mNavigationMode)) {
             mNavigationBarController.setIconsDark(mNavigationLight, animateChange());
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
index 9021b74..415fb92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
@@ -28,6 +28,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.CommandQueue.Callbacks;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -230,6 +231,14 @@
     }
 
     /**
+     * Return whether to use the tint calculated in this class for nav icons.
+     */
+    public boolean supportsIconTintForNavMode(int navigationMode) {
+        // In gesture mode, we already do region sampling to update tint based on content beneath.
+        return !QuickStepContract.isGesturalMode(navigationMode);
+    }
+
+    /**
      * Interface to apply a specific dark intensity.
      */
     public interface DarkIntensityApplier {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index a9753ac..16aac4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -506,23 +506,11 @@
                     mPanelAlphaAnimator.getProperty(), Interpolators.ALPHA_IN);
     private final NotificationEntryManager mEntryManager;
 
-    private final CommunalSourceMonitor.Callback mCommunalSourceMonitorCallback =
-            new CommunalSourceMonitor.Callback() {
-                @Override
-                public void onSourceAvailable(WeakReference<CommunalSource> source) {
-                    setCommunalSource(source);
-                }
-            };
+    private final CommunalSourceMonitor.Callback mCommunalSourceMonitorCallback;
 
     private WeakReference<CommunalSource> mCommunalSource;
 
-    private final CommunalSource.Callback mCommunalSourceCallback =
-            new CommunalSource.Callback() {
-                @Override
-                public void onDisconnected() {
-                    setCommunalSource(null /*source*/);
-                }
-            };
+    private final CommunalSource.Callback mCommunalSourceCallback;
 
     private final CommandQueue mCommandQueue;
     private final NotificationLockscreenUserManager mLockscreenUserManager;
@@ -904,6 +892,15 @@
 
         mMaxKeyguardNotifications = resources.getInteger(R.integer.keyguard_max_notification_count);
         mKeyguardUnfoldTransition = unfoldComponent.map(c -> c.getKeyguardUnfoldTransition());
+
+        mCommunalSourceCallback = () -> {
+            mUiExecutor.execute(() -> setCommunalSource(null /*source*/));
+        };
+
+        mCommunalSourceMonitorCallback = (source) -> {
+            mUiExecutor.execute(() -> setCommunalSource(source));
+        };
+
         updateUserSwitcherFlags();
         onFinishInflate();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 44b3d8a..94b010d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -508,6 +508,7 @@
     private final NotificationsController mNotificationsController;
     private final OngoingCallController mOngoingCallController;
     private final SystemStatusAnimationScheduler mAnimationScheduler;
+    private final StatusBarSignalPolicy mStatusBarSignalPolicy;
     private final StatusBarLocationPublisher mStatusBarLocationPublisher;
     private final StatusBarIconController mStatusBarIconController;
     private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
@@ -699,6 +700,7 @@
             AutoHideController autoHideController,
             StatusBarWindowController statusBarWindowController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
+            StatusBarSignalPolicy statusBarSignalPolicy,
             PulseExpansionHandler pulseExpansionHandler,
             NotificationWakeUpCoordinator notificationWakeUpCoordinator,
             KeyguardBypassController keyguardBypassController,
@@ -876,6 +878,7 @@
         mWallpaperController = wallpaperController;
         mOngoingCallController = ongoingCallController;
         mAnimationScheduler = animationScheduler;
+        mStatusBarSignalPolicy = statusBarSignalPolicy;
         mStatusBarLocationPublisher = locationPublisher;
         mStatusBarIconController = statusBarIconController;
         mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager;
@@ -930,6 +933,7 @@
             mBubblesOptional.get().setExpandListener(mBubbleExpandListener);
         }
 
+        mStatusBarSignalPolicy.init();
         mKeyguardIndicationController.init();
 
         mColorExtractor.addOnColorsChangedListener(mOnColorsChangedListener);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
index a77a097..ae3b7ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
@@ -30,11 +30,11 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
-import android.media.AudioAttributes;
 import android.os.Bundle;
 import android.os.PowerManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.util.Log;
@@ -106,10 +106,8 @@
     private final VibrationEffect mCameraLaunchGestureVibrationEffect;
 
 
-    private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
-            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
-            .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
-            .build();
+    private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
+            VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
 
     @Inject
     StatusBarCommandQueueCallbacks(
@@ -611,9 +609,9 @@
     }
 
     private void vibrateForCameraGesture() {
-        // Make sure to pass -1 for repeat so VibratorService doesn't stop us when going to sleep.
         mVibratorOptional.ifPresent(
-                v -> v.vibrate(mCameraLaunchGestureVibrationEffect, VIBRATION_ATTRIBUTES));
+                v -> v.vibrate(mCameraLaunchGestureVibrationEffect,
+                        HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES));
     }
 
     private static VibrationEffect getCameraGestureVibrationEffect(
@@ -627,6 +625,8 @@
                     .compose();
         }
         if (vibratorOptional.isPresent() && vibratorOptional.get().hasAmplitudeControl()) {
+            // Make sure to pass -1 for repeat so VibratorManagerService doesn't stop us when going
+            // to sleep.
             return VibrationEffect.createWaveform(
                     StatusBar.CAMERA_LAUNCH_GESTURE_VIBRATION_TIMINGS,
                     StatusBar.CAMERA_LAUNCH_GESTURE_VIBRATION_AMPLITUDES,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 7ab4a1e..0d23d66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -441,6 +441,8 @@
      * dragging it and translation should be deferred {@see KeyguardBouncer#show(boolean, boolean)}
      */
     public void showBouncer(boolean scrimmed) {
+        resetAlternateAuth(false);
+
         if (mShowing && !mBouncer.isShowing()) {
             mBouncer.show(false /* resetSecuritySelection */, scrimmed);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index 6c81054..b0206f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -73,17 +73,15 @@
     private boolean mHideWifi;
     private boolean mHideEthernet;
     private boolean mActivityEnabled;
-    private boolean mForceHideWifi;
 
     // Track as little state as possible, and only for padding purposes
     private boolean mIsAirplaneMode = false;
     private boolean mIsWifiEnabled = false;
-    private boolean mWifiVisible = false;
 
-    private ArrayList<MobileIconState> mMobileStates = new ArrayList<MobileIconState>();
-    private ArrayList<CallIndicatorIconState> mCallIndicatorStates =
-            new ArrayList<CallIndicatorIconState>();
+    private ArrayList<MobileIconState> mMobileStates = new ArrayList<>();
+    private ArrayList<CallIndicatorIconState> mCallIndicatorStates = new ArrayList<>();
     private WifiIconState mWifiIconState = new WifiIconState();
+    private boolean mInitialized;
 
     @Inject
     public StatusBarSignalPolicy(
@@ -113,9 +111,15 @@
         mSlotCallStrength =
                 mContext.getString(com.android.internal.R.string.status_bar_call_strength);
         mActivityEnabled = mContext.getResources().getBoolean(R.bool.config_showActivity);
+    }
 
-
-        tunerService.addTunable(this, StatusBarIconController.ICON_HIDE_LIST);
+    /** Call to initilaize and register this classw with the system. */
+    public void init() {
+        if (mInitialized) {
+            return;
+        }
+        mInitialized = true;
+        mTunerService.addTunable(this, StatusBarIconController.ICON_HIDE_LIST);
         mNetworkController.addCallback(this);
         mSecurityController.addCallback(this);
     }
@@ -163,7 +167,7 @@
             mHideAirplane = hideAirplane;
             mHideMobile = hideMobile;
             mHideEthernet = hideEthernet;
-            mHideWifi = hideWifi || mForceHideWifi;
+            mHideWifi = hideWifi;
             // Re-register to get new callbacks.
             mNetworkController.removeCallback(this);
             mNetworkController.addCallback(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 1130ec2..ed52a81 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -36,6 +36,8 @@
 import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
 
+import androidx.annotation.Nullable;
+
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.animation.DialogListener;
@@ -303,13 +305,32 @@
      * the screen off / close system dialogs broadcast.
      * <p>
      * <strong>Note:</strong> Don't call dialog.setOnDismissListener() after
-     * calling this because it causes a leak of BroadcastReceiver.
+     * calling this because it causes a leak of BroadcastReceiver. Instead, call the version that
+     * takes an extra Runnable as a parameter.
      *
      * @param dialog The dialog to be associated with the listener.
      */
     public static void registerDismissListener(Dialog dialog) {
+        registerDismissListener(dialog, null);
+    }
+
+
+    /**
+     * Registers a listener that dismisses the given dialog when it receives
+     * the screen off / close system dialogs broadcast.
+     * <p>
+     * <strong>Note:</strong> Don't call dialog.setOnDismissListener() after
+     * calling this because it causes a leak of BroadcastReceiver.
+     *
+     * @param dialog The dialog to be associated with the listener.
+     * @param dismissAction An action to run when the dialog is dismissed.
+     */
+    public static void registerDismissListener(Dialog dialog, @Nullable Runnable dismissAction) {
         DismissReceiver dismissReceiver = new DismissReceiver(dialog);
-        dialog.setOnDismissListener(d -> dismissReceiver.unregister());
+        dialog.setOnDismissListener(d -> {
+            dismissReceiver.unregister();
+            if (dismissAction != null) dismissAction.run();
+        });
         dismissReceiver.register();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 0cb13c8..b3f59b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -98,6 +98,7 @@
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
 import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
+import com.android.systemui.statusbar.phone.StatusBarSignalPolicy;
 import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragmentLogger;
@@ -148,6 +149,7 @@
             AutoHideController autoHideController,
             StatusBarWindowController statusBarWindowController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
+            StatusBarSignalPolicy statusBarSignalPolicy,
             PulseExpansionHandler pulseExpansionHandler,
             NotificationWakeUpCoordinator notificationWakeUpCoordinator,
             KeyguardBypassController keyguardBypassController,
@@ -250,6 +252,7 @@
                 autoHideController,
                 statusBarWindowController,
                 keyguardUpdateMonitor,
+                statusBarSignalPolicy,
                 pulseExpansionHandler,
                 notificationWakeUpCoordinator,
                 keyguardBypassController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index 5acce7f..b5ee62d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -45,9 +45,10 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.qs.GlobalSetting;
+import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.settings.CurrentUserTracker;
 import com.android.systemui.util.Utils;
+import com.android.systemui.util.settings.GlobalSettings;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -66,8 +67,8 @@
     private final ArrayList<Callback> mCallbacks = new ArrayList<>();
     private final Object mCallbacksLock = new Object();
     private final Context mContext;
-    private final GlobalSetting mModeSetting;
-    private final GlobalSetting mConfigSetting;
+    private final SettingObserver mModeSetting;
+    private final SettingObserver mConfigSetting;
     private final NotificationManager mNoMan;
     private final AlarmManager mAlarmManager;
     private final SetupObserver mSetupObserver;
@@ -85,19 +86,20 @@
             Context context,
             @Main Handler handler,
             BroadcastDispatcher broadcastDispatcher,
-            DumpManager dumpManager) {
+            DumpManager dumpManager,
+            GlobalSettings globalSettings) {
         super(broadcastDispatcher);
         mContext = context;
-        mModeSetting = new GlobalSetting(mContext, handler, Global.ZEN_MODE) {
+        mModeSetting = new SettingObserver(globalSettings, handler, Global.ZEN_MODE) {
             @Override
-            protected void handleValueChanged(int value) {
+            protected void handleValueChanged(int value, boolean observedChange) {
                 updateZenMode(value);
                 fireZenChanged(value);
             }
         };
-        mConfigSetting = new GlobalSetting(mContext, handler, Global.ZEN_MODE_CONFIG_ETAG) {
+        mConfigSetting = new SettingObserver(globalSettings, handler, Global.ZEN_MODE_CONFIG_ETAG) {
             @Override
-            protected void handleValueChanged(int value) {
+            protected void handleValueChanged(int value, boolean observedChange) {
                 updateZenModeConfig();
             }
         };
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 5d0b1c0..63ca94c 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -260,6 +260,18 @@
             }
         };
         mKeyguardUpdateMonitor.registerCallback(mSplitScreenKeyguardCallback);
+
+        mWakefulnessLifecycle.addObserver(new WakefulnessLifecycle.Observer() {
+            @Override
+            public void onFinishedWakingUp() {
+                splitScreen.onFinishedWakingUp();
+            }
+
+            @Override
+            public void onFinishedGoingToSleep() {
+                splitScreen.onFinishedGoingToSleep();
+            }
+        });
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index 0e86964..1cf21ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -351,9 +351,8 @@
         mSystemClock.advanceTime(205);
         mController.onTouchOutsideView();
 
-        // THEN show the bouncer and reset alt auth
+        // THEN show the bouncer
         verify(mStatusBarKeyguardViewManager).showBouncer(eq(true));
-        verify(mStatusBarKeyguardViewManager).resetAlternateAuth(anyBoolean());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourcePrimerTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourcePrimerTest.java
new file mode 100644
index 0000000..659b1a3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourcePrimerTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal;
+
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.testing.AndroidTestingRunner;
+
+import androidx.concurrent.futures.CallbackToFutureAdapter;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Optional;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class CommunalSourcePrimerTest extends SysuiTestCase {
+    private static final String TEST_COMPONENT_NAME = "com.google.tests/.CommunalService";
+    private static final int MAX_RETRIES = 5;
+    private static final int RETRY_DELAY_MS = 1000;
+
+    @Mock
+    private Context mContext;
+
+    @Mock
+    private Resources mResources;
+
+    private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
+
+    @Mock
+    private CommunalSource mSource;
+
+    @Mock
+    private CommunalSourceMonitor mCommunalSourceMonitor;
+
+    @Mock
+    private CommunalSource.Connector mConnector;
+
+    @Mock
+    private CommunalSource.Observer mObserver;
+
+    private CommunalSourcePrimer mPrimer;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        when(mResources.getInteger(R.integer.config_communalSourceMaxReconnectAttempts))
+                .thenReturn(MAX_RETRIES);
+        when(mResources.getInteger(R.integer.config_communalSourceReconnectBaseDelay))
+                .thenReturn(RETRY_DELAY_MS);
+        when(mResources.getString(R.string.config_communalSourceComponent))
+                .thenReturn(TEST_COMPONENT_NAME);
+
+        mPrimer = new CommunalSourcePrimer(mContext, mResources, mFakeExecutor,
+                mCommunalSourceMonitor, Optional.of(mConnector), Optional.of(mObserver));
+    }
+
+    @Test
+    public void testConnect() {
+        when(mConnector.connect()).thenReturn(
+                CallbackToFutureAdapter.getFuture(completer -> {
+                    completer.set(Optional.of(mSource));
+                    return "test";
+                }));
+
+        mPrimer.onBootCompleted();
+        mFakeExecutor.runAllReady();
+        verify(mCommunalSourceMonitor).setSource(mSource);
+    }
+
+    @Test
+    public void testRetryOnBindFailure() throws Exception {
+        when(mConnector.connect()).thenReturn(
+                CallbackToFutureAdapter.getFuture(completer -> {
+                    completer.set(Optional.empty());
+                    return "test";
+                }));
+
+        mPrimer.onBootCompleted();
+        mFakeExecutor.runAllReady();
+
+        // Verify attempts happen. Note that we account for the retries plus initial attempt, which
+        // is not scheduled.
+        for (int attemptCount = 0; attemptCount < MAX_RETRIES + 1; attemptCount++) {
+            verify(mConnector, times(1)).connect();
+            clearInvocations(mConnector);
+            mFakeExecutor.advanceClockToNext();
+            mFakeExecutor.runAllReady();
+        }
+
+        verify(mCommunalSourceMonitor, never()).setSource(Mockito.notNull());
+    }
+
+    @Test
+    public void testAttemptOnPackageChange() {
+        when(mConnector.connect()).thenReturn(
+                CallbackToFutureAdapter.getFuture(completer -> {
+                    completer.set(Optional.empty());
+                    return "test";
+                }));
+
+        mPrimer.onBootCompleted();
+        mFakeExecutor.runAllReady();
+
+        final ArgumentCaptor<CommunalSource.Observer.Callback> callbackCaptor =
+                ArgumentCaptor.forClass(CommunalSource.Observer.Callback.class);
+        verify(mObserver).addCallback(callbackCaptor.capture());
+
+        clearInvocations(mConnector);
+        callbackCaptor.getValue().onSourceChanged();
+
+        verify(mConnector, times(1)).connect();
+    }
+
+    @Test
+    public void testDisconnect() {
+        final ArgumentCaptor<CommunalSource.Callback> callbackCaptor =
+                ArgumentCaptor.forClass(CommunalSource.Callback.class);
+
+        when(mConnector.connect()).thenReturn(
+                CallbackToFutureAdapter.getFuture(completer -> {
+                    completer.set(Optional.of(mSource));
+                    return "test";
+                }));
+
+        mPrimer.onBootCompleted();
+        mFakeExecutor.runAllReady();
+        verify(mCommunalSourceMonitor).setSource(mSource);
+        verify(mSource).addCallback(callbackCaptor.capture());
+
+        clearInvocations(mConnector);
+        callbackCaptor.getValue().onDisconnected();
+        mFakeExecutor.runAllReady();
+
+        verify(mConnector).connect();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
index 175ec87f..a6e567e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
@@ -104,37 +104,54 @@
     fun testPlayerOrdering() {
         // Test values: key, data, last active time
         val playingLocal = Triple("playing local",
-            DATA.copy(active = true, isPlaying = true, isLocalSession = true, resumption = false),
+            DATA.copy(active = true, isPlaying = true,
+                    playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false),
             4500L)
 
-        val playingRemote = Triple("playing remote",
-            DATA.copy(active = true, isPlaying = true, isLocalSession = false, resumption = false),
+        val playingCast = Triple("playing cast",
+            DATA.copy(active = true, isPlaying = true,
+                    playbackLocation = MediaData.PLAYBACK_CAST_LOCAL, resumption = false),
             5000L)
 
         val pausedLocal = Triple("paused local",
-            DATA.copy(active = true, isPlaying = false, isLocalSession = true, resumption = false),
+            DATA.copy(active = true, isPlaying = false,
+                    playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false),
             1000L)
 
-        val pausedRemote = Triple("paused remote",
-            DATA.copy(active = true, isPlaying = false, isLocalSession = false, resumption = false),
+        val pausedCast = Triple("paused cast",
+            DATA.copy(active = true, isPlaying = false,
+                    playbackLocation = MediaData.PLAYBACK_CAST_LOCAL, resumption = false),
             2000L)
 
+        val playingRcn = Triple("playing RCN",
+            DATA.copy(active = true, isPlaying = true,
+                    playbackLocation = MediaData.PLAYBACK_CAST_REMOTE, resumption = false),
+            5000L)
+
+        val pausedRcn = Triple("paused RCN",
+                DATA.copy(active = true, isPlaying = false,
+                        playbackLocation = MediaData.PLAYBACK_CAST_REMOTE, resumption = false),
+                5000L)
+
         val resume1 = Triple("resume 1",
-            DATA.copy(active = false, isPlaying = false, isLocalSession = true, resumption = true),
+            DATA.copy(active = false, isPlaying = false,
+                    playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true),
             500L)
 
         val resume2 = Triple("resume 2",
-            DATA.copy(active = false, isPlaying = false, isLocalSession = true, resumption = true),
+            DATA.copy(active = false, isPlaying = false,
+                    playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true),
             1000L)
 
         // Expected ordering for media players:
         // Actively playing local sessions
-        // Actively playing remote sessions
-        // Paused sessions, by last active
+        // Actively playing cast sessions
+        // Paused local and cast sessions, by last active
+        // RCNs
         // Resume controls, by last active
 
-        val expected = listOf(playingLocal, playingRemote, pausedRemote, pausedLocal, resume2,
-            resume1)
+        val expected = listOf(playingLocal, playingCast, pausedCast, pausedLocal, playingRcn,
+                pausedRcn, resume2, resume1)
 
         expected.forEach {
             clock.setCurrentTimeMillis(it.third)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
index 66b6470..f870da3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
@@ -74,8 +74,8 @@
         mManager.addListener(mListener);
 
         mMediaData = new MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null,
-                new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, true, null, true,
-                false, KEY, false, false, false, 0L);
+                new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, true, null,
+                MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L);
         mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
index 2b2fc51..f44cc38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -1,5 +1,6 @@
 package com.android.systemui.media
 
+import android.app.Notification
 import android.app.Notification.MediaStyle
 import android.app.PendingIntent
 import android.app.smartspace.SmartspaceAction
@@ -229,6 +230,30 @@
     }
 
     @Test
+    fun testOnNotificationAdded_isRcn_markedRemote() {
+        val bundle = Bundle().apply {
+            putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, "Remote Cast Notification")
+        }
+        val rcn = SbnBuilder().run {
+            setPkg("com.android.systemui") // System package
+            modifyNotification(context).also {
+                it.setSmallIcon(android.R.drawable.ic_media_pause)
+                it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+                it.addExtras(bundle)
+            }
+            build()
+        }
+
+        mediaDataManager.onNotificationAdded(KEY, rcn)
+        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
+                eq(false))
+        assertThat(mediaDataCaptor.value!!.playbackLocation).isEqualTo(
+                MediaData.PLAYBACK_CAST_REMOTE)
+    }
+
+    @Test
     fun testOnNotificationRemoved_callsListener() {
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
         mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = mock(MediaData::class.java))
@@ -306,7 +331,8 @@
         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
                 eq(false))
         val data = mediaDataCaptor.value
-        val dataRemoteWithResume = data.copy(resumeAction = Runnable {}, isLocalSession = false)
+        val dataRemoteWithResume = data.copy(resumeAction = Runnable {},
+                playbackLocation = MediaData.PLAYBACK_CAST_LOCAL)
         mediaDataManager.onMediaDataLoaded(KEY, null, dataRemoteWithResume)
 
         // WHEN the notification is removed
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
index 8dc9eff..421f9be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
@@ -42,7 +42,8 @@
     val mockito = MockitoJUnit.rule()
 
     companion object {
-        val LOCAL = true
+        val LOCAL = MediaData.PLAYBACK_LOCAL
+        val REMOTE = MediaData.PLAYBACK_CAST_LOCAL
         val RESUMPTION = true
         val PLAYING = true
         val UNDETERMINED = null
@@ -58,7 +59,7 @@
         val dataIsPlaying = createMediaData("app1", PLAYING, LOCAL, !RESUMPTION)
 
         val playerIsRemote = mock(MediaControlPanel::class.java)
-        val dataIsRemote = createMediaData("app2", PLAYING, !LOCAL, !RESUMPTION)
+        val dataIsRemote = createMediaData("app2", PLAYING, REMOTE, !RESUMPTION)
 
         MediaPlayerData.addMediaPlayer("2", dataIsRemote, playerIsRemote, systemClock)
         MediaPlayerData.addMediaPlayer("1", dataIsPlaying, playerIsPlaying, systemClock)
@@ -100,13 +101,13 @@
         val dataIsPlaying = createMediaData("app1", PLAYING, LOCAL, !RESUMPTION)
 
         val playerIsPlayingAndRemote = mock(MediaControlPanel::class.java)
-        val dataIsPlayingAndRemote = createMediaData("app2", PLAYING, !LOCAL, !RESUMPTION)
+        val dataIsPlayingAndRemote = createMediaData("app2", PLAYING, REMOTE, !RESUMPTION)
 
         val playerIsStoppedAndLocal = mock(MediaControlPanel::class.java)
         val dataIsStoppedAndLocal = createMediaData("app3", !PLAYING, LOCAL, !RESUMPTION)
 
         val playerIsStoppedAndRemote = mock(MediaControlPanel::class.java)
-        val dataIsStoppedAndRemote = createMediaData("app4", !PLAYING, !LOCAL, !RESUMPTION)
+        val dataIsStoppedAndRemote = createMediaData("app4", !PLAYING, REMOTE, !RESUMPTION)
 
         val playerCanResume = mock(MediaControlPanel::class.java)
         val dataCanResume = createMediaData("app5", !PLAYING, LOCAL, RESUMPTION)
@@ -127,8 +128,8 @@
         val players = MediaPlayerData.players()
         assertThat(players).hasSize(6)
         assertThat(players).containsExactly(playerIsPlaying, playerIsPlayingAndRemote,
-            playerIsStoppedAndRemote, playerIsStoppedAndLocal, playerCanResume,
-            playerUndetermined).inOrder()
+            playerIsStoppedAndRemote, playerIsStoppedAndLocal, playerUndetermined,
+            playerCanResume).inOrder()
     }
 
     @Test
@@ -160,9 +161,10 @@
     private fun createMediaData(
         app: String,
         isPlaying: Boolean?,
-        isLocalSession: Boolean,
+        location: Int,
         resumption: Boolean
     ) =
-        MediaData(0, false, 0, app, null, null, null, null, emptyList(), emptyList<Int>(), "",
-            null, null, null, true, null, isLocalSession, resumption, null, false, isPlaying)
+        MediaData(0, false, 0, app, null, null, null, null, emptyList(), emptyList<Int>(),
+            "package:" + app, null, null, null, true, null, location, resumption, "key:" + app,
+            false, isPlaying)
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
index a17a03d..30ee2e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
@@ -211,10 +211,20 @@
     }
 
     @Test
-    fun testOnLoad_remotePlayback_doesNotCheck() {
-        // When media data is loaded that has not been checked yet, and is not local
-        val dataRemote = data.copy(isLocalSession = false)
-        resumeListener.onMediaDataLoaded(KEY, null, dataRemote)
+    fun testOnLoad_localCast_doesNotCheck() {
+        // When media data is loaded that has not been checked yet, and is a local cast
+        val dataCast = data.copy(playbackLocation = MediaData.PLAYBACK_CAST_LOCAL)
+        resumeListener.onMediaDataLoaded(KEY, null, dataCast)
+
+        // Then we do not take action
+        verify(mediaDataManager, never()).setResumeAction(any(), any())
+    }
+
+    @Test
+    fun testOnload_remoteCast_doesNotCheck() {
+        // When media data is loaded that has not been checked yet, and is a remote cast
+        val dataRcn = data.copy(playbackLocation = MediaData.PLAYBACK_CAST_REMOTE)
+        resumeListener.onMediaDataLoaded(KEY, null, dataRcn)
 
         // Then we do not take action
         verify(mediaDataManager, never()).setResumeAction(any(), any())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index f7e60ca..09ec4ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -469,7 +469,7 @@
         when(entry.getSbn()).thenReturn(sbn);
         when(sbn.getNotification()).thenReturn(notification);
         when(sbn.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
-        when(notification.hasMediaSession()).thenReturn(true);
+        when(notification.isMediaNotification()).thenReturn(true);
         when(notification.getLargeIcon()).thenReturn(null);
 
         assertThat(mMediaOutputController.getNotificationIcon()).isNull();
@@ -489,7 +489,7 @@
         when(entry.getSbn()).thenReturn(sbn);
         when(sbn.getNotification()).thenReturn(notification);
         when(sbn.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
-        when(notification.hasMediaSession()).thenReturn(true);
+        when(notification.isMediaNotification()).thenReturn(true);
         when(notification.getLargeIcon()).thenReturn(icon);
 
         assertThat(mMediaOutputController.getNotificationIcon() instanceof IconCompat).isTrue();
@@ -509,7 +509,7 @@
         when(entry.getSbn()).thenReturn(sbn);
         when(sbn.getNotification()).thenReturn(notification);
         when(sbn.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
-        when(notification.hasMediaSession()).thenReturn(false);
+        when(notification.isMediaNotification()).thenReturn(false);
         when(notification.getLargeIcon()).thenReturn(icon);
 
         assertThat(mMediaOutputController.getNotificationIcon()).isNull();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
new file mode 100644
index 0000000..734faec
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.navigationbar;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.view.accessibility.AccessibilityManager;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import dagger.Lazy;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NavBarHelperTest extends SysuiTestCase {
+
+    @Mock
+    AccessibilityManager mAccessibilityManager;
+    @Mock
+    AccessibilityManagerWrapper mAccessibilityManagerWrapper;
+    @Mock
+    AccessibilityButtonModeObserver mAccessibilityButtonModeObserver;
+    @Mock
+    OverviewProxyService mOverviewProxyService;
+    @Mock
+    Lazy<AssistManager> mAssistManagerLazy;
+    @Mock
+    AssistManager mAssistManager;
+    @Mock
+    NavigationModeController mNavigationModeController;
+    @Mock
+    UserTracker mUserTracker;
+    @Mock
+    ComponentName mAssistantComponent;
+    @Mock
+    DumpManager mDumpManager;
+    @Mock
+    NavBarHelper.NavbarTaskbarStateUpdater mNavbarTaskbarStateUpdater;
+
+    private NavBarHelper mNavBarHelper;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        when(mAssistManagerLazy.get()).thenReturn(mAssistManager);
+        when(mAssistManager.getAssistInfoForUser(anyInt())).thenReturn(mAssistantComponent);
+        when(mUserTracker.getUserId()).thenReturn(1);
+
+        mNavBarHelper = new NavBarHelper(mAccessibilityManager,
+                mAccessibilityManagerWrapper, mAccessibilityButtonModeObserver,
+                mOverviewProxyService, mAssistManagerLazy, mNavigationModeController,
+                mUserTracker, mDumpManager);
+
+    }
+
+    @Test
+    public void registerListenersInCtor() {
+        verify(mAccessibilityButtonModeObserver, times(1)).addListener(mNavBarHelper);
+        verify(mNavigationModeController, times(1)).addListener(mNavBarHelper);
+        verify(mOverviewProxyService, times(1)).addCallback(mNavBarHelper);
+    }
+
+    @Test
+    public void registerAssistantContentObserver() {
+        mNavBarHelper.init(mContext);
+        verify(mAssistManager, times(1)).getAssistInfoForUser(anyInt());
+    }
+
+    @Test
+    public void callbacksFiredWhenRegistering() {
+        mNavBarHelper.init(mContext);
+        mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
+        verify(mNavbarTaskbarStateUpdater, times(1))
+                .updateAccessibilityServicesState();
+        verify(mNavbarTaskbarStateUpdater, times(1))
+                .updateAssistantAvailable(anyBoolean());
+    }
+
+    @Test
+    public void assistantCallbacksFiredAfterConnecting() {
+        mNavBarHelper.init(mContext);
+        // 1st set of callbacks get called when registering
+        mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
+
+        mNavBarHelper.onConnectionChanged(false);
+        // assert no more callbacks fired
+        verify(mNavbarTaskbarStateUpdater, times(1))
+                .updateAccessibilityServicesState();
+        verify(mNavbarTaskbarStateUpdater, times(1))
+                .updateAssistantAvailable(anyBoolean());
+
+        mNavBarHelper.onConnectionChanged(true);
+        // assert no more callbacks fired
+        verify(mNavbarTaskbarStateUpdater, times(1))
+                .updateAccessibilityServicesState();
+        verify(mNavbarTaskbarStateUpdater, times(2))
+                .updateAssistantAvailable(anyBoolean());
+    }
+
+    @Test
+    public void a11yCallbacksFiredAfterModeChange() {
+        mNavBarHelper.init(mContext);
+        // 1st set of callbacks get called when registering
+        mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
+
+        mNavBarHelper.onAccessibilityButtonModeChanged(0);
+        verify(mNavbarTaskbarStateUpdater, times(2))
+                .updateAccessibilityServicesState();
+        verify(mNavbarTaskbarStateUpdater, times(1))
+                .updateAssistantAvailable(anyBoolean());
+    }
+
+    @Test
+    public void assistantCallbacksFiredAfterNavModeChange() {
+        mNavBarHelper.init(mContext);
+        // 1st set of callbacks get called when registering
+        mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
+
+        mNavBarHelper.onNavigationModeChanged(0);
+        verify(mNavbarTaskbarStateUpdater, times(1))
+                .updateAccessibilityServicesState();
+        verify(mNavbarTaskbarStateUpdater, times(2))
+                .updateAssistantAvailable(anyBoolean());
+    }
+
+    @Test
+    public void removeListenerNoCallbacksFired() {
+        mNavBarHelper.init(mContext);
+        // 1st set of callbacks get called when registering
+        mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
+
+        // Remove listener
+        mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
+
+        // Would have fired 2nd callback if not removed
+        mNavBarHelper.onAccessibilityButtonModeChanged(0);
+
+        // assert no more callbacks fired
+        verify(mNavbarTaskbarStateUpdater, times(1))
+                .updateAccessibilityServicesState();
+        verify(mNavbarTaskbarStateUpdater, times(1))
+                .updateAssistantAvailable(anyBoolean());
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
index 4fc329f..9d2541c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
@@ -45,6 +45,7 @@
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.AutoHideController;
+import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import org.junit.After;
@@ -82,11 +83,12 @@
                         mCommandQueue,
                         Dependency.get(Dependency.MAIN_HANDLER),
                         mock(ConfigurationController.class),
-                        mock(NavigationBarA11yHelper.class),
+                        mock(NavBarHelper.class),
                         mock(TaskbarDelegate.class),
                         mNavigationBarFactory,
                         mock(DumpManager.class),
-                        mock(AutoHideController.class)));
+                        mock(AutoHideController.class),
+                        mock(LightBarController.class)));
         initializeNavigationBars();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 50b7171..e038b6e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -136,7 +136,7 @@
     @Mock
     EdgeBackGestureHandler mEdgeBackGestureHandler;
     @Mock
-    NavigationBarA11yHelper mNavigationBarA11yHelper;
+    NavBarHelper mNavBarHelper;
     @Mock
     private LightBarController mLightBarController;
     @Mock
@@ -227,6 +227,7 @@
                 new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_SYSTEMUI)
                     .setLong(HOME_BUTTON_LONG_PRESS_DURATION_MS, 100)
                     .build());
+        when(mNavBarHelper.getLongPressHomeEnabled()).thenReturn(true);
         mNavigationBar.onViewAttachedToWindow(mNavigationBar.createView(null));
 
         mNavigationBar.onHomeTouch(mNavigationBar.getView(), MotionEvent.obtain(
@@ -330,14 +331,14 @@
     public void testA11yEventAfterDetach() {
         View v = mNavigationBar.createView(null);
         mNavigationBar.onViewAttachedToWindow(v);
-        verify(mNavigationBarA11yHelper).registerA11yEventListener(any(
-                NavigationBarA11yHelper.NavA11yEventListener.class));
+        verify(mNavBarHelper).registerNavTaskStateUpdater(any(
+                NavBarHelper.NavbarTaskbarStateUpdater.class));
         mNavigationBar.onViewDetachedFromWindow(v);
-        verify(mNavigationBarA11yHelper).removeA11yEventListener(any(
-                NavigationBarA11yHelper.NavA11yEventListener.class));
+        verify(mNavBarHelper).removeNavTaskStateUpdater(any(
+                NavBarHelper.NavbarTaskbarStateUpdater.class));
 
         // Should be safe even though the internal view is now null.
-        mNavigationBar.updateAccessibilityServicesState();
+        mNavigationBar.updateAcessibilityStateFlags();
     }
 
     private NavigationBar createNavBar(Context context) {
@@ -367,7 +368,7 @@
                 mHandler,
                 mock(NavigationBarOverlayController.class),
                 mUiEventLogger,
-                mNavigationBarA11yHelper,
+                mNavBarHelper,
                 mock(UserTracker.class),
                 mLightBarController,
                 mLightBarcontrollerFactory,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
index d2bba36..26f04fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
@@ -1,6 +1,8 @@
 package com.android.systemui.qs
 
+import android.os.Handler
 import android.os.UserManager
+import android.provider.Settings
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.testing.ViewUtils
@@ -16,10 +18,12 @@
 import com.android.systemui.globalactions.GlobalActionsDialogLite
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.qs.FooterActionsController.ExpansionState
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.phone.MultiUserSwitchController
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.statusbar.policy.UserInfoController
 import com.android.systemui.tuner.TunerService
+import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.utils.leaks.FakeTunerService
 import com.android.systemui.utils.leaks.LeakCheckedTest
 import com.google.common.truth.Truth.assertThat
@@ -42,6 +46,8 @@
     @Mock
     private lateinit var userManager: UserManager
     @Mock
+    private lateinit var userTracker: UserTracker
+    @Mock
     private lateinit var activityStarter: ActivityStarter
     @Mock
     private lateinit var deviceProvisionedController: DeviceProvisionedController
@@ -62,11 +68,13 @@
     private lateinit var view: FooterActionsView
     private val falsingManager: FalsingManagerFake = FalsingManagerFake()
     private lateinit var testableLooper: TestableLooper
+    private lateinit var fakeSettings: FakeSettings
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
+        fakeSettings = FakeSettings()
         injectLeakCheckedDependencies(*LeakCheckedTest.ALL_SUPPORTED_CLASSES)
         val fakeTunerService = Dependency.get(TunerService::class.java) as FakeTunerService
 
@@ -74,10 +82,11 @@
                 .inflate(R.layout.footer_actions, null) as FooterActionsView
 
         controller = FooterActionsController(view, qsPanelController, activityStarter,
-                userManager, userInfoController, multiUserSwitchController,
+                userManager, userTracker, userInfoController, multiUserSwitchController,
                 deviceProvisionedController, falsingManager, metricsLogger, fakeTunerService,
                 globalActionsDialog, uiEventLogger, showPMLiteButton = true,
-                buttonsVisibleState = ExpansionState.EXPANDED)
+                buttonsVisibleState = ExpansionState.EXPANDED, fakeSettings,
+                Handler(testableLooper.looper))
         controller.init()
         ViewUtils.attachView(view)
         // View looper is the testable looper associated with the test
@@ -122,4 +131,24 @@
 
         assertThat(multiUserSwitch.visibility).isEqualTo(View.VISIBLE)
     }
+
+    @Test
+    fun testMultiUserSwitchUpdatedWhenSettingChanged() {
+        // When expanded, listening is true
+        controller.setListening(true)
+        testableLooper.processAllMessages()
+
+        val multiUserSwitch = view.requireViewById<View>(R.id.multi_user_switch)
+        assertThat(multiUserSwitch.visibility).isNotEqualTo(View.VISIBLE)
+
+        // The setting is only used as an indicator for whether the view should refresh. The actual
+        // value of the setting is ignored; isMultiUserEnabled is the source of truth
+        whenever(multiUserSwitchController.isMultiUserEnabled).thenReturn(true)
+
+        // Changing the value of USER_SWITCHER_ENABLED should cause the view to update
+        fakeSettings.putIntForUser(Settings.Global.USER_SWITCHER_ENABLED, 1, userTracker.userId)
+        testableLooper.processAllMessages()
+
+        assertThat(multiUserSwitch.visibility).isEqualTo(View.VISIBLE)
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt
index f41d7b1..e2a0626 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt
@@ -1,7 +1,6 @@
 package com.android.systemui.qs
 
 import android.testing.AndroidTestingRunner
-import android.view.ViewGroup
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import org.junit.Before
@@ -9,7 +8,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.junit.MockitoJUnit
@@ -18,13 +16,9 @@
 @SmallTest
 class QSSquishinessControllerTest : SysuiTestCase() {
 
-    @Mock private lateinit var qqsFooterActionsView: FooterActionsView
-    @Mock private lateinit var qqsFooterActionsViewLP: ViewGroup.MarginLayoutParams
     @Mock private lateinit var qsAnimator: QSAnimator
     @Mock private lateinit var qsPanelController: QSPanelController
     @Mock private lateinit var quickQsPanelController: QuickQSPanelController
-    @Mock private lateinit var tileLayout: TileLayout
-    @Mock private lateinit var pagedTileLayout: PagedTileLayout
 
     @JvmField @Rule val mockitoRule = MockitoJUnit.rule()
 
@@ -32,11 +26,8 @@
 
     @Before
     fun setup() {
-        qsSquishinessController = QSSquishinessController(qqsFooterActionsView, qsAnimator,
+        qsSquishinessController = QSSquishinessController(qsAnimator,
                 qsPanelController, quickQsPanelController)
-        `when`(quickQsPanelController.tileLayout).thenReturn(tileLayout)
-        `when`(qsPanelController.tileLayout).thenReturn(pagedTileLayout)
-        `when`(qqsFooterActionsView.layoutParams).thenReturn(qqsFooterActionsViewLP)
     }
 
     @Test
@@ -51,7 +42,7 @@
     @Test
     fun setSquishiness_updatesTiles() {
         qsSquishinessController.squishiness = 0.5f
-        verify(tileLayout).setSquishinessFraction(0.5f)
-        verify(pagedTileLayout).setSquishinessFraction(0.5f)
+        verify(qsPanelController).setSquishinessFraction(0.5f)
+        verify(quickQsPanelController).setSquishinessFraction(0.5f)
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/SecureSettingTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/SettingObserverTest.kt
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/SecureSettingTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/qs/SettingObserverTest.kt
index 6af8402..4be6890 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/SecureSettingTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/SettingObserverTest.kt
@@ -35,7 +35,7 @@
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
-class SecureSettingTest : SysuiTestCase() {
+class SettingObserverTest : SysuiTestCase() {
 
     companion object {
         private const val TEST_SETTING = "setting"
@@ -46,7 +46,7 @@
     }
 
     private lateinit var testableLooper: TestableLooper
-    private lateinit var setting: SecureSetting
+    private lateinit var setting: SettingObserver
     private lateinit var secureSettings: SecureSettings
 
     private lateinit var callback: Callback
@@ -56,7 +56,7 @@
         testableLooper = TestableLooper.get(this)
         secureSettings = FakeSettings()
 
-        setting = object : SecureSetting(
+        setting = object : SettingObserver(
                 secureSettings,
                 Handler(testableLooper.looper),
                 TEST_SETTING,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
index f99703e..3ea2cc5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
@@ -16,22 +16,28 @@
 
 package com.android.systemui.qs.tiles
 
+import android.app.Dialog
 import android.content.ContextWrapper
 import android.content.SharedPreferences
 import android.os.Handler
 import android.provider.Settings
+import android.provider.Settings.Global.ZEN_MODE_OFF
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.statusbar.policy.ZenModeController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.util.settings.SecureSettings
 import com.google.common.truth.Truth.assertThat
@@ -40,9 +46,12 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 import java.io.File
+import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -70,6 +79,10 @@
     private lateinit var zenModeController: ZenModeController
     @Mock
     private lateinit var sharedPreferences: SharedPreferences
+    @Mock
+    private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+    @Mock
+    private lateinit var hostDialog: Dialog
 
     private lateinit var secureSettings: SecureSettings
     private lateinit var testableLooper: TestableLooper
@@ -81,15 +94,17 @@
         testableLooper = TestableLooper.get(this)
         secureSettings = FakeSettings()
 
-        Mockito.`when`(qsHost.userId).thenReturn(DEFAULT_USER)
-        Mockito.`when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
+        whenever(qsHost.userId).thenReturn(DEFAULT_USER)
+        whenever(qsHost.uiEventLogger).thenReturn(uiEventLogger)
+        whenever(dialogLaunchAnimator.showFromView(any(), any(), anyBoolean()))
+                .thenReturn(hostDialog)
 
         val wrappedContext = object : ContextWrapper(context) {
             override fun getSharedPreferences(file: File?, mode: Int): SharedPreferences {
                 return sharedPreferences
             }
         }
-        Mockito.`when`(qsHost.context).thenReturn(wrappedContext)
+        whenever(qsHost.context).thenReturn(wrappedContext)
 
         tile = DndTile(
             qsHost,
@@ -102,7 +117,8 @@
             qsLogger,
             zenModeController,
             sharedPreferences,
-            secureSettings
+            secureSettings,
+            dialogLaunchAnimator
         )
     }
 
@@ -147,4 +163,32 @@
 
         assertThat(tile.state.forceExpandIcon).isTrue()
     }
+
+    @Test
+    fun testLaunchDialogFromViewWhenPrompt() {
+        whenever(zenModeController.zen).thenReturn(ZEN_MODE_OFF)
+
+        secureSettings.putIntForUser(KEY, Settings.Secure.ZEN_DURATION_PROMPT, DEFAULT_USER)
+        testableLooper.processAllMessages()
+
+        val view = View(context)
+        tile.handleClick(view)
+        testableLooper.processAllMessages()
+
+        verify(dialogLaunchAnimator).showFromView(any(), eq(view), anyBoolean())
+    }
+
+    @Test
+    fun testNoLaunchDialogWhenNotPrompt() {
+        whenever(zenModeController.zen).thenReturn(ZEN_MODE_OFF)
+
+        secureSettings.putIntForUser(KEY, 60, DEFAULT_USER)
+        testableLooper.processAllMessages()
+
+        val view = View(context)
+        tile.handleClick(view)
+        testableLooper.processAllMessages()
+
+        verify(dialogLaunchAnimator, never()).showFromView(any(), any(), anyBoolean())
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/CommunalCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/CommunalCoordinatorTest.java
index 01e4cce0..f4452bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/CommunalCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/CommunalCoordinatorTest.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.collection.coordinator;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
 import android.test.suitebuilder.annotation.SmallTest;
@@ -29,6 +30,8 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -39,6 +42,8 @@
 
 @SmallTest
 public class CommunalCoordinatorTest extends SysuiTestCase {
+    private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+
     @Mock
     CommunalStateController mCommunalStateController;
     @Mock
@@ -57,7 +62,7 @@
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        mCoordinator = new CommunalCoordinator(mNotificationEntryManager,
+        mCoordinator = new CommunalCoordinator(mExecutor, mNotificationEntryManager,
                 mNotificationLockscreenUserManager, mCommunalStateController);
     }
 
@@ -84,6 +89,12 @@
         // Verify that notifications are filtered out when communal is showing and that the filter
         // pipeline is notified.
         stateCallback.onCommunalViewShowingChanged();
+        // Make sure callback depends on executor to run.
+        verify(mFilterListener, never()).onPluggableInvalidated(any());
+        verify(mNotificationEntryManager, never()).updateNotifications(any());
+
+        mExecutor.runAllReady();
+
         verify(mFilterListener).onPluggableInvalidated(any());
         verify(mNotificationEntryManager).updateNotifications(any());
         assert (filter.shouldFilterOut(mNotificationEntry, 0));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
index 0faf5d4..a0e91fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
@@ -438,8 +438,8 @@
         assertEquals("returns false when view is null", false,
                 NotificationSwipeHelper.isTouchInView(mEvent, null));
 
-        doReturn(5f).when(mEvent).getX();
-        doReturn(10f).when(mEvent).getY();
+        doReturn(5f).when(mEvent).getRawX();
+        doReturn(10f).when(mEvent).getRawY();
 
         doReturn(20).when(mView).getWidth();
         doReturn(20).when(mView).getHeight();
@@ -455,7 +455,7 @@
         assertTrue("Touch is within the view",
                 mSwipeHelper.isTouchInView(mEvent, mView));
 
-        doReturn(50f).when(mEvent).getX();
+        doReturn(50f).when(mEvent).getRawX();
 
         assertFalse("Touch is not within the view",
                 mSwipeHelper.isTouchInView(mEvent, mView));
@@ -466,8 +466,8 @@
         assertEquals("returns false when view is null", false,
                 NotificationSwipeHelper.isTouchInView(mEvent, null));
 
-        doReturn(5f).when(mEvent).getX();
-        doReturn(10f).when(mEvent).getY();
+        doReturn(5f).when(mEvent).getRawX();
+        doReturn(10f).when(mEvent).getRawY();
 
         doReturn(20).when(mNotificationRow).getWidth();
         doReturn(20).when(mNotificationRow).getActualHeight();
@@ -483,7 +483,7 @@
         assertTrue("Touch is within the view",
                 mSwipeHelper.isTouchInView(mEvent, mNotificationRow));
 
-        doReturn(50f).when(mEvent).getX();
+        doReturn(50f).when(mEvent).getRawX();
 
         assertFalse("Touch is not within the view",
                 mSwipeHelper.isTouchInView(mEvent, mNotificationRow));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index 3f5d220..2289936 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -51,7 +51,7 @@
 import com.android.systemui.qs.AutoAddTracker;
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.qs.ReduceBrightColorsController;
-import com.android.systemui.qs.SecureSetting;
+import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.CastController.CastDevice;
 import com.android.systemui.statusbar.policy.DataSaverController;
@@ -249,7 +249,7 @@
 
         verify(mWalletController, times(2)).getWalletPosition();
 
-        SecureSetting setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING);
+        SettingObserver setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING);
         assertEquals(USER + 1, setting.getCurrentUser());
         assertTrue(setting.isListening());
     }
@@ -299,7 +299,7 @@
 
         verify(mWalletController, times(2)).getWalletPosition();
 
-        SecureSetting setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING);
+        SettingObserver setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING);
         assertEquals(USER + 1, setting.getCurrentUser());
         assertFalse(setting.isListening());
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 25fd801..07debe6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -45,6 +45,7 @@
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -101,6 +102,8 @@
     private WakefulnessLifecycle mWakefulnessLifecycle;
     @Mock
     private ScreenLifecycle mScreenLifecycle;
+    @Mock
+    private StatusBarStateController mStatusBarStateController;
     private BiometricUnlockController mBiometricUnlockController;
 
     @Before
@@ -123,7 +126,7 @@
                 mUpdateMonitor, res.getResources(), mKeyguardBypassController, mDozeParameters,
                 mMetricsLogger, mDumpManager, mPowerManager,
                 mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle,
-                mAuthController);
+                mAuthController, mStatusBarStateController);
         mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
         mBiometricUnlockController.setBiometricModeListener(mBiometricModeListener);
     }
@@ -378,6 +381,23 @@
     }
 
     @Test
+    public void onUdfpsConsecutivelyFailedThreeTimes_showBouncer() {
+        // GIVEN UDFPS is supported
+        when(mUpdateMonitor.isUdfpsSupported()).thenReturn(true);
+
+        // WHEN udfps fails twice - then don't show the bouncer
+        mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
+        mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
+        verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
+
+        // WHEN udfps fails the third time
+        mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
+
+        // THEN show the bouncer
+        verify(mStatusBarKeyguardViewManager).showBouncer(true);
+    }
+
+    @Test
     public void onFinishedGoingToSleep_authenticatesWhenPending() {
         when(mUpdateMonitor.isGoingToSleep()).thenReturn(true);
         mBiometricUnlockController.onFinishedGoingToSleep(-1);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index 81ddc67..270c64d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
@@ -358,6 +358,7 @@
     private NotificationsQuickSettingsContainer mNotificationContainerParent;
     private List<View.OnAttachStateChangeListener> mOnAttachStateChangeListeners;
     private FalsingManagerFake mFalsingManager = new FalsingManagerFake();
+    private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
 
     @Before
     public void setup() {
@@ -511,7 +512,7 @@
                 mQuickAccessWalletController,
                 mQrCodeScannerController,
                 mRecordingController,
-                new FakeExecutor(new FakeSystemClock()),
+                mExecutor,
                 mSecureSettings,
                 mSplitShadeHeaderController,
                 mUnlockedScreenOffAnimationController,
@@ -936,6 +937,7 @@
                 ArgumentCaptor.forClass(WeakReference.class);
 
         monitorCallback.getValue().onSourceAvailable(new WeakReference<>(mCommunalSource));
+        mExecutor.runAllReady();
         verify(mCommunalHostViewController).show(sourceCapture.capture());
         assertThat(sourceCapture.getValue().get()).isEqualTo(mCommunalSource);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 6f174cb..c5bdfed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -374,6 +374,21 @@
     }
 
     @Test
+    public void testHideAltAuth_onShowBouncer() {
+        // GIVEN alt auth is showing
+        mStatusBarKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
+        when(mBouncer.isShowing()).thenReturn(false);
+        when(mAlternateAuthInterceptor.isShowingAlternateAuthBouncer()).thenReturn(true);
+        reset(mAlternateAuthInterceptor);
+
+        // WHEN showBouncer is called
+        mStatusBarKeyguardViewManager.showBouncer(true);
+
+        // THEN alt bouncer should be hidden
+        verify(mAlternateAuthInterceptor).hideAlternateAuthBouncer();
+    }
+
+    @Test
     public void testUpdateResources_delegatesToBouncer() {
         mStatusBarKeyguardViewManager.updateResources();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 911dca7..1df576e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -210,6 +210,7 @@
     @Mock private AmbientDisplayConfiguration mAmbientDisplayConfiguration;
     @Mock private NotificationLogger.ExpansionStateLogger mExpansionStateLogger;
     @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @Mock private StatusBarSignalPolicy mStatusBarSignalPolicy;
     @Mock private NotificationShadeWindowView mNotificationShadeWindowView;
     @Mock private BroadcastDispatcher mBroadcastDispatcher;
     @Mock private AssistManager mAssistManager;
@@ -379,6 +380,7 @@
                 mAutoHideController,
                 mStatusBarWindowController,
                 mKeyguardUpdateMonitor,
+                mStatusBarSignalPolicy,
                 mPulseExpansionHandler,
                 mNotificationWakeUpCoordinator,
                 mKeyguardBypassController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
index a39d580..de2012a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
@@ -31,6 +31,7 @@
 import android.os.UserManager
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import android.view.ThreadedRenderer
 import androidx.test.filters.SmallTest
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.logging.testing.UiEventLoggerFake
@@ -63,6 +64,8 @@
 import org.mockito.Mockito.`when`
 import org.mockito.Mockito.any
 import org.mockito.Mockito.anyString
+import org.mockito.Mockito.doNothing
+import org.mockito.Mockito.doReturn
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
@@ -90,6 +93,7 @@
     @Mock private lateinit var latencyTracker: LatencyTracker
     @Mock private lateinit var dialogShower: UserSwitchDialogController.DialogShower
     @Mock private lateinit var notificationShadeWindowView: NotificationShadeWindowView
+    @Mock private lateinit var threadedRenderer: ThreadedRenderer
     private lateinit var testableLooper: TestableLooper
     private lateinit var uiBgExecutor: FakeExecutor
     private lateinit var uiEventLogger: UiEventLoggerFake
@@ -146,6 +150,16 @@
                 dumpManager)
         userSwitcherController.mPauseRefreshUsers = true
 
+        // Since userSwitcherController involves InteractionJankMonitor.
+        // Let's fulfill the dependencies.
+        val mockedContext = mock(Context::class.java)
+        doReturn(mockedContext).`when`(notificationShadeWindowView).context
+        doReturn(true).`when`(notificationShadeWindowView).isAttachedToWindow
+        doNothing().`when`(threadedRenderer).addObserver(any())
+        doNothing().`when`(threadedRenderer).removeObserver(any())
+        doReturn(threadedRenderer).`when`(notificationShadeWindowView).threadedRenderer
+        userSwitcherController.init(notificationShadeWindowView)
+
         picture = UserIcons.convertToBitmap(context.getDrawable(R.drawable.ic_avatar_user))
         userSwitcherController.init(notificationShadeWindowView)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
index 336f2b1..3fe1a9f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
@@ -36,6 +36,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.statusbar.policy.ZenModeController.Callback;
+import com.android.systemui.util.settings.FakeSettings;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -70,7 +71,8 @@
                 mContext,
                 Handler.createAsync(Looper.myLooper()),
                 mBroadcastDispatcher,
-                mDumpManager);
+                mDumpManager,
+                new FakeSettings());
     }
 
     @Test
diff --git a/services/core/java/com/android/server/NsdService.java b/services/core/java/com/android/server/NsdService.java
index c9608a5..3e02084 100644
--- a/services/core/java/com/android/server/NsdService.java
+++ b/services/core/java/com/android/server/NsdService.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -20,24 +20,27 @@
 import android.content.Context;
 import android.content.Intent;
 import android.database.ContentObserver;
-import android.net.NetworkStack;
 import android.net.Uri;
 import android.net.nsd.INsdManager;
+import android.net.nsd.INsdManagerCallback;
+import android.net.nsd.INsdServiceConnector;
 import android.net.nsd.NsdManager;
 import android.net.nsd.NsdServiceInfo;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.IBinder;
 import android.os.Message;
-import android.os.Messenger;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Base64;
+import android.util.Log;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.AsyncChannel;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
@@ -72,12 +75,11 @@
     /**
      * Clients receiving asynchronous messages
      */
-    private final HashMap<Messenger, ClientInfo> mClients = new HashMap<>();
+    private final HashMap<NsdServiceConnector, ClientInfo> mClients = new HashMap<>();
 
     /* A map from unique id to client info */
     private final SparseArray<ClientInfo> mIdToClientInfoMap= new SparseArray<>();
 
-    private final AsyncChannel mReplyChannel = new AsyncChannel();
     private final long mCleanupDelayMs;
 
     private static final int INVALID_ID = 0;
@@ -149,65 +151,66 @@
         class DefaultState extends State {
             @Override
             public boolean processMessage(Message msg) {
-                ClientInfo cInfo = null;
+                final ClientInfo cInfo;
+                final int clientId = msg.arg2;
                 switch (msg.what) {
-                    case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
-                        if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
-                            AsyncChannel c = (AsyncChannel) msg.obj;
-                            if (DBG) Slog.d(TAG, "New client listening to asynchronous messages");
-                            c.sendMessage(AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED);
-                            cInfo = new ClientInfo(c, msg.replyTo);
-                            mClients.put(msg.replyTo, cInfo);
-                        } else {
-                            Slog.e(TAG, "Client connection failure, error=" + msg.arg1);
+                    case NsdManager.REGISTER_CLIENT:
+                        final Pair<NsdServiceConnector, INsdManagerCallback> arg =
+                                (Pair<NsdServiceConnector, INsdManagerCallback>) msg.obj;
+                        final INsdManagerCallback cb = arg.second;
+                        try {
+                            cb.asBinder().linkToDeath(arg.first, 0);
+                            cInfo = new ClientInfo(cb);
+                            mClients.put(arg.first, cInfo);
+                        } catch (RemoteException e) {
+                            Log.w(TAG, "Client " + clientId + " has already died");
                         }
                         break;
-                    case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
-                        switch (msg.arg1) {
-                            case AsyncChannel.STATUS_SEND_UNSUCCESSFUL:
-                                Slog.e(TAG, "Send failed, client connection lost");
-                                break;
-                            case AsyncChannel.STATUS_REMOTE_DISCONNECTION:
-                                if (DBG) Slog.d(TAG, "Client disconnected");
-                                break;
-                            default:
-                                if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1);
-                                break;
-                        }
-
-                        cInfo = mClients.get(msg.replyTo);
+                    case NsdManager.UNREGISTER_CLIENT:
+                        final NsdServiceConnector connector = (NsdServiceConnector) msg.obj;
+                        cInfo = mClients.remove(connector);
                         if (cInfo != null) {
                             cInfo.expungeAllRequests();
-                            mClients.remove(msg.replyTo);
                             if (cInfo.isLegacy()) {
                                 mLegacyClientCount -= 1;
                             }
                         }
                         maybeScheduleStop();
                         break;
-                    case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
-                        AsyncChannel ac = new AsyncChannel();
-                        ac.connect(mContext, getHandler(), msg.replyTo);
-                        break;
                     case NsdManager.DISCOVER_SERVICES:
-                        replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED,
-                                NsdManager.FAILURE_INTERNAL_ERROR);
+                        cInfo = getClientInfoForReply(msg);
+                        if (cInfo != null) {
+                            cInfo.onDiscoverServicesFailed(
+                                    clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+                        }
                        break;
                     case NsdManager.STOP_DISCOVERY:
-                       replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED,
-                               NsdManager.FAILURE_INTERNAL_ERROR);
+                        cInfo = getClientInfoForReply(msg);
+                        if (cInfo != null) {
+                            cInfo.onStopDiscoveryFailed(
+                                    clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+                        }
                         break;
                     case NsdManager.REGISTER_SERVICE:
-                        replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED,
-                                NsdManager.FAILURE_INTERNAL_ERROR);
+                        cInfo = getClientInfoForReply(msg);
+                        if (cInfo != null) {
+                            cInfo.onRegisterServiceFailed(
+                                    clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+                        }
                         break;
                     case NsdManager.UNREGISTER_SERVICE:
-                        replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED,
-                                NsdManager.FAILURE_INTERNAL_ERROR);
+                        cInfo = getClientInfoForReply(msg);
+                        if (cInfo != null) {
+                            cInfo.onUnregisterServiceFailed(
+                                    clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+                        }
                         break;
                     case NsdManager.RESOLVE_SERVICE:
-                        replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED,
-                                NsdManager.FAILURE_INTERNAL_ERROR);
+                        cInfo = getClientInfoForReply(msg);
+                        if (cInfo != null) {
+                            cInfo.onResolveServiceFailed(
+                                    clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+                        }
                         break;
                     case NsdManager.DAEMON_CLEANUP:
                         mDaemon.maybeStop();
@@ -215,7 +218,7 @@
                     // This event should be only sent by the legacy (target SDK < S) clients.
                     // Mark the sending client as legacy.
                     case NsdManager.DAEMON_STARTUP:
-                        cInfo = mClients.get(msg.replyTo);
+                        cInfo = getClientInfoForReply(msg);
                         if (cInfo != null) {
                             cancelStop();
                             cInfo.setLegacy();
@@ -230,6 +233,11 @@
                 }
                 return HANDLED;
             }
+
+            private ClientInfo getClientInfoForReply(Message msg) {
+                final ListenerArgs args = (ListenerArgs) msg.obj;
+                return mClients.get(args.connector);
+            }
         }
 
         class DisabledState extends State {
@@ -289,122 +297,119 @@
 
             @Override
             public boolean processMessage(Message msg) {
-                ClientInfo clientInfo;
-                NsdServiceInfo servInfo;
-                int id;
+                final ClientInfo clientInfo;
+                final int id;
+                final int clientId = msg.arg2;
+                final ListenerArgs args;
                 switch (msg.what) {
-                    case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
-                        return NOT_HANDLED;
-                    case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
-                        return NOT_HANDLED;
                     case NsdManager.DISABLE:
                         //TODO: cleanup clients
                         transitionTo(mDisabledState);
                         break;
                     case NsdManager.DISCOVER_SERVICES:
                         if (DBG) Slog.d(TAG, "Discover services");
-                        servInfo = (NsdServiceInfo) msg.obj;
-                        clientInfo = mClients.get(msg.replyTo);
+                        args = (ListenerArgs) msg.obj;
+                        clientInfo = mClients.get(args.connector);
 
                         if (requestLimitReached(clientInfo)) {
-                            replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED,
-                                    NsdManager.FAILURE_MAX_LIMIT);
+                            clientInfo.onDiscoverServicesFailed(
+                                    clientId, NsdManager.FAILURE_MAX_LIMIT);
                             break;
                         }
 
                         maybeStartDaemon();
                         id = getUniqueId();
-                        if (discoverServices(id, servInfo.getServiceType())) {
+                        if (discoverServices(id, args.serviceInfo.getServiceType())) {
                             if (DBG) {
                                 Slog.d(TAG, "Discover " + msg.arg2 + " " + id +
-                                        servInfo.getServiceType());
+                                        args.serviceInfo.getServiceType());
                             }
-                            storeRequestMap(msg.arg2, id, clientInfo, msg.what);
-                            replyToMessage(msg, NsdManager.DISCOVER_SERVICES_STARTED, servInfo);
+                            storeRequestMap(clientId, id, clientInfo, msg.what);
+                            clientInfo.onDiscoverServicesStarted(clientId, args.serviceInfo);
                         } else {
                             stopServiceDiscovery(id);
-                            replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED,
+                            clientInfo.onDiscoverServicesFailed(clientId,
                                     NsdManager.FAILURE_INTERNAL_ERROR);
                         }
                         break;
                     case NsdManager.STOP_DISCOVERY:
                         if (DBG) Slog.d(TAG, "Stop service discovery");
-                        clientInfo = mClients.get(msg.replyTo);
+                        args = (ListenerArgs) msg.obj;
+                        clientInfo = mClients.get(args.connector);
 
                         try {
-                            id = clientInfo.mClientIds.get(msg.arg2);
+                            id = clientInfo.mClientIds.get(clientId);
                         } catch (NullPointerException e) {
-                            replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED,
-                                    NsdManager.FAILURE_INTERNAL_ERROR);
+                            clientInfo.onStopDiscoveryFailed(
+                                    clientId, NsdManager.FAILURE_INTERNAL_ERROR);
                             break;
                         }
-                        removeRequestMap(msg.arg2, id, clientInfo);
+                        removeRequestMap(clientId, id, clientInfo);
                         if (stopServiceDiscovery(id)) {
-                            replyToMessage(msg, NsdManager.STOP_DISCOVERY_SUCCEEDED);
+                            clientInfo.onStopDiscoverySucceeded(clientId);
                         } else {
-                            replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED,
-                                    NsdManager.FAILURE_INTERNAL_ERROR);
+                            clientInfo.onStopDiscoveryFailed(
+                                    clientId, NsdManager.FAILURE_INTERNAL_ERROR);
                         }
                         break;
                     case NsdManager.REGISTER_SERVICE:
                         if (DBG) Slog.d(TAG, "Register service");
-                        clientInfo = mClients.get(msg.replyTo);
+                        args = (ListenerArgs) msg.obj;
+                        clientInfo = mClients.get(args.connector);
                         if (requestLimitReached(clientInfo)) {
-                            replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED,
-                                    NsdManager.FAILURE_MAX_LIMIT);
+                            clientInfo.onRegisterServiceFailed(
+                                    clientId, NsdManager.FAILURE_MAX_LIMIT);
                             break;
                         }
 
                         maybeStartDaemon();
                         id = getUniqueId();
-                        if (registerService(id, (NsdServiceInfo) msg.obj)) {
-                            if (DBG) Slog.d(TAG, "Register " + msg.arg2 + " " + id);
-                            storeRequestMap(msg.arg2, id, clientInfo, msg.what);
+                        if (registerService(id, args.serviceInfo)) {
+                            if (DBG) Slog.d(TAG, "Register " + clientId + " " + id);
+                            storeRequestMap(clientId, id, clientInfo, msg.what);
                             // Return success after mDns reports success
                         } else {
                             unregisterService(id);
-                            replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED,
-                                    NsdManager.FAILURE_INTERNAL_ERROR);
+                            clientInfo.onRegisterServiceFailed(
+                                    clientId, NsdManager.FAILURE_INTERNAL_ERROR);
                         }
                         break;
                     case NsdManager.UNREGISTER_SERVICE:
                         if (DBG) Slog.d(TAG, "unregister service");
-                        clientInfo = mClients.get(msg.replyTo);
-                        try {
-                            id = clientInfo.mClientIds.get(msg.arg2);
-                        } catch (NullPointerException e) {
-                            replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED,
-                                    NsdManager.FAILURE_INTERNAL_ERROR);
+                        args = (ListenerArgs) msg.obj;
+                        clientInfo = mClients.get(args.connector);
+                        if (clientInfo == null) {
+                            Slog.e(TAG, "Unknown connector in unregistration");
                             break;
                         }
-                        removeRequestMap(msg.arg2, id, clientInfo);
+                        id = clientInfo.mClientIds.get(clientId);
+                        removeRequestMap(clientId, id, clientInfo);
                         if (unregisterService(id)) {
-                            replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_SUCCEEDED);
+                            clientInfo.onUnregisterServiceSucceeded(clientId);
                         } else {
-                            replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED,
-                                    NsdManager.FAILURE_INTERNAL_ERROR);
+                            clientInfo.onUnregisterServiceFailed(
+                                    clientId, NsdManager.FAILURE_INTERNAL_ERROR);
                         }
                         break;
                     case NsdManager.RESOLVE_SERVICE:
                         if (DBG) Slog.d(TAG, "Resolve service");
-                        servInfo = (NsdServiceInfo) msg.obj;
-                        clientInfo = mClients.get(msg.replyTo);
-
+                        args = (ListenerArgs) msg.obj;
+                        clientInfo = mClients.get(args.connector);
 
                         if (clientInfo.mResolvedService != null) {
-                            replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED,
-                                    NsdManager.FAILURE_ALREADY_ACTIVE);
+                            clientInfo.onResolveServiceFailed(
+                                    clientId, NsdManager.FAILURE_ALREADY_ACTIVE);
                             break;
                         }
 
                         maybeStartDaemon();
                         id = getUniqueId();
-                        if (resolveService(id, servInfo)) {
+                        if (resolveService(id, args.serviceInfo)) {
                             clientInfo.mResolvedService = new NsdServiceInfo();
-                            storeRequestMap(msg.arg2, id, clientInfo, msg.what);
+                            storeRequestMap(clientId, id, clientInfo, msg.what);
                         } else {
-                            replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED,
-                                    NsdManager.FAILURE_INTERNAL_ERROR);
+                            clientInfo.onResolveServiceFailed(
+                                    clientId, NsdManager.FAILURE_INTERNAL_ERROR);
                         }
                         break;
                     case NsdManager.NATIVE_DAEMON_EVENT:
@@ -449,30 +454,27 @@
                     case NativeResponseCode.SERVICE_FOUND:
                         /* NNN uniqueId serviceName regType domain */
                         servInfo = new NsdServiceInfo(cooked[2], cooked[3]);
-                        clientInfo.mChannel.sendMessage(NsdManager.SERVICE_FOUND, 0,
-                                clientId, servInfo);
+                        clientInfo.onServiceFound(clientId, servInfo);
                         break;
                     case NativeResponseCode.SERVICE_LOST:
                         /* NNN uniqueId serviceName regType domain */
                         servInfo = new NsdServiceInfo(cooked[2], cooked[3]);
-                        clientInfo.mChannel.sendMessage(NsdManager.SERVICE_LOST, 0,
-                                clientId, servInfo);
+                        clientInfo.onServiceLost(clientId, servInfo);
                         break;
                     case NativeResponseCode.SERVICE_DISCOVERY_FAILED:
                         /* NNN uniqueId errorCode */
-                        clientInfo.mChannel.sendMessage(NsdManager.DISCOVER_SERVICES_FAILED,
-                                NsdManager.FAILURE_INTERNAL_ERROR, clientId);
+                        clientInfo.onDiscoverServicesFailed(
+                                clientId, NsdManager.FAILURE_INTERNAL_ERROR);
                         break;
                     case NativeResponseCode.SERVICE_REGISTERED:
                         /* NNN regId serviceName regType */
                         servInfo = new NsdServiceInfo(cooked[2], null);
-                        clientInfo.mChannel.sendMessage(NsdManager.REGISTER_SERVICE_SUCCEEDED,
-                                id, clientId, servInfo);
+                        clientInfo.onRegisterServiceSucceeded(clientId, servInfo);
                         break;
                     case NativeResponseCode.SERVICE_REGISTRATION_FAILED:
                         /* NNN regId errorCode */
-                        clientInfo.mChannel.sendMessage(NsdManager.REGISTER_SERVICE_FAILED,
-                               NsdManager.FAILURE_INTERNAL_ERROR, clientId);
+                        clientInfo.onRegisterServiceFailed(
+                                clientId, NsdManager.FAILURE_INTERNAL_ERROR);
                         break;
                     case NativeResponseCode.SERVICE_UPDATED:
                         /* NNN regId */
@@ -511,8 +513,8 @@
                         if (getAddrInfo(id2, cooked[3])) {
                             storeRequestMap(clientId, id2, clientInfo, NsdManager.RESOLVE_SERVICE);
                         } else {
-                            clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
-                                    NsdManager.FAILURE_INTERNAL_ERROR, clientId);
+                            clientInfo.onResolveServiceFailed(
+                                    clientId, NsdManager.FAILURE_INTERNAL_ERROR);
                             clientInfo.mResolvedService = null;
                         }
                         break;
@@ -521,26 +523,26 @@
                         stopResolveService(id);
                         removeRequestMap(clientId, id, clientInfo);
                         clientInfo.mResolvedService = null;
-                        clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
-                                NsdManager.FAILURE_INTERNAL_ERROR, clientId);
+                        clientInfo.onResolveServiceFailed(
+                                clientId, NsdManager.FAILURE_INTERNAL_ERROR);
                         break;
                     case NativeResponseCode.SERVICE_GET_ADDR_FAILED:
                         /* NNN resolveId errorCode */
                         stopGetAddrInfo(id);
                         removeRequestMap(clientId, id, clientInfo);
                         clientInfo.mResolvedService = null;
-                        clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
-                                NsdManager.FAILURE_INTERNAL_ERROR, clientId);
+                        clientInfo.onResolveServiceFailed(
+                                clientId, NsdManager.FAILURE_INTERNAL_ERROR);
                         break;
                     case NativeResponseCode.SERVICE_GET_ADDR_SUCCESS:
                         /* NNN resolveId hostname ttl addr */
                         try {
                             clientInfo.mResolvedService.setHost(InetAddress.getByName(cooked[4]));
-                            clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_SUCCEEDED,
-                                   0, clientId, clientInfo.mResolvedService);
+                            clientInfo.onResolveServiceSucceeded(
+                                    clientId, clientInfo.mResolvedService);
                         } catch (java.net.UnknownHostException e) {
-                            clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
-                                    NsdManager.FAILURE_INTERNAL_ERROR, clientId);
+                            clientInfo.onResolveServiceFailed(
+                                    clientId, NsdManager.FAILURE_INTERNAL_ERROR);
                         }
                         stopGetAddrInfo(id);
                         removeRequestMap(clientId, id, clientInfo);
@@ -601,15 +603,71 @@
         return service;
     }
 
-    public Messenger getMessenger() {
+    @Override
+    public INsdServiceConnector connect(INsdManagerCallback cb) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INTERNET, "NsdService");
-        return new Messenger(mNsdStateMachine.getHandler());
+        final INsdServiceConnector connector = new NsdServiceConnector();
+        mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
+                NsdManager.REGISTER_CLIENT, new Pair<>(connector, cb)));
+        return connector;
     }
 
-    public void setEnabled(boolean isEnabled) {
-        NetworkStack.checkNetworkStackPermission(mContext);
-        mNsdSettings.putEnabledStatus(isEnabled);
-        notifyEnabled(isEnabled);
+    private static class ListenerArgs {
+        public final NsdServiceConnector connector;
+        public final NsdServiceInfo serviceInfo;
+        ListenerArgs(NsdServiceConnector connector, NsdServiceInfo serviceInfo) {
+            this.connector = connector;
+            this.serviceInfo = serviceInfo;
+        }
+    }
+
+    private class NsdServiceConnector extends INsdServiceConnector.Stub
+            implements IBinder.DeathRecipient  {
+        @Override
+        public void registerService(int listenerKey, NsdServiceInfo serviceInfo) {
+            mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
+                    NsdManager.REGISTER_SERVICE, 0, listenerKey,
+                    new ListenerArgs(this, serviceInfo)));
+        }
+
+        @Override
+        public void unregisterService(int listenerKey) {
+            mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
+                    NsdManager.UNREGISTER_SERVICE, 0, listenerKey,
+                    new ListenerArgs(this, null)));
+        }
+
+        @Override
+        public void discoverServices(int listenerKey, NsdServiceInfo serviceInfo) {
+            mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
+                    NsdManager.DISCOVER_SERVICES, 0, listenerKey,
+                    new ListenerArgs(this, serviceInfo)));
+        }
+
+        @Override
+        public void stopDiscovery(int listenerKey) {
+            mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
+                    NsdManager.STOP_DISCOVERY, 0, listenerKey, new ListenerArgs(this, null)));
+        }
+
+        @Override
+        public void resolveService(int listenerKey, NsdServiceInfo serviceInfo) {
+            mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
+                    NsdManager.RESOLVE_SERVICE, 0, listenerKey,
+                    new ListenerArgs(this, serviceInfo)));
+        }
+
+        @Override
+        public void startDaemon() {
+            mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
+                    NsdManager.DAEMON_STARTUP, new ListenerArgs(this, null)));
+        }
+
+        @Override
+        public void binderDied() {
+            mNsdStateMachine.sendMessage(
+                    mNsdStateMachine.obtainMessage(NsdManager.UNREGISTER_CLIENT, this));
+        }
     }
 
     private void notifyEnabled(boolean isEnabled) {
@@ -832,43 +890,11 @@
         mNsdStateMachine.dump(fd, pw, args);
     }
 
-    /* arg2 on the source message has an id that needs to be retained in replies
-     * see NsdManager for details */
-    private Message obtainMessage(Message srcMsg) {
-        Message msg = Message.obtain();
-        msg.arg2 = srcMsg.arg2;
-        return msg;
-    }
-
-    private void replyToMessage(Message msg, int what) {
-        if (msg.replyTo == null) return;
-        Message dstMsg = obtainMessage(msg);
-        dstMsg.what = what;
-        mReplyChannel.replyToMessage(msg, dstMsg);
-    }
-
-    private void replyToMessage(Message msg, int what, int arg1) {
-        if (msg.replyTo == null) return;
-        Message dstMsg = obtainMessage(msg);
-        dstMsg.what = what;
-        dstMsg.arg1 = arg1;
-        mReplyChannel.replyToMessage(msg, dstMsg);
-    }
-
-    private void replyToMessage(Message msg, int what, Object obj) {
-        if (msg.replyTo == null) return;
-        Message dstMsg = obtainMessage(msg);
-        dstMsg.what = what;
-        dstMsg.obj = obj;
-        mReplyChannel.replyToMessage(msg, dstMsg);
-    }
-
     /* Information tracked per client */
     private class ClientInfo {
 
         private static final int MAX_LIMIT = 10;
-        private final AsyncChannel mChannel;
-        private final Messenger mMessenger;
+        private final INsdManagerCallback mCb;
         /* Remembers a resolved service until getaddrinfo completes */
         private NsdServiceInfo mResolvedService;
 
@@ -881,17 +907,14 @@
         // The target SDK of this client < Build.VERSION_CODES.S
         private boolean mIsLegacy = false;
 
-        private ClientInfo(AsyncChannel c, Messenger m) {
-            mChannel = c;
-            mMessenger = m;
-            if (DBG) Slog.d(TAG, "New client, channel: " + c + " messenger: " + m);
+        private ClientInfo(INsdManagerCallback cb) {
+            mCb = cb;
+            if (DBG) Slog.d(TAG, "New client");
         }
 
         @Override
         public String toString() {
             StringBuilder sb = new StringBuilder();
-            sb.append("mChannel ").append(mChannel).append("\n");
-            sb.append("mMessenger ").append(mMessenger).append("\n");
             sb.append("mResolvedService ").append(mResolvedService).append("\n");
             sb.append("mIsLegacy ").append(mIsLegacy).append("\n");
             for(int i = 0; i< mClientIds.size(); i++) {
@@ -949,6 +972,102 @@
             }
             return mClientIds.keyAt(idx);
         }
+
+        void onDiscoverServicesStarted(int listenerKey, NsdServiceInfo info) {
+            try {
+                mCb.onDiscoverServicesStarted(listenerKey, info);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling onDiscoverServicesStarted", e);
+            }
+        }
+
+        void onDiscoverServicesFailed(int listenerKey, int error) {
+            try {
+                mCb.onDiscoverServicesFailed(listenerKey, error);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling onDiscoverServicesFailed", e);
+            }
+        }
+
+        void onServiceFound(int listenerKey, NsdServiceInfo info) {
+            try {
+                mCb.onServiceFound(listenerKey, info);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling onServiceFound(", e);
+            }
+        }
+
+        void onServiceLost(int listenerKey, NsdServiceInfo info) {
+            try {
+                mCb.onServiceLost(listenerKey, info);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling onServiceLost(", e);
+            }
+        }
+
+        void onStopDiscoveryFailed(int listenerKey, int error) {
+            try {
+                mCb.onStopDiscoveryFailed(listenerKey, error);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling onStopDiscoveryFailed", e);
+            }
+        }
+
+        void onStopDiscoverySucceeded(int listenerKey) {
+            try {
+                mCb.onStopDiscoverySucceeded(listenerKey);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling onStopDiscoverySucceeded", e);
+            }
+        }
+
+        void onRegisterServiceFailed(int listenerKey, int error) {
+            try {
+                mCb.onRegisterServiceFailed(listenerKey, error);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling onRegisterServiceFailed", e);
+            }
+        }
+
+        void onRegisterServiceSucceeded(int listenerKey, NsdServiceInfo info) {
+            try {
+                mCb.onRegisterServiceSucceeded(listenerKey, info);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling onRegisterServiceSucceeded", e);
+            }
+        }
+
+        void onUnregisterServiceFailed(int listenerKey, int error) {
+            try {
+                mCb.onUnregisterServiceFailed(listenerKey, error);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling onUnregisterServiceFailed", e);
+            }
+        }
+
+        void onUnregisterServiceSucceeded(int listenerKey) {
+            try {
+                mCb.onUnregisterServiceSucceeded(listenerKey);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling onUnregisterServiceSucceeded", e);
+            }
+        }
+
+        void onResolveServiceFailed(int listenerKey, int error) {
+            try {
+                mCb.onResolveServiceFailed(listenerKey, error);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling onResolveServiceFailed", e);
+            }
+        }
+
+        void onResolveServiceSucceeded(int listenerKey, NsdServiceInfo info) {
+            try {
+                mCb.onResolveServiceSucceeded(listenerKey, info);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling onResolveServiceSucceeded", e);
+            }
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index b019789..ac20a08 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -685,6 +685,7 @@
             final UUID errorId = mTraceErrorLogger.generateErrorId();
             if (mTraceErrorLogger.isAddErrorIdEnabled()) {
                 mTraceErrorLogger.addErrorIdToTrace("system_server", errorId);
+                mTraceErrorLogger.addSubjectToTrace(subject, errorId);
             }
 
             // Log the atom as early as possible since it is used as a mechanism to trigger
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ded115b..1fe4d14 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -642,10 +642,11 @@
 
     final BroadcastQueue mFgBroadcastQueue;
     final BroadcastQueue mBgBroadcastQueue;
-    final BroadcastQueue mOffloadBroadcastQueue;
+    final BroadcastQueue mBgOffloadBroadcastQueue;
+    final BroadcastQueue mFgOffloadBroadcastQueue;
     // Convenient for easy iteration over the queues. Foreground is first
     // so that dispatch of foreground broadcasts gets precedence.
-    final BroadcastQueue[] mBroadcastQueues = new BroadcastQueue[3];
+    final BroadcastQueue[] mBroadcastQueues = new BroadcastQueue[4];
 
     @GuardedBy("this")
     BroadcastStats mLastBroadcastStats;
@@ -656,12 +657,20 @@
     TraceErrorLogger mTraceErrorLogger;
 
     BroadcastQueue broadcastQueueForIntent(Intent intent) {
-        if (isOnOffloadQueue(intent.getFlags())) {
+        if (isOnFgOffloadQueue(intent.getFlags())) {
             if (DEBUG_BROADCAST_BACKGROUND) {
                 Slog.i(TAG_BROADCAST,
-                        "Broadcast intent " + intent + " on offload queue");
+                        "Broadcast intent " + intent + " on foreground offload queue");
             }
-            return mOffloadBroadcastQueue;
+            return mFgOffloadBroadcastQueue;
+        }
+
+        if (isOnBgOffloadQueue(intent.getFlags())) {
+            if (DEBUG_BROADCAST_BACKGROUND) {
+                Slog.i(TAG_BROADCAST,
+                        "Broadcast intent " + intent + " on background offload queue");
+            }
+            return mBgOffloadBroadcastQueue;
         }
 
         final boolean isFg = (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0;
@@ -2263,7 +2272,8 @@
         mPendingStartActivityUids = new PendingStartActivityUids(mContext);
         mUseFifoUiScheduling = false;
         mEnableOffloadQueue = false;
-        mFgBroadcastQueue = mBgBroadcastQueue = mOffloadBroadcastQueue = null;
+        mFgBroadcastQueue = mBgBroadcastQueue = mBgOffloadBroadcastQueue =
+                mFgOffloadBroadcastQueue = null;
         mComponentAliasResolver = new ComponentAliasResolver(this);
     }
 
@@ -2324,11 +2334,14 @@
                 "foreground", foreConstants, false);
         mBgBroadcastQueue = new BroadcastQueue(this, mHandler,
                 "background", backConstants, true);
-        mOffloadBroadcastQueue = new BroadcastQueue(this, mHandler,
-                "offload", offloadConstants, true);
+        mBgOffloadBroadcastQueue = new BroadcastQueue(this, mHandler,
+                "offload_bg", offloadConstants, true);
+        mFgOffloadBroadcastQueue = new BroadcastQueue(this, mHandler,
+                "offload_fg", foreConstants, true);
         mBroadcastQueues[0] = mFgBroadcastQueue;
         mBroadcastQueues[1] = mBgBroadcastQueue;
-        mBroadcastQueues[2] = mOffloadBroadcastQueue;
+        mBroadcastQueues[2] = mBgOffloadBroadcastQueue;
+        mBroadcastQueues[3] = mFgOffloadBroadcastQueue;
 
         mServices = new ActiveServices(this);
         mCpHelper = new ContentProviderHelper(this, true);
@@ -12552,13 +12565,15 @@
     boolean isPendingBroadcastProcessLocked(int pid) {
         return mFgBroadcastQueue.isPendingBroadcastProcessLocked(pid)
                 || mBgBroadcastQueue.isPendingBroadcastProcessLocked(pid)
-                || mOffloadBroadcastQueue.isPendingBroadcastProcessLocked(pid);
+                || mBgOffloadBroadcastQueue.isPendingBroadcastProcessLocked(pid)
+                || mFgOffloadBroadcastQueue.isPendingBroadcastProcessLocked(pid);
     }
 
     boolean isPendingBroadcastProcessLocked(ProcessRecord app) {
         return mFgBroadcastQueue.isPendingBroadcastProcessLocked(app)
                 || mBgBroadcastQueue.isPendingBroadcastProcessLocked(app)
-                || mOffloadBroadcastQueue.isPendingBroadcastProcessLocked(app);
+                || mBgOffloadBroadcastQueue.isPendingBroadcastProcessLocked(app)
+                || mFgOffloadBroadcastQueue.isPendingBroadcastProcessLocked(app);
     }
 
     void skipPendingBroadcastLocked(int pid) {
@@ -14007,8 +14022,10 @@
             BroadcastQueue queue;
 
             synchronized(this) {
-                if (isOnOffloadQueue(flags)) {
-                    queue = mOffloadBroadcastQueue;
+                if (isOnFgOffloadQueue(flags)) {
+                    queue = mFgOffloadBroadcastQueue;
+                } else if (isOnBgOffloadQueue(flags)) {
+                    queue = mBgOffloadBroadcastQueue;
                 } else {
                     queue = (flags & Intent.FLAG_RECEIVER_FOREGROUND) != 0
                             ? mFgBroadcastQueue : mBgBroadcastQueue;
@@ -16302,7 +16319,7 @@
                         Binder.getCallingUid(), Binder.getCallingPid(), UserHandle.USER_ALL);
                 if ((changes & ActivityInfo.CONFIG_LOCALE) != 0) {
                     intent = new Intent(Intent.ACTION_LOCALE_CHANGED);
-                    intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
+                    intent.addFlags(Intent.FLAG_RECEIVER_OFFLOAD_FOREGROUND
                             | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
                             | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
                     if (initLocale || !mProcessesReady) {
@@ -17479,7 +17496,11 @@
         }
     }
 
-    private boolean isOnOffloadQueue(int flags) {
+    private boolean isOnFgOffloadQueue(int flags) {
+        return ((flags & Intent.FLAG_RECEIVER_OFFLOAD_FOREGROUND) != 0);
+    }
+
+    private boolean isOnBgOffloadQueue(int flags) {
         return (mEnableOffloadQueue && ((flags & Intent.FLAG_RECEIVER_OFFLOAD) != 0));
     }
 
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index 4220506..18ad1f5 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -286,6 +286,7 @@
                     && mService.mTraceErrorLogger.isAddErrorIdEnabled()) {
                 errorId = mService.mTraceErrorLogger.generateErrorId();
                 mService.mTraceErrorLogger.addErrorIdToTrace(mApp.processName, errorId);
+                mService.mTraceErrorLogger.addSubjectToTrace(annotation, errorId);
             } else {
                 errorId = null;
             }
diff --git a/services/core/java/com/android/server/am/TraceErrorLogger.java b/services/core/java/com/android/server/am/TraceErrorLogger.java
index c658100..29a9b5c 100644
--- a/services/core/java/com/android/server/am/TraceErrorLogger.java
+++ b/services/core/java/com/android/server/am/TraceErrorLogger.java
@@ -54,4 +54,17 @@
                 COUNTER_PREFIX + processName + "#" + errorId.toString(),
                 PLACEHOLDER_VALUE);
     }
+
+    /**
+     * Pushes a counter containing an ANR/Watchdog subject and a unique id so that the subject
+     * can be uniquely identified.
+     *
+     * @param subject The subject to include in the trace.
+     * @param errorId The unique id with which to tag the trace.
+     */
+    public void addSubjectToTrace(String subject, UUID errorId) {
+        Trace.traceCounter(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                String.format("Subject(for ErrorId %s):%s", errorId.toString(), subject),
+                PLACEHOLDER_VALUE);
+    }
 }
diff --git a/services/core/java/com/android/server/app/OWNERS b/services/core/java/com/android/server/app/OWNERS
new file mode 100644
index 0000000..aaebbfa
--- /dev/null
+++ b/services/core/java/com/android/server/app/OWNERS
@@ -0,0 +1 @@
+per-file GameManager* = file:/GAME_MANAGER_OWNERS
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
new file mode 100644
index 0000000..05e1bdd
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -0,0 +1,476 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+
+import static com.android.server.inputmethod.InputMethodManagerService.MSG_INITIALIZE_IME;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManagerInternal;
+import android.content.res.Resources;
+import android.inputmethodservice.InputMethodService;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.view.IWindowManager;
+import android.view.WindowManager;
+import android.view.inputmethod.InputMethod;
+import android.view.inputmethod.InputMethodInfo;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.InputBindResult;
+import com.android.internal.inputmethod.UnbindReason;
+import com.android.internal.view.IInputMethod;
+import com.android.server.wm.WindowManagerInternal;
+
+/**
+ * A controller managing the state of the input method binding.
+ */
+final class InputMethodBindingController {
+    static final boolean DEBUG = false;
+    private static final String TAG = InputMethodBindingController.class.getSimpleName();
+
+    @NonNull private final InputMethodManagerService mService;
+    @NonNull private final Context mContext;
+    @NonNull private final ArrayMap<String, InputMethodInfo> mMethodMap;
+    @NonNull private final InputMethodUtils.InputMethodSettings mSettings;
+    @NonNull private final PackageManagerInternal mPackageManagerInternal;
+    @NonNull private final IWindowManager mIWindowManager;
+    @NonNull private final WindowManagerInternal mWindowManagerInternal;
+    @NonNull private final Resources mRes;
+
+    private long mLastBindTime;
+    private boolean mHasConnection;
+    @Nullable private String mCurId;
+    @Nullable private String mSelectedMethodId;
+    @Nullable private Intent mCurIntent;
+    @Nullable private IInputMethod mCurMethod;
+    private int mCurMethodUid = Process.INVALID_UID;
+    private IBinder mCurToken;
+    private int mCurSeq;
+    private boolean mVisibleBound;
+
+    /**
+     * Binding flags for establishing connection to the {@link InputMethodService}.
+     */
+    private static final int IME_CONNECTION_BIND_FLAGS =
+            Context.BIND_AUTO_CREATE
+                    | Context.BIND_NOT_VISIBLE
+                    | Context.BIND_NOT_FOREGROUND
+                    | Context.BIND_IMPORTANT_BACKGROUND;
+    /**
+     * Binding flags for establishing connection to the {@link InputMethodService} when
+     * config_killableInputMethods is enabled.
+     */
+    private static final int IME_CONNECTION_LOW_PRIORITY_BIND_FLAGS =
+            Context.BIND_AUTO_CREATE
+                    | Context.BIND_REDUCTION_FLAGS;
+    /**
+     * Binding flags used only while the {@link InputMethodService} is showing window.
+     */
+    private static final int IME_VISIBLE_BIND_FLAGS =
+            Context.BIND_AUTO_CREATE
+                    | Context.BIND_TREAT_LIKE_ACTIVITY
+                    | Context.BIND_FOREGROUND_SERVICE
+                    | Context.BIND_INCLUDE_CAPABILITIES
+                    | Context.BIND_SHOWING_UI
+                    | Context.BIND_SCHEDULE_LIKE_TOP_APP;
+
+    /**
+     * Binding flags for establishing connection to the {@link InputMethodService}.
+     *
+     * <p>
+     * This defaults to {@link InputMethodBindingController#IME_CONNECTION_BIND_FLAGS} unless
+     * config_killableInputMethods is enabled, in which case this takes the value of
+     * {@link InputMethodBindingController#IME_CONNECTION_LOW_PRIORITY_BIND_FLAGS}.
+     */
+    private final int mImeConnectionBindFlags;
+
+    InputMethodBindingController(@NonNull InputMethodManagerService service) {
+        mService = service;
+        mContext = mService.mContext;
+        mMethodMap = mService.mMethodMap;
+        mSettings = mService.mSettings;
+        mPackageManagerInternal = mService.mPackageManagerInternal;
+        mIWindowManager = mService.mIWindowManager;
+        mWindowManagerInternal = mService.mWindowManagerInternal;
+        mRes = mService.mRes;
+
+        // If configured, use low priority flags to make the IME killable by the lowmemorykiller
+        final boolean lowerIMEPriority = mRes.getBoolean(
+                com.android.internal.R.bool.config_killableInputMethods);
+
+        if (lowerIMEPriority) {
+            mImeConnectionBindFlags =
+                    InputMethodBindingController.IME_CONNECTION_LOW_PRIORITY_BIND_FLAGS;
+        } else {
+            mImeConnectionBindFlags = InputMethodBindingController.IME_CONNECTION_BIND_FLAGS;
+        }
+    }
+
+    /**
+     * Time that we last initiated a bind to the input method, to determine
+     * if we should try to disconnect and reconnect to it.
+     */
+    long getLastBindTime() {
+        return mLastBindTime;
+    }
+
+    /**
+     * Set to true if our ServiceConnection is currently actively bound to
+     * a service (whether or not we have gotten its IBinder back yet).
+     */
+    boolean hasConnection() {
+        return mHasConnection;
+    }
+
+    /**
+     * Id obtained with {@link InputMethodInfo#getId()} for the input method that we are currently
+     * connected to or in the process of connecting to.
+     *
+     * <p>This can be {@code null} when no input method is connected.</p>
+     *
+     * @see #getSelectedMethodId()
+     */
+    @Nullable
+    String getCurId() {
+        return mCurId;
+    }
+
+    /**
+     * Id obtained with {@link InputMethodInfo#getId()} for the currently selected input method.
+     * This is to be synchronized with the secure settings keyed with
+     * {@link android.provider.Settings.Secure#DEFAULT_INPUT_METHOD}.
+     *
+     * <p>This can be transiently {@code null} when the system is re-initializing input method
+     * settings, e.g., the system locale is just changed.</p>
+     *
+     * <p>Note that {@link #getCurId()} is used to track which IME is being connected to
+     * {@link com.android.server.inputmethod.InputMethodManagerService}.</p>
+     *
+     * @see #getCurId()
+     */
+    @Nullable
+    String getSelectedMethodId() {
+        return mSelectedMethodId;
+    }
+
+    void setSelectedMethodId(@Nullable String selectedMethodId) {
+        mSelectedMethodId = selectedMethodId;
+    }
+
+    /**
+     * The token we have made for the currently active input method, to
+     * identify it in the future.
+     */
+    IBinder getCurToken() {
+        return mCurToken;
+    }
+
+    /**
+     * The Intent used to connect to the current input method.
+     */
+    @Nullable
+    Intent getCurIntent() {
+        return mCurIntent;
+    }
+
+    /**
+     * The current binding sequence number, incremented every time there is
+     * a new bind performed.
+     */
+    int getSequenceNumber() {
+        return mCurSeq;
+    }
+
+    /**
+     * Increase the current binding sequence number by one.
+     * Reset to 1 on overflow.
+     */
+    void advanceSequenceNumber() {
+        mCurSeq += 1;
+        if (mCurSeq <= 0) {
+            mCurSeq = 1;
+        }
+    }
+
+    /**
+     * If non-null, this is the input method service we are currently connected
+     * to.
+     */
+    @Nullable
+    IInputMethod getCurMethod() {
+        return mCurMethod;
+    }
+
+    /**
+     * If not {@link Process#INVALID_UID}, then the UID of {@link #getCurIntent()}.
+     */
+    int getCurMethodUid() {
+        return mCurMethodUid;
+    }
+
+    /**
+     * Indicates whether {@link #getVisibleConnection} is currently in use.
+     */
+    boolean isVisibleBound() {
+        return mVisibleBound;
+    }
+
+    /**
+     * Used to bring IME service up to visible adjustment while it is being shown.
+     */
+    @NonNull
+    ServiceConnection getVisibleConnection() {
+        return mVisibleConnection;
+    }
+
+    private final ServiceConnection mVisibleConnection = new ServiceConnection() {
+        @Override public void onBindingDied(ComponentName name) {
+            synchronized (mMethodMap) {
+                if (mVisibleBound) {
+                    unbindVisibleConnectionLocked();
+                }
+            }
+        }
+
+        @Override public void onServiceConnected(ComponentName name, IBinder service) {
+        }
+
+        @Override public void onServiceDisconnected(ComponentName name) {
+        }
+    };
+
+    /**
+     * Used to bind the IME while it is not currently being shown.
+     */
+    private final ServiceConnection mMainConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.onServiceConnected");
+            synchronized (mMethodMap) {
+                if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
+                    mCurMethod = IInputMethod.Stub.asInterface(service);
+                    updateCurrentMethodUidLocked();
+                    if (mCurToken == null) {
+                        Slog.w(TAG, "Service connected without a token!");
+                        unbindCurrentMethodLocked();
+                        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+                        return;
+                    }
+                    if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
+                    // Dispatch display id for InputMethodService to update context display.
+                    mService.executeOrSendMessage(mCurMethod,
+                            mService.mCaller.obtainMessageIOO(MSG_INITIALIZE_IME,
+                                    mMethodMap.get(mSelectedMethodId).getConfigChanges(),
+                                    mCurMethod, mCurToken));
+                    mService.scheduleNotifyImeUidToAudioService(mCurMethodUid);
+                    mService.reRequestCurrentClientSessionLocked();
+                }
+            }
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+        }
+
+        @GuardedBy("mMethodMap")
+        private void updateCurrentMethodUidLocked() {
+            final String curMethodPackage = mCurIntent.getComponent().getPackageName();
+            final int curMethodUid = mPackageManagerInternal.getPackageUid(
+                    curMethodPackage, 0 /* flags */, mSettings.getCurrentUserId());
+            if (curMethodUid < 0) {
+                Slog.e(TAG, "Failed to get UID for package=" + curMethodPackage);
+                mCurMethodUid = Process.INVALID_UID;
+            } else {
+                mCurMethodUid = curMethodUid;
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(@NonNull ComponentName name) {
+            // Note that mContext.unbindService(this) does not trigger this.  Hence if we are
+            // here the
+            // disconnection is not intended by IMMS (e.g. triggered because the current IMS
+            // crashed),
+            // which is irregular but can eventually happen for everyone just by continuing
+            // using the
+            // device.  Thus it is important to make sure that all the internal states are
+            // properly
+            // refreshed when this method is called back.  Running
+            //    adb install -r <APK that implements the current IME>
+            // would be a good way to trigger such a situation.
+            synchronized (mMethodMap) {
+                if (DEBUG) {
+                    Slog.v(TAG, "Service disconnected: " + name + " mCurIntent=" + mCurIntent);
+                }
+                if (mCurMethod != null && mCurIntent != null
+                        && name.equals(mCurIntent.getComponent())) {
+                    // We consider this to be a new bind attempt, since the system
+                    // should now try to restart the service for us.
+                    mLastBindTime = SystemClock.uptimeMillis();
+                    mService.clearClientSessionsLocked();
+                    mService.clearInputShowRequestLocked();
+                    mService.unbindCurrentClientLocked(UnbindReason.DISCONNECT_IME);
+                }
+            }
+        }
+    };
+
+    @GuardedBy("mMethodMap")
+    void unbindCurrentMethodLocked() {
+        if (mVisibleBound) {
+            unbindVisibleConnectionLocked();
+        }
+
+        if (mHasConnection) {
+            unbindMainConnectionLocked();
+        }
+
+        if (mCurToken != null) {
+            removeCurrentTokenLocked();
+            mService.resetSystemUiLocked();
+        }
+
+        mCurId = null;
+        mService.clearClientSessionsLocked();
+    }
+
+    @GuardedBy("mMethodMap")
+    void clearCurMethodLocked() {
+        mCurMethod = null;
+        mCurMethodUid = Process.INVALID_UID;
+    }
+
+    @GuardedBy("mMethodMap")
+    private void removeCurrentTokenLocked() {
+        int curTokenDisplayId = mService.getCurTokenDisplayId();
+
+        if (DEBUG) {
+            Slog.v(TAG,
+                    "Removing window token: " + mCurToken + " for display: " + curTokenDisplayId);
+        }
+        mWindowManagerInternal.removeWindowToken(mCurToken, false /* removeWindows */,
+                false /* animateExit */, curTokenDisplayId);
+        mCurToken = null;
+    }
+
+    @GuardedBy("mMethodMap")
+    @NonNull
+    InputBindResult bindCurrentMethodLocked(int displayIdToShowIme) {
+        InputMethodInfo info = mMethodMap.get(mSelectedMethodId);
+        if (info == null) {
+            throw new IllegalArgumentException("Unknown id: " + mSelectedMethodId);
+        }
+
+        mCurIntent = createImeBindingIntent(info.getComponent());
+
+        if (bindCurrentInputMethodServiceMainConnectionLocked()) {
+            mCurId = info.getId();
+            mLastBindTime = SystemClock.uptimeMillis();
+
+            addFreshWindowTokenLocked(displayIdToShowIme);
+            return new InputBindResult(
+                    InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
+                    null, null, mCurId, mCurSeq, false);
+        }
+
+        Slog.w(InputMethodManagerService.TAG,
+                "Failure connecting to input method service: " + mCurIntent);
+        mCurIntent = null;
+        return InputBindResult.IME_NOT_CONNECTED;
+    }
+
+    @NonNull
+    private Intent createImeBindingIntent(ComponentName component) {
+        Intent intent = new Intent(InputMethod.SERVICE_INTERFACE);
+        intent.setComponent(component);
+        intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
+                com.android.internal.R.string.input_method_binding_label);
+        intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
+                mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS),
+                PendingIntent.FLAG_IMMUTABLE));
+        return intent;
+    }
+
+    @GuardedBy("mMethodMap")
+    private void addFreshWindowTokenLocked(int displayIdToShowIme) {
+        mCurToken = new Binder();
+
+        mService.setCurTokenDisplayId(displayIdToShowIme);
+
+        try {
+            if (DEBUG) {
+                Slog.v(TAG, "Adding window token: " + mCurToken + " for display: "
+                        + displayIdToShowIme);
+            }
+            mIWindowManager.addWindowToken(mCurToken, WindowManager.LayoutParams.TYPE_INPUT_METHOD,
+                    displayIdToShowIme, null /* options */);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Could not add window token " + mCurToken + " for display "
+                    + displayIdToShowIme, e);
+        }
+    }
+
+    @GuardedBy("mMethodMap")
+    void unbindMainConnectionLocked() {
+        mContext.unbindService(mMainConnection);
+        mHasConnection = false;
+    }
+
+    @GuardedBy("mMethodMap")
+    void unbindVisibleConnectionLocked() {
+        mContext.unbindService(mVisibleConnection);
+        mVisibleBound = false;
+    }
+
+    @GuardedBy("mMethodMap")
+    private boolean bindCurrentInputMethodServiceLocked(ServiceConnection conn, int flags) {
+        if (mCurIntent == null || conn == null) {
+            Slog.e(TAG, "--- bind failed: service = " + mCurIntent + ", conn = " + conn);
+            return false;
+        }
+        return mContext.bindServiceAsUser(mCurIntent, conn, flags,
+                new UserHandle(mSettings.getCurrentUserId()));
+    }
+
+    @GuardedBy("mMethodMap")
+    boolean bindCurrentInputMethodServiceVisibleConnectionLocked() {
+        mVisibleBound = bindCurrentInputMethodServiceLocked(mVisibleConnection,
+                IME_VISIBLE_BIND_FLAGS);
+        return mVisibleBound;
+    }
+
+    @GuardedBy("mMethodMap")
+    boolean bindCurrentInputMethodServiceMainConnectionLocked() {
+        mHasConnection = bindCurrentInputMethodServiceLocked(mMainConnection,
+                mImeConnectionBindFlags);
+        return mHasConnection;
+    }
+
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index cb76d832..c879e3d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -72,13 +72,11 @@
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.ServiceConnection;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
@@ -208,7 +206,7 @@
  * This class provides a system service that manages input methods.
  */
 public class InputMethodManagerService extends IInputMethodManager.Stub
-        implements ServiceConnection, Handler.Callback {
+        implements Handler.Callback {
     static final boolean DEBUG = false;
     static final String TAG = "InputMethodManagerService";
     public static final String PROTO_ARG = "--proto";
@@ -261,44 +259,6 @@
     private static final String HANDLER_THREAD_NAME = "android.imms";
 
     /**
-     * Binding flags for establishing connection to the {@link InputMethodService}.
-     */
-    private static final int IME_CONNECTION_BIND_FLAGS =
-            Context.BIND_AUTO_CREATE
-            | Context.BIND_NOT_VISIBLE
-            | Context.BIND_NOT_FOREGROUND
-            | Context.BIND_IMPORTANT_BACKGROUND;
-
-    /**
-     * Binding flags for establishing connection to the {@link InputMethodService} when
-     * config_killableInputMethods is enabled.
-     */
-    private static final int IME_CONNECTION_LOW_PRIORITY_BIND_FLAGS =
-            Context.BIND_AUTO_CREATE
-            | Context.BIND_REDUCTION_FLAGS;
-
-    /**
-     * Binding flags for establishing connection to the {@link InputMethodService}.
-     *
-     * <p>
-     * This defaults to {@link #IME_CONNECTION_BIND_FLAGS} unless config_killableInputMethods is
-     * enabled, in which case this takes the value of
-     * {@link #IME_CONNECTION_LOW_PRIORITY_BIND_FLAGS}.
-     */
-    private final int mImeConnectionBindFlags;
-
-    /**
-     * Binding flags used only while the {@link InputMethodService} is showing window.
-     */
-    private static final int IME_VISIBLE_BIND_FLAGS =
-            Context.BIND_AUTO_CREATE
-            | Context.BIND_TREAT_LIKE_ACTIVITY
-            | Context.BIND_FOREGROUND_SERVICE
-            | Context.BIND_INCLUDE_CAPABILITIES
-            | Context.BIND_SHOWING_UI
-            | Context.BIND_SCHEDULE_LIKE_TOP_APP;
-
-    /**
      * A protected broadcast intent action for internal use for {@link PendingIntent} in
      * the notification.
      */
@@ -321,11 +281,11 @@
     final boolean mHasFeature;
     private final ArrayMap<String, List<InputMethodSubtype>> mAdditionalSubtypeMap =
             new ArrayMap<>();
-    private final boolean mIsLowRam;
     private final AppOpsManager mAppOpsManager;
     private final UserManager mUserManager;
     private final UserManagerInternal mUserManagerInternal;
     private final InputMethodMenuController mMenuController;
+    private final InputMethodBindingController mBindingController;
 
     /**
      * Cache the result of {@code LocalServices.getService(AudioManagerInternal.class)}.
@@ -351,31 +311,20 @@
     @GuardedBy("mMethodMap")
     private int mMethodMapUpdateCount = 0;
 
-    // Used to bring IME service up to visible adjustment while it is being shown.
-    final ServiceConnection mVisibleConnection = new ServiceConnection() {
-        @Override public void onBindingDied(ComponentName name) {
-            synchronized (mMethodMap) {
-                if (mVisibleBound) {
-                    mContext.unbindService(mVisibleConnection);
-                    mVisibleBound = false;
-                }
-            }
-        }
-
-        @Override public void onServiceConnected(ComponentName name, IBinder service) {
-        }
-
-        @Override public void onServiceDisconnected(ComponentName name) {
-        }
-    };
-    boolean mVisibleBound = false;
+    /**
+     * Indicates whether {@link InputMethodBindingController#getVisibleConnection} is currently
+     * in use.
+     */
+    private boolean isVisibleBound() {
+        return mBindingController.isVisibleBound();
+    }
 
     // Ongoing notification
     private NotificationManager mNotificationManager;
     KeyguardManager mKeyguardManager;
     private @Nullable StatusBarManagerService mStatusBar;
-    private Notification.Builder mImeSwitcherNotification;
-    private PendingIntent mImeSwitchPendingIntent;
+    private final Notification.Builder mImeSwitcherNotification;
+    private final PendingIntent mImeSwitchPendingIntent;
     private boolean mShowOngoingImeSwitcherForPhones;
     private boolean mNotificationShown;
 
@@ -463,25 +412,41 @@
 
     /**
      * Id obtained with {@link InputMethodInfo#getId()} for the currently selected input method.
-     * method.  This is to be synchronized with the secure settings keyed with
+     * This is to be synchronized with the secure settings keyed with
      * {@link Settings.Secure#DEFAULT_INPUT_METHOD}.
      *
      * <p>This can be transiently {@code null} when the system is re-initializing input method
      * settings, e.g., the system locale is just changed.</p>
      *
-     * <p>Note that {@link #mCurId} is used to track which IME is being connected to
-     * {@link InputMethodManagerService}.</p>
+     * <p>Note that {@link InputMethodBindingController#getCurId()} is used to track which IME is
+     * being connected to {@link InputMethodManagerService}.</p>
      *
-     * @see #mCurId
+     * @see InputMethodBindingController#getCurId()
      */
     @Nullable
-    String mCurMethodId;
+    private String getSelectedMethodId() {
+        return mBindingController.getSelectedMethodId();
+    }
+
+    private void setSelectedMethodId(@Nullable String selectedMethodId) {
+        mBindingController.setSelectedMethodId(selectedMethodId);
+    }
 
     /**
      * The current binding sequence number, incremented every time there is
      * a new bind performed.
      */
-    int mCurSeq;
+    private int getSequenceNumber() {
+        return mBindingController.getSequenceNumber();
+    }
+
+    /**
+     * Increase the current binding sequence number by one.
+     * Reset to 1 on overflow.
+     */
+    private void advanceSequenceNumber() {
+        mBindingController.advanceSequenceNumber();
+    }
 
     /**
      * {@code true} if the Ime policy has been set to {@link WindowManager#DISPLAY_IME_POLICY_HIDE}.
@@ -493,7 +458,7 @@
     /**
      * The client that is currently bound to an input method.
      */
-    ClientState mCurClient;
+    private ClientState mCurClient;
 
     /**
      * The last window token that we confirmed to be focused.  This is always updated upon reports
@@ -538,10 +503,12 @@
      *
      * <p>This can be {@code null} when no input method is connected.</p>
      *
-     * @see #mCurMethodId
+     * @see #getSelectedMethodId()
      */
     @Nullable
-    String mCurId;
+    private String getCurId() {
+        return mBindingController.getCurId();
+    }
 
     /**
      * The current subtype of the current input method.
@@ -557,12 +524,14 @@
      * Set to true if our ServiceConnection is currently actively bound to
      * a service (whether or not we have gotten its IBinder back yet).
      */
-    boolean mHaveConnection;
+    private boolean hasConnection() {
+        return mBindingController.hasConnection();
+    }
 
     /**
      * Set if the client has asked for the input method to be shown.
      */
-    boolean mShowRequested;
+    private boolean mShowRequested;
 
     /**
      * Set if we were explicitly told to show the input method.
@@ -577,7 +546,7 @@
     /**
      * Set if we last told the input method to show itself.
      */
-    boolean mInputShown;
+    private boolean mInputShown;
 
     /**
      * {@code true} if the current input method is in fullscreen mode.
@@ -587,17 +556,30 @@
     /**
      * The Intent used to connect to the current input method.
      */
-    Intent mCurIntent;
+    @Nullable
+    private Intent getCurIntent() {
+        return mBindingController.getCurIntent();
+    }
 
     /**
      * The token we have made for the currently active input method, to
      * identify it in the future.
      */
-    IBinder mCurToken;
+    private IBinder getCurToken() {
+        return mBindingController.getCurToken();
+    }
 
     /**
      * The displayId of current active input method.
      */
+    int getCurTokenDisplayId() {
+        return mCurTokenDisplayId;
+    }
+
+    void setCurTokenDisplayId(int curTokenDisplayId) {
+        mCurTokenDisplayId = curTokenDisplayId;
+    }
+
     int mCurTokenDisplayId = INVALID_DISPLAY;
 
     /**
@@ -619,18 +601,25 @@
      * If non-null, this is the input method service we are currently connected
      * to.
      */
-    IInputMethod mCurMethod;
+    @Nullable
+    private IInputMethod getCurMethod() {
+        return mBindingController.getCurMethod();
+    }
 
     /**
-     * If not {@link Process#INVALID_UID}, then the UID of {@link #mCurIntent}.
+     * If not {@link Process#INVALID_UID}, then the UID of {@link #getCurIntent()}.
      */
-    int mCurMethodUid = Process.INVALID_UID;
+    private int getCurMethodUid() {
+        return mBindingController.getCurMethodUid();
+    }
 
     /**
      * Time that we last initiated a bind to the input method, to determine
      * if we should try to disconnect and reconnect to it.
      */
-    long mLastBindTime;
+    private long getLastBindTime() {
+        return mBindingController.getLastBindTime();
+    }
 
     /**
      * Have we called mCurMethod.bindInput()?
@@ -648,7 +637,7 @@
      */
     boolean mIsInteractive = true;
 
-    private IPlatformCompat mPlatformCompat;
+    private final IPlatformCompat mPlatformCompat;
 
     int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
 
@@ -671,7 +660,7 @@
      * </dd>
      * </dl>
      * <em>Do not update this value outside of {@link #setImeWindowStatus(IBinder, int, int)} and
-     * {@link #unbindCurrentMethodLocked()}.</em>
+     * {@link InputMethodBindingController#unbindCurrentMethodLocked()}.</em>
      */
     int mImeWindowVis;
 
@@ -758,7 +747,7 @@
     private final WeakHashMap<IBinder, IBinder> mImeTargetWindowMap = new WeakHashMap<>();
 
     private static final class SoftInputShowHideHistory {
-        private Entry[] mEntries = new Entry[16];
+        private final Entry[] mEntries = new Entry[16];
         private int mNextIndex = 0;
         private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
 
@@ -1512,7 +1501,7 @@
     private UserSwitchHandlerTask mUserSwitchHandlerTask;
 
     public static final class Lifecycle extends SystemService {
-        private InputMethodManagerService mService;
+        private final InputMethodManagerService mService;
 
         public Lifecycle(Context context) {
             super(context);
@@ -1610,13 +1599,9 @@
         mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
-        mImeDisplayValidator = displayId -> mWindowManagerInternal.getDisplayImePolicy(displayId);
-        mCaller = new HandlerCaller(context, thread.getLooper(), new HandlerCaller.Callback() {
-            @Override
-            public void executeMessage(Message msg) {
-                handleMessage(msg);
-            }
-        }, true /*asyncHandler*/);
+        mImeDisplayValidator = mWindowManagerInternal::getDisplayImePolicy;
+        mCaller = new HandlerCaller(context, thread.getLooper(), this::handleMessage,
+                true /*asyncHandler*/);
         mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
         mUserManager = mContext.getSystemService(UserManager.class);
         mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
@@ -1625,7 +1610,6 @@
         mPlatformCompat = IPlatformCompat.Stub.asInterface(
                 ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
         mSlotIme = mContext.getString(com.android.internal.R.string.status_bar_ime);
-        mIsLowRam = ActivityManager.isLowRamDeviceStatic();
 
         Bundle extras = new Bundle();
         extras.putBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, true);
@@ -1666,22 +1650,14 @@
         mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
                 mSettings, context);
         mMenuController = new InputMethodMenuController(this);
-
-        // If configured, use low priority flags to make the IME killable by the lowmemorykiller
-        final boolean lowerIMEPriority = mRes.getBoolean(
-                com.android.internal.R.bool.config_killableInputMethods);
-
-        if (lowerIMEPriority) {
-            mImeConnectionBindFlags = IME_CONNECTION_LOW_PRIORITY_BIND_FLAGS;
-        } else {
-            mImeConnectionBindFlags = IME_CONNECTION_BIND_FLAGS;
-        }
+        mBindingController = new InputMethodBindingController(this);
     }
 
     @GuardedBy("mMethodMap")
     private void resetDefaultImeLocked(Context context) {
         // Do not reset the default (current) IME when it is a 3rd-party IME
-        if (mCurMethodId != null && !mMethodMap.get(mCurMethodId).isSystem()) {
+        String selectedMethodId = getSelectedMethodId();
+        if (selectedMethodId != null && !mMethodMap.get(selectedMethodId).isSystem()) {
             return;
         }
         final List<InputMethodInfo> suitableImes = InputMethodUtils.getDefaultEnabledImes(
@@ -1796,9 +1772,7 @@
                 mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
                 mNotificationManager = mContext.getSystemService(NotificationManager.class);
                 mStatusBar = statusBar;
-                if (mStatusBar != null) {
-                    mStatusBar.setIconVisibility(mSlotIme, false);
-                }
+                hideStatusBarIconLocked();
                 updateSystemUiLocked(mImeWindowVis, mBackDisposition);
                 mShowOngoingImeSwitcherForPhones = mRes.getBoolean(
                         com.android.internal.R.bool.show_ongoing_ime_switcher);
@@ -1890,7 +1864,7 @@
         if (token == null) {
             throw new InvalidParameterException("token must not be null.");
         }
-        if (token != mCurToken) {
+        if (token != getCurToken()) {
             Slog.e(TAG, "Ignoring " + Debug.getCaller() + " due to an invalid token."
                     + " uid:" + Binder.getCallingUid() + " token:" + token);
             return false;
@@ -1898,17 +1872,6 @@
         return true;
     }
 
-    @GuardedBy("mMethodMap")
-    private boolean bindCurrentInputMethodServiceLocked(
-            Intent service, ServiceConnection conn, int flags) {
-        if (service == null || conn == null) {
-            Slog.e(TAG, "--- bind failed: service = " + service + ", conn = " + conn);
-            return false;
-        }
-        return mContext.bindServiceAsUser(service, conn, flags,
-                new UserHandle(mSettings.getCurrentUserId()));
-    }
-
     @Override
     public List<InputMethodInfo> getInputMethodList(@UserIdInt int userId) {
         if (UserHandle.getCallingUserId() != userId) {
@@ -1989,14 +1952,15 @@
     @GuardedBy("mMethodMap")
     private void onCreateInlineSuggestionsRequestLocked(@UserIdInt int userId,
             InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback callback) {
-        final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
+        final InputMethodInfo imi = mMethodMap.get(getSelectedMethodId());
         try {
+            IInputMethod curMethod = getCurMethod();
             if (userId == mSettings.getCurrentUserId() && imi != null
-                    && imi.isInlineSuggestionsEnabled() && mCurMethod != null) {
-                executeOrSendMessage(mCurMethod,
-                        mCaller.obtainMessageOOO(MSG_INLINE_SUGGESTIONS_REQUEST, mCurMethod,
+                    && imi.isInlineSuggestionsEnabled() && curMethod != null) {
+                executeOrSendMessage(curMethod,
+                        mCaller.obtainMessageOOO(MSG_INLINE_SUGGESTIONS_REQUEST, curMethod,
                                 requestInfo, new InlineSuggestionsRequestCallbackDecorator(callback,
-                                        imi.getPackageName(), mCurTokenDisplayId, mCurToken,
+                                        imi.getPackageName(), mCurTokenDisplayId, getCurToken(),
                                         this)));
             } else {
                 callback.onInlineSuggestionsUnsupported();
@@ -2132,8 +2096,9 @@
             boolean allowsImplicitlySelectedSubtypes, @UserIdInt int userId) {
         if (userId == mSettings.getCurrentUserId()) {
             final InputMethodInfo imi;
-            if (imiId == null && mCurMethodId != null) {
-                imi = mMethodMap.get(mCurMethodId);
+            String selectedMethodId = getSelectedMethodId();
+            if (imiId == null && selectedMethodId != null) {
+                imi = mMethodMap.get(selectedMethodId);
             } else {
                 imi = mMethodMap.get(imiId);
             }
@@ -2230,9 +2195,10 @@
                             mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_REMOVE_CLIENT);
                     if (mBoundToMethod) {
                         mBoundToMethod = false;
-                        if (mCurMethod != null) {
-                            executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
-                                    MSG_UNBIND_INPUT, mCurMethod));
+                        IInputMethod curMethod = getCurMethod();
+                        if (curMethod != null) {
+                            executeOrSendMessage(curMethod, mCaller.obtainMessageO(
+                                    MSG_UNBIND_INPUT, curMethod));
                         }
                     }
                     mCurClient = null;
@@ -2260,16 +2226,17 @@
                     + mCurClient.client.asBinder());
             if (mBoundToMethod) {
                 mBoundToMethod = false;
-                if (mCurMethod != null) {
-                    executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
-                            MSG_UNBIND_INPUT, mCurMethod));
+                IInputMethod curMethod = getCurMethod();
+                if (curMethod != null) {
+                    executeOrSendMessage(curMethod, mCaller.obtainMessageO(
+                            MSG_UNBIND_INPUT, curMethod));
                 }
             }
 
             scheduleSetActiveToClient(mCurClient, false /* active */, false /* fullscreen */,
                     false /* reportToImeController */);
             executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIIO(
-                    MSG_UNBIND_CLIENT, mCurSeq, unbindClientReason, mCurClient.client));
+                    MSG_UNBIND_CLIENT, getSequenceNumber(), unbindClientReason, mCurClient.client));
             mCurClient.sessionRequested = false;
             mCurClient = null;
 
@@ -2278,6 +2245,12 @@
     }
 
     @GuardedBy("mMethodMap")
+    void clearInputShowRequestLocked() {
+        mShowRequested = mInputShown;
+        mInputShown = false;
+    }
+
+    @GuardedBy("mMethodMap")
     private int getImeShowFlagsLocked() {
         int flags = 0;
         if (mShowForced) {
@@ -2304,16 +2277,18 @@
     @NonNull
     InputBindResult attachNewInputLocked(@StartInputReason int startInputReason, boolean initial) {
         if (!mBoundToMethod) {
-            executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
-                    MSG_BIND_INPUT, mCurMethod, mCurClient.binding));
+            IInputMethod curMethod = getCurMethod();
+            executeOrSendMessage(curMethod, mCaller.obtainMessageOO(
+                    MSG_BIND_INPUT, curMethod, mCurClient.binding));
             mBoundToMethod = true;
         }
 
         final Binder startInputToken = new Binder();
-        final StartInputInfo info = new StartInputInfo(mSettings.getCurrentUserId(), mCurToken,
-                mCurTokenDisplayId, mCurId, startInputReason, !initial,
+        final StartInputInfo info = new StartInputInfo(mSettings.getCurrentUserId(), getCurToken(),
+                mCurTokenDisplayId, getCurId(), startInputReason, !initial,
                 UserHandle.getUserId(mCurClient.uid), mCurClient.selfReportedDisplayId,
-                mCurFocusedWindow, mCurAttribute, mCurFocusedWindowSoftInputMode, mCurSeq);
+                mCurFocusedWindow, mCurAttribute, mCurFocusedWindowSoftInputMode,
+                getSequenceNumber());
         mImeTargetWindowMap.put(startInputToken, mCurFocusedWindow);
         mStartInputHistory.addEntry(info);
 
@@ -2324,7 +2299,8 @@
         // INTERACT_ACROSS_USERS(_FULL) permissions, which is actually almost always the case.
         if (mSettings.getCurrentUserId() == UserHandle.getUserId(mCurClient.uid)) {
             mPackageManagerInternal.grantImplicitAccess(mSettings.getCurrentUserId(),
-                    null /* intent */, UserHandle.getAppId(mCurMethodUid), mCurClient.uid, true);
+                    null /* intent */, UserHandle.getAppId(getCurMethodUid()), mCurClient.uid,
+                    true /* direct */);
         }
 
         final SessionState session = mCurClient.curSession;
@@ -2336,12 +2312,14 @@
             showCurrentInputLocked(mCurFocusedWindow, getAppShowFlagsLocked(), null,
                     SoftInputShowHideReason.ATTACH_NEW_INPUT);
         }
-        final InputMethodInfo curInputMethodInfo = mMethodMap.get(mCurId);
+
+        String curId = getCurId();
+        final InputMethodInfo curInputMethodInfo = mMethodMap.get(curId);
         final boolean suppressesSpellChecker =
                 curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker();
         return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
                 session.session, (session.channel != null ? session.channel.dup() : null),
-                mCurId, mCurSeq, suppressesSpellChecker);
+                curId, getSequenceNumber(), suppressesSpellChecker);
     }
 
     @GuardedBy("mMethodMap")
@@ -2350,7 +2328,8 @@
             @NonNull EditorInfo attribute, @StartInputFlags int startInputFlags,
             @StartInputReason int startInputReason) {
         // If no method is currently selected, do nothing.
-        if (mCurMethodId == null) {
+        String selectedMethodId = getSelectedMethodId();
+        if (selectedMethodId == null) {
             return InputBindResult.NO_IME;
         }
 
@@ -2359,7 +2338,7 @@
             // party code.
             return new InputBindResult(
                     InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY,
-                    null, null, mCurMethodId, mCurSeq, false);
+                    null, null, selectedMethodId, getSequenceNumber(), false);
         }
 
         if (!InputMethodUtils.checkIfPackageBelongsToUid(mAppOpsManager, cs.uid,
@@ -2387,19 +2366,11 @@
         mImeHiddenByDisplayPolicy = false;
 
         if (mCurClient != cs) {
-            // If the client is changing, we need to switch over to the new
-            // one.
-            unbindCurrentClientLocked(UnbindReason.SWITCH_CLIENT);
-            // If the screen is on, inform the new client it is active
-            if (mIsInteractive) {
-                scheduleSetActiveToClient(cs, true /* active */, false /* fullscreen */,
-                        false /* reportToImeController */);
-            }
+            prepareClientSwitchLocked(cs);
         }
 
         // Bump up the sequence for this client and attach it.
-        mCurSeq++;
-        if (mCurSeq <= 0) mCurSeq = 1;
+        advanceSequenceNumber();
         mCurClient = cs;
         mCurInputContext = inputContext;
         mCurAttribute = attribute;
@@ -2407,24 +2378,57 @@
         // Check if the input method is changing.
         // We expect the caller has already verified that the client is allowed to access this
         // display ID.
-        if (mCurId != null && mCurId.equals(mCurMethodId)
-                && displayIdToShowIme == mCurTokenDisplayId) {
+        if (isSelectedMethodBound(displayIdToShowIme)) {
             if (cs.curSession != null) {
                 // Fast case: if we are already connected to the input method,
                 // then just return it.
                 return attachNewInputLocked(startInputReason,
                         (startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0);
             }
-            if (mHaveConnection) {
-                if (mCurMethod != null) {
-                    // Return to client, and we will get back with it when
-                    // we have had a session made for it.
-                    requestClientSessionLocked(cs);
-                    return new InputBindResult(
-                            InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION,
-                            null, null, mCurId, mCurSeq, false);
-                } else if (SystemClock.uptimeMillis()
-                        < (mLastBindTime+TIME_TO_RECONNECT)) {
+
+            InputBindResult bindResult = tryReuseConnectionLocked(cs);
+            if (bindResult != null) {
+                return bindResult;
+            }
+        }
+
+        mBindingController.unbindCurrentMethodLocked();
+
+        return mBindingController.bindCurrentMethodLocked(displayIdToShowIme);
+    }
+
+    private boolean isSelectedMethodBound(int displayIdToShowIme) {
+        String curId = getCurId();
+        return curId != null && curId.equals(getSelectedMethodId())
+                && displayIdToShowIme == mCurTokenDisplayId;
+    }
+
+    @GuardedBy("mMethodMap")
+    private void prepareClientSwitchLocked(ClientState cs) {
+        // If the client is changing, we need to switch over to the new
+        // one.
+        unbindCurrentClientLocked(UnbindReason.SWITCH_CLIENT);
+        // If the screen is on, inform the new client it is active
+        if (mIsInteractive) {
+            scheduleSetActiveToClient(cs, true /* active */, false /* fullscreen */,
+                    false /* reportToImeController */);
+        }
+    }
+
+    @GuardedBy("mMethodMap")
+    @Nullable
+    private InputBindResult tryReuseConnectionLocked(@NonNull ClientState cs) {
+        if (hasConnection()) {
+            if (getCurMethod() != null) {
+                // Return to client, and we will get back with it when
+                // we have had a session made for it.
+                requestClientSessionLocked(cs);
+                return new InputBindResult(
+                        InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION,
+                        null, null, getCurId(), getSequenceNumber(), false);
+            } else {
+                long bindingDuration = SystemClock.uptimeMillis() - getLastBindTime();
+                if (bindingDuration < TIME_TO_RECONNECT) {
                     // In this case we have connected to the service, but
                     // don't yet have its interface.  If it hasn't been too
                     // long since we did the connection, we'll return to
@@ -2434,51 +2438,14 @@
                     // to see if we can get back in touch with the service.
                     return new InputBindResult(
                             InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
-                            null, null, mCurId, mCurSeq, false);
+                            null, null, getCurId(), getSequenceNumber(), false);
                 } else {
                     EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,
-                            mCurMethodId, SystemClock.uptimeMillis()-mLastBindTime, 0);
+                            getSelectedMethodId(), bindingDuration, 0);
                 }
             }
         }
-
-        InputMethodInfo info = mMethodMap.get(mCurMethodId);
-        if (info == null) {
-            throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
-        }
-
-        unbindCurrentMethodLocked();
-
-        mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE);
-        mCurIntent.setComponent(info.getComponent());
-        mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
-                com.android.internal.R.string.input_method_binding_label);
-        mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
-                mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS),
-                PendingIntent.FLAG_IMMUTABLE));
-
-        if (bindCurrentInputMethodServiceLocked(mCurIntent, this, mImeConnectionBindFlags)) {
-            mLastBindTime = SystemClock.uptimeMillis();
-            mHaveConnection = true;
-            mCurId = info.getId();
-            mCurToken = new Binder();
-            mCurTokenDisplayId = displayIdToShowIme;
-            try {
-                if (DEBUG) {
-                    Slog.v(TAG, "Adding window token: " + mCurToken + " for display: "
-                            + mCurTokenDisplayId);
-                }
-                mIWindowManager.addWindowToken(mCurToken, LayoutParams.TYPE_INPUT_METHOD,
-                        mCurTokenDisplayId, null /* options */);
-            } catch (RemoteException e) {
-            }
-            return new InputBindResult(
-                    InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
-                    null, null, mCurId, mCurSeq, false);
-        }
-        mCurIntent = null;
-        Slog.w(TAG, "Failure connecting to input method service: " + mCurIntent);
-        return InputBindResult.IME_NOT_CONNECTED;
+        return null;
     }
 
     @FunctionalInterface
@@ -2514,46 +2481,11 @@
     }
 
     @AnyThread
-    private void scheduleNotifyImeUidToAudioService(int uid) {
+    void scheduleNotifyImeUidToAudioService(int uid) {
         mCaller.removeMessages(MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE);
         mCaller.obtainMessageI(MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE, uid).sendToTarget();
     }
 
-    @Override
-    public void onServiceConnected(ComponentName name, IBinder service) {
-        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.onServiceConnected");
-        synchronized (mMethodMap) {
-            if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
-                mCurMethod = IInputMethod.Stub.asInterface(service);
-                final String curMethodPackage = mCurIntent.getComponent().getPackageName();
-                final int curMethodUid = mPackageManagerInternal.getPackageUid(
-                        curMethodPackage, 0 /* flags */, mSettings.getCurrentUserId());
-                if (curMethodUid < 0) {
-                    Slog.e(TAG, "Failed to get UID for package=" + curMethodPackage);
-                    mCurMethodUid = Process.INVALID_UID;
-                } else {
-                    mCurMethodUid = curMethodUid;
-                }
-                if (mCurToken == null) {
-                    Slog.w(TAG, "Service connected without a token!");
-                    unbindCurrentMethodLocked();
-                    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-                    return;
-                }
-                if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
-                // Dispatch display id for InputMethodService to update context display.
-                executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(MSG_INITIALIZE_IME,
-                        mMethodMap.get(mCurMethodId).getConfigChanges(), mCurMethod, mCurToken));
-                scheduleNotifyImeUidToAudioService(mCurMethodUid);
-                if (mCurClient != null) {
-                    clearClientSessionLocked(mCurClient);
-                    requestClientSessionLocked(mCurClient);
-                }
-            }
-        }
-        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-    }
-
     void onSessionCreated(IInputMethod method, IInputMethodSession session,
             InputChannel channel) {
         synchronized (mMethodMap) {
@@ -2562,8 +2494,9 @@
                 channel.dispose();
                 return;
             }
-            if (mCurMethod != null && method != null
-                    && mCurMethod.asBinder() == method.asBinder()) {
+            IInputMethod curMethod = getCurMethod();
+            if (curMethod != null && method != null
+                    && curMethod.asBinder() == method.asBinder()) {
                 if (mCurClient != null) {
                     clearClientSessionLocked(mCurClient);
                     mCurClient.curSession = new SessionState(mCurClient,
@@ -2584,53 +2517,40 @@
     }
 
     @GuardedBy("mMethodMap")
-    void unbindCurrentMethodLocked() {
-        if (mVisibleBound) {
-            mContext.unbindService(mVisibleConnection);
-            mVisibleBound = false;
-        }
-
-        if (mHaveConnection) {
-            mContext.unbindService(this);
-            mHaveConnection = false;
-        }
-
-        if (mCurToken != null) {
-            if (DEBUG) {
-                Slog.v(TAG, "Removing window token: " + mCurToken + " for display: "
-                        + mCurTokenDisplayId);
-            }
-            mWindowManagerInternal.removeWindowToken(mCurToken, false /* removeWindows */,
-                    false /* animateExit */, mCurTokenDisplayId);
-            // Set IME window status as invisible when unbind current method.
-            mImeWindowVis = 0;
-            mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
-            updateSystemUiLocked(mImeWindowVis, mBackDisposition);
-            mCurToken = null;
-            mCurTokenDisplayId = INVALID_DISPLAY;
-            mCurHostInputToken = null;
-        }
-
-        mCurId = null;
-        clearCurMethodLocked();
+    void resetSystemUiLocked() {
+        // Set IME window status as invisible when unbinding current method.
+        mImeWindowVis = 0;
+        mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
+        updateSystemUiLocked(mImeWindowVis, mBackDisposition);
+        mCurTokenDisplayId = INVALID_DISPLAY;
+        mCurHostInputToken = null;
     }
 
     @GuardedBy("mMethodMap")
     void resetCurrentMethodAndClientLocked(@UnbindReason int unbindClientReason) {
-        mCurMethodId = null;
-        unbindCurrentMethodLocked();
+        setSelectedMethodId(null);
+        mBindingController.unbindCurrentMethodLocked();
         unbindCurrentClientLocked(unbindClientReason);
     }
 
     @GuardedBy("mMethodMap")
+    void reRequestCurrentClientSessionLocked() {
+        if (mCurClient != null) {
+            clearClientSessionLocked(mCurClient);
+            requestClientSessionLocked(mCurClient);
+        }
+    }
+
+    @GuardedBy("mMethodMap")
     void requestClientSessionLocked(ClientState cs) {
         if (!cs.sessionRequested) {
             if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs);
             InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString());
             cs.sessionRequested = true;
-            executeOrSendMessage(mCurMethod, mCaller.obtainMessageOOO(
-                    MSG_CREATE_SESSION, mCurMethod, channels[1],
-                    new MethodCallback(this, mCurMethod, channels[0])));
+            IInputMethod curMethod = getCurMethod();
+            executeOrSendMessage(curMethod, mCaller.obtainMessageOOO(
+                    MSG_CREATE_SESSION, curMethod, channels[1],
+                    new MethodCallback(this, curMethod, channels[0])));
         }
     }
 
@@ -2661,8 +2581,8 @@
     }
 
     @GuardedBy("mMethodMap")
-    void clearCurMethodLocked() {
-        if (mCurMethod != null) {
+    void clearClientSessionsLocked() {
+        if (getCurMethod() != null) {
             final int numClients = mClients.size();
             for (int i = 0; i < numClients; ++i) {
                 clearClientSessionLocked(mClients.valueAt(i));
@@ -2670,41 +2590,13 @@
 
             finishSessionLocked(mEnabledSession);
             mEnabledSession = null;
-            mCurMethod = null;
-            mCurMethodUid = Process.INVALID_UID;
-            scheduleNotifyImeUidToAudioService(mCurMethodUid);
+            mBindingController.clearCurMethodLocked();
+            scheduleNotifyImeUidToAudioService(Process.INVALID_UID);
         }
-        if (mStatusBar != null) {
-            mStatusBar.setIconVisibility(mSlotIme, false);
-        }
+        hideStatusBarIconLocked();
         mInFullscreenMode = false;
     }
 
-    @Override
-    public void onServiceDisconnected(ComponentName name) {
-        // Note that mContext.unbindService(this) does not trigger this.  Hence if we are here the
-        // disconnection is not intended by IMMS (e.g. triggered because the current IMS crashed),
-        // which is irregular but can eventually happen for everyone just by continuing using the
-        // device.  Thus it is important to make sure that all the internal states are properly
-        // refreshed when this method is called back.  Running
-        //    adb install -r <APK that implements the current IME>
-        // would be a good way to trigger such a situation.
-        synchronized (mMethodMap) {
-            if (DEBUG) Slog.v(TAG, "Service disconnected: " + name
-                    + " mCurIntent=" + mCurIntent);
-            if (mCurMethod != null && mCurIntent != null
-                    && name.equals(mCurIntent.getComponent())) {
-                clearCurMethodLocked();
-                // We consider this to be a new bind attempt, since the system
-                // should now try to restart the service for us.
-                mLastBindTime = SystemClock.uptimeMillis();
-                mShowRequested = mInputShown;
-                mInputShown = false;
-                unbindCurrentClientLocked(UnbindReason.DISCONNECT_IME);
-            }
-        }
-    }
-
     @BinderThread
     private void updateStatusIcon(@NonNull IBinder token, String packageName,
             @DrawableRes int iconId) {
@@ -2716,9 +2608,7 @@
             try {
                 if (iconId == 0) {
                     if (DEBUG) Slog.d(TAG, "hide the small icon for the input method");
-                    if (mStatusBar != null) {
-                        mStatusBar.setIconVisibility(mSlotIme, false);
-                    }
+                    hideStatusBarIconLocked();
                 } else if (packageName != null) {
                     if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
                     CharSequence contentDescription = null;
@@ -2745,6 +2635,13 @@
     }
 
     @GuardedBy("mMethodMap")
+    private void hideStatusBarIconLocked() {
+        if (mStatusBar != null) {
+            mStatusBar.setIconVisibility(mSlotIme, false);
+        }
+    }
+
+    @GuardedBy("mMethodMap")
     private boolean shouldShowImeSwitcherLocked(int visibility) {
         if (!mShowOngoingImeSwitcherForPhones) return false;
         if (mMenuController.getSwitchingDialogLocked() != null) return false;
@@ -2808,11 +2705,6 @@
         return false;
     }
 
-    @GuardedBy("mMethodMap")
-    private boolean isKeyguardLocked() {
-        return mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
-    }
-
     @BinderThread
     @SuppressWarnings("deprecation")
     private void setImeWindowStatus(@NonNull IBinder token, int vis, int backDisposition) {
@@ -2883,7 +2775,7 @@
     // Caution! This method is called in this class. Handle multi-user carefully
     @GuardedBy("mMethodMap")
     private void updateSystemUiLocked(int vis, int backDisposition) {
-        if (mCurToken == null) {
+        if (getCurToken() == null) {
             return;
         }
         if (DEBUG) {
@@ -2903,10 +2795,10 @@
             // mImeWindowVis should be updated before calling shouldShowImeSwitcherLocked().
             final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis);
             if (mStatusBar != null) {
-                mStatusBar.setImeWindowStatus(mCurTokenDisplayId, mCurToken, vis, backDisposition,
-                        needsToShowImeSwitcher);
+                mStatusBar.setImeWindowStatus(mCurTokenDisplayId, getCurToken(), vis,
+                        backDisposition, needsToShowImeSwitcher);
             }
-            final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
+            final InputMethodInfo imi = mMethodMap.get(getSelectedMethodId());
             if (imi != null && needsToShowImeSwitcher) {
                 // Used to load label
                 final CharSequence title = mRes.getText(
@@ -3014,7 +2906,7 @@
         }
 
         // See if we need to notify a subtype change within the same IME.
-        if (id.equals(mCurMethodId)) {
+        if (id.equals(getSelectedMethodId())) {
             final int subtypeCount = info.getSubtypeCount();
             if (subtypeCount <= 0) {
                 return;
@@ -3035,10 +2927,11 @@
             }
             if (newSubtype != oldSubtype) {
                 setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true);
-                if (mCurMethod != null) {
+                IInputMethod curMethod = getCurMethod();
+                if (curMethod != null) {
                     try {
                         updateSystemUiLocked(mImeWindowVis, mBackDisposition);
-                        mCurMethod.changeInputMethodSubtype(newSubtype);
+                        curMethod.changeInputMethodSubtype(newSubtype);
                     } catch (RemoteException e) {
                         Slog.w(TAG, "Failed to call changeInputMethodSubtype");
                     }
@@ -3056,7 +2949,7 @@
             // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
             // because mCurMethodId is stored as a history in
             // setSelectedInputMethodAndSubtypeLocked().
-            mCurMethodId = id;
+            setSelectedMethodId(id);
 
             if (LocalServices.getService(ActivityManagerInternal.class).isSystemReady()) {
                 Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
@@ -3150,35 +3043,37 @@
         }
 
         boolean res = false;
-        if (mCurMethod != null) {
-            if (DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + mCurToken);
+        IInputMethod curMethod = getCurMethod();
+        if (curMethod != null) {
+            if (DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + getCurToken());
             // create a placeholder token for IMS so that IMS cannot inject windows into client app.
             Binder showInputToken = new Binder();
             mShowRequestWindowMap.put(showInputToken, windowToken);
-            executeOrSendMessage(mCurMethod, mCaller.obtainMessageIIOOO(MSG_SHOW_SOFT_INPUT,
-                    getImeShowFlagsLocked(), reason, mCurMethod, resultReceiver, showInputToken));
+            executeOrSendMessage(curMethod, mCaller.obtainMessageIIOOO(MSG_SHOW_SOFT_INPUT,
+                    getImeShowFlagsLocked(), reason, curMethod, resultReceiver,
+                    showInputToken));
             mInputShown = true;
-            if (mHaveConnection && !mVisibleBound) {
-                bindCurrentInputMethodServiceLocked(
-                        mCurIntent, mVisibleConnection, IME_VISIBLE_BIND_FLAGS);
-                mVisibleBound = true;
+            if (hasConnection() && !isVisibleBound()) {
+                mBindingController.bindCurrentInputMethodServiceVisibleConnectionLocked();
             }
             res = true;
-        } else if (mHaveConnection && SystemClock.uptimeMillis()
-                >= (mLastBindTime+TIME_TO_RECONNECT)) {
-            // The client has asked to have the input method shown, but
-            // we have been sitting here too long with a connection to the
-            // service and no interface received, so let's disconnect/connect
-            // to try to prod things along.
-            EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, mCurMethodId,
-                    SystemClock.uptimeMillis()-mLastBindTime,1);
-            Slog.w(TAG, "Force disconnect/connect to the IME in showCurrentInputLocked()");
-            mContext.unbindService(this);
-            bindCurrentInputMethodServiceLocked(mCurIntent, this, mImeConnectionBindFlags);
         } else {
-            if (DEBUG) {
-                Slog.d(TAG, "Can't show input: connection = " + mHaveConnection + ", time = "
-                        + ((mLastBindTime+TIME_TO_RECONNECT) - SystemClock.uptimeMillis()));
+            long bindingDuration = SystemClock.uptimeMillis() - getLastBindTime();
+            if (hasConnection() && bindingDuration >= TIME_TO_RECONNECT) {
+                // The client has asked to have the input method shown, but
+                // we have been sitting here too long with a connection to the
+                // service and no interface received, so let's disconnect/connect
+                // to try to prod things along.
+                EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, getSelectedMethodId(),
+                        bindingDuration, 1);
+                Slog.w(TAG, "Force disconnect/connect to the IME in showCurrentInputLocked()");
+                mBindingController.unbindMainConnectionLocked();
+                mBindingController.bindCurrentInputMethodServiceMainConnectionLocked();
+            } else {
+                if (DEBUG) {
+                    Slog.d(TAG, "Can't show input: connection = " + hasConnection() + ", time = "
+                            + (TIME_TO_RECONNECT - bindingDuration));
+                }
             }
         }
 
@@ -3249,7 +3144,8 @@
         // since Android Eclair.  That's why we need to accept IMM#hideSoftInput() even when only
         // IMMS#InputShown indicates that the software keyboard is shown.
         // TODO: Clean up, IMMS#mInputShown, IMMS#mImeWindowVis and mShowRequested.
-        final boolean shouldHideSoftInput = (mCurMethod != null) && (mInputShown
+        IInputMethod curMethod = getCurMethod();
+        final boolean shouldHideSoftInput = (curMethod != null) && (mInputShown
                 || (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
         boolean res;
         if (shouldHideSoftInput) {
@@ -3259,15 +3155,14 @@
             // 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.
-            executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOOO(MSG_HIDE_SOFT_INPUT,
-                    reason, mCurMethod, resultReceiver, hideInputToken));
+            executeOrSendMessage(curMethod, mCaller.obtainMessageIOOO(MSG_HIDE_SOFT_INPUT,
+                    reason, curMethod, resultReceiver, hideInputToken));
             res = true;
         } else {
             res = false;
         }
-        if (mHaveConnection && mVisibleBound) {
-            mContext.unbindService(mVisibleConnection);
-            mVisibleBound = false;
+        if (hasConnection() && isVisibleBound()) {
+            mBindingController.unbindVisibleConnectionLocked();
         }
         mInputShown = false;
         mShowRequested = false;
@@ -3507,7 +3402,7 @@
                         // Note that we can trust client's display ID as long as it matches
                         // to the display ID obtained from the window.
                         if (cs.selfReportedDisplayId != mCurTokenDisplayId) {
-                            unbindCurrentMethodLocked();
+                            mBindingController.unbindCurrentMethodLocked();
                         }
                     }
                 } else if (isTextEditor && doAutoShow
@@ -3633,10 +3528,10 @@
         if (mCurFocusedWindowClient != null && client != null
                 && mCurFocusedWindowClient.client.asBinder() == client.asBinder()) {
             return true;
-        } else if (mCurIntent != null && InputMethodUtils.checkIfPackageBelongsToUid(
+        } else if (getCurIntent() != null && InputMethodUtils.checkIfPackageBelongsToUid(
                 mAppOpsManager,
                 uid,
-                mCurIntent.getComponent().getPackageName())) {
+                getCurIntent().getComponent().getPackageName())) {
             return true;
         }
         return false;
@@ -3722,7 +3617,7 @@
             if (!calledFromValidUserLocked()) {
                 return;
             }
-            executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
+            executeOrSendMessage(getCurMethod(), mCaller.obtainMessageO(
                     MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId));
         }
     }
@@ -3743,7 +3638,7 @@
             String targetLastImiId = null;
             int subtypeId = NOT_A_SUBTYPE_ID;
             if (lastIme != null && lastImi != null) {
-                final boolean imiIdIsSame = lastImi.getId().equals(mCurMethodId);
+                final boolean imiIdIsSame = lastImi.getId().equals(getSelectedMethodId());
                 final int lastSubtypeHash = Integer.parseInt(lastIme.second);
                 final int currentSubtypeHash = mCurrentSubtype == null ? NOT_A_SUBTYPE_ID
                         : mCurrentSubtype.hashCode();
@@ -3789,7 +3684,7 @@
             if (!TextUtils.isEmpty(targetLastImiId)) {
                 if (DEBUG) {
                     Slog.d(TAG, "Switch to: " + lastImi.getId() + ", " + lastIme.second
-                            + ", from: " + mCurMethodId + ", " + subtypeId);
+                            + ", from: " + getSelectedMethodId() + ", " + subtypeId);
                 }
                 setInputMethodWithSubtypeIdLocked(token, targetLastImiId, subtypeId);
                 return true;
@@ -3806,7 +3701,7 @@
                 return false;
             }
             final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
-                    onlyCurrentIme, mMethodMap.get(mCurMethodId), mCurrentSubtype);
+                    onlyCurrentIme, mMethodMap.get(getSelectedMethodId()), mCurrentSubtype);
             if (nextSubtype == null) {
                 return false;
             }
@@ -3823,7 +3718,8 @@
                 return false;
             }
             final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
-                    false /* onlyCurrentIme */, mMethodMap.get(mCurMethodId), mCurrentSubtype);
+                    false /* onlyCurrentIme */, mMethodMap.get(getSelectedMethodId()),
+                    mCurrentSubtype);
             if (nextSubtype == null) {
                 return false;
             }
@@ -4037,8 +3933,8 @@
     private void dumpDebug(ProtoOutputStream proto, long fieldId) {
         synchronized (mMethodMap) {
             final long token = proto.start(fieldId);
-            proto.write(CUR_METHOD_ID, mCurMethodId);
-            proto.write(CUR_SEQ, mCurSeq);
+            proto.write(CUR_METHOD_ID, getSelectedMethodId());
+            proto.write(CUR_SEQ, getSequenceNumber());
             proto.write(CUR_CLIENT, Objects.toString(mCurClient));
             proto.write(CUR_FOCUSED_WINDOW_NAME,
                     mWindowManagerInternal.getWindowName(mCurFocusedWindow));
@@ -4049,17 +3945,17 @@
             if (mCurAttribute != null) {
                 mCurAttribute.dumpDebug(proto, CUR_ATTRIBUTE);
             }
-            proto.write(CUR_ID, mCurId);
+            proto.write(CUR_ID, getCurId());
             proto.write(SHOW_REQUESTED, mShowRequested);
             proto.write(SHOW_EXPLICITLY_REQUESTED, mShowExplicitlyRequested);
             proto.write(SHOW_FORCED, mShowForced);
             proto.write(INPUT_SHOWN, mInputShown);
             proto.write(IN_FULLSCREEN_MODE, mInFullscreenMode);
-            proto.write(CUR_TOKEN, Objects.toString(mCurToken));
+            proto.write(CUR_TOKEN, Objects.toString(getCurToken()));
             proto.write(CUR_TOKEN_DISPLAY_ID, mCurTokenDisplayId);
             proto.write(SYSTEM_READY, mSystemReady);
             proto.write(LAST_SWITCH_USER_ID, mLastSwitchUserId);
-            proto.write(HAVE_CONNECTION, mHaveConnection);
+            proto.write(HAVE_CONNECTION, hasConnection());
             proto.write(BOUND_TO_METHOD, mBoundToMethod);
             proto.write(IS_INTERACTIVE, mIsInteractive);
             proto.write(BACK_DISPOSITION, mBackDisposition);
@@ -4077,14 +3973,14 @@
             Slog.d(TAG, "Got the notification of a user action.");
         }
         synchronized (mMethodMap) {
-            if (mCurToken != token) {
+            if (getCurToken() != token) {
                 if (DEBUG) {
                     Slog.d(TAG, "Ignoring the user action notification from IMEs that are no longer"
                             + " active.");
                 }
                 return;
             }
-            final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
+            final InputMethodInfo imi = mMethodMap.get(getSelectedMethodId());
             if (imi != null) {
                 mSwitchingController.onUserActionLocked(imi, mCurrentSubtype);
             }
@@ -4128,7 +4024,7 @@
                         "Using null token requires permission "
                         + android.Manifest.permission.WRITE_SECURE_SETTINGS);
             }
-        } else if (mCurToken != token) {
+        } else if (getCurToken() != token) {
             Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid()
                     + " token: " + token);
             return;
@@ -4500,7 +4396,7 @@
                 boolean reportToImeController = false;
                 try {
                     reportToImeController = mPlatformCompat.isChangeEnabledByUid(
-                            FINISH_INPUT_NO_FALLBACK_CONNECTION, mCurMethodUid);
+                            FINISH_INPUT_NO_FALLBACK_CONNECTION, getCurMethodUid());
                 } catch (RemoteException e) {
                 }
                 scheduleSetActiveToClient(mCurClient, mIsInteractive, mInFullscreenMode,
@@ -4790,7 +4686,7 @@
     @GuardedBy("mMethodMap")
     private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
             boolean setSubtypeOnly) {
-        mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype);
+        mSettings.saveCurrentInputMethodAndSubtypeToHistory(getSelectedMethodId(), mCurrentSubtype);
 
         // Set Subtype here
         if (imi == null || subtypeId < 0) {
@@ -4849,17 +4745,18 @@
 
     @GuardedBy("mMethodMap")
     InputMethodSubtype getCurrentInputMethodSubtypeLocked() {
-        if (mCurMethodId == null) {
+        String selectedMethodId = getSelectedMethodId();
+        if (selectedMethodId == null) {
             return null;
         }
         final boolean subtypeIsSelected = mSettings.isSubtypeSelected();
-        final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
+        final InputMethodInfo imi = mMethodMap.get(selectedMethodId);
         if (imi == null || imi.getSubtypeCount() == 0) {
             return null;
         }
         if (!subtypeIsSelected || mCurrentSubtype == null
                 || !InputMethodUtils.isValidSubtypeId(imi, mCurrentSubtype.hashCode())) {
-            int subtypeId = mSettings.getSelectedInputMethodSubtypeId(mCurMethodId);
+            int subtypeId = mSettings.getSelectedInputMethodSubtypeId(selectedMethodId);
             if (subtypeId == NOT_A_SUBTYPE_ID) {
                 // If there are no selected subtypes, the framework will try to find
                 // the most applicable subtype from explicitly or implicitly enabled
@@ -4889,7 +4786,7 @@
 
     @Nullable
     String getCurrentMethodId() {
-        return mCurMethodId;
+        return getSelectedMethodId();
     }
 
     private List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
@@ -5063,11 +4960,11 @@
 
         synchronized (mMethodMap) {
             final int uid = Binder.getCallingUid();
-            if (mCurMethodId == null) {
+            if (getSelectedMethodId() == null) {
                 return null;
             }
-            if (mCurToken != token) {
-                Slog.e(TAG, "Ignoring createInputContentUriToken mCurToken=" + mCurToken
+            if (getCurToken() != token) {
+                Slog.e(TAG, "Ignoring createInputContentUriToken mCurToken=" + getCurToken()
                         + " token=" + token);
                 return null;
             }
@@ -5202,23 +5099,23 @@
                 p.println("    sessionRequested=" + ci.sessionRequested);
                 p.println("    curSession=" + ci.curSession);
             }
-            p.println("  mCurMethodId=" + mCurMethodId);
+            p.println("  mCurMethodId=" + getSelectedMethodId());
             client = mCurClient;
-            p.println("  mCurClient=" + client + " mCurSeq=" + mCurSeq);
+            p.println("  mCurClient=" + client + " mCurSeq=" + getSequenceNumber());
             p.println("  mCurPerceptible=" + mCurPerceptible);
             p.println("  mCurFocusedWindow=" + mCurFocusedWindow
                     + " softInputMode=" +
                     InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode)
                     + " client=" + mCurFocusedWindowClient);
             focusedWindowClient = mCurFocusedWindowClient;
-            p.println("  mCurId=" + mCurId + " mHaveConnection=" + mHaveConnection
-                    + " mBoundToMethod=" + mBoundToMethod + " mVisibleBound=" + mVisibleBound);
-            p.println("  mCurToken=" + mCurToken);
+            p.println("  mCurId=" + getCurId() + " mHaveConnection=" + hasConnection()
+                    + " mBoundToMethod=" + mBoundToMethod + " mVisibleBound=" + isVisibleBound());
+            p.println("  mCurToken=" + getCurToken());
             p.println("  mCurTokenDisplayId=" + mCurTokenDisplayId);
             p.println("  mCurHostInputToken=" + mCurHostInputToken);
-            p.println("  mCurIntent=" + mCurIntent);
-            method = mCurMethod;
-            p.println("  mCurMethod=" + mCurMethod);
+            p.println("  mCurIntent=" + getCurIntent());
+            method = getCurMethod();
+            p.println("  mCurMethod=" + getCurMethod());
             p.println("  mEnabledSession=" + mEnabledSession);
             p.println("  mShowRequested=" + mShowRequested
                     + " mShowExplicitlyRequested=" + mShowExplicitlyRequested
@@ -5724,7 +5621,7 @@
                 if (userId == mSettings.getCurrentUserId()) {
                     hideCurrentInputLocked(mCurFocusedWindow, 0, null,
                             SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND);
-                    unbindCurrentMethodLocked();
+                    mBindingController.unbindCurrentMethodLocked();
                     // Reset the current IME
                     resetSelectedInputMethodAndSubtypeLocked(null);
                     // Also reset the settings of the current IME
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index ffeaad1..457c2fd 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -154,10 +154,10 @@
         final ActivityTaskManagerInternal.PackageConfigurationUpdater updater =
                 mActivityTaskManagerInternal.createPackageConfigurationUpdater(appPackageName,
                         userId);
-        boolean isSuccess = updater.setLocales(locales).commit();
+        boolean isConfigChanged = updater.setLocales(locales).commit();
 
         //We want to send the broadcasts only if config was actually updated on commit.
-        if (isSuccess) {
+        if (isConfigChanged) {
             notifyAppWhoseLocaleChanged(appPackageName, userId, locales);
             notifyInstallerOfAppWhoseLocaleChanged(appPackageName, userId, locales);
             notifyRegisteredReceivers(appPackageName, userId, locales);
diff --git a/services/core/java/com/android/server/notification/BadgeExtractor.java b/services/core/java/com/android/server/notification/BadgeExtractor.java
index 70edfa1..642cbb3 100644
--- a/services/core/java/com/android/server/notification/BadgeExtractor.java
+++ b/services/core/java/com/android/server/notification/BadgeExtractor.java
@@ -69,11 +69,8 @@
 
         if (mConfig.isMediaNotificationFilteringEnabled()) {
             final Notification notif = record.getNotification();
-            if (notif.hasMediaSession()) {
-                if (notif.isStyle(Notification.DecoratedMediaCustomViewStyle.class)
-                        || notif.isStyle(Notification.MediaStyle.class)) {
-                    record.setShowBadge(false);
-                }
+            if (notif.isMediaNotification()) {
+                record.setShowBadge(false);
             }
         }
         return null;
diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java
index 8aae6e0..583cdd5 100644
--- a/services/core/java/com/android/server/notification/NotificationComparator.java
+++ b/services/core/java/com/android/server/notification/NotificationComparator.java
@@ -179,7 +179,7 @@
     }
 
     private boolean isMediaNotification(NotificationRecord record) {
-        return record.getNotification().hasMediaSession();
+        return record.getNotification().isMediaNotification();
     }
 
     private boolean isCallCategory(NotificationRecord record) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index bde5543..e117cc6 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -6822,13 +6822,11 @@
 
 
         // blocked apps
-        boolean isMediaNotification = n.isMediaNotification()
-                && n.extras.getParcelable(Notification.EXTRA_MEDIA_SESSION) != null;
         boolean isBlocked = !areNotificationsEnabledForPackageInt(pkg, uid);
         synchronized (mNotificationLock) {
             isBlocked |= isRecordBlockedLocked(r);
         }
-        if (isBlocked && !isMediaNotification) {
+        if (isBlocked && !n.isMediaNotification()) {
             if (DBG) {
                 Slog.e(TAG, "Suppressing notification from package " + r.getSbn().getPackageName()
                         + " by user request.");
@@ -7217,10 +7215,8 @@
                     final StatusBarNotification n = r.getSbn();
                     final Notification notification = n.getNotification();
 
-                    boolean isMediaNotification = notification.isMediaNotification()
-                            && notification.extras.getParcelable(
-                                    Notification.EXTRA_MEDIA_SESSION) != null;
-                    if (!isMediaNotification && (appBanned || isRecordBlockedLocked(r))) {
+                    if (!notification.isMediaNotification()
+                            && (appBanned || isRecordBlockedLocked(r))) {
                         mUsageStats.registerBlocked(r);
                         if (DBG) {
                             Slog.e(TAG, "Suppressing notification from package " + pkg);
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index 7a301d4..a9df4ba 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -617,6 +617,10 @@
     @Computer.LiveImplementation(override = LiveImplementation.MANDATORY)
     int getUidTargetSdkVersion(int uid);
 
+    /**
+     * @see PackageManagerInternal#getProcessesForUid(int)
+     */
+    @Computer.LiveImplementation(override = LiveImplementation.MANDATORY)
     @Nullable
     ArrayMap<String, ProcessInfo> getProcessesForUid(int uid);
     // End block
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 39ed9c2..6622a77 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2050,6 +2050,7 @@
             }
         }
 
+        final String packageName = pkg.getPackageName();
         for (Map.Entry<String, String> entry : fsverityCandidates.entrySet()) {
             final String filePath = entry.getKey();
             final String signaturePath = entry.getValue();
@@ -2077,10 +2078,13 @@
                     try {
                         // A file may already have fs-verity, e.g. when reused during a split
                         // install. If the measurement succeeds, no need to attempt to set up.
-                        mPm.mInstaller.assertFsverityRootHashMatches(filePath, rootHash);
+                        mPm.mInstaller.assertFsverityRootHashMatches(packageName, filePath,
+                                rootHash);
                     } catch (Installer.InstallerException e) {
-                        mPm.mInstaller.installApkVerity(filePath, fd, result.getContentSize());
-                        mPm.mInstaller.assertFsverityRootHashMatches(filePath, rootHash);
+                        mPm.mInstaller.installApkVerity(packageName, filePath, fd,
+                                result.getContentSize());
+                        mPm.mInstaller.assertFsverityRootHashMatches(packageName, filePath,
+                                rootHash);
                     }
                 } finally {
                     IoUtils.closeQuietly(fd);
@@ -2349,10 +2353,9 @@
                 // Set install reason for users that are having the package newly installed.
                 final int[] allUsersList = mPm.mUserManager.getUserIds();
                 if (userId == UserHandle.USER_ALL) {
-                    // TODO(b/152629990): It appears that the package doesn't actually get newly
-                    //  installed in this case, so the installReason shouldn't get modified?
                     for (int currentUserId : allUsersList) {
-                        if (!previousUserIds.contains(currentUserId)) {
+                        if (!previousUserIds.contains(currentUserId)
+                                && ps.getInstalled(currentUserId)) {
                             ps.setInstallReason(installReason, currentUserId);
                         }
                     }
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index c8bd2c0..a380344 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -497,7 +497,7 @@
      *
      * @throws InstallerException if {@code dexopt} fails.
      */
-    public boolean dexopt(String apkPath, int uid, @Nullable String pkgName, String instructionSet,
+    public boolean dexopt(String apkPath, int uid, String pkgName, String instructionSet,
             int dexoptNeeded, @Nullable String outputPath, int dexFlags,
             String compilerFilter, @Nullable String volumeUuid, @Nullable String classLoaderContext,
             @Nullable String seInfo, boolean downgrade, int targetSdkVersion,
@@ -585,11 +585,14 @@
         }
     }
 
-    public void rmPackageDir(String packageDir) throws InstallerException {
+    /**
+     * Remove a directory belonging to a package.
+     */
+    public void rmPackageDir(String packageName, String packageDir) throws InstallerException {
         if (!checkBeforeRemote()) return;
         BlockGuard.getVmPolicy().onPathAccess(packageDir);
         try {
-            mInstalld.rmPackageDir(packageDir);
+            mInstalld.rmPackageDir(packageName, packageDir);
         } catch (Exception e) {
             throw InstallerException.from(e);
         }
@@ -662,35 +665,44 @@
         }
     }
 
-    public void createOatDir(String oatDir, String dexInstructionSet)
+    /**
+     * Creates an oat dir for given package and instruction set.
+     */
+    public void createOatDir(String packageName, String oatDir, String dexInstructionSet)
             throws InstallerException {
         if (!checkBeforeRemote()) return;
         try {
-            mInstalld.createOatDir(oatDir, dexInstructionSet);
+            mInstalld.createOatDir(packageName, oatDir, dexInstructionSet);
         } catch (Exception e) {
             throw InstallerException.from(e);
         }
     }
 
-    public void linkFile(String relativePath, String fromBase, String toBase)
+    /**
+     * Creates a hardlink for a path.
+     */
+    public void linkFile(String packageName, String relativePath, String fromBase, String toBase)
             throws InstallerException {
         if (!checkBeforeRemote()) return;
         BlockGuard.getVmPolicy().onPathAccess(fromBase);
         BlockGuard.getVmPolicy().onPathAccess(toBase);
         try {
-            mInstalld.linkFile(relativePath, fromBase, toBase);
+            mInstalld.linkFile(packageName, relativePath, fromBase, toBase);
         } catch (Exception e) {
             throw InstallerException.from(e);
         }
     }
 
-    public void moveAb(String apkPath, String instructionSet, String outputPath)
+    /**
+     * Moves oat/vdex/art from "B" set defined by ro.boot.slot_suffix to the default set.
+     */
+    public void moveAb(String packageName, String apkPath, String instructionSet, String outputPath)
             throws InstallerException {
         if (!checkBeforeRemote()) return;
         BlockGuard.getVmPolicy().onPathAccess(apkPath);
         BlockGuard.getVmPolicy().onPathAccess(outputPath);
         try {
-            mInstalld.moveAb(apkPath, instructionSet, outputPath);
+            mInstalld.moveAb(packageName, apkPath, instructionSet, outputPath);
         } catch (Exception e) {
             throw InstallerException.from(e);
         }
@@ -700,35 +712,41 @@
      * Deletes the optimized artifacts generated by ART and returns the number
      * of freed bytes.
      */
-    public long deleteOdex(String apkPath, String instructionSet, String outputPath)
-            throws InstallerException {
+    public long deleteOdex(String packageName, String apkPath, String instructionSet,
+            String outputPath) throws InstallerException {
         if (!checkBeforeRemote()) return -1;
         BlockGuard.getVmPolicy().onPathAccess(apkPath);
         BlockGuard.getVmPolicy().onPathAccess(outputPath);
         try {
-            return mInstalld.deleteOdex(apkPath, instructionSet, outputPath);
+            return mInstalld.deleteOdex(packageName, apkPath, instructionSet, outputPath);
         } catch (Exception e) {
             throw InstallerException.from(e);
         }
     }
 
-    public void installApkVerity(String filePath, FileDescriptor verityInput, int contentSize)
-            throws InstallerException {
+    /**
+     * Enables legacy apk-verity for an apk.
+     */
+    public void installApkVerity(String packageName, String filePath, FileDescriptor verityInput,
+            int contentSize) throws InstallerException {
         if (!checkBeforeRemote()) return;
         BlockGuard.getVmPolicy().onPathAccess(filePath);
         try {
-            mInstalld.installApkVerity(filePath, verityInput, contentSize);
+            mInstalld.installApkVerity(packageName, filePath, verityInput, contentSize);
         } catch (Exception e) {
             throw InstallerException.from(e);
         }
     }
 
-    public void assertFsverityRootHashMatches(String filePath, @NonNull byte[] expectedHash)
-            throws InstallerException {
+    /**
+     * Checks if provided hash matches the file's fs-verity merkle tree root hash.
+     */
+    public void assertFsverityRootHashMatches(String packageName, String filePath,
+            @NonNull byte[] expectedHash) throws InstallerException {
         if (!checkBeforeRemote()) return;
         BlockGuard.getVmPolicy().onPathAccess(filePath);
         try {
-            mInstalld.assertFsverityRootHashMatches(filePath, expectedHash);
+            mInstalld.assertFsverityRootHashMatches(packageName, filePath, expectedHash);
         } catch (Exception e) {
             throw InstallerException.from(e);
         }
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 9e6f4f7..c125fe1 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -411,6 +411,7 @@
             final List<String> paths =
                     AndroidPackageUtils.getAllCodePathsExcludingResourceOnly(pkg);
             final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
+            final String packageName = pkg.getPackageName();
             for (String dexCodeInstructionSet : dexCodeInstructionSets) {
                 for (String path : paths) {
                     String oatDir = PackageDexOptimizer.getOatDir(
@@ -420,7 +421,7 @@
 
                     packagePaths++;
                     try {
-                        installer.moveAb(path, dexCodeInstructionSet, oatDir);
+                        installer.moveAb(packageName, path, dexCodeInstructionSet, oatDir);
                         pathsSuccessful++;
                     } catch (InstallerException e) {
                     }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index c47ca4b..8f14cf8 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -558,10 +558,7 @@
                 Slog.d(TAG, "Marking session " + sessionId + " as failed: " + errorMessage);
                 childSessions = getChildSessionsLocked();
             }
-            destroyInternal();
-            for (PackageInstallerSession child : childSessions) {
-                child.destroyInternal();
-            }
+            destroy();
             mCallback.onStagedSessionChanged(PackageInstallerSession.this);
         }
 
@@ -579,10 +576,7 @@
                 Slog.d(TAG, "Marking session " + sessionId + " as applied");
                 childSessions = getChildSessionsLocked();
             }
-            destroyInternal();
-            for (PackageInstallerSession child : childSessions) {
-                child.destroyInternal();
-            }
+            destroy();
             mCallback.onStagedSessionChanged(PackageInstallerSession.this);
         }
 
@@ -2111,18 +2105,14 @@
     private void onSessionVerificationFailure(int error, String msg) {
         final String msgWithErrorCode = PackageManager.installStatusToString(error, msg);
         Slog.e(TAG, "Failed to verify session " + sessionId + " [" + msgWithErrorCode + "]");
-        // Session is sealed and committed but could not be verified, we need to destroy it.
-        destroyInternal();
-        if (isMultiPackage()) {
-            for (PackageInstallerSession childSession : getChildSessions()) {
-                childSession.destroyInternal();
-            }
-        }
         if (isStaged()) {
+            // This will clean up the session when it reaches the terminal state
             mStagedSession.setSessionFailed(
                     SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, msgWithErrorCode);
             mStagedSession.notifyEndPreRebootVerification();
         } else {
+            // Session is sealed and committed but could not be verified, we need to destroy it.
+            destroy();
             // Dispatch message to remove session from PackageInstallerService.
             dispatchSessionFinished(error, msg, null);
         }
@@ -2412,6 +2402,7 @@
             try {
                 final List<File> fromFiles = mResolvedInheritedFiles;
                 final File toDir = stageDir;
+                final String tempPackageName = toDir.getName();
 
                 if (LOGD) Slog.d(TAG, "Inherited files: " + mResolvedInheritedFiles);
                 if (!mResolvedInheritedFiles.isEmpty() && mInheritedFilesBase == null) {
@@ -2421,7 +2412,7 @@
                 if (isLinkPossible(fromFiles, toDir)) {
                     if (!mResolvedInstructionSets.isEmpty()) {
                         final File oatDir = new File(toDir, "oat");
-                        createOatDirs(mResolvedInstructionSets, oatDir);
+                        createOatDirs(tempPackageName, mResolvedInstructionSets, oatDir);
                     }
                     // pre-create lib dirs for linking if necessary
                     if (!mResolvedNativeLibPaths.isEmpty()) {
@@ -2444,7 +2435,7 @@
                                     new File(libDir, archDirPath));
                         }
                     }
-                    linkFiles(fromFiles, toDir, mInheritedFilesBase);
+                    linkFiles(tempPackageName, fromFiles, toDir, mInheritedFilesBase);
                 } else {
                     // TODO: this should delegate to DCS so the system process
                     // avoids holding open FDs into containers.
@@ -3539,18 +3530,19 @@
         throw new IOException("File: " + pathStr + " outside base: " + baseStr);
     }
 
-    private void createOatDirs(List<String> instructionSets, File fromDir)
+    private void createOatDirs(String packageName, List<String> instructionSets, File fromDir)
             throws PackageManagerException {
         for (String instructionSet : instructionSets) {
             try {
-                mInstaller.createOatDir(fromDir.getAbsolutePath(), instructionSet);
+                mInstaller.createOatDir(packageName, fromDir.getAbsolutePath(), instructionSet);
             } catch (InstallerException e) {
                 throw PackageManagerException.from(e);
             }
         }
     }
 
-    private void linkFile(String relativePath, String fromBase, String toBase) throws IOException {
+    private void linkFile(String packageName, String relativePath, String fromBase, String toBase)
+            throws IOException {
         try {
             // Try
             final IncrementalFileStorages incrementalFileStorages = getIncrementalFileStorages();
@@ -3558,21 +3550,21 @@
                     fromBase, toBase)) {
                 return;
             }
-            mInstaller.linkFile(relativePath, fromBase, toBase);
+            mInstaller.linkFile(packageName, relativePath, fromBase, toBase);
         } catch (InstallerException | IOException e) {
             throw new IOException("failed linkOrCreateDir(" + relativePath + ", "
                     + fromBase + ", " + toBase + ")", e);
         }
     }
 
-    private void linkFiles(List<File> fromFiles, File toDir, File fromDir)
+    private void linkFiles(String packageName, List<File> fromFiles, File toDir, File fromDir)
             throws IOException {
         for (File fromFile : fromFiles) {
             final String relativePath = getRelativePath(fromFile, fromDir);
             final String fromBase = fromDir.getAbsolutePath();
             final String toBase = toDir.getAbsolutePath();
 
-            linkFile(relativePath, fromBase, toBase);
+            linkFile(packageName, relativePath, fromBase, toBase);
         }
 
         Slog.d(TAG, "Linked " + fromFiles.size() + " files into " + toDir);
@@ -4265,6 +4257,28 @@
         return params.isStaged ? mStagedSession.getSessionErrorMessage() : "";
     }
 
+    /**
+     * Free up storage used by this session and its children.
+     * Must not be called on a child session.
+     */
+    private void destroy() {
+        // TODO(b/173194203): destroy() is called indirectly by
+        //  PackageInstallerService#restoreAndApplyStagedSessionIfNeeded on an orphan child session.
+        //  Enable this assertion when we figure out a better way to clean up orphan sessions.
+        // assertNotChild("destroy");
+
+        // TODO(b/173194203): destroyInternal() should be used by destroy() only.
+        //  For the sake of consistency, a session should be destroyed as a whole. The caller
+        //  should always call destroy() for cleanup without knowing it has child sessions or not.
+        destroyInternal();
+        for (PackageInstallerSession child : getChildSessions()) {
+            child.destroyInternal();
+        }
+    }
+
+    /**
+     * Free up storage used by this session.
+     */
     private void destroyInternal() {
         final IncrementalFileStorages incrementalFileStorages;
         synchronized (mLock) {
@@ -4287,7 +4301,8 @@
                 incrementalFileStorages.cleanUpAndMarkComplete();
             }
             if (stageDir != null) {
-                mInstaller.rmPackageDir(stageDir.getAbsolutePath());
+                final String tempPackageName = stageDir.getName();
+                mInstaller.rmPackageDir(tempPackageName, stageDir.getAbsolutePath());
             }
         } catch (InstallerException ignored) {
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index ab01961..747bbfa5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1500,16 +1500,19 @@
         final CompatChange.ChangeListener selinuxChangeListener = packageName -> {
             synchronized (m.mInstallLock) {
                 final AndroidPackage pkg;
+                final PackageSetting ps;
                 final SharedUserSetting sharedUser;
                 final String oldSeInfo;
-                final PackageStateInternal packageState = m.getPackageStateInternal(packageName);
-                if (packageState == null) {
-                    Slog.e(TAG, "Failed to find package setting " + packageName);
-                    return;
+                synchronized (m.mLock) {
+                    ps = m.mSettings.getPackageLPr(packageName);
+                    if (ps == null) {
+                        Slog.e(TAG, "Failed to find package setting " + packageName);
+                        return;
+                    }
+                    pkg = ps.getPkg();
+                    sharedUser = ps.getSharedUser();
+                    oldSeInfo = AndroidPackageUtils.getSeInfo(pkg, ps);
                 }
-                pkg = packageState.getPkg();
-                sharedUser = packageState.getSharedUser();
-                oldSeInfo = AndroidPackageUtils.getSeInfo(pkg, packageState);
 
                 if (pkg == null) {
                     Slog.e(TAG, "Failed to find package " + packageName);
@@ -1521,7 +1524,7 @@
                 if (!newSeInfo.equals(oldSeInfo)) {
                     Slog.i(TAG, "Updating seInfo for package " + packageName + " from: "
                             + oldSeInfo + " to: " + newSeInfo);
-                    packageState.getTransientState().setOverrideSeInfo(newSeInfo);
+                    ps.getPkgState().setOverrideSeInfo(newSeInfo);
                     m.mAppDataHelper.prepareAppDataAfterInstallLIF(pkg);
                 }
             }
@@ -5646,6 +5649,9 @@
                         synchronized (mLock) {
                             mInstantAppRegistry.deleteInstantApplicationMetadataLPw(
                                     packageName, userId);
+                            if (succeeded) {
+                                resetComponentEnabledSettingsIfNeededLPw(packageName, userId);
+                            }
                         }
                     }
                     if (succeeded) {
@@ -5722,6 +5728,62 @@
     }
 
     /**
+     * Update component enabled settings to {@link PackageManager#COMPONENT_ENABLED_STATE_DEFAULT}
+     * if the resetEnabledSettingsOnAppDataCleared is {@code true}.
+     */
+    private void resetComponentEnabledSettingsIfNeededLPw(String packageName, int userId) {
+        final AndroidPackage pkg = packageName != null ? mPackages.get(packageName) : null;
+        if (pkg == null || !pkg.isResetEnabledSettingsOnAppDataCleared()) {
+            return;
+        }
+        final PackageSetting pkgSetting = mSettings.getPackageLPr(packageName);
+        if (pkgSetting == null) {
+            return;
+        }
+        final ArrayList<String> updatedComponents = new ArrayList<>();
+        final Consumer<? super ParsedMainComponent> resetSettings = (component) -> {
+            if (pkgSetting.restoreComponentLPw(component.getClassName(), userId)) {
+                updatedComponents.add(component.getClassName());
+            }
+        };
+        for (int i = 0; i < pkg.getActivities().size(); i++) {
+            resetSettings.accept(pkg.getActivities().get(i));
+        }
+        for (int i = 0; i < pkg.getReceivers().size(); i++) {
+            resetSettings.accept(pkg.getReceivers().get(i));
+        }
+        for (int i = 0; i < pkg.getServices().size(); i++) {
+            resetSettings.accept(pkg.getServices().get(i));
+        }
+        for (int i = 0; i < pkg.getProviders().size(); i++) {
+            resetSettings.accept(pkg.getProviders().get(i));
+        }
+        if (ArrayUtils.isEmpty(updatedComponents)) {
+            // nothing changed
+            return;
+        }
+
+        updateSequenceNumberLP(pkgSetting, new int[] { userId });
+        updateInstantAppInstallerLocked(packageName);
+        scheduleWritePackageRestrictionsLocked(userId);
+
+        final ArrayList<String> pendingComponents = mPendingBroadcasts.get(userId, packageName);
+        if (pendingComponents == null) {
+            mPendingBroadcasts.put(userId, packageName, updatedComponents);
+        } else {
+            for (int i = 0; i < updatedComponents.size(); i++) {
+                final String updatedComponent = updatedComponents.get(i);
+                if (!pendingComponents.contains(updatedComponent)) {
+                    pendingComponents.add(updatedComponent);
+                }
+            }
+        }
+        if (!mHandler.hasMessages(SEND_PENDING_BROADCAST)) {
+            mHandler.sendEmptyMessageDelayed(SEND_PENDING_BROADCAST, BROADCAST_DELAY);
+        }
+    }
+
+    /**
      * Remove entries from the keystore daemon. Will only remove it if the
      * {@code appId} is valid.
      */
@@ -6789,24 +6851,24 @@
         }
         enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */,
                 true /* checkShell */, "stop package");
-        boolean shouldUnhibernate = false;
         // writer
         synchronized (mLock) {
             final PackageSetting ps = mSettings.getPackageLPr(packageName);
-            if (ps != null && ps.getStopped(userId) && !stopped) {
-                shouldUnhibernate = true;
-            }
             if (!shouldFilterApplication(ps, callingUid, userId)
                     && mSettings.setPackageStoppedStateLPw(this, packageName, stopped, userId)) {
                 scheduleWritePackageRestrictionsLocked(userId);
             }
         }
-        if (shouldUnhibernate) {
+        // If this would cause the app to leave force-stop, then also make sure to unhibernate the
+        // app if needed.
+        if (!stopped) {
             mHandler.post(() -> {
                 AppHibernationManagerInternal ah =
                         mInjector.getLocalService(AppHibernationManagerInternal.class);
-                ah.setHibernatingForUser(packageName, userId, false);
-                ah.setHibernatingGlobally(packageName, false);
+                if (ah != null && ah.isHibernatingForUser(packageName, userId)) {
+                    ah.setHibernatingForUser(packageName, userId, false);
+                    ah.setHibernatingGlobally(packageName, false);
+                }
             });
         }
     }
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 3a2ac1c..48b893b 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -94,9 +94,10 @@
                     }
                 }
 
-                mInstaller.rmPackageDir(codePath.getAbsolutePath());
+                final String packageName = codePath.getName();
+                mInstaller.rmPackageDir(packageName, codePath.getAbsolutePath());
                 if (needRemoveParent) {
-                    mInstaller.rmPackageDir(codePathParent.getAbsolutePath());
+                    mInstaller.rmPackageDir(packageName, codePathParent.getAbsolutePath());
                     removeCachedResult(codePathParent);
                 }
             } catch (Installer.InstallerException e) {
diff --git a/services/core/java/com/android/server/pm/ScanPackageHelper.java b/services/core/java/com/android/server/pm/ScanPackageHelper.java
index 6cc94ce..9b08ef9 100644
--- a/services/core/java/com/android/server/pm/ScanPackageHelper.java
+++ b/services/core/java/com/android/server/pm/ScanPackageHelper.java
@@ -894,14 +894,15 @@
      * Returns if forced apk verification can be skipped for the whole package, including splits.
      */
     private boolean canSkipForcedPackageVerification(AndroidPackage pkg) {
-        if (!canSkipForcedApkVerification(pkg.getBaseApkPath())) {
+        final String packageName = pkg.getPackageName();
+        if (!canSkipForcedApkVerification(packageName, pkg.getBaseApkPath())) {
             return false;
         }
         // TODO: Allow base and splits to be verified individually.
         String[] splitCodePaths = pkg.getSplitCodePaths();
         if (!ArrayUtils.isEmpty(splitCodePaths)) {
             for (int i = 0; i < splitCodePaths.length; i++) {
-                if (!canSkipForcedApkVerification(splitCodePaths[i])) {
+                if (!canSkipForcedApkVerification(packageName, splitCodePaths[i])) {
                     return false;
                 }
             }
@@ -914,7 +915,7 @@
      * whether the apk contains signed root hash.  Note that the signer's certificate still needs to
      * match one in a trusted source, and should be done separately.
      */
-    private boolean canSkipForcedApkVerification(String apkPath) {
+    private boolean canSkipForcedApkVerification(String packageName, String apkPath) {
         if (!PackageManagerServiceUtils.isLegacyApkVerityEnabled()) {
             return VerityUtils.hasFsverity(apkPath);
         }
@@ -926,7 +927,8 @@
             }
             synchronized (mPm.mInstallLock) {
                 // Returns whether the observed root hash matches what kernel has.
-                mPm.mInstaller.assertFsverityRootHashMatches(apkPath, rootHashObserved);
+                mPm.mInstaller.assertFsverityRootHashMatches(packageName, apkPath,
+                        rootHashObserved);
                 return true;
             }
         } catch (Installer.InstallerException | IOException | DigestException
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 302826f..40d8845 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -20,6 +20,8 @@
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 
 import android.Manifest;
+import android.accounts.Account;
+import android.accounts.AccountManager;
 import android.annotation.ColorRes;
 import android.annotation.DrawableRes;
 import android.annotation.NonNull;
@@ -85,6 +87,7 @@
 import android.security.GateKeeper;
 import android.service.gatekeeper.IGateKeeperService;
 import android.stats.devicepolicy.DevicePolicyEnums;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
@@ -3512,6 +3515,39 @@
         }
     }
 
+    @Override
+    public UserHandle createUserWithAttributes(
+            String userName, String userType, @UserInfoFlag int flags,
+            Bitmap userIcon,
+            String accountName, String accountType, PersistableBundle accountOptions) {
+        checkManageOrCreateUsersPermission(flags);
+
+        if (someUserHasAccountNoChecks(accountName, accountType)) {
+            throw new ServiceSpecificException(
+                    UserManager.USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS);
+        }
+
+        UserInfo userInfo;
+        try {
+            userInfo = createUserInternal(userName, userType, flags,
+                    UserHandle.USER_NULL, null);
+
+            if (userInfo == null) {
+                throw new ServiceSpecificException(UserManager.USER_OPERATION_ERROR_UNKNOWN);
+            }
+        } catch (UserManager.CheckedUserOperationException e) {
+            throw e.toServiceSpecificException();
+        }
+
+        if (userIcon != null) {
+            mLocalService.setUserIcon(userInfo.id, userIcon);
+        }
+
+        setSeedAccountDataNoChecks(userInfo.id, accountName, accountType, accountOptions, true);
+
+        return userInfo.getUserHandle();
+    }
+
     private UserInfo createUserInternal(@Nullable String name, @NonNull String userType,
             @UserInfoFlag int flags, @UserIdInt int parentId,
             @Nullable String[] disallowedPackages)
@@ -4934,7 +4970,12 @@
     @Override
     public void setSeedAccountData(@UserIdInt int userId, String accountName, String accountType,
             PersistableBundle accountOptions, boolean persist) {
-        checkManageUsersPermission("Require MANAGE_USERS permission to set user seed data");
+        checkManageUsersPermission("set user seed account data");
+        setSeedAccountDataNoChecks(userId, accountName, accountType, accountOptions, persist);
+    }
+
+    private void setSeedAccountDataNoChecks(@UserIdInt int userId, String accountName,
+            String accountType, PersistableBundle accountOptions, boolean persist) {
         synchronized (mPackagesLock) {
             final UserData userData;
             synchronized (mUsersLock) {
@@ -4996,14 +5037,18 @@
     }
 
     @Override
-    public boolean someUserHasSeedAccount(String accountName, String accountType)
-            throws RemoteException {
-        checkManageUsersPermission("Cannot check seed account information");
+    public boolean someUserHasSeedAccount(String accountName, String accountType) {
+        checkManageUsersPermission("check seed account information");
+        return someUserHasSeedAccountNoChecks(accountName, accountType);
+    }
+
+    private boolean someUserHasSeedAccountNoChecks(String accountName, String accountType) {
         synchronized (mUsersLock) {
             final int userSize = mUsers.size();
             for (int i = 0; i < userSize; i++) {
                 final UserData data = mUsers.valueAt(i);
                 if (data.info.isInitialized()) continue;
+                if (mRemovingUserIds.get(data.info.id)) continue;
                 if (data.seedAccountName == null || !data.seedAccountName.equals(accountName)) {
                     continue;
                 }
@@ -5017,6 +5062,25 @@
     }
 
     @Override
+    public boolean someUserHasAccount(String accountName, String accountType) {
+        checkManageOrCreateUsersPermission("check seed account information");
+        return someUserHasAccountNoChecks(accountName, accountType);
+    }
+
+    private boolean someUserHasAccountNoChecks(
+            String accountName, String accountType) {
+        if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) {
+            return false;
+        }
+
+        final Account account = new Account(accountName, accountType);
+
+        return Binder.withCleanCallingIdentity(() ->
+                AccountManager.get(mContext).someUserHasAccount(account)
+                        || someUserHasSeedAccountNoChecks(accountName, accountType));
+    }
+
+    @Override
     public void onShellCommand(FileDescriptor in, FileDescriptor out,
             FileDescriptor err, String[] args, ShellCallback callback,
             ResultReceiver resultReceiver) {
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 5820489..5371454 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -1043,10 +1043,12 @@
     public long deleteOptimizedFiles(ArtPackageInfo packageInfo) {
         long freedBytes = 0;
         boolean hadErrors = false;
+        final String packageName = packageInfo.getPackageName();
         for (String codePath : packageInfo.getCodePaths()) {
             for (String isa : packageInfo.getInstructionSets()) {
                 try {
-                    freedBytes += mInstaller.deleteOdex(codePath, isa, packageInfo.getOatDir());
+                    freedBytes += mInstaller.deleteOdex(packageName, codePath, isa,
+                            packageInfo.getOatDir());
                 } catch (InstallerException e) {
                     Log.e(TAG, "Failed deleting oat files for " + codePath, e);
                     hadErrors = true;
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 ece0a62b..e207ff1 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -702,6 +702,14 @@
                         DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, userId),
                 userId, CONTACTS_PERMISSIONS);
 
+        // Maps
+        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, 0)) {
+            grantPermissionsToSystemPackage(pm,
+                getDefaultSystemHandlerActivityPackageForCategory(pm,
+                        Intent.CATEGORY_APP_MAPS, userId),
+                userId, FOREGROUND_LOCATION_PERMISSIONS);
+        }
+
         // Email
         grantPermissionsToSystemPackage(pm,
                 getDefaultSystemHandlerActivityPackageForCategory(pm,
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 2369c5e..131e587 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -332,6 +332,10 @@
     private static final int POWER_BUTTON_SUPPRESSION_DELAY_DEFAULT_MILLIS = 800;
     private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES =
             VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH);
+    private static final VibrationAttributes PHYSICAL_EMULATION_VIBRATION_ATTRIBUTES =
+            VibrationAttributes.createForUsage(VibrationAttributes.USAGE_PHYSICAL_EMULATION);
+    private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
+            VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
 
     /**
      * Keyguard stuff
@@ -1115,21 +1119,21 @@
                 break;
             case LONG_PRESS_POWER_GLOBAL_ACTIONS:
                 mPowerKeyHandled = true;
-                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
+                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false,
                         "Power - Long Press - Global Actions");
                 showGlobalActions();
                 break;
             case LONG_PRESS_POWER_SHUT_OFF:
             case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM:
                 mPowerKeyHandled = true;
-                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
+                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false,
                         "Power - Long Press - Shut Off");
                 sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
                 mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF);
                 break;
             case LONG_PRESS_POWER_GO_TO_VOICE_ASSIST:
                 mPowerKeyHandled = true;
-                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
+                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false,
                         "Power - Long Press - Go To Voice Assist");
                 // Some devices allow the voice assistant intent during setup (and use that intent
                 // to launch something else, like Settings). So we explicitly allow that via the
@@ -1153,7 +1157,7 @@
                 break;
             case VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS:
                 mPowerKeyHandled = true;
-                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
+                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false,
                         "Power - Very Long Press - Show Global Actions");
                 showGlobalActions();
                 break;
@@ -2098,7 +2102,8 @@
                                 mPowerKeyHandled = true;
                                 break;
                             case POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS:
-                                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
+                                performHapticFeedback(
+                                        HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false,
                                         "Power + Volume Up - Global Actions");
                                 showGlobalActions();
                                 mPowerKeyHandled = true;
@@ -5291,7 +5296,7 @@
             return false;
         }
 
-        mVibrator.vibrate(uid, packageName, effect, reason, TOUCH_VIBRATION_ATTRIBUTES);
+        mVibrator.vibrate(uid, packageName, effect, reason, getVibrationAttributes(effectId));
         return true;
     }
 
@@ -5320,6 +5325,7 @@
             case HapticFeedbackConstants.GESTURE_START:
                 return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
             case HapticFeedbackConstants.LONG_PRESS:
+            case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON:
             case HapticFeedbackConstants.EDGE_SQUEEZE:
                 return VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK);
             case HapticFeedbackConstants.REJECT:
@@ -5358,6 +5364,22 @@
         }
     }
 
+    private VibrationAttributes getVibrationAttributes(int effectId) {
+        switch (effectId) {
+            case HapticFeedbackConstants.EDGE_SQUEEZE:
+            case HapticFeedbackConstants.EDGE_RELEASE:
+                return PHYSICAL_EMULATION_VIBRATION_ATTRIBUTES;
+            case HapticFeedbackConstants.ASSISTANT_BUTTON:
+            case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON:
+            case HapticFeedbackConstants.ROTARY_SCROLL_TICK:
+            case HapticFeedbackConstants.ROTARY_SCROLL_ITEM_FOCUS:
+            case HapticFeedbackConstants.ROTARY_SCROLL_LIMIT:
+                return HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES;
+            default:
+                return TOUCH_VIBRATION_ATTRIBUTES;
+        }
+    }
+
     @Override
     public void keepScreenOnStartedLw() {
     }
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 6d0f08d..70a804b 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -110,8 +110,8 @@
     private static final VibrationEffect CHARGING_VIBRATION_EFFECT =
             VibrationEffect.createWaveform(CHARGING_VIBRATION_TIME, CHARGING_VIBRATION_AMPLITUDE,
                     -1);
-    private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES =
-            VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH);
+    private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
+            VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
 
     private final Object mLock = new Object();
 
@@ -807,7 +807,7 @@
         final boolean vibrate = Settings.Secure.getIntForUser(mContext.getContentResolver(),
                 Settings.Secure.CHARGING_VIBRATION_ENABLED, 1, userId) != 0;
         if (vibrate) {
-            mVibrator.vibrate(CHARGING_VIBRATION_EFFECT, TOUCH_VIBRATION_ATTRIBUTES);
+            mVibrator.vibrate(CHARGING_VIBRATION_EFFECT, HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES);
         }
 
         // play sound
diff --git a/services/core/java/com/android/server/stats/bootstrap/StatsBootstrapAtomService.java b/services/core/java/com/android/server/stats/bootstrap/StatsBootstrapAtomService.java
new file mode 100644
index 0000000..0d420a5
--- /dev/null
+++ b/services/core/java/com/android/server/stats/bootstrap/StatsBootstrapAtomService.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.stats.bootstrap;
+
+import android.content.Context;
+import android.os.IStatsBootstrapAtomService;
+import android.os.StatsBootstrapAtom;
+import android.os.StatsBootstrapAtomValue;
+import android.util.Slog;
+import android.util.StatsEvent;
+import android.util.StatsLog;
+
+import com.android.server.SystemService;
+
+/**
+ * Proxy service for logging pushed atoms to statsd
+ *
+ * @hide
+ */
+public class StatsBootstrapAtomService extends IStatsBootstrapAtomService.Stub {
+
+    private static final String TAG = "StatsBootstrapAtomService";
+    private static final boolean DEBUG = false;
+
+    @Override
+    public void reportBootstrapAtom(StatsBootstrapAtom atom) {
+        if (atom.atomId < 1 || atom.atomId >= 10000) {
+            Slog.e(TAG, "Atom ID " + atom.atomId + " is not a valid atom ID");
+            return;
+        }
+        StatsEvent.Builder builder = StatsEvent.newBuilder().setAtomId(atom.atomId);
+        for (StatsBootstrapAtomValue value : atom.values) {
+            switch (value.getTag()) {
+                case StatsBootstrapAtomValue.boolValue:
+                    builder.writeBoolean(value.getBoolValue());
+                    break;
+                case StatsBootstrapAtomValue.intValue:
+                    builder.writeInt(value.getIntValue());
+                    break;
+                case StatsBootstrapAtomValue.longValue:
+                    builder.writeLong(value.getLongValue());
+                    break;
+                case StatsBootstrapAtomValue.floatValue:
+                    builder.writeFloat(value.getFloatValue());
+                    break;
+                case StatsBootstrapAtomValue.stringValue:
+                    builder.writeString(value.getStringValue());
+                    break;
+                case StatsBootstrapAtomValue.bytesValue:
+                    builder.writeByteArray(value.getBytesValue());
+                    break;
+                default:
+                    Slog.e(TAG, "Unexpected value type " + value.getTag()
+                            + " when logging atom " + atom.atomId);
+                    return;
+
+            }
+        }
+        StatsLog.write(builder.usePooledBuffer().build());
+    }
+
+    /**
+     * Lifecycle and related code
+     */
+    public static final class Lifecycle extends SystemService {
+        private StatsBootstrapAtomService mStatsBootstrapAtomService;
+
+        public Lifecycle(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void onStart() {
+            mStatsBootstrapAtomService = new StatsBootstrapAtomService();
+            try {
+                publishBinderService(Context.STATS_BOOTSTRAP_ATOM_SERVICE,
+                        mStatsBootstrapAtomService);
+                if (DEBUG) Slog.d(TAG, "Published " + Context.STATS_BOOTSTRAP_ATOM_SERVICE);
+            } catch (Exception e) {
+                Slog.e(TAG, "Failed to publishBinderService", e);
+            }
+        }
+    }
+
+}
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index e9d5ad6..e69acc3 100755
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -470,14 +470,16 @@
     }
 
     private void stopUser(int userId) {
-        if (userId == mCurrentUserId) {
-            switchUser(ActivityManager.getCurrentUser());
-            return;
-        }
+        synchronized (mLock) {
+            if (userId == mCurrentUserId) {
+                switchUser(ActivityManager.getCurrentUser());
+                return;
+            }
 
-        releaseSessionOfUserLocked(userId);
-        unbindServiceOfUserLocked(userId);
-        mRunningProfiles.remove(userId);
+            releaseSessionOfUserLocked(userId);
+            unbindServiceOfUserLocked(userId);
+            mRunningProfiles.remove(userId);
+        }
     }
 
     private void startProfileLocked(int userId) {
diff --git a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
index 4d1ff9e..dfb0752 100644
--- a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
@@ -27,6 +27,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
 import android.media.tv.interactive.ITvIAppClient;
 import android.media.tv.interactive.ITvIAppManager;
 import android.media.tv.interactive.ITvIAppManagerCallback;
@@ -42,6 +43,7 @@
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.ArrayMap;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -58,10 +60,12 @@
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+
 /**
  * This class provides a system service that manages interactive TV applications.
  */
@@ -81,6 +85,8 @@
     @GuardedBy("mLock")
     private final SparseArray<UserState> mUserStates = new SparseArray<>();
 
+    private final UserManager mUserManager;
+
     /**
      * Initializes the system service.
      * <p>
@@ -93,6 +99,7 @@
     public TvIAppManagerService(Context context) {
         super(context);
         mContext = context;
+        mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
     }
 
     @GuardedBy("mLock")
@@ -330,11 +337,188 @@
         mContext.registerReceiverAsUser(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
-                // TODO: handle switch / start / stop user
+                String action = intent.getAction();
+                if (Intent.ACTION_USER_SWITCHED.equals(action)) {
+                    switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
+                } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
+                    removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
+                } else if (Intent.ACTION_USER_STARTED.equals(action)) {
+                    int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
+                    startUser(userId);
+                } else if (Intent.ACTION_USER_STOPPED.equals(action)) {
+                    int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
+                    stopUser(userId);
+                }
             }
         }, UserHandle.ALL, intentFilter, null, null);
     }
 
+    private void switchUser(int userId) {
+        synchronized (mLock) {
+            if (mCurrentUserId == userId) {
+                return;
+            }
+            UserInfo userInfo = mUserManager.getUserInfo(userId);
+            if (userInfo.isProfile()) {
+                Slog.w(TAG, "cannot switch to a profile!");
+                return;
+            }
+
+            for (int runningId : mRunningProfiles) {
+                releaseSessionOfUserLocked(runningId);
+                unbindServiceOfUserLocked(runningId);
+            }
+            mRunningProfiles.clear();
+            releaseSessionOfUserLocked(mCurrentUserId);
+            unbindServiceOfUserLocked(mCurrentUserId);
+
+            mCurrentUserId = userId;
+            buildTvIAppServiceListLocked(userId, null);
+        }
+    }
+
+    private void removeUser(int userId) {
+        synchronized (mLock) {
+            UserState userState = getUserStateLocked(userId);
+            if (userState == null) {
+                return;
+            }
+            // Release all created sessions.
+            for (SessionState state : userState.mSessionStateMap.values()) {
+                if (state.mSession != null) {
+                    try {
+                        state.mSession.release();
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "error in release", e);
+                    }
+                }
+            }
+            userState.mSessionStateMap.clear();
+
+            // Unregister all callbacks and unbind all services.
+            for (ServiceState serviceState : userState.mServiceStateMap.values()) {
+                if (serviceState.mService != null) {
+                    if (serviceState.mCallback != null) {
+                        try {
+                            serviceState.mService.unregisterCallback(serviceState.mCallback);
+                        } catch (RemoteException e) {
+                            Slog.e(TAG, "error in unregisterCallback", e);
+                        }
+                    }
+                    mContext.unbindService(serviceState.mConnection);
+                }
+            }
+            userState.mServiceStateMap.clear();
+
+            // Clear everything else.
+            userState.mIAppMap.clear();
+            userState.mPackageSet.clear();
+            userState.mClientStateMap.clear();
+            userState.mCallbacks.kill();
+
+            mRunningProfiles.remove(userId);
+            mUserStates.remove(userId);
+
+            if (userId == mCurrentUserId) {
+                switchUser(UserHandle.USER_SYSTEM);
+            }
+        }
+    }
+
+    private void startUser(int userId) {
+        synchronized (mLock) {
+            if (userId == mCurrentUserId || mRunningProfiles.contains(userId)) {
+                // user already started
+                return;
+            }
+            UserInfo userInfo = mUserManager.getUserInfo(userId);
+            UserInfo parentInfo = mUserManager.getProfileParent(userId);
+            if (userInfo.isProfile()
+                    && parentInfo != null
+                    && parentInfo.id == mCurrentUserId) {
+                // only the children of the current user can be started in background
+                startProfileLocked(userId);
+            }
+        }
+    }
+
+    private void stopUser(int userId) {
+        synchronized (mLock) {
+            if (userId == mCurrentUserId) {
+                switchUser(ActivityManager.getCurrentUser());
+                return;
+            }
+
+            releaseSessionOfUserLocked(userId);
+            unbindServiceOfUserLocked(userId);
+            mRunningProfiles.remove(userId);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void startProfileLocked(int userId) {
+        mRunningProfiles.add(userId);
+        buildTvIAppServiceListLocked(userId, null);
+    }
+
+    @GuardedBy("mLock")
+    private void releaseSessionOfUserLocked(int userId) {
+        UserState userState = getUserStateLocked(userId);
+        if (userState == null) {
+            return;
+        }
+        List<SessionState> sessionStatesToRelease = new ArrayList<>();
+        for (SessionState sessionState : userState.mSessionStateMap.values()) {
+            if (sessionState.mSession != null) {
+                sessionStatesToRelease.add(sessionState);
+            }
+        }
+        for (SessionState sessionState : sessionStatesToRelease) {
+            try {
+                sessionState.mSession.release();
+            } catch (RemoteException e) {
+                Slog.e(TAG, "error in release", e);
+            }
+            clearSessionAndNotifyClientLocked(sessionState);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void unbindServiceOfUserLocked(int userId) {
+        UserState userState = getUserStateLocked(userId);
+        if (userState == null) {
+            return;
+        }
+        for (Iterator<ComponentName> it = userState.mServiceStateMap.keySet().iterator();
+                it.hasNext(); ) {
+            ComponentName component = it.next();
+            ServiceState serviceState = userState.mServiceStateMap.get(component);
+            if (serviceState != null && serviceState.mSessionTokens.isEmpty()) {
+                if (serviceState.mCallback != null) {
+                    try {
+                        serviceState.mService.unregisterCallback(serviceState.mCallback);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "error in unregisterCallback", e);
+                    }
+                }
+                mContext.unbindService(serviceState.mConnection);
+                it.remove();
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void clearSessionAndNotifyClientLocked(SessionState state) {
+        if (state.mClient != null) {
+            try {
+                state.mClient.onSessionReleased(state.mSeq);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "error in onSessionReleased", e);
+            }
+        }
+        removeSessionStateLocked(state.mSessionToken, state.mUserId);
+    }
+
     private SessionState getSessionState(IBinder sessionToken) {
         // TODO: implement user state and get session from it.
         return null;
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index bc21f1b..71a6b22 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -16,6 +16,14 @@
 
 package com.android.server.vibrator;
 
+import static android.os.VibrationAttributes.USAGE_ALARM;
+import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
+import static android.os.VibrationAttributes.USAGE_HARDWARE_FEEDBACK;
+import static android.os.VibrationAttributes.USAGE_NOTIFICATION;
+import static android.os.VibrationAttributes.USAGE_PHYSICAL_EMULATION;
+import static android.os.VibrationAttributes.USAGE_RINGTONE;
+import static android.os.VibrationAttributes.USAGE_TOUCH;
+
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.IUidObserver;
@@ -90,6 +98,8 @@
     @GuardedBy("mLock")
     private int mHapticFeedbackIntensity;
     @GuardedBy("mLock")
+    private int mHardwareFeedbackIntensity;
+    @GuardedBy("mLock")
     private int mNotificationIntensity;
     @GuardedBy("mLock")
     private int mRingIntensity;
@@ -232,17 +242,20 @@
      * @return The vibration intensity, one of Vibrator.VIBRATION_INTENSITY_*
      */
     public int getDefaultIntensity(int usageHint) {
-        if (isAlarm(usageHint)) {
+        if (usageHint == USAGE_ALARM) {
             return Vibrator.VIBRATION_INTENSITY_HIGH;
         }
         synchronized (mLock) {
             if (mVibrator != null) {
-                if (isRingtone(usageHint)) {
-                    return mVibrator.getDefaultRingVibrationIntensity();
-                } else if (isNotification(usageHint)) {
-                    return mVibrator.getDefaultNotificationVibrationIntensity();
-                } else if (isHapticFeedback(usageHint)) {
-                    return mVibrator.getDefaultHapticFeedbackIntensity();
+                switch (usageHint) {
+                    case USAGE_RINGTONE:
+                        return mVibrator.getDefaultRingVibrationIntensity();
+                    case USAGE_NOTIFICATION:
+                        return mVibrator.getDefaultNotificationVibrationIntensity();
+                    case USAGE_TOUCH:
+                    case USAGE_HARDWARE_FEEDBACK:
+                    case USAGE_PHYSICAL_EMULATION:
+                        return mVibrator.getDefaultHapticFeedbackIntensity();
                 }
             }
         }
@@ -257,16 +270,20 @@
      */
     public int getCurrentIntensity(int usageHint) {
         synchronized (mLock) {
-            if (isRingtone(usageHint)) {
-                return mRingIntensity;
-            } else if (isNotification(usageHint)) {
-                return mNotificationIntensity;
-            } else if (isHapticFeedback(usageHint)) {
-                return mHapticFeedbackIntensity;
-            } else if (isAlarm(usageHint)) {
-                return Vibrator.VIBRATION_INTENSITY_HIGH;
-            } else {
-                return Vibrator.VIBRATION_INTENSITY_MEDIUM;
+            switch (usageHint) {
+                case USAGE_RINGTONE:
+                    return mRingIntensity;
+                case USAGE_NOTIFICATION:
+                    return mNotificationIntensity;
+                case USAGE_TOUCH:
+                    return mHapticFeedbackIntensity;
+                case USAGE_HARDWARE_FEEDBACK:
+                case USAGE_PHYSICAL_EMULATION:
+                    return mHardwareFeedbackIntensity;
+                case USAGE_ALARM:
+                    return Vibrator.VIBRATION_INTENSITY_HIGH;
+                default:
+                    return Vibrator.VIBRATION_INTENSITY_MEDIUM;
             }
         }
     }
@@ -289,7 +306,7 @@
      * for ringtone usage only. All other usages are allowed independently of ringer mode.
      */
     public boolean shouldVibrateForRingerMode(int usageHint) {
-        if (!isRingtone(usageHint)) {
+        if (usageHint != USAGE_RINGTONE) {
             return true;
         }
         synchronized (mLock) {
@@ -324,8 +341,10 @@
      * {@link VibrationAttributes#USAGE_COMMUNICATION_REQUEST} usages are allowed to vibrate.
      */
     public boolean shouldVibrateForPowerMode(int usageHint) {
-        return !mLowPowerMode || isRingtone(usageHint) || isAlarm(usageHint)
-                || usageHint == VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
+        synchronized (mLock) {
+            return !mLowPowerMode || usageHint == USAGE_RINGTONE || usageHint == USAGE_ALARM
+                    || usageHint == USAGE_COMMUNICATION_REQUEST;
+        }
     }
 
     /** Return {@code true} if input devices should vibrate instead of this device. */
@@ -338,22 +357,6 @@
         return mZenMode != Settings.Global.ZEN_MODE_OFF;
     }
 
-    private static boolean isNotification(int usageHint) {
-        return usageHint == VibrationAttributes.USAGE_NOTIFICATION;
-    }
-
-    private static boolean isRingtone(int usageHint) {
-        return usageHint == VibrationAttributes.USAGE_RINGTONE;
-    }
-
-    private static boolean isHapticFeedback(int usageHint) {
-        return usageHint == VibrationAttributes.USAGE_TOUCH;
-    }
-
-    private static boolean isAlarm(int usageHint) {
-        return usageHint == VibrationAttributes.USAGE_ALARM;
-    }
-
     private static boolean isClassAlarm(int usageHint) {
         return (usageHint & VibrationAttributes.USAGE_CLASS_MASK)
                 == VibrationAttributes.USAGE_CLASS_ALARM;
@@ -365,18 +368,35 @@
             mVibrateWhenRinging = getSystemSetting(Settings.System.VIBRATE_WHEN_RINGING, 0) != 0;
             mApplyRampingRinger = getGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0) != 0;
             mHapticFeedbackIntensity = getSystemSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
-                    getDefaultIntensity(VibrationAttributes.USAGE_TOUCH));
+                    getDefaultIntensity(USAGE_TOUCH));
+            mHardwareFeedbackIntensity = getSystemSetting(
+                    Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY,
+                    getHardwareFeedbackIntensityWhenSettingIsMissing(mHapticFeedbackIntensity));
             mNotificationIntensity = getSystemSetting(
                     Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                    getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION));
+                    getDefaultIntensity(USAGE_NOTIFICATION));
             mRingIntensity = getSystemSetting(Settings.System.RING_VIBRATION_INTENSITY,
-                    getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE));
+                    getDefaultIntensity(USAGE_RINGTONE));
             mVibrateInputDevices = getSystemSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0) > 0;
             mZenMode = getGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
         }
         notifyListeners();
     }
 
+    /**
+     * Return the value to be used for {@link Settings.System#HARDWARE_HAPTIC_FEEDBACK_INTENSITY}
+     * when the value was not set by the user.
+     *
+     * <p>This should adapt the behavior preceding the introduction of this new setting key, which
+     * is to apply {@link Settings.System#HAPTIC_FEEDBACK_INTENSITY} unless it's disabled.
+     */
+    private int getHardwareFeedbackIntensityWhenSettingIsMissing(int hapticFeedbackIntensity) {
+        if (hapticFeedbackIntensity == Vibrator.VIBRATION_INTENSITY_OFF) {
+            return getDefaultIntensity(USAGE_HARDWARE_FEEDBACK);
+        }
+        return hapticFeedbackIntensity;
+    }
+
     @Override
     public String toString() {
         return "VibrationSettings{"
@@ -389,18 +409,20 @@
                 + ", mHapticChannelMaxVibrationAmplitude=" + getHapticChannelMaxVibrationAmplitude()
                 + ", mRampStepDuration=" + mRampStepDuration
                 + ", mRampDownDuration=" + mRampDownDuration
+                + ", mHardwareHapticFeedbackIntensity="
+                + intensityToString(getCurrentIntensity(USAGE_HARDWARE_FEEDBACK))
                 + ", mHapticFeedbackIntensity="
-                + intensityToString(getCurrentIntensity(VibrationAttributes.USAGE_TOUCH))
+                + intensityToString(getCurrentIntensity(USAGE_TOUCH))
                 + ", mHapticFeedbackDefaultIntensity="
-                + intensityToString(getDefaultIntensity(VibrationAttributes.USAGE_TOUCH))
+                + intensityToString(getDefaultIntensity(USAGE_TOUCH))
                 + ", mNotificationIntensity="
-                + intensityToString(getCurrentIntensity(VibrationAttributes.USAGE_NOTIFICATION))
+                + intensityToString(getCurrentIntensity(USAGE_NOTIFICATION))
                 + ", mNotificationDefaultIntensity="
-                + intensityToString(getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION))
+                + intensityToString(getDefaultIntensity(USAGE_NOTIFICATION))
                 + ", mRingIntensity="
-                + intensityToString(getCurrentIntensity(VibrationAttributes.USAGE_RINGTONE))
+                + intensityToString(getCurrentIntensity(USAGE_RINGTONE))
                 + ", mRingDefaultIntensity="
-                + intensityToString(getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE))
+                + intensityToString(getDefaultIntensity(USAGE_RINGTONE))
                 + '}';
     }
 
@@ -410,15 +432,15 @@
             proto.write(VibratorManagerServiceDumpProto.HAPTIC_FEEDBACK_INTENSITY,
                     mHapticFeedbackIntensity);
             proto.write(VibratorManagerServiceDumpProto.HAPTIC_FEEDBACK_DEFAULT_INTENSITY,
-                    getDefaultIntensity(VibrationAttributes.USAGE_TOUCH));
+                    getDefaultIntensity(USAGE_TOUCH));
             proto.write(VibratorManagerServiceDumpProto.NOTIFICATION_INTENSITY,
                     mNotificationIntensity);
             proto.write(VibratorManagerServiceDumpProto.NOTIFICATION_DEFAULT_INTENSITY,
-                    getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION));
+                    getDefaultIntensity(USAGE_NOTIFICATION));
             proto.write(VibratorManagerServiceDumpProto.RING_INTENSITY,
                     mRingIntensity);
             proto.write(VibratorManagerServiceDumpProto.RING_DEFAULT_INTENSITY,
-                    getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE));
+                    getDefaultIntensity(USAGE_RINGTONE));
         }
     }
 
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index a327382..fdd9913 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -287,6 +287,9 @@
                 }
                 // If we waited, the queue may have changed, so let the loop run again.
                 if (waitTime <= 0) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Play vibration consuming next step...");
+                    }
                     mStepQueue.consumeNext();
                 }
                 Vibration.Status status = mStop ? Vibration.Status.CANCELLED
diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java
index 4b7fd90..4a1b95b 100644
--- a/services/core/java/com/android/server/vibrator/VibratorController.java
+++ b/services/core/java/com/android/server/vibrator/VibratorController.java
@@ -40,21 +40,22 @@
     private static final int SUGGESTED_FREQUENCY_SAFE_RANGE = 200;
 
     private final Object mLock = new Object();
-    private final NativeWrapper mNativeWrapper;
 
     @GuardedBy("mLock")
-    private VibratorInfo mVibratorInfo;
-    @GuardedBy("mLock")
-    private boolean mVibratorInfoLoadSuccessful;
-    @GuardedBy("mLock")
+    private final NativeWrapper mNativeWrapper;
+
+    // Vibrator state listeners that support concurrent updates and broadcasts, but should lock
+    // while broadcasting to guarantee delivery order.
     private final RemoteCallbackList<IVibratorStateListener> mVibratorStateListeners =
             new RemoteCallbackList<>();
-    @GuardedBy("mLock")
-    private boolean mIsVibrating;
-    @GuardedBy("mLock")
-    private boolean mIsUnderExternalControl;
-    @GuardedBy("mLock")
-    private float mCurrentAmplitude;
+
+    // Vibrator state variables that are updated from synchronized blocks but can be read anytime
+    // for a snippet of the current known vibrator state/info.
+    private volatile VibratorInfo mVibratorInfo;
+    private volatile boolean mVibratorInfoLoadSuccessful;
+    private volatile boolean mIsVibrating;
+    private volatile boolean mIsUnderExternalControl;
+    private volatile float mCurrentAmplitude;
 
     /** Listener for vibration completion callbacks from native. */
     public interface OnVibrationCompleteListener {
@@ -86,35 +87,39 @@
 
     /** Register state listener for this vibrator. */
     public boolean registerVibratorStateListener(IVibratorStateListener listener) {
-        synchronized (mLock) {
-            final long token = Binder.clearCallingIdentity();
-            try {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            // Register the listener and send the first state atomically, to avoid potentially
+            // out of order broadcasts in between.
+            synchronized (mLock) {
                 if (!mVibratorStateListeners.register(listener)) {
                     return false;
                 }
                 // Notify its callback after new client registered.
-                notifyStateListenerLocked(listener);
-                return true;
-            } finally {
-                Binder.restoreCallingIdentity(token);
+                notifyStateListener(listener, mIsVibrating);
             }
+            return true;
+        } finally {
+            Binder.restoreCallingIdentity(token);
         }
     }
 
     /** Remove registered state listener for this vibrator. */
     public boolean unregisterVibratorStateListener(IVibratorStateListener listener) {
-        synchronized (mLock) {
-            final long token = Binder.clearCallingIdentity();
-            try {
-                return mVibratorStateListeners.unregister(listener);
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return mVibratorStateListeners.unregister(listener);
+        } finally {
+            Binder.restoreCallingIdentity(token);
         }
     }
 
     /** Reruns the query to the vibrator to load the {@link VibratorInfo}, if not yet successful. */
     public void reloadVibratorInfoIfNeeded() {
+        // Early check outside lock, for quick return.
+        if (mVibratorInfoLoadSuccessful) {
+            return;
+        }
         synchronized (mLock) {
             if (mVibratorInfoLoadSuccessful) {
                 return;
@@ -132,16 +137,12 @@
 
     /** Checks if the {@link VibratorInfo} was loaded from the vibrator hardware successfully. */
     boolean isVibratorInfoLoadSuccessful() {
-        synchronized (mLock) {
-            return mVibratorInfoLoadSuccessful;
-        }
+        return mVibratorInfoLoadSuccessful;
     }
 
     /** Return the {@link VibratorInfo} representing the vibrator controlled by this instance. */
     public VibratorInfo getVibratorInfo() {
-        synchronized (mLock) {
-            return mVibratorInfo;
-        }
+        return mVibratorInfo;
     }
 
     /**
@@ -151,9 +152,7 @@
      * automatically notified to any registered {@link IVibratorStateListener} on change.
      */
     public boolean isVibrating() {
-        synchronized (mLock) {
-            return mIsVibrating;
-        }
+        return mIsVibrating;
     }
 
     /**
@@ -168,16 +167,12 @@
      * <p>If {@link #isVibrating()} is false then this will be zero.
      */
     public float getCurrentAmplitude() {
-        synchronized (mLock) {
-            return mCurrentAmplitude;
-        }
+        return mCurrentAmplitude;
     }
 
     /** Return {@code true} if this vibrator is under external control, false otherwise. */
     public boolean isUnderExternalControl() {
-        synchronized (mLock) {
-            return mIsUnderExternalControl;
-        }
+        return mIsUnderExternalControl;
     }
 
     /**
@@ -187,14 +182,14 @@
      * @return true if this vibrator has this capability, false otherwise
      */
     public boolean hasCapability(long capability) {
-        synchronized (mLock) {
-            return mVibratorInfo.hasCapability(capability);
-        }
+        return mVibratorInfo.hasCapability(capability);
     }
 
     /** Return {@code true} if the underlying vibrator is currently available, false otherwise. */
     public boolean isAvailable() {
-        return mNativeWrapper.isAvailable();
+        synchronized (mLock) {
+            return mNativeWrapper.isAvailable();
+        }
     }
 
     /**
@@ -203,10 +198,10 @@
      * <p>This will affect the state of {@link #isUnderExternalControl()}.
      */
     public void setExternalControl(boolean externalControl) {
+        if (!mVibratorInfo.hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
+            return;
+        }
         synchronized (mLock) {
-            if (!mVibratorInfo.hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
-                return;
-            }
             mIsUnderExternalControl = externalControl;
             mNativeWrapper.setExternalControl(externalControl);
         }
@@ -217,10 +212,10 @@
      * if given {@code effect} is {@code null}.
      */
     public void updateAlwaysOn(int id, @Nullable PrebakedSegment prebaked) {
+        if (!mVibratorInfo.hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) {
+            return;
+        }
         synchronized (mLock) {
-            if (!mVibratorInfo.hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) {
-                return;
-            }
             if (prebaked == null) {
                 mNativeWrapper.alwaysOnDisable(id);
             } else {
@@ -256,7 +251,7 @@
             long duration = mNativeWrapper.on(milliseconds, vibrationId);
             if (duration > 0) {
                 mCurrentAmplitude = -1;
-                notifyVibratorOnLocked();
+                notifyListenerOnVibrating(true);
             }
             return duration;
         }
@@ -277,7 +272,7 @@
                     prebaked.getEffectStrength(), vibrationId);
             if (duration > 0) {
                 mCurrentAmplitude = -1;
-                notifyVibratorOnLocked();
+                notifyListenerOnVibrating(true);
             }
             return duration;
         }
@@ -293,14 +288,14 @@
      * do not support the input or a negative number if the operation failed.
      */
     public long on(PrimitiveSegment[] primitives, long vibrationId) {
+        if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
+            return 0;
+        }
         synchronized (mLock) {
-            if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
-                return 0;
-            }
             long duration = mNativeWrapper.compose(primitives, vibrationId);
             if (duration > 0) {
                 mCurrentAmplitude = -1;
-                notifyVibratorOnLocked();
+                notifyListenerOnVibrating(true);
             }
             return duration;
         }
@@ -315,15 +310,15 @@
      * @return The duration of the effect playing, or 0 if unsupported.
      */
     public long on(RampSegment[] primitives, long vibrationId) {
+        if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) {
+            return 0;
+        }
         synchronized (mLock) {
-            if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) {
-                return 0;
-            }
             int braking = mVibratorInfo.getDefaultBraking();
             long duration = mNativeWrapper.composePwle(primitives, braking, vibrationId);
             if (duration > 0) {
                 mCurrentAmplitude = -1;
-                notifyVibratorOnLocked();
+                notifyListenerOnVibrating(true);
             }
             return duration;
         }
@@ -334,7 +329,7 @@
         synchronized (mLock) {
             mNativeWrapper.off();
             mCurrentAmplitude = 0;
-            notifyVibratorOffLocked();
+            notifyListenerOnVibrating(false);
         }
     }
 
@@ -349,51 +344,31 @@
 
     @Override
     public String toString() {
-        synchronized (mLock) {
-            return "VibratorController{"
-                    + "mVibratorInfo=" + mVibratorInfo
-                    + ", mVibratorInfoLoadSuccessful=" + mVibratorInfoLoadSuccessful
-                    + ", mIsVibrating=" + mIsVibrating
-                    + ", mCurrentAmplitude=" + mCurrentAmplitude
-                    + ", mIsUnderExternalControl=" + mIsUnderExternalControl
-                    + ", mVibratorStateListeners count="
-                    + mVibratorStateListeners.getRegisteredCallbackCount()
-                    + '}';
-        }
+        return "VibratorController{"
+                + "mVibratorInfo=" + mVibratorInfo
+                + ", mVibratorInfoLoadSuccessful=" + mVibratorInfoLoadSuccessful
+                + ", mIsVibrating=" + mIsVibrating
+                + ", mCurrentAmplitude=" + mCurrentAmplitude
+                + ", mIsUnderExternalControl=" + mIsUnderExternalControl
+                + ", mVibratorStateListeners count="
+                + mVibratorStateListeners.getRegisteredCallbackCount()
+                + '}';
     }
 
     @GuardedBy("mLock")
-    private void notifyVibratorOnLocked() {
-        if (!mIsVibrating) {
-            mIsVibrating = true;
-            notifyStateListenersLocked();
+    private void notifyListenerOnVibrating(boolean isVibrating) {
+        if (mIsVibrating != isVibrating) {
+            mIsVibrating = isVibrating;
+            // The broadcast method is safe w.r.t. register/unregister listener methods, but lock
+            // is required here to guarantee delivery order.
+            mVibratorStateListeners.broadcast(
+                    listener -> notifyStateListener(listener, isVibrating));
         }
     }
 
-    @GuardedBy("mLock")
-    private void notifyVibratorOffLocked() {
-        if (mIsVibrating) {
-            mIsVibrating = false;
-            notifyStateListenersLocked();
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void notifyStateListenersLocked() {
-        final int length = mVibratorStateListeners.beginBroadcast();
+    private void notifyStateListener(IVibratorStateListener listener, boolean isVibrating) {
         try {
-            for (int i = 0; i < length; i++) {
-                notifyStateListenerLocked(mVibratorStateListeners.getBroadcastItem(i));
-            }
-        } finally {
-            mVibratorStateListeners.finishBroadcast();
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void notifyStateListenerLocked(IVibratorStateListener listener) {
-        try {
-            listener.onVibrating(mIsVibrating);
+            listener.onVibrating(isVibrating);
         } catch (RemoteException | RuntimeException e) {
             Slog.e(TAG, "Vibrator state listener failed to call", e);
         }
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 0675ad6..5d40c23 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -391,6 +391,9 @@
             fillVibrationFallbacks(vib, effect);
 
             synchronized (mLock) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Starting vibrate for vibration  " + vib.id);
+                }
                 Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(vib);
                 if (ignoreStatus != null) {
                     endVibrationLocked(vib, ignoreStatus);
@@ -498,6 +501,9 @@
     @VisibleForTesting
     void updateServiceState() {
         synchronized (mLock) {
+            if (DEBUG) {
+                Slog.d(TAG, "Updating device state...");
+            }
             boolean inputDevicesChanged = mInputDeviceDelegate.updateInputDeviceVibrators(
                     mVibrationSettings.shouldVibrateInputDevices());
 
@@ -611,6 +617,9 @@
         Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
         try {
             Vibration vib = mCurrentVibration.getVibration();
+            if (DEBUG) {
+                Slog.d(TAG, "Reporting vibration " + vib.id + " finished with status " + status);
+            }
             endVibrationLocked(vib, status);
             finishAppOpModeLocked(vib.uid, vib.opPkg);
         } finally {
@@ -1062,11 +1071,17 @@
                 Slog.d(TAG, "Vibrators released after finished vibration");
             }
             synchronized (mLock) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Processing vibrators released callback");
+                }
                 mCurrentVibration = null;
                 if (mNextVibration != null) {
                     VibrationThread vibThread = mNextVibration;
                     mNextVibration = null;
-                    startVibrationThreadLocked(vibThread);
+                    Vibration.Status status = startVibrationThreadLocked(vibThread);
+                    if (status != Vibration.Status.RUNNING) {
+                        endVibrationLocked(vibThread.getVibration(), status);
+                    }
                 }
             }
         }
@@ -1248,6 +1263,9 @@
         void dumpText(PrintWriter pw) {
             pw.println("Vibrator Manager Service:");
             synchronized (mLock) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Dumping vibrator manager service to text...");
+                }
                 pw.println("  mVibrationSettings:");
                 pw.println("    " + mVibrationSettings);
                 pw.println();
@@ -1290,6 +1308,9 @@
             final ProtoOutputStream proto = new ProtoOutputStream(fd);
 
             synchronized (mLock) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Dumping vibrator manager service to proto...");
+                }
                 mVibrationSettings.dumpProto(proto);
                 if (mCurrentVibration != null) {
                     mCurrentVibration.getVibration().getDebugInfo().dumpProto(proto,
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index ea22d92..2a1bb0b 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -6224,15 +6224,15 @@
     public boolean inputDispatchingTimedOut(String reason, int windowPid) {
         ActivityRecord anrActivity;
         WindowProcessController anrApp;
-        boolean windowFromSameProcessAsActivity;
+        boolean blameActivityProcess;
         synchronized (mAtmService.mGlobalLock) {
             anrActivity = getWaitingHistoryRecordLocked();
             anrApp = app;
-            windowFromSameProcessAsActivity =
-                    !hasProcess() || app.getPid() == windowPid || windowPid == INVALID_PID;
+            blameActivityProcess =  hasProcess()
+                    && (app.getPid() == windowPid || windowPid == INVALID_PID);
         }
 
-        if (windowFromSameProcessAsActivity) {
+        if (blameActivityProcess) {
             return mAtmService.mAmInternal.inputDispatchingTimedOut(anrApp.mOwner,
                     anrActivity.shortComponentName, anrActivity.info.applicationInfo,
                     shortComponentName, app, false, reason);
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 210b0ae..ecc8587 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -496,11 +496,13 @@
      * @param activityIntent intent to start the activity.
      * @param activityOptions ActivityOptions to start the activity with.
      * @param resultTo the caller activity
+     * @param callingUid the caller uid
+     * @param callingPid the caller pid
      * @return the start result.
      */
     int startActivityInTaskFragment(@NonNull TaskFragment taskFragment,
             @NonNull Intent activityIntent, @Nullable Bundle activityOptions,
-            @Nullable IBinder resultTo) {
+            @Nullable IBinder resultTo, int callingUid, int callingPid) {
         final ActivityRecord caller =
                 resultTo != null ? ActivityRecord.forTokenLocked(resultTo) : null;
         return obtainStarter(activityIntent, "startActivityInTaskFragment")
@@ -508,8 +510,8 @@
                 .setInTaskFragment(taskFragment)
                 .setResultTo(resultTo)
                 .setRequestCode(-1)
-                .setCallingUid(Binder.getCallingUid())
-                .setCallingPid(Binder.getCallingPid())
+                .setCallingUid(callingUid)
+                .setCallingPid(callingPid)
                 .setUserId(caller != null ? caller.mUserId : mService.getCurrentUserId())
                 .execute();
     }
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index df9a6d2..8788225 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -590,7 +590,7 @@
             }
             // We don't want the organizer to handle transition of non-embedded activity of other
             // app.
-            if (r.getUid() != rootActivity.getUid() && !r.isEmbedded()) {
+            if (r.getUid() != task.effectiveUid && !r.isEmbedded()) {
                 leafTask = null;
                 break;
             }
diff --git a/services/core/java/com/android/server/wm/PackageConfigPersister.java b/services/core/java/com/android/server/wm/PackageConfigPersister.java
index 3de98f1..7a7fb65 100644
--- a/services/core/java/com/android/server/wm/PackageConfigPersister.java
+++ b/services/core/java/com/android/server/wm/PackageConfigPersister.java
@@ -169,21 +169,35 @@
         }
     }
 
+    /**
+     * Returns true when the app specific configuration is successfully stored or removed based on
+     * the current requested configuration. It will return false when the requested
+     * configuration is same as the pre-existing app-specific configuration.
+     */
     @GuardedBy("mLock")
     boolean updateFromImpl(String packageName, int userId,
             PackageConfigurationUpdaterImpl impl) {
         synchronized (mLock) {
-            PackageConfigRecord record = findRecordOrCreate(mModified, packageName, userId);
-            if (impl.getNightMode() != null) {
-                record.mNightMode = impl.getNightMode();
+            boolean isRecordPresent = false;
+            PackageConfigRecord record = findRecord(mModified, packageName, userId);
+            if (record != null) {
+                isRecordPresent = true;
+            } else {
+                record = findRecordOrCreate(mModified, packageName, userId);
             }
-            if (impl.getLocales() != null) {
-                record.mLocales = impl.getLocales();
-            }
+            boolean isNightModeChanged = updateNightMode(impl.getNightMode(), record);
+            boolean isLocalesChanged = updateLocales(impl.getLocales(), record);
+
             if ((record.mNightMode == null || record.isResetNightMode())
                     && (record.mLocales == null || record.mLocales.isEmpty())) {
                 // if all values default to system settings, we can remove the package.
                 removePackage(packageName, userId);
+                // if there was a pre-existing record for the package that was deleted,
+                // we return true (since it was successfully deleted), else false (since there was
+                // no change to the previous state).
+                return isRecordPresent;
+            } else if (!isNightModeChanged && !isLocalesChanged) {
+                return false;
             } else {
                 final PackageConfigRecord pendingRecord =
                         findRecord(mPendingWrite, record.mName, record.mUserId);
@@ -195,7 +209,8 @@
                     writeRecord = pendingRecord;
                 }
 
-                if (!updateNightMode(record, writeRecord) && !updateLocales(record, writeRecord)) {
+                if (!updateNightMode(record.mNightMode, writeRecord)
+                        && !updateLocales(record.mLocales, writeRecord)) {
                     return false;
                 }
 
@@ -203,24 +218,24 @@
                     Slog.d(TAG, "PackageConfigUpdater save config " + writeRecord);
                 }
                 mPersisterQueue.addItem(new WriteProcessItem(writeRecord), false /* flush */);
+                return true;
             }
-            return true;
         }
     }
 
-    private boolean updateNightMode(PackageConfigRecord record, PackageConfigRecord writeRecord) {
-        if (record.mNightMode == null || record.mNightMode.equals(writeRecord.mNightMode)) {
+    private boolean updateNightMode(Integer requestedNightMode, PackageConfigRecord record) {
+        if (requestedNightMode == null || requestedNightMode.equals(record.mNightMode)) {
             return false;
         }
-        writeRecord.mNightMode = record.mNightMode;
+        record.mNightMode = requestedNightMode;
         return true;
     }
 
-    private boolean updateLocales(PackageConfigRecord record, PackageConfigRecord writeRecord) {
-        if (record.mLocales == null || record.mLocales.equals(writeRecord.mLocales)) {
+    private boolean updateLocales(LocaleList requestedLocaleList, PackageConfigRecord record) {
+        if (requestedLocaleList == null || requestedLocaleList.equals(record.mLocales)) {
             return false;
         }
-        writeRecord.mLocales = record.mLocales;
+        record.mLocales = requestedLocaleList;
         return true;
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 0eaa25b..929f221 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1413,29 +1413,29 @@
 
         boolean pauseImmediately = false;
         boolean shouldAutoPip = false;
-        if (resuming != null && (resuming.info.flags & FLAG_RESUME_WHILE_PAUSING) != 0) {
-            // If the flag RESUME_WHILE_PAUSING is set, then continue to schedule the previous
-            // activity to be paused, while at the same time resuming the new resume activity
-            // only if the previous activity can't go into Pip since we want to give Pip
-            // activities a chance to enter Pip before resuming the next activity.
-            final boolean lastResumedCanPip = prev != null && prev.checkEnterPictureInPictureState(
-                    "shouldResumeWhilePausing", userLeaving);
+        if (resuming != null) {
+            // Resuming the new resume activity only if the previous activity can't go into Pip
+            // since we want to give Pip activities a chance to enter Pip before resuming the
+            // next activity.
+            final boolean lastResumedCanPip = prev.checkEnterPictureInPictureState(
+                    "shouldAutoPipWhilePausing", userLeaving);
             if (lastResumedCanPip && prev.pictureInPictureArgs.isAutoEnterEnabled()) {
                 shouldAutoPip = true;
             } else if (!lastResumedCanPip) {
-                pauseImmediately = true;
+                // If the flag RESUME_WHILE_PAUSING is set, then continue to schedule the previous
+                // activity to be paused.
+                pauseImmediately = (resuming.info.flags & FLAG_RESUME_WHILE_PAUSING) != 0;
             } else {
                 // The previous activity may still enter PIP even though it did not allow auto-PIP.
             }
         }
 
-        boolean didAutoPip = false;
         if (prev.attachedToProcess()) {
             if (shouldAutoPip) {
+                boolean didAutoPip = mAtmService.enterPictureInPictureMode(
+                        prev, prev.pictureInPictureArgs);
                 ProtoLog.d(WM_DEBUG_STATES, "Auto-PIP allowed, entering PIP mode "
-                        + "directly: %s", prev);
-
-                didAutoPip = mAtmService.enterPictureInPictureMode(prev, prev.pictureInPictureArgs);
+                        + "directly: %s, didAutoPip: %b", prev, didAutoPip);
             } else {
                 schedulePauseActivity(prev, userLeaving, pauseImmediately, reason);
             }
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index d911656..6d83fb6 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -434,6 +434,9 @@
         private final TaskFragment mTaskFragment;
         private final IBinder mErrorCallback;
         private final Throwable mException;
+        // Set when the event is deferred due to the host task is invisible. The defer time will
+        // be the last active time of the host task.
+        private long mDeferTime;
 
         private PendingTaskFragmentEvent(TaskFragment taskFragment,
                 ITaskFragmentOrganizer taskFragmentOrg, @EventType int eventType) {
@@ -503,11 +506,45 @@
                 || mPendingTaskFragmentEvents.isEmpty()) {
             return;
         }
+
+        final ArrayList<Task> visibleTasks = new ArrayList<>();
+        final ArrayList<Task> invisibleTasks = new ArrayList<>();
+        final ArrayList<PendingTaskFragmentEvent> candidateEvents = new ArrayList<>();
         for (int i = 0, n = mPendingTaskFragmentEvents.size(); i < n; i++) {
-            PendingTaskFragmentEvent event = mPendingTaskFragmentEvents.get(i);
-            dispatchEvent(event);
+            final PendingTaskFragmentEvent event = mPendingTaskFragmentEvents.get(i);
+            final Task task = event.mTaskFragment != null ? event.mTaskFragment.getTask() : null;
+            if (task != null && (task.lastActiveTime <= event.mDeferTime
+                    || !isTaskVisible(task, visibleTasks, invisibleTasks))) {
+                // Defer sending events to the TaskFragment until the host task is active again.
+                event.mDeferTime = task.lastActiveTime;
+                continue;
+            }
+            candidateEvents.add(event);
         }
-        mPendingTaskFragmentEvents.clear();
+        final int numEvents = candidateEvents.size();
+        for (int i = 0; i < numEvents; i++) {
+            dispatchEvent(candidateEvents.get(i));
+        }
+        if (numEvents > 0) {
+            mPendingTaskFragmentEvents.removeAll(candidateEvents);
+        }
+    }
+
+    private static boolean isTaskVisible(Task task, ArrayList<Task> knownVisibleTasks,
+            ArrayList<Task> knownInvisibleTasks) {
+        if (knownVisibleTasks.contains(task)) {
+            return true;
+        }
+        if (knownInvisibleTasks.contains(task)) {
+            return false;
+        }
+        if (task.shouldBeVisible(null /* starting */)) {
+            knownVisibleTasks.add(task);
+            return true;
+        } else {
+            knownInvisibleTasks.add(task);
+            return false;
+        }
     }
 
     void dispatchPendingInfoChangedEvent(TaskFragment taskFragment) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 3d479d1..0649b25 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -587,7 +587,7 @@
             case HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT: {
                 final TaskFragmentCreationParams taskFragmentCreationOptions =
                         hop.getTaskFragmentCreationOptions();
-                createTaskFragment(taskFragmentCreationOptions, errorCallbackToken);
+                createTaskFragment(taskFragmentCreationOptions, errorCallbackToken, caller);
                 break;
             }
             case HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT: {
@@ -630,7 +630,7 @@
                 final TaskFragment tf = mLaunchTaskFragments.get(fragmentToken);
                 final int result = mService.getActivityStartController()
                         .startActivityInTaskFragment(tf, activityIntent, activityOptions,
-                                hop.getCallingActivity());
+                                hop.getCallingActivity(), caller.mUid, caller.mPid);
                 if (!isStartResultSuccessful(result)) {
                     sendTaskFragmentOperationFailure(tf.getTaskFragmentOrganizer(),
                             errorCallbackToken,
@@ -1199,7 +1199,7 @@
     }
 
     void createTaskFragment(@NonNull TaskFragmentCreationParams creationParams,
-            @Nullable IBinder errorCallbackToken) {
+            @Nullable IBinder errorCallbackToken, @NonNull CallerInfo caller) {
         final ActivityRecord ownerActivity =
                 ActivityRecord.forTokenLocked(creationParams.getOwnerToken());
         final ITaskFragmentOrganizer organizer = ITaskFragmentOrganizer.Stub.asInterface(
@@ -1217,9 +1217,9 @@
             sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
             return;
         }
-        // The ownerActivity has to belong to the same app as the root Activity of the target Task.
-        final ActivityRecord rootActivity = ownerActivity.getTask().getRootActivity();
-        if (rootActivity.getUid() != ownerActivity.getUid()) {
+        // The ownerActivity has to belong to the same app as the target Task.
+        if (ownerActivity.getTask().effectiveUid != ownerActivity.getUid()
+                || ownerActivity.getTask().effectiveUid != caller.mUid) {
             final Throwable exception =
                     new IllegalArgumentException("Not allowed to operate with the ownerToken while "
                             + "the root activity of the target task belong to the different app");
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 0a55003..e4c8871 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -29,6 +29,8 @@
 #include <android/hardware/gnss/2.1/IGnssMeasurement.h>
 #include <android/hardware/gnss/BnGnss.h>
 #include <android/hardware/gnss/BnGnssCallback.h>
+#include <android/hardware/gnss/BnGnssGeofence.h>
+#include <android/hardware/gnss/BnGnssGeofenceCallback.h>
 #include <android/hardware/gnss/BnGnssMeasurementCallback.h>
 #include <android/hardware/gnss/BnGnssPowerIndicationCallback.h>
 #include <android/hardware/gnss/BnGnssPsdsCallback.h>
@@ -191,6 +193,9 @@
 using android::hardware::gnss::PsdsType;
 using IGnssAidl = android::hardware::gnss::IGnss;
 using IGnssCallbackAidl = android::hardware::gnss::IGnssCallback;
+using IGnssBatchingAidl = android::hardware::gnss::IGnssBatching;
+using IGnssGeofenceAidl = android::hardware::gnss::IGnssGeofence;
+using IGnssGeofenceCallbackAidl = android::hardware::gnss::IGnssGeofenceCallback;
 using IGnssPsdsAidl = android::hardware::gnss::IGnssPsds;
 using IGnssPsdsCallbackAidl = android::hardware::gnss::IGnssPsdsCallback;
 using IGnssConfigurationAidl = android::hardware::gnss::IGnssConfiguration;
@@ -216,6 +221,8 @@
 sp<IGnss_V2_0> gnssHal_V2_0 = nullptr;
 sp<IGnss_V2_1> gnssHal_V2_1 = nullptr;
 sp<IGnssAidl> gnssHalAidl = nullptr;
+sp<IGnssBatchingAidl> gnssBatchingAidlIface = nullptr;
+sp<IGnssGeofenceAidl> gnssGeofenceAidlIface = nullptr;
 sp<IGnssPsdsAidl> gnssPsdsAidlIface = nullptr;
 sp<IGnssXtra> gnssXtraIface = nullptr;
 sp<IAGnssRil_V1_0> agnssRilIface = nullptr;
@@ -709,35 +716,25 @@
     return Void();
 }
 
-/*
- * GnssGeofenceCallback class implements the callback methods for the
- * IGnssGeofence interface.
- */
-struct GnssGeofenceCallback : public IGnssGeofenceCallback {
-    // Methods from ::android::hardware::gps::V1_0::IGnssGeofenceCallback follow.
-    Return<void> gnssGeofenceTransitionCb(
-            int32_t geofenceId,
-            const GnssLocation_V1_0& location,
-            GeofenceTransition transition,
-            hardware::gnss::V1_0::GnssUtcTime timestamp) override;
-    Return<void>
-    gnssGeofenceStatusCb(
-            GeofenceAvailability status,
-            const GnssLocation_V1_0& location) override;
-    Return<void> gnssGeofenceAddCb(int32_t geofenceId,
-                                   GeofenceStatus status) override;
-    Return<void> gnssGeofenceRemoveCb(int32_t geofenceId,
-                                      GeofenceStatus status) override;
-    Return<void> gnssGeofencePauseCb(int32_t geofenceId,
-                                     GeofenceStatus status) override;
-    Return<void> gnssGeofenceResumeCb(int32_t geofenceId,
-                                      GeofenceStatus status) override;
+/** Util class for GnssGeofenceCallback methods. */
+struct GnssGeofenceCallbackUtil {
+    template <class T>
+    static void gnssGeofenceTransitionCb(int geofenceId, const T& location, int transition,
+                                         int64_t timestampMillis);
+    template <class T>
+    static void gnssGeofenceStatusCb(int availability, const T& lastLocation);
+    static void gnssGeofenceAddCb(int geofenceId, int status);
+    static void gnssGeofenceRemoveCb(int geofenceId, int status);
+    static void gnssGeofencePauseCb(int geofenceId, int status);
+    static void gnssGeofenceResumeCb(int geofenceId, int status);
+
+private:
+    GnssGeofenceCallbackUtil() = delete;
 };
 
-Return<void> GnssGeofenceCallback::gnssGeofenceTransitionCb(
-        int32_t geofenceId, const GnssLocation_V1_0& location,
-        GeofenceTransition transition,
-        hardware::gnss::V1_0::GnssUtcTime timestamp) {
+template <class T>
+void GnssGeofenceCallbackUtil::gnssGeofenceTransitionCb(int geofenceId, const T& location,
+                                                        int transition, int64_t timestamp) {
     JNIEnv* env = getJniEnv();
 
     jobject jLocation = translateGnssLocation(env, location);
@@ -751,27 +748,22 @@
 
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
     env->DeleteLocalRef(jLocation);
-    return Void();
 }
 
-Return<void>
-GnssGeofenceCallback::gnssGeofenceStatusCb(GeofenceAvailability status,
-                                           const GnssLocation_V1_0& location) {
+template <class T>
+void GnssGeofenceCallbackUtil::gnssGeofenceStatusCb(int availability, const T& lastLocation) {
     JNIEnv* env = getJniEnv();
 
-    jobject jLocation = translateGnssLocation(env, location);
+    jobject jLocation = translateGnssLocation(env, lastLocation);
 
-    env->CallVoidMethod(mCallbacksObj, method_reportGeofenceStatus, status,
-                        jLocation);
+    env->CallVoidMethod(mCallbacksObj, method_reportGeofenceStatus, availability, jLocation);
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
     env->DeleteLocalRef(jLocation);
-    return Void();
 }
 
-Return<void> GnssGeofenceCallback::gnssGeofenceAddCb(int32_t geofenceId,
-                                                    GeofenceStatus status) {
+void GnssGeofenceCallbackUtil::gnssGeofenceAddCb(int geofenceId, int status) {
     JNIEnv* env = getJniEnv();
-    if (status != IGnssGeofenceCallback::GeofenceStatus::OPERATION_SUCCESS) {
+    if (status != IGnssGeofenceCallbackAidl::OPERATION_SUCCESS) {
         ALOGE("%s: Error in adding a Geofence: %d\n", __func__, status);
     }
 
@@ -780,13 +772,11 @@
                         geofenceId,
                         status);
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
-    return Void();
 }
 
-Return<void> GnssGeofenceCallback::gnssGeofenceRemoveCb(int32_t geofenceId,
-                                                       GeofenceStatus status) {
+void GnssGeofenceCallbackUtil::gnssGeofenceRemoveCb(int geofenceId, int status) {
     JNIEnv* env = getJniEnv();
-    if (status != IGnssGeofenceCallback::GeofenceStatus::OPERATION_SUCCESS) {
+    if (status != IGnssGeofenceCallbackAidl::OPERATION_SUCCESS) {
         ALOGE("%s: Error in removing a Geofence: %d\n", __func__, status);
     }
 
@@ -794,13 +784,11 @@
                         method_reportGeofenceRemoveStatus,
                         geofenceId, status);
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
-    return Void();
 }
 
-Return<void> GnssGeofenceCallback::gnssGeofencePauseCb(int32_t geofenceId,
-                                                      GeofenceStatus status) {
+void GnssGeofenceCallbackUtil::gnssGeofencePauseCb(int geofenceId, int status) {
     JNIEnv* env = getJniEnv();
-    if (status != IGnssGeofenceCallback::GeofenceStatus::OPERATION_SUCCESS) {
+    if (status != IGnssGeofenceCallbackAidl::OPERATION_SUCCESS) {
         ALOGE("%s: Error in pausing Geofence: %d\n", __func__, status);
     }
 
@@ -808,13 +796,11 @@
                         method_reportGeofencePauseStatus,
                         geofenceId, status);
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
-    return Void();
 }
 
-Return<void> GnssGeofenceCallback::gnssGeofenceResumeCb(int32_t geofenceId,
-                                                       GeofenceStatus status) {
+void GnssGeofenceCallbackUtil::gnssGeofenceResumeCb(int geofenceId, int status) {
     JNIEnv* env = getJniEnv();
-    if (status != IGnssGeofenceCallback::GeofenceStatus::OPERATION_SUCCESS) {
+    if (status != IGnssGeofenceCallbackAidl::OPERATION_SUCCESS) {
         ALOGE("%s: Error in resuming Geofence: %d\n", __func__, status);
     }
 
@@ -822,6 +808,104 @@
                         method_reportGeofenceResumeStatus,
                         geofenceId, status);
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
+}
+
+/*
+ * GnssGeofenceCallbackAidl class implements the callback methods for the IGnssGeofence AIDL
+ * interface.
+ */
+struct GnssGeofenceCallbackAidl : public android::hardware::gnss::BnGnssGeofenceCallback {
+    Status gnssGeofenceTransitionCb(int geofenceId, const GnssLocationAidl& location,
+                                    int transition, int64_t timestampMillis) override;
+    Status gnssGeofenceStatusCb(int availability, const GnssLocationAidl& lastLocation) override;
+    Status gnssGeofenceAddCb(int geofenceId, int status) override;
+    Status gnssGeofenceRemoveCb(int geofenceId, int status) override;
+    Status gnssGeofencePauseCb(int geofenceId, int status) override;
+    Status gnssGeofenceResumeCb(int geofenceId, int status) override;
+};
+
+Status GnssGeofenceCallbackAidl::gnssGeofenceTransitionCb(int geofenceId,
+                                                          const GnssLocationAidl& location,
+                                                          int transition, int64_t timestampMillis) {
+    GnssGeofenceCallbackUtil::gnssGeofenceTransitionCb(geofenceId, location, transition,
+                                                       timestampMillis);
+    return Status::ok();
+}
+
+Status GnssGeofenceCallbackAidl::gnssGeofenceStatusCb(int availability,
+                                                      const GnssLocationAidl& lastLocation) {
+    GnssGeofenceCallbackUtil::gnssGeofenceStatusCb(availability, lastLocation);
+    return Status::ok();
+}
+
+Status GnssGeofenceCallbackAidl::gnssGeofenceAddCb(int geofenceId, int status) {
+    GnssGeofenceCallbackUtil::gnssGeofenceAddCb(geofenceId, status);
+    return Status::ok();
+}
+
+Status GnssGeofenceCallbackAidl::gnssGeofenceRemoveCb(int geofenceId, int status) {
+    GnssGeofenceCallbackUtil::gnssGeofenceRemoveCb(geofenceId, status);
+    return Status::ok();
+}
+
+Status GnssGeofenceCallbackAidl::gnssGeofencePauseCb(int geofenceId, int status) {
+    GnssGeofenceCallbackUtil::gnssGeofencePauseCb(geofenceId, status);
+    return Status::ok();
+}
+
+Status GnssGeofenceCallbackAidl::gnssGeofenceResumeCb(int geofenceId, int status) {
+    GnssGeofenceCallbackUtil::gnssGeofenceResumeCb(geofenceId, status);
+    return Status::ok();
+}
+
+/*
+ * GnssGeofenceCallback class implements the callback methods for the
+ * IGnssGeofence HIDL interface.
+ */
+struct GnssGeofenceCallback : public IGnssGeofenceCallback {
+    // Methods from ::android::hardware::gps::V1_0::IGnssGeofenceCallback follow.
+    Return<void> gnssGeofenceTransitionCb(int32_t geofenceId, const GnssLocation_V1_0& location,
+                                          GeofenceTransition transition,
+                                          hardware::gnss::V1_0::GnssUtcTime timestamp) override;
+    Return<void> gnssGeofenceStatusCb(GeofenceAvailability status,
+                                      const GnssLocation_V1_0& location) override;
+    Return<void> gnssGeofenceAddCb(int32_t geofenceId, GeofenceStatus status) override;
+    Return<void> gnssGeofenceRemoveCb(int32_t geofenceId, GeofenceStatus status) override;
+    Return<void> gnssGeofencePauseCb(int32_t geofenceId, GeofenceStatus status) override;
+    Return<void> gnssGeofenceResumeCb(int32_t geofenceId, GeofenceStatus status) override;
+};
+
+Return<void> GnssGeofenceCallback::gnssGeofenceTransitionCb(
+        int32_t geofenceId, const GnssLocation_V1_0& location, GeofenceTransition transition,
+        hardware::gnss::V1_0::GnssUtcTime timestamp) {
+    GnssGeofenceCallbackUtil::gnssGeofenceTransitionCb(geofenceId, location, (int)transition,
+                                                       (int64_t)timestamp);
+    return Void();
+}
+
+Return<void> GnssGeofenceCallback::gnssGeofenceStatusCb(GeofenceAvailability availability,
+                                                        const GnssLocation_V1_0& location) {
+    GnssGeofenceCallbackUtil::gnssGeofenceStatusCb((int)availability, location);
+    return Void();
+}
+
+Return<void> GnssGeofenceCallback::gnssGeofenceAddCb(int32_t geofenceId, GeofenceStatus status) {
+    GnssGeofenceCallbackUtil::gnssGeofenceAddCb(geofenceId, (int)status);
+    return Void();
+}
+
+Return<void> GnssGeofenceCallback::gnssGeofenceRemoveCb(int32_t geofenceId, GeofenceStatus status) {
+    GnssGeofenceCallbackUtil::gnssGeofenceRemoveCb(geofenceId, (int)status);
+    return Void();
+}
+
+Return<void> GnssGeofenceCallback::gnssGeofencePauseCb(int32_t geofenceId, GeofenceStatus status) {
+    GnssGeofenceCallbackUtil::gnssGeofencePauseCb(geofenceId, (int)status);
+    return Void();
+}
+
+Return<void> GnssGeofenceCallback::gnssGeofenceResumeCb(int32_t geofenceId, GeofenceStatus status) {
+    GnssGeofenceCallbackUtil::gnssGeofenceResumeCb(geofenceId, (int)status);
     return Void();
 }
 
@@ -1279,11 +1363,13 @@
         android_location_gnss_hal_GnssNative_set_gps_service_handle();
     }
 
-    if (gnssHal == nullptr) {
+    if (gnssHal == nullptr && gnssHalAidl == nullptr) {
         ALOGE("Unable to get GPS service\n");
         return;
     }
 
+    // TODO: linkToDeath for AIDL HAL
+
     gnssHalDeathRecipient = new GnssDeathRecipient();
     hardware::Return<bool> linked = gnssHal->linkToDeath(gnssHalDeathRecipient, /*cookie*/ 0);
     if (!linked.isOk()) {
@@ -1303,7 +1389,7 @@
         } else {
             ALOGD("Unable to get a handle to PSDS AIDL interface.");
         }
-    } else {
+    } else if (gnssHal != nullptr) {
         auto gnssXtra = gnssHal->getExtensionXtra();
         if (!gnssXtra.isOk()) {
             ALOGD("Unable to get a handle to Xtra");
@@ -1320,7 +1406,7 @@
             agnssRilIface_V2_0 = agnssRil_V2_0;
             agnssRilIface = agnssRilIface_V2_0;
         }
-    } else {
+    } else if (gnssHal != nullptr) {
         auto agnssRil_V1_0 = gnssHal->getExtensionAGnssRil();
         if (!agnssRil_V1_0.isOk()) {
             ALOGD("Unable to get a handle to AGnssRil");
@@ -1336,7 +1422,7 @@
         } else {
             agnssIface_V2_0 = agnss_V2_0;
         }
-    } else {
+    } else if (gnssHal != nullptr) {
         auto agnss_V1_0 = gnssHal->getExtensionAGnss();
         if (!agnss_V1_0.isOk()) {
             ALOGD("Unable to get a handle to AGnss");
@@ -1345,11 +1431,13 @@
         }
     }
 
-    auto gnssNavigationMessage = gnssHal->getExtensionGnssNavigationMessage();
-    if (!gnssNavigationMessage.isOk()) {
-        ALOGD("Unable to get a handle to GnssNavigationMessage");
-    } else {
-        gnssNavigationMessageIface = gnssNavigationMessage;
+    if (gnssHal != nullptr) {
+        auto gnssNavigationMessage = gnssHal->getExtensionGnssNavigationMessage();
+        if (!gnssNavigationMessage.isOk()) {
+            ALOGD("Unable to get a handle to GnssNavigationMessage");
+        } else {
+            gnssNavigationMessageIface = gnssNavigationMessage;
+        }
     }
 
     // Allow all causal combinations between IGnss.hal and IGnssMeasurement.hal. That means,
@@ -1387,12 +1475,12 @@
                     std::make_unique<android::gnss::GnssMeasurement_V1_1>(gnssMeasurement);
         }
     }
-    if (gnssMeasurementIface == nullptr) {
-         auto gnssMeasurement = gnssHal->getExtensionGnssMeasurement();
-         if (checkHidlReturn(gnssMeasurement, "Unable to get a handle to GnssMeasurement_V1_0")) {
-             gnssMeasurementIface =
-                     std::make_unique<android::gnss::GnssMeasurement_V1_0>(gnssMeasurement);
-         }
+    if (gnssHal != nullptr && gnssMeasurementIface == nullptr) {
+        auto gnssMeasurement = gnssHal->getExtensionGnssMeasurement();
+        if (checkHidlReturn(gnssMeasurement, "Unable to get a handle to GnssMeasurement_V1_0")) {
+            gnssMeasurementIface =
+                    std::make_unique<android::gnss::GnssMeasurement_V1_0>(gnssMeasurement);
+        }
     }
 
     if (gnssHal_V2_1 != nullptr) {
@@ -1434,7 +1522,7 @@
             gnssDebugIface = gnssDebugIface_V2_0;
         }
     }
-    if (gnssDebugIface == nullptr) {
+    if (gnssHal != nullptr && gnssDebugIface == nullptr) {
         auto gnssDebug = gnssHal->getExtensionGnssDebug();
         if (!gnssDebug.isOk()) {
             ALOGD("Unable to get a handle to GnssDebug");
@@ -1443,11 +1531,13 @@
         }
     }
 
-    auto gnssNi = gnssHal->getExtensionGnssNi();
-    if (!gnssNi.isOk()) {
-        ALOGD("Unable to get a handle to GnssNi");
-    } else {
-        gnssNiIface = gnssNi;
+    if (gnssHal != nullptr) {
+        auto gnssNi = gnssHal->getExtensionGnssNi();
+        if (!gnssNi.isOk()) {
+            ALOGD("Unable to get a handle to GnssNi");
+        } else {
+            gnssNiIface = gnssNi;
+        }
     }
 
     if (gnssHalAidl != nullptr) {
@@ -1488,11 +1578,17 @@
         }
     }
 
-    auto gnssGeofencing = gnssHal->getExtensionGnssGeofencing();
-    if (!gnssGeofencing.isOk()) {
-        ALOGD("Unable to get a handle to GnssGeofencing");
-    } else {
-        gnssGeofencingIface = gnssGeofencing;
+    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+        sp<IGnssGeofenceAidl> gnssGeofenceAidl;
+        auto status = gnssHalAidl->getExtensionGnssGeofence(&gnssGeofenceAidl);
+        if (checkAidlStatus(status, "Unable to get a handle to GnssGeofence interface.")) {
+            gnssGeofenceAidlIface = gnssGeofenceAidl;
+        }
+    } else if (gnssHal != nullptr) {
+        auto gnssGeofencing = gnssHal->getExtensionGnssGeofencing();
+        if (checkHidlReturn(gnssGeofencing, "Unable to get a handle to GnssGeofencing")) {
+            gnssGeofencingIface = gnssGeofencing;
+        }
     }
 
     if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
@@ -1507,7 +1603,7 @@
             gnssBatchingIface = std::make_unique<gnss::GnssBatching_V2_0>(gnssBatching_V2_0);
         }
     }
-    if (gnssBatchingIface == nullptr) {
+    if (gnssHal != nullptr && gnssBatchingIface == nullptr) {
         auto gnssBatching_V1_0 = gnssHal->getExtensionGnssBatching();
         if (checkHidlReturn(gnssBatching_V1_0, "Unable to get a handle to GnssBatching")) {
             gnssBatchingIface = std::make_unique<gnss::GnssBatching_V1_0>(gnssBatching_V1_0);
@@ -1568,7 +1664,7 @@
     /*
      * Fail if the main interface fails to initialize
      */
-    if (gnssHal == nullptr) {
+    if (gnssHal == nullptr && gnssHalAidl == nullptr) {
         ALOGE("Unable to initialize GNSS HAL.");
         return JNI_FALSE;
     }
@@ -1583,7 +1679,7 @@
         result = gnssHal_V2_0->setCallback_2_0(gnssCbIface);
     } else if (gnssHal_V1_1 != nullptr) {
         result = gnssHal_V1_1->setCallback_1_1(gnssCbIface);
-    } else {
+    } else if (gnssHal != nullptr) {
         result = gnssHal->setCallback(gnssCbIface);
     }
 
@@ -1630,10 +1726,18 @@
     }
 
     // Set IGnssGeofencing.hal callback.
-    sp<IGnssGeofenceCallback> gnssGeofencingCbIface = new GnssGeofenceCallback();
-    if (gnssGeofencingIface != nullptr) {
+    if (gnssGeofenceAidlIface != nullptr) {
+        sp<IGnssGeofenceCallbackAidl> gnssGeofenceCallbackAidl = new GnssGeofenceCallbackAidl();
+        auto status = gnssGeofenceAidlIface->setCallback(gnssGeofenceCallbackAidl);
+        if (!checkAidlStatus(status, "IGnssGeofenceAidl setCallback() failed.")) {
+            gnssGeofenceAidlIface = nullptr;
+        }
+    } else if (gnssGeofencingIface != nullptr) {
+        sp<IGnssGeofenceCallback> gnssGeofencingCbIface = new GnssGeofenceCallback();
         auto status = gnssGeofencingIface->setCallback(gnssGeofencingCbIface);
-        checkHidlReturn(status, "IGnssGeofencing setCallback() failed.");
+        if (!checkHidlReturn(status, "IGnssGeofencing setCallback() failed.")) {
+            gnssGeofencingIface = nullptr;
+        }
     } else {
         ALOGI("Unable to initialize IGnssGeofencing interface.");
     }
@@ -1693,12 +1797,15 @@
 }
 
 static void android_location_gnss_hal_GnssNative_cleanup(JNIEnv* /* env */, jclass) {
-    if (gnssHal == nullptr) {
-        return;
+    if (gnssHalAidl != nullptr) {
+        auto status = gnssHalAidl->close();
+        checkAidlStatus(status, "IGnssAidl close() failed.");
     }
 
-    auto result = gnssHal->cleanup();
-    checkHidlReturn(result, "IGnss cleanup() failed.");
+    if (gnssHal != nullptr) {
+        auto result = gnssHal->cleanup();
+        checkHidlReturn(result, "IGnss cleanup() failed.");
+    }
 }
 
 static jboolean android_location_gnss_hal_GnssNative_set_position_mode(
@@ -2177,57 +2284,85 @@
 
 static jboolean android_location_gnss_hal_GnssNative_is_geofence_supported(JNIEnv* /* env */,
                                                                            jclass) {
-    return (gnssGeofencingIface != nullptr) ? JNI_TRUE : JNI_FALSE;
+    if (gnssGeofencingIface == nullptr && gnssGeofenceAidlIface == nullptr) {
+        return JNI_FALSE;
+    }
+    return JNI_TRUE;
 }
 
 static jboolean android_location_gnss_hal_GnssNative_add_geofence(
         JNIEnv* /* env */, jclass, jint geofenceId, jdouble latitude, jdouble longitude,
         jdouble radius, jint last_transition, jint monitor_transition,
         jint notification_responsiveness, jint unknown_timer) {
-    if (gnssGeofencingIface == nullptr) {
-        ALOGE("%s: IGnssGeofencing interface not available.", __func__);
-        return JNI_FALSE;
+    if (gnssGeofenceAidlIface != nullptr) {
+        auto status =
+                gnssGeofenceAidlIface->addGeofence(geofenceId, latitude, longitude, radius,
+                                                   last_transition, monitor_transition,
+                                                   notification_responsiveness, unknown_timer);
+        return checkAidlStatus(status, "IGnssGeofenceAidl addGeofence() failed.");
     }
 
-    auto result = gnssGeofencingIface->addGeofence(
-            geofenceId, latitude, longitude, radius,
-            static_cast<IGnssGeofenceCallback::GeofenceTransition>(last_transition),
-            monitor_transition, notification_responsiveness, unknown_timer);
-    return checkHidlReturn(result, "IGnssGeofencing addGeofence() failed.");
+    if (gnssGeofencingIface != nullptr) {
+        auto result = gnssGeofencingIface
+                              ->addGeofence(geofenceId, latitude, longitude, radius,
+                                            static_cast<IGnssGeofenceCallback::GeofenceTransition>(
+                                                    last_transition),
+                                            monitor_transition, notification_responsiveness,
+                                            unknown_timer);
+        return checkHidlReturn(result, "IGnssGeofencing addGeofence() failed.");
+    }
+
+    ALOGE("%s: IGnssGeofencing interface not available.", __func__);
+    return JNI_FALSE;
 }
 
 static jboolean android_location_gnss_hal_GnssNative_remove_geofence(JNIEnv* /* env */, jclass,
                                                                      jint geofenceId) {
-    if (gnssGeofencingIface == nullptr) {
-        ALOGE("%s: IGnssGeofencing interface not available.", __func__);
-        return JNI_FALSE;
+    if (gnssGeofenceAidlIface != nullptr) {
+        auto status = gnssGeofenceAidlIface->removeGeofence(geofenceId);
+        return checkAidlStatus(status, "IGnssGeofenceAidl removeGeofence() failed.");
     }
 
-    auto result = gnssGeofencingIface->removeGeofence(geofenceId);
-    return checkHidlReturn(result, "IGnssGeofencing removeGeofence() failed.");
+    if (gnssGeofencingIface != nullptr) {
+        auto result = gnssGeofencingIface->removeGeofence(geofenceId);
+        return checkHidlReturn(result, "IGnssGeofencing removeGeofence() failed.");
+    }
+
+    ALOGE("%s: IGnssGeofencing interface not available.", __func__);
+    return JNI_FALSE;
 }
 
 static jboolean android_location_gnss_hal_GnssNative_pause_geofence(JNIEnv* /* env */, jclass,
                                                                     jint geofenceId) {
-    if (gnssGeofencingIface == nullptr) {
-        ALOGE("%s: IGnssGeofencing interface not available.", __func__);
-        return JNI_FALSE;
+    if (gnssGeofenceAidlIface != nullptr) {
+        auto status = gnssGeofenceAidlIface->pauseGeofence(geofenceId);
+        return checkAidlStatus(status, "IGnssGeofenceAidl pauseGeofence() failed.");
     }
 
-    auto result = gnssGeofencingIface->pauseGeofence(geofenceId);
-    return checkHidlReturn(result, "IGnssGeofencing pauseGeofence() failed.");
+    if (gnssGeofencingIface != nullptr) {
+        auto result = gnssGeofencingIface->pauseGeofence(geofenceId);
+        return checkHidlReturn(result, "IGnssGeofencing pauseGeofence() failed.");
+    }
+
+    ALOGE("%s: IGnssGeofencing interface not available.", __func__);
+    return JNI_FALSE;
 }
 
 static jboolean android_location_gnss_hal_GnssNative_resume_geofence(JNIEnv* /* env */, jclass,
                                                                      jint geofenceId,
                                                                      jint monitor_transition) {
-    if (gnssGeofencingIface == nullptr) {
-        ALOGE("%s: IGnssGeofencing interface not available.", __func__);
-        return JNI_FALSE;
+    if (gnssGeofenceAidlIface != nullptr) {
+        auto status = gnssGeofenceAidlIface->resumeGeofence(geofenceId, monitor_transition);
+        return checkAidlStatus(status, "IGnssGeofenceAidl resumeGeofence() failed.");
     }
 
-    auto result = gnssGeofencingIface->resumeGeofence(geofenceId, monitor_transition);
-    return checkHidlReturn(result, "IGnssGeofencing resumeGeofence() failed.");
+    if (gnssGeofencingIface != nullptr) {
+        auto result = gnssGeofencingIface->resumeGeofence(geofenceId, monitor_transition);
+        return checkHidlReturn(result, "IGnssGeofencing resumeGeofence() failed.");
+    }
+
+    ALOGE("%s: IGnssGeofencing interface not available.", __func__);
+    return JNI_FALSE;
 }
 
 static jboolean android_location_gnss_hal_GnssNative_is_antenna_info_supported(JNIEnv* env,
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 727f265..3839a9f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -8460,7 +8460,9 @@
     @Override
     public boolean hasDeviceOwner() {
         final CallerIdentity caller = getCallerIdentity();
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || canManageUsers(caller));
+        Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+                        || canManageUsers(caller)
+                        || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
         return mOwners.hasDeviceOwner();
     }
 
@@ -8640,7 +8642,8 @@
         if (!mHasFeature) {
             return null;
         }
-        Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
+        Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity())
+                || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
 
         synchronized (getLockObject()) {
             if (!mOwners.hasDeviceOwner()) {
@@ -8986,7 +8989,8 @@
             return DevicePolicyManager.STATE_USER_UNMANAGED;
         }
         final CallerIdentity caller = getCallerIdentity();
-        Preconditions.checkCallAuthorization(canManageUsers(caller));
+        Preconditions.checkCallAuthorization(canManageUsers(caller)
+                || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
 
         return getUserProvisioningState(caller.getUserId());
     }
@@ -9240,7 +9244,8 @@
         if (!mHasFeature) {
             return null;
         }
-        Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
+        Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity())
+                || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
         return getProfileOwnerNameUnchecked(userHandle);
     }
 
@@ -16466,7 +16471,8 @@
         if (!mHasFeature) {
             return false;
         }
-        Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
+        Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity())
+                || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
 
         long id = mInjector.binderClearCallingIdentity();
         try {
@@ -16492,7 +16498,8 @@
         if (!mHasFeature) {
             return false;
         }
-        Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
+        Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity())
+                || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
 
         return mInjector.binderWithCleanCallingIdentity(() -> isUnattendedManagedKioskUnchecked());
     }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 6d542bf..2f9e334 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -267,6 +267,8 @@
             "com.android.server.stats.StatsCompanion$Lifecycle";
     private static final String STATS_PULL_ATOM_SERVICE_CLASS =
             "com.android.server.stats.pull.StatsPullAtomService";
+    private static final String STATS_BOOTSTRAP_ATOM_SERVICE_LIFECYCLE_CLASS =
+            "com.android.server.stats.bootstrap.StatsBootstrapAtomService$Lifecycle";
     private static final String USB_SERVICE_CLASS =
             "com.android.server.usb.UsbService$Lifecycle";
     private static final String MIDI_SERVICE_CLASS =
@@ -371,6 +373,8 @@
             "com.android.server.blob.BlobStoreManagerService";
     private static final String APP_SEARCH_MANAGER_SERVICE_CLASS =
             "com.android.server.appsearch.AppSearchManagerService";
+    private static final String ISOLATED_COMPILATION_SERVICE_CLASS =
+            "com.android.server.compos.IsolatedCompilationService";
     private static final String ROLLBACK_MANAGER_SERVICE_CLASS =
             "com.android.server.rollback.RollbackManagerService";
     private static final String ALARM_MANAGER_SERVICE_CLASS =
@@ -2541,6 +2545,11 @@
         mSystemServiceManager.startService(STATS_PULL_ATOM_SERVICE_CLASS);
         t.traceEnd();
 
+        // Log atoms to statsd from bootstrap processes.
+        t.traceBegin("StatsBootstrapAtomService");
+        mSystemServiceManager.startService(STATS_BOOTSTRAP_ATOM_SERVICE_LIFECYCLE_CLASS);
+        t.traceEnd();
+
         // Incidentd and dumpstated helper
         t.traceBegin("StartIncidentCompanionService");
         mSystemServiceManager.startService(IncidentCompanionService.class);
@@ -2711,6 +2720,12 @@
         mSystemServiceManager.startService(APP_SEARCH_MANAGER_SERVICE_CLASS);
         t.traceEnd();
 
+        if (SystemProperties.getBoolean("ro.config.isolated_compilation_enabled", false)) {
+            t.traceBegin("IsolatedCompilationService");
+            mSystemServiceManager.startService(ISOLATED_COMPILATION_SERVICE_CLASS);
+            t.traceEnd();
+        }
+
         t.traceBegin("StartMediaCommunicationService");
         mSystemServiceManager.startService(MEDIA_COMMUNICATION_SERVICE_CLASS);
         t.traceEnd();
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index fb7ef84..62a16f7 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -91,10 +91,12 @@
             if (mIProfcollect == null) {
                 return;
             }
-            if (serviceHasSupportedTraceProvider()) {
-                registerObservers();
-            }
-            ProfcollectBGJobService.schedule(getContext());
+            BackgroundThread.get().getThreadHandler().post(() -> {
+                if (serviceHasSupportedTraceProvider()) {
+                    registerObservers();
+                    ProfcollectBGJobService.schedule(getContext());
+                }
+            });
         }
     }
 
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index 349eb22..dc93e53 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -223,6 +223,7 @@
         AndroidPackage::isVendor,
         AndroidPackage::isVisibleToInstantApps,
         AndroidPackage::isVmSafeMode,
+        AndroidPackage::isResetEnabledSettingsOnAppDataCleared,
         AndroidPackage::getMaxAspectRatio,
         AndroidPackage::getMinAspectRatio,
         AndroidPackage::hasPreserveLegacyExternalStorage,
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt
index bbfa461..cfc81e6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt
@@ -29,6 +29,7 @@
 import com.android.server.apphibernation.AppHibernationService
 import com.android.server.extendedtestutils.wheneverStatic
 import com.android.server.testutils.whenever
+import org.junit.Assert
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Before
@@ -36,7 +37,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
@@ -102,9 +102,10 @@
         val pm = createPackageManagerService()
         rule.system().validateFinalState()
 
-        pm.setPackageStoppedState(TEST_PACKAGE_NAME, true, TEST_USER_ID)
         TestableLooper.get(this).processAllMessages()
-        Mockito.clearInvocations(appHibernationManager)
+
+        whenever(appHibernationManager.isHibernatingForUser(TEST_PACKAGE_NAME, TEST_USER_ID))
+            .thenReturn(true)
 
         pm.setPackageStoppedState(TEST_PACKAGE_NAME, false, TEST_USER_ID)
 
@@ -115,6 +116,31 @@
     }
 
     @Test
+    fun testExitForceStop_nonExistingAppHibernationManager_doesNotThrowException() {
+        whenever(rule.mocks().injector.getLocalService(AppHibernationManagerInternal::class.java))
+            .thenReturn(null)
+
+        rule.system().stageScanExistingPackage(
+            TEST_PACKAGE_NAME,
+            1L,
+            rule.system().dataAppDirectory)
+        val pm = createPackageManagerService()
+        rule.system().validateFinalState()
+
+        TestableLooper.get(this).processAllMessages()
+
+        whenever(appHibernationManager.isHibernatingForUser(TEST_PACKAGE_NAME, TEST_USER_ID))
+            .thenReturn(true)
+
+        try {
+            pm.setPackageStoppedState(TEST_PACKAGE_NAME, false, TEST_USER_ID)
+            TestableLooper.get(this).processAllMessages()
+        } catch (e: Exception) {
+            Assert.fail("Method throws exception when AppHibernationManager is not ready.\n$e")
+        }
+    }
+
+    @Test
     fun testGetOptimizablePackages_ExcludesGloballyHibernatingPackages() {
         rule.system().stageScanExistingPackage(
             TEST_PACKAGE_NAME,
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index ba580ec..e3c60fd 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -108,6 +108,7 @@
 
     data: [
         ":JobTestApp",
+        ":StubTestApp",
     ],
 
     java_resources: [
diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml
index 5a0f1ee..4c638d6 100644
--- a/services/tests/servicestests/AndroidTest.xml
+++ b/services/tests/servicestests/AndroidTest.xml
@@ -28,6 +28,17 @@
         <option name="test-file-name" value="SimpleServiceTestApp3.apk" />
     </target_preparer>
 
+    <!-- Create place to store apks -->
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="mkdir -p /data/local/tmp/servicestests" />
+        <option name="teardown-command" value="rm -rf /data/local/tmp/servicestests"/>
+    </target_preparer>
+
+    <!-- Load additional APKs onto device -->
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="push" value="StubTestApp.apk->/data/local/tmp/servicestests/StubTestApp.apk"/>
+    </target_preparer>
+
     <option name="test-tag" value="FrameworksServicesTests" />
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.android.frameworks.servicestests" />
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
index 734fdba..6a85c8b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.pm;
 
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.fail;
@@ -27,11 +29,17 @@
 import static java.lang.reflect.Modifier.isStatic;
 
 import android.annotation.Nullable;
+import android.app.AppGlobals;
 import android.content.IIntentReceiver;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.SparseArray;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.HexDump;
@@ -42,6 +50,7 @@
 
 import org.junit.After;
 import org.junit.Assert;
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -62,6 +71,11 @@
 // bit FrameworksServicesTests:com.android.server.pm.PackageManagerServiceTest
 @RunWith(AndroidJUnit4.class)
 public class PackageManagerServiceTest {
+
+    private static final String TEST_DATA_PATH = "/data/local/tmp/servicestests/";
+    private static final String TEST_APP_APK = "StubTestApp.apk";
+    private static final String TEST_PKG_NAME = "com.android.servicestests.apps.stubapp";
+
     @Before
     public void setUp() throws Exception {
     }
@@ -509,6 +523,11 @@
         for (Method m : coreMethods) {
             if (m != null) {
                 final String name = "ComputerEngine." + displayName(m);
+                if (name.contains(".lambda$static")) {
+                    // skip static lambda function
+                    continue;
+                }
+
                 final int modifiers = m.getModifiers();
                 if (isPrivate(modifiers)) {
                     // Okay
@@ -598,4 +617,119 @@
         Collections.sort(knownPackageIds);
         return knownPackageIds;
     }
+
+    @Test
+    public void testInstallReason_afterUpdate_keepUnchanged() throws Exception {
+        final IPackageManager pm = AppGlobals.getPackageManager();
+        final File testApk = new File(TEST_DATA_PATH, TEST_APP_APK);
+        try {
+            // Try to install test APK with reason INSTALL_REASON_POLICY
+            runShellCommand("pm install --install-reason 1 " + testApk);
+            assertWithMessage("The install reason of test APK is incorrect.").that(
+                    pm.getInstallReason(TEST_PKG_NAME, UserHandle.myUserId())).isEqualTo(
+                    PackageManager.INSTALL_REASON_POLICY);
+
+            // Try to update test APK with different reason INSTALL_REASON_USER
+            runShellCommand("pm install --install-reason 4 " + testApk);
+            assertWithMessage("The install reason should keep unchanged after update.").that(
+                    pm.getInstallReason(TEST_PKG_NAME, UserHandle.myUserId())).isEqualTo(
+                    PackageManager.INSTALL_REASON_POLICY);
+        } finally {
+            runShellCommand("pm uninstall " + TEST_PKG_NAME);
+        }
+    }
+
+    @Test
+    public void testInstallReason_userRemainsUninstalled_keepUnknown() throws Exception {
+        Assume.assumeTrue(UserManager.supportsMultipleUsers());
+        final IPackageManager pm = AppGlobals.getPackageManager();
+        final UserManager um = UserManager.get(
+                InstrumentationRegistry.getInstrumentation().getContext());
+        final File testApk = new File(TEST_DATA_PATH, TEST_APP_APK);
+        int userId = UserHandle.USER_NULL;
+        try {
+            // Try to install test APK with reason INSTALL_REASON_POLICY
+            runShellCommand("pm install --install-reason 1 " + testApk);
+            assertWithMessage("The install reason of test APK is incorrect.").that(
+                    pm.getInstallReason(TEST_PKG_NAME, UserHandle.myUserId())).isEqualTo(
+                    PackageManager.INSTALL_REASON_POLICY);
+
+            // Create and start the 2nd user.
+            userId = um.createUser("Test User", 0 /* flags */).getUserHandle().getIdentifier();
+            runShellCommand("am start-user -w " + userId);
+            // Since the test APK isn't installed on the 2nd user, the reason should be unknown.
+            assertWithMessage("The install reason in 2nd user should be unknown.").that(
+                    pm.getInstallReason(TEST_PKG_NAME, userId)).isEqualTo(
+                    PackageManager.INSTALL_REASON_UNKNOWN);
+
+            // Try to update test APK with different reason INSTALL_REASON_USER
+            runShellCommand("pm install --install-reason 4 " + testApk);
+            assertWithMessage("The install reason in 2nd user should keep unknown.").that(
+                    pm.getInstallReason(TEST_PKG_NAME, userId)).isEqualTo(
+                    PackageManager.INSTALL_REASON_UNKNOWN);
+        } finally {
+            runShellCommand("pm uninstall " + TEST_PKG_NAME);
+            if (userId != UserHandle.USER_NULL) {
+                um.removeUser(userId);
+            }
+        }
+    }
+
+    @Test
+    public void testInstallReason_installForAllUsers_sameReason() throws Exception {
+        Assume.assumeTrue(UserManager.supportsMultipleUsers());
+        final IPackageManager pm = AppGlobals.getPackageManager();
+        final UserManager um = UserManager.get(
+                InstrumentationRegistry.getInstrumentation().getContext());
+        final File testApk = new File(TEST_DATA_PATH, TEST_APP_APK);
+        int userId = UserHandle.USER_NULL;
+        try {
+            // Create and start the 2nd user.
+            userId = um.createUser("Test User", 0 /* flags */).getUserHandle().getIdentifier();
+            runShellCommand("am start-user -w " + userId);
+
+            // Try to install test APK to all users with reason INSTALL_REASON_POLICY
+            runShellCommand("pm install --install-reason 1 " + testApk);
+            assertWithMessage("The install reason is inconsistent across users.").that(
+                    pm.getInstallReason(TEST_PKG_NAME, UserHandle.myUserId())).isEqualTo(
+                    pm.getInstallReason(TEST_PKG_NAME, userId));
+        } finally {
+            runShellCommand("pm uninstall " + TEST_PKG_NAME);
+            if (userId != UserHandle.USER_NULL) {
+                um.removeUser(userId);
+            }
+        }
+    }
+
+    @Test
+    public void testInstallReason_installSeparately_withSeparatedReason() throws Exception {
+        Assume.assumeTrue(UserManager.supportsMultipleUsers());
+        final IPackageManager pm = AppGlobals.getPackageManager();
+        final UserManager um = UserManager.get(
+                InstrumentationRegistry.getInstrumentation().getContext());
+        final File testApk = new File(TEST_DATA_PATH, TEST_APP_APK);
+        int userId = UserHandle.USER_NULL;
+        try {
+            // Create and start the 2nd user.
+            userId = um.createUser("Test User", 0 /* flags */).getUserHandle().getIdentifier();
+            runShellCommand("am start-user -w " + userId);
+
+            // Try to install test APK on the current user with reason INSTALL_REASON_POLICY
+            runShellCommand("pm install --user cur --install-reason 1 " + testApk);
+            assertWithMessage("The install reason on the current user is incorrect.").that(
+                    pm.getInstallReason(TEST_PKG_NAME, UserHandle.myUserId())).isEqualTo(
+                    PackageManager.INSTALL_REASON_POLICY);
+
+            // Try to install test APK on the 2nd user with reason INSTALL_REASON_USER
+            runShellCommand("pm install --user " + userId + " --install-reason 4 " + testApk);
+            assertWithMessage("The install reason on the 2nd user is incorrect.").that(
+                    pm.getInstallReason(TEST_PKG_NAME, userId)).isEqualTo(
+                    PackageManager.INSTALL_REASON_USER);
+        } finally {
+            runShellCommand("pm uninstall " + TEST_PKG_NAME);
+            if (userId != UserHandle.USER_NULL) {
+                um.removeUser(userId);
+            }
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index 5d93e3d..ab37e9b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -25,6 +25,7 @@
 import static android.content.pm.parsing.ParsingPackageUtils.parsePublicKey;
 import static android.content.res.Resources.ID_NULL;
 
+import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.CoreMatchers.notNullValue;
@@ -86,7 +87,6 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.CountDownLatch;
@@ -1004,6 +1004,13 @@
         }
     }
 
+    private void verifyKeySetData(PackageKeySetData originalData, PackageKeySetData testData) {
+        assertThat(originalData.getProperSigningKeySet(),
+                equalTo(testData.getProperSigningKeySet()));
+        assertThat(originalData.getUpgradeKeySets(), is(testData.getUpgradeKeySets()));
+        assertThat(originalData.getAliases(), is(testData.getAliases()));
+    }
+
     private void verifySettingCopy(PackageSetting origPkgSetting, PackageSetting testPkgSetting) {
         assertThat(origPkgSetting, is(not(testPkgSetting)));
         assertThat(origPkgSetting.getAppId(), is(testPkgSetting.getAppId()));
@@ -1018,8 +1025,7 @@
         assertSame(origPkgSetting.getInstallSource(), testPkgSetting.getInstallSource());
         assertThat(origPkgSetting.isInstallPermissionsFixed(),
                 is(testPkgSetting.isInstallPermissionsFixed()));
-        assertSame(origPkgSetting.getKeySetData(), testPkgSetting.getKeySetData());
-        assertThat(origPkgSetting.getKeySetData(), is(testPkgSetting.getKeySetData()));
+        verifyKeySetData(origPkgSetting.getKeySetData(), testPkgSetting.getKeySetData());
         assertThat(origPkgSetting.getLastUpdateTime(), is(testPkgSetting.getLastUpdateTime()));
         assertSame(origPkgSetting.getLegacyNativeLibraryPath(),
                 testPkgSetting.getLegacyNativeLibraryPath());
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
index 7d24a2f..a9cbad2 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
@@ -16,6 +16,19 @@
 
 package com.android.server.vibrator;
 
+import static android.os.VibrationAttributes.USAGE_ALARM;
+import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
+import static android.os.VibrationAttributes.USAGE_HARDWARE_FEEDBACK;
+import static android.os.VibrationAttributes.USAGE_NOTIFICATION;
+import static android.os.VibrationAttributes.USAGE_PHYSICAL_EMULATION;
+import static android.os.VibrationAttributes.USAGE_RINGTONE;
+import static android.os.VibrationAttributes.USAGE_TOUCH;
+import static android.os.VibrationAttributes.USAGE_UNKNOWN;
+import static android.os.Vibrator.VIBRATION_INTENSITY_HIGH;
+import static android.os.Vibrator.VIBRATION_INTENSITY_LOW;
+import static android.os.Vibrator.VIBRATION_INTENSITY_MEDIUM;
+import static android.os.Vibrator.VIBRATION_INTENSITY_OFF;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -39,9 +52,7 @@
 import android.os.PowerManagerInternal;
 import android.os.PowerSaveState;
 import android.os.UserHandle;
-import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
-import android.os.Vibrator;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
@@ -70,16 +81,19 @@
 public class VibrationSettingsTest {
 
     private static final int UID = 1;
-    private static final int USER_OPERATION_TIMEOUT_MILLIS = 60_000; // 1 min
     private static final PowerSaveState NORMAL_POWER_STATE = new PowerSaveState.Builder().build();
     private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder()
             .setBatterySaverEnabled(true).build();
 
-    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
-    @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+    @Rule
+    public MockitoRule mMockitoRule = MockitoJUnit.rule();
+    @Rule
+    public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
 
-    @Mock private VibrationSettings.OnVibratorSettingsChanged mListenerMock;
-    @Mock private PowerManagerInternal mPowerManagerInternalMock;
+    @Mock
+    private VibrationSettings.OnVibratorSettingsChanged mListenerMock;
+    @Mock
+    private PowerManagerInternal mPowerManagerInternalMock;
 
     private TestLooper mTestLooper;
     private ContextWrapper mContextSpy;
@@ -129,14 +143,12 @@
         setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
         setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
         setGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_ALARMS);
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_OFF);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_OFF);
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
 
-        verify(mListenerMock, times(7)).onChange();
+        verify(mListenerMock, times(8)).onChange();
     }
 
     @Test
@@ -171,89 +183,83 @@
         VibrationSettings vibrationSettings = new VibrationSettings(mContextSpy,
                 new Handler(mTestLooper.getLooper()));
 
-        assertFalse(vibrationSettings.shouldVibrateForRingerMode(
-                VibrationAttributes.USAGE_RINGTONE));
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(VibrationAttributes.USAGE_ALARM));
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(VibrationAttributes.USAGE_TOUCH));
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(
-                VibrationAttributes.USAGE_NOTIFICATION));
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(
-                VibrationAttributes.USAGE_COMMUNICATION_REQUEST));
+        assertFalse(vibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
+        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_ALARM));
+        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_TOUCH));
+        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_NOTIFICATION));
+        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_COMMUNICATION_REQUEST));
+        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_HARDWARE_FEEDBACK));
     }
 
     @Test
     public void shouldVibrateForRingerMode_withoutRingtoneUsage_returnsTrue() {
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(VibrationAttributes.USAGE_ALARM));
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(VibrationAttributes.USAGE_TOUCH));
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(
-                VibrationAttributes.USAGE_NOTIFICATION));
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(
-                VibrationAttributes.USAGE_COMMUNICATION_REQUEST));
+        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_ALARM));
+        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_TOUCH));
+        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_NOTIFICATION));
+        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_COMMUNICATION_REQUEST));
+        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_HARDWARE_FEEDBACK));
     }
 
     @Test
     public void shouldVibrateForRingerMode_withVibrateWhenRinging_ignoreSettingsForSilentMode() {
-        int usageRingtone = VibrationAttributes.USAGE_RINGTONE;
         setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
 
         setRingerMode(AudioManager.RINGER_MODE_SILENT);
-        assertFalse(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+        assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
 
         setRingerMode(AudioManager.RINGER_MODE_MAX);
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
 
         setRingerMode(AudioManager.RINGER_MODE_NORMAL);
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
 
         setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
     }
 
     @Test
     public void shouldVibrateForRingerMode_withApplyRampingRinger_ignoreSettingsForSilentMode() {
-        int usageRingtone = VibrationAttributes.USAGE_RINGTONE;
         setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
         setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 1);
 
         setRingerMode(AudioManager.RINGER_MODE_SILENT);
-        assertFalse(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+        assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
 
         setRingerMode(AudioManager.RINGER_MODE_MAX);
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
 
         setRingerMode(AudioManager.RINGER_MODE_NORMAL);
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
 
         setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
     }
 
     @Test
     public void shouldVibrateForRingerMode_withAllSettingsOff_onlyVibratesForVibrateMode() {
-        int usageRingtone = VibrationAttributes.USAGE_RINGTONE;
         setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
         setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
 
         setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
-        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+        assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
 
         setRingerMode(AudioManager.RINGER_MODE_SILENT);
-        assertFalse(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+        assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
 
         setRingerMode(AudioManager.RINGER_MODE_MAX);
-        assertFalse(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+        assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
 
         setRingerMode(AudioManager.RINGER_MODE_NORMAL);
-        assertFalse(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+        assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
     }
 
     @Test
     public void shouldVibrateForUid_withForegroundOnlyUsage_returnsTrueWhInForeground() {
-        assertTrue(mVibrationSettings.shouldVibrateForUid(UID, VibrationAttributes.USAGE_TOUCH));
+        assertTrue(mVibrationSettings.shouldVibrateForUid(UID, USAGE_TOUCH));
 
         mVibrationSettings.mUidObserver.onUidStateChanged(
                 UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0);
-        assertFalse(mVibrationSettings.shouldVibrateForUid(UID, VibrationAttributes.USAGE_TOUCH));
+        assertFalse(mVibrationSettings.shouldVibrateForUid(UID, USAGE_TOUCH));
     }
 
     @Test
@@ -261,38 +267,32 @@
         mVibrationSettings.mUidObserver.onUidStateChanged(
                 UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0);
 
-        assertTrue(mVibrationSettings.shouldVibrateForUid(UID, VibrationAttributes.USAGE_ALARM));
-        assertTrue(mVibrationSettings.shouldVibrateForUid(UID,
-                VibrationAttributes.USAGE_COMMUNICATION_REQUEST));
-        assertTrue(mVibrationSettings.shouldVibrateForUid(UID,
-                VibrationAttributes.USAGE_NOTIFICATION));
-        assertTrue(mVibrationSettings.shouldVibrateForUid(UID, VibrationAttributes.USAGE_RINGTONE));
+        assertTrue(mVibrationSettings.shouldVibrateForUid(UID, USAGE_ALARM));
+        assertTrue(mVibrationSettings.shouldVibrateForUid(UID, USAGE_COMMUNICATION_REQUEST));
+        assertTrue(mVibrationSettings.shouldVibrateForUid(UID, USAGE_NOTIFICATION));
+        assertTrue(mVibrationSettings.shouldVibrateForUid(UID, USAGE_RINGTONE));
     }
 
     @Test
     public void shouldVibrateForPowerMode_withLowPowerAndAllowedUsage_returnTrue() {
         mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
 
-        assertTrue(mVibrationSettings.shouldVibrateForPowerMode(VibrationAttributes.USAGE_ALARM));
-        assertTrue(mVibrationSettings.shouldVibrateForPowerMode(
-                VibrationAttributes.USAGE_RINGTONE));
-        assertTrue(mVibrationSettings.shouldVibrateForPowerMode(
-                VibrationAttributes.USAGE_COMMUNICATION_REQUEST));
+        assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_ALARM));
+        assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_RINGTONE));
+        assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_COMMUNICATION_REQUEST));
     }
 
     @Test
     public void shouldVibrateForPowerMode_withRestrictedUsage_returnsFalseWhileInLowPowerMode() {
         mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
 
-        assertTrue(mVibrationSettings.shouldVibrateForPowerMode(VibrationAttributes.USAGE_TOUCH));
-        assertTrue(mVibrationSettings.shouldVibrateForPowerMode(
-                VibrationAttributes.USAGE_NOTIFICATION));
+        assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_TOUCH));
+        assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_NOTIFICATION));
 
         mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
 
-        assertFalse(mVibrationSettings.shouldVibrateForPowerMode(VibrationAttributes.USAGE_TOUCH));
-        assertFalse(mVibrationSettings.shouldVibrateForPowerMode(
-                VibrationAttributes.USAGE_NOTIFICATION));
+        assertFalse(mVibrationSettings.shouldVibrateForPowerMode(USAGE_TOUCH));
+        assertFalse(mVibrationSettings.shouldVibrateForPowerMode(USAGE_NOTIFICATION));
     }
 
     @Test
@@ -324,108 +324,128 @@
 
     @Test
     public void getDefaultIntensity_beforeSystemReady_returnsMediumToAllExceptAlarm() {
-        mFakeVibrator.setDefaultHapticFeedbackIntensity(Vibrator.VIBRATION_INTENSITY_HIGH);
-        mFakeVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_HIGH);
-        mFakeVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_HIGH);
+        mFakeVibrator.setDefaultHapticFeedbackIntensity(VIBRATION_INTENSITY_HIGH);
+        mFakeVibrator.setDefaultNotificationVibrationIntensity(VIBRATION_INTENSITY_HIGH);
+        mFakeVibrator.setDefaultRingVibrationIntensity(VIBRATION_INTENSITY_HIGH);
 
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_OFF);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_OFF);
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
 
         VibrationSettings vibrationSettings = new VibrationSettings(mContextSpy,
                 new Handler(mTestLooper.getLooper()));
 
-        assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH,
-                vibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_ALARM));
-        assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM,
-                vibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_TOUCH));
-        assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM,
-                vibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION));
-        assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM,
-                vibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_UNKNOWN));
-        assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM,
-                vibrationSettings.getDefaultIntensity(
-                        VibrationAttributes.USAGE_PHYSICAL_EMULATION));
-        assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM,
-                vibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE));
+        assertEquals(VIBRATION_INTENSITY_HIGH,
+                vibrationSettings.getDefaultIntensity(USAGE_ALARM));
+        assertEquals(VIBRATION_INTENSITY_MEDIUM,
+                vibrationSettings.getDefaultIntensity(USAGE_TOUCH));
+        assertEquals(VIBRATION_INTENSITY_MEDIUM,
+                vibrationSettings.getDefaultIntensity(USAGE_HARDWARE_FEEDBACK));
+        assertEquals(VIBRATION_INTENSITY_MEDIUM,
+                vibrationSettings.getDefaultIntensity(USAGE_PHYSICAL_EMULATION));
+        assertEquals(VIBRATION_INTENSITY_MEDIUM,
+                vibrationSettings.getDefaultIntensity(USAGE_NOTIFICATION));
+        assertEquals(VIBRATION_INTENSITY_MEDIUM,
+                vibrationSettings.getDefaultIntensity(USAGE_UNKNOWN));
+        assertEquals(VIBRATION_INTENSITY_MEDIUM,
+                vibrationSettings.getDefaultIntensity(USAGE_RINGTONE));
     }
 
     @Test
     public void getDefaultIntensity_returnsIntensityFromVibratorService() {
-        mFakeVibrator.setDefaultHapticFeedbackIntensity(Vibrator.VIBRATION_INTENSITY_HIGH);
-        mFakeVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_MEDIUM);
-        mFakeVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_LOW);
+        mFakeVibrator.setDefaultHapticFeedbackIntensity(VIBRATION_INTENSITY_HIGH);
+        mFakeVibrator.setDefaultNotificationVibrationIntensity(VIBRATION_INTENSITY_MEDIUM);
+        mFakeVibrator.setDefaultRingVibrationIntensity(VIBRATION_INTENSITY_LOW);
 
-        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_OFF);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_OFF);
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
 
-        assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH,
-                mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_ALARM));
-        assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH,
-                mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_TOUCH));
-        assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM,
-                mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION));
-        assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM,
-                mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_UNKNOWN));
-        assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM,
-                mVibrationSettings.getDefaultIntensity(
-                        VibrationAttributes.USAGE_PHYSICAL_EMULATION));
-        assertEquals(Vibrator.VIBRATION_INTENSITY_LOW,
-                mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE));
+        assertEquals(VIBRATION_INTENSITY_HIGH,
+                mVibrationSettings.getDefaultIntensity(USAGE_ALARM));
+        assertEquals(VIBRATION_INTENSITY_HIGH,
+                mVibrationSettings.getDefaultIntensity(USAGE_TOUCH));
+        assertEquals(VIBRATION_INTENSITY_HIGH,
+                mVibrationSettings.getDefaultIntensity(USAGE_HARDWARE_FEEDBACK));
+        assertEquals(VIBRATION_INTENSITY_HIGH,
+                mVibrationSettings.getDefaultIntensity(USAGE_PHYSICAL_EMULATION));
+        assertEquals(VIBRATION_INTENSITY_MEDIUM,
+                mVibrationSettings.getDefaultIntensity(USAGE_NOTIFICATION));
+        assertEquals(VIBRATION_INTENSITY_MEDIUM,
+                mVibrationSettings.getDefaultIntensity(USAGE_UNKNOWN));
+        assertEquals(VIBRATION_INTENSITY_LOW,
+                mVibrationSettings.getDefaultIntensity(USAGE_RINGTONE));
     }
 
     @Test
     public void getCurrentIntensity_returnsIntensityFromSettings() {
-        mFakeVibrator.setDefaultHapticFeedbackIntensity(Vibrator.VIBRATION_INTENSITY_OFF);
-        mFakeVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_OFF);
-        mFakeVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_OFF);
+        mFakeVibrator.setDefaultHapticFeedbackIntensity(VIBRATION_INTENSITY_OFF);
+        mFakeVibrator.setDefaultNotificationVibrationIntensity(VIBRATION_INTENSITY_OFF);
+        mFakeVibrator.setDefaultRingVibrationIntensity(VIBRATION_INTENSITY_OFF);
 
-        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_HIGH);
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH);
+        setUserSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_LOW);
         setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_MEDIUM);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_LOW);
+                VIBRATION_INTENSITY_MEDIUM);
+        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW);
 
-        assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH,
-                mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_ALARM));
-        assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH,
-                mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_TOUCH));
-        assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM,
-                mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_NOTIFICATION));
-        assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM,
-                mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_UNKNOWN));
-        assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM,
-                mVibrationSettings.getCurrentIntensity(
-                        VibrationAttributes.USAGE_PHYSICAL_EMULATION));
-        assertEquals(Vibrator.VIBRATION_INTENSITY_LOW,
-                mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_RINGTONE));
+        assertEquals(VIBRATION_INTENSITY_HIGH, mVibrationSettings.getCurrentIntensity(USAGE_ALARM));
+        assertEquals(VIBRATION_INTENSITY_HIGH, mVibrationSettings.getCurrentIntensity(USAGE_TOUCH));
+        assertEquals(VIBRATION_INTENSITY_LOW,
+                mVibrationSettings.getCurrentIntensity(USAGE_HARDWARE_FEEDBACK));
+        assertEquals(VIBRATION_INTENSITY_LOW,
+                mVibrationSettings.getCurrentIntensity(USAGE_PHYSICAL_EMULATION));
+        assertEquals(VIBRATION_INTENSITY_MEDIUM,
+                mVibrationSettings.getCurrentIntensity(USAGE_NOTIFICATION));
+        assertEquals(VIBRATION_INTENSITY_MEDIUM,
+                mVibrationSettings.getCurrentIntensity(USAGE_UNKNOWN));
+        assertEquals(VIBRATION_INTENSITY_LOW,
+                mVibrationSettings.getCurrentIntensity(USAGE_RINGTONE));
     }
 
     @Test
     public void getCurrentIntensity_updateTriggeredAfterUserSwitched() {
-        mFakeVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_OFF);
-        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
-                Vibrator.VIBRATION_INTENSITY_HIGH);
-        assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH,
-                mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_RINGTONE));
+        mFakeVibrator.setDefaultRingVibrationIntensity(VIBRATION_INTENSITY_OFF);
+        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH);
+        assertEquals(VIBRATION_INTENSITY_HIGH,
+                mVibrationSettings.getCurrentIntensity(USAGE_RINGTONE));
 
         // Switching user is not working with FakeSettingsProvider.
         // Testing the broadcast flow manually.
         Settings.System.putIntForUser(mContextSpy.getContentResolver(),
-                Settings.System.RING_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_LOW,
+                Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW,
                 UserHandle.USER_CURRENT);
         mVibrationSettings.mUserReceiver.onReceive(mContextSpy,
                 new Intent(Intent.ACTION_USER_SWITCHED));
-        assertEquals(Vibrator.VIBRATION_INTENSITY_LOW,
-                mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_RINGTONE));
+        assertEquals(VIBRATION_INTENSITY_LOW,
+                mVibrationSettings.getCurrentIntensity(USAGE_RINGTONE));
+    }
+
+    @Test
+    public void getCurrentIntensity_noHardwareFeedbackValueUsesHapticFeedbackValue() {
+        mFakeVibrator.setDefaultHapticFeedbackIntensity(VIBRATION_INTENSITY_MEDIUM);
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
+        assertEquals(VIBRATION_INTENSITY_OFF, mVibrationSettings.getCurrentIntensity(USAGE_TOUCH));
+        // If haptic feedback is off, fallback to default value.
+        assertEquals(VIBRATION_INTENSITY_MEDIUM,
+                mVibrationSettings.getCurrentIntensity(USAGE_HARDWARE_FEEDBACK));
+        assertEquals(VIBRATION_INTENSITY_MEDIUM,
+                mVibrationSettings.getCurrentIntensity(USAGE_PHYSICAL_EMULATION));
+
+        // Switching user is not working with FakeSettingsProvider.
+        // Testing the broadcast flow manually.
+        Settings.System.putIntForUser(mContextSpy.getContentResolver(),
+                Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH,
+                UserHandle.USER_CURRENT);
+        mVibrationSettings.mUserReceiver.onReceive(mContextSpy,
+                new Intent(Intent.ACTION_USER_SWITCHED));
+        assertEquals(VIBRATION_INTENSITY_HIGH,
+                mVibrationSettings.getCurrentIntensity(USAGE_TOUCH));
+        assertEquals(VIBRATION_INTENSITY_HIGH,
+                mVibrationSettings.getCurrentIntensity(USAGE_HARDWARE_FEEDBACK));
+        assertEquals(VIBRATION_INTENSITY_HIGH,
+                mVibrationSettings.getCurrentIntensity(USAGE_PHYSICAL_EMULATION));
     }
 
     @Test
diff --git a/services/tests/servicestests/test-apps/StubApp/Android.bp b/services/tests/servicestests/test-apps/StubApp/Android.bp
new file mode 100644
index 0000000..99deb3f
--- /dev/null
+++ b/services/tests/servicestests/test-apps/StubApp/Android.bp
@@ -0,0 +1,37 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+    name: "StubTestApp",
+
+    sdk_version: "current",
+
+    srcs: ["**/*.java"],
+
+    dex_preopt: {
+        enabled: false,
+    },
+    optimize: {
+        enabled: false,
+    },
+}
diff --git a/services/tests/servicestests/test-apps/StubApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/StubApp/AndroidManifest.xml
new file mode 100644
index 0000000..90172e7
--- /dev/null
+++ b/services/tests/servicestests/test-apps/StubApp/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.servicestests.apps.stubapp">
+
+    <application android:label="StubTestApp">
+        <activity android:name=".TestActivity"
+                  android:exported="true" />
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/services/tests/servicestests/test-apps/StubApp/src/com/android/servicestests/apps/stubapp/TestActivity.java b/services/tests/servicestests/test-apps/StubApp/src/com/android/servicestests/apps/stubapp/TestActivity.java
new file mode 100644
index 0000000..0d94676
--- /dev/null
+++ b/services/tests/servicestests/test-apps/StubApp/src/com/android/servicestests/apps/stubapp/TestActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.servicestests.apps.stubapp;
+
+import android.app.Activity;
+
+public class TestActivity extends Activity {
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 5808964..ea3a4cd 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -8511,6 +8511,18 @@
         assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
                 r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse();
 
+        // using the style, but incorrect type in session - blocked
+        nb.setStyle(new Notification.MediaStyle());
+        Bundle extras = new Bundle();
+        extras.putParcelable(Notification.EXTRA_MEDIA_SESSION, new Intent());
+        nb.addExtras(extras);
+        sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0,
+                nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+        r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
+                r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse();
+
         // style + media session - bypasses block
         nb.setStyle(new Notification.MediaStyle().setMediaSession(mock(MediaSession.Token.class)));
         sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
index ea01963..29ef339 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
@@ -89,6 +89,7 @@
 import android.media.session.MediaSession;
 import android.os.Binder;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Process;
@@ -746,6 +747,18 @@
         assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
                 r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse();
 
+        // using the style, but incorrect type in session - blocked
+        nb.setStyle(new Notification.MediaStyle());
+        Bundle extras = new Bundle();
+        extras.putParcelable(Notification.EXTRA_MEDIA_SESSION, new Intent());
+        nb.addExtras(extras);
+        sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0,
+                nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+        r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
+                r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse();
+
         // style + media session - bypasses block
         nb.setStyle(new Notification.MediaStyle().setMediaSession(mock(MediaSession.Token.class)));
         sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0,
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 1266b2e..afc2b87 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -854,12 +854,19 @@
         ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
                 mAtm.mInternal.createPackageConfigurationUpdater(DEFAULT_PACKAGE_NAME,
                         DEFAULT_USER_ID);
+
+        // committing empty locales, when no config is set should return false.
+        assertFalse(packageConfigUpdater.setLocales(LocaleList.getEmptyLocaleList()).commit());
+
         // committing new configuration returns true;
         assertTrue(packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
                 .commit());
         // applying the same configuration returns false.
         assertFalse(packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
                 .commit());
+
+        // committing empty locales and undefined nightMode should return true (deletes the
+        // pre-existing record) if some config was previously set.
         assertTrue(packageConfigUpdater.setLocales(LocaleList.getEmptyLocaleList())
                 .setNightMode(Configuration.UI_MODE_NIGHT_UNDEFINED).commit());
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 5d0e34a..6fa306b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -803,6 +803,7 @@
         final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
         final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
         openingActivity.allDrawn = true;
+        task.effectiveUid = openingActivity.getUid();
         spyOn(mDisplayContent.mAppTransition);
 
         // Prepare a transition.
@@ -879,6 +880,7 @@
         final ActivityRecord closingActivity = taskFragment.getTopMostActivity();
         closingActivity.allDrawn = true;
         closingActivity.info.applicationInfo.uid = 12345;
+        task.effectiveUid = closingActivity.getUid();
         // Opening non-embedded activity with different UID.
         final ActivityRecord openingActivity = createActivityRecord(task);
         openingActivity.info.applicationInfo.uid = 54321;
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index d89d64a..a5c6dc0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -21,6 +21,7 @@
 import static com.android.server.wm.testing.Assert.assertThrows;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
@@ -352,28 +353,66 @@
     }
 
     @Test
-    public void testApplyTransaction_enforceHierarchyChange_createTaskFragment() {
+    public void testApplyTransaction_enforceHierarchyChange_createTaskFragment()
+            throws RemoteException {
+        mController.registerOrganizer(mIOrganizer);
+        final ActivityRecord activity = createActivityRecord(mDisplayContent);
+        final int uid = Binder.getCallingUid();
+        activity.info.applicationInfo.uid = uid;
+        activity.getTask().effectiveUid = uid;
+        final IBinder fragmentToken = new Binder();
+        final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder(
+                mOrganizerToken, fragmentToken, activity.token).build();
         mOrganizer.applyTransaction(mTransaction);
 
         // Allow organizer to create TaskFragment and start/reparent activity to TaskFragment.
-        final TaskFragmentCreationParams mockParams = mock(TaskFragmentCreationParams.class);
-        doReturn(mOrganizerToken).when(mockParams).getOrganizer();
-        mTransaction.createTaskFragment(mockParams);
+        mTransaction.createTaskFragment(params);
         mTransaction.startActivityInTaskFragment(
                 mFragmentToken, null /* callerToken */, new Intent(), null /* activityOptions */);
         mTransaction.reparentActivityToTaskFragment(mFragmentToken, mock(IBinder.class));
         mTransaction.setAdjacentTaskFragments(mFragmentToken, mock(IBinder.class),
                 null /* options */);
+        mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
 
-        // It is expected to fail for the mock TaskFragmentCreationParams. It is ok as we are
-        // testing the security check here.
-        assertThrows(IllegalArgumentException.class, () -> {
-            try {
-                mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
-            } catch (RemoteException e) {
-                fail();
-            }
-        });
+        // Successfully created a TaskFragment.
+        final TaskFragment taskFragment = mAtm.mWindowOrganizerController
+                .getTaskFragment(fragmentToken);
+        assertNotNull(taskFragment);
+        assertEquals(activity.getTask(), taskFragment.getTask());
+    }
+
+    @Test
+    public void testApplyTransaction_createTaskFragment_failForDifferentUid()
+            throws RemoteException {
+        mController.registerOrganizer(mIOrganizer);
+        final ActivityRecord activity = createActivityRecord(mDisplayContent);
+        final int uid = Binder.getCallingUid();
+        final IBinder fragmentToken = new Binder();
+        final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder(
+                mOrganizerToken, fragmentToken, activity.token).build();
+        mOrganizer.applyTransaction(mTransaction);
+        mTransaction.createTaskFragment(params);
+
+        // Fail to create TaskFragment when the task uid is different from caller.
+        activity.info.applicationInfo.uid = uid;
+        activity.getTask().effectiveUid = uid + 1;
+        mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
+
+        assertNull(mAtm.mWindowOrganizerController.getTaskFragment(fragmentToken));
+
+        // Fail to create TaskFragment when the task uid is different from owner activity.
+        activity.info.applicationInfo.uid = uid + 1;
+        activity.getTask().effectiveUid = uid;
+        mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
+
+        assertNull(mAtm.mWindowOrganizerController.getTaskFragment(fragmentToken));
+
+        // Successfully created a TaskFragment for same uid.
+        activity.info.applicationInfo.uid = uid;
+        activity.getTask().effectiveUid = uid;
+        mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
+
+        assertNotNull(mAtm.mWindowOrganizerController.getTaskFragment(fragmentToken));
     }
 
     @Test
@@ -424,4 +463,26 @@
 
         verify(mAtm.mRootWindowContainer).resumeFocusedTasksTopActivities();
     }
+
+    @Test
+    public void testDeferPendingTaskFragmentEventsOfInvisibleTask() {
+        // Task - TaskFragment - Activity.
+        final Task task = createTask(mDisplayContent);
+        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+                .setParentTask(task)
+                .setOrganizer(mOrganizer)
+                .build();
+
+        // Mock the task to invisible
+        doReturn(false).when(task).shouldBeVisible(any());
+
+        // Sending events
+        mController.registerOrganizer(mIOrganizer);
+        taskFragment.mTaskFragmentAppearedSent = true;
+        mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
+        mController.dispatchPendingEvents();
+
+        // Verifies that event was not sent
+        verify(mOrganizer, never()).onTaskFragmentInfoChanged(any());
+    }
 }
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index abbce1c..98f619f 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1687,7 +1687,11 @@
      *
      * @param accountHandle The handle for the account retrieve a number for.
      * @return A string representation of the line 1 phone number.
+     * @deprecated use {@link SubscriptionManager#getPhoneNumber(int)} instead, which takes a
+     *             Telephony Subscription ID that can be retrieved with the {@code accountHandle}
+     *             from {@link TelephonyManager#getSubscriptionId(PhoneAccountHandle)}.
      */
+    @Deprecated
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges or default SMS app
     @RequiresPermission(anyOf = {
             android.Manifest.permission.READ_PHONE_STATE,
diff --git a/telephony/common/Android.bp b/telephony/common/Android.bp
index 1cacc03..b0a812b 100644
--- a/telephony/common/Android.bp
+++ b/telephony/common/Android.bp
@@ -21,7 +21,10 @@
 
 filegroup {
     name: "framework-mms-shared-srcs",
-    visibility: ["//packages/apps/Bluetooth"],
+    visibility: [
+        "//packages/apps/Bluetooth",
+	"//packages/modules/Bluetooth/android/app",
+    ],
     srcs: [
         "com/google/android/mms/**/*.java",
     ],
diff --git a/telephony/java/android/service/euicc/EuiccService.java b/telephony/java/android/service/euicc/EuiccService.java
index 7d857a2..fabe612 100644
--- a/telephony/java/android/service/euicc/EuiccService.java
+++ b/telephony/java/android/service/euicc/EuiccService.java
@@ -856,9 +856,10 @@
             mExecutor.execute(new Runnable() {
                 @Override
                 public void run() {
+                    // TODO(b/207392528: use portIndex API once implemented)
                     int result =
-                            EuiccService.this.onSwitchToSubscriptionWithPort(
-                                    slotId, portIndex, iccid, forceDeactivateSim);
+                            EuiccService.this.onSwitchToSubscription(
+                                    slotId, iccid, forceDeactivateSim);
                     try {
                         callback.onComplete(result);
                     } catch (RemoteException e) {
diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java
index 23cf511..e88106c 100644
--- a/telephony/java/android/telephony/Annotation.java
+++ b/telephony/java/android/telephony/Annotation.java
@@ -1,6 +1,8 @@
 package android.telephony;
 
 import android.annotation.IntDef;
+import android.net.NetworkAgent;
+import android.net.NetworkCapabilities;
 import android.telecom.Connection;
 import android.telephony.data.ApnSetting;
 
@@ -664,4 +666,59 @@
         TelephonyManager.THERMAL_MITIGATION_RESULT_INVALID_STATE,
         TelephonyManager.THERMAL_MITIGATION_RESULT_UNKNOWN_ERROR})
     public @interface ThermalMitigationResult {}
+
+    /**
+     * Per Android API guideline 8.15, annotation can't be public APIs. So duplicate
+     * android.net.NetworkCapabilities.NetCapability here. Must update here when new capabilities
+     * are added in {@link NetworkCapabilities}.
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "NET_CAPABILITY_" }, value = {
+            NetworkCapabilities.NET_CAPABILITY_MMS,
+            NetworkCapabilities.NET_CAPABILITY_SUPL,
+            NetworkCapabilities.NET_CAPABILITY_DUN,
+            NetworkCapabilities.NET_CAPABILITY_FOTA,
+            NetworkCapabilities.NET_CAPABILITY_IMS,
+            NetworkCapabilities.NET_CAPABILITY_CBS,
+            NetworkCapabilities.NET_CAPABILITY_WIFI_P2P,
+            NetworkCapabilities.NET_CAPABILITY_IA,
+            NetworkCapabilities.NET_CAPABILITY_RCS,
+            NetworkCapabilities.NET_CAPABILITY_XCAP,
+            NetworkCapabilities.NET_CAPABILITY_EIMS,
+            NetworkCapabilities.NET_CAPABILITY_NOT_METERED,
+            NetworkCapabilities.NET_CAPABILITY_INTERNET,
+            NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED,
+            NetworkCapabilities.NET_CAPABILITY_TRUSTED,
+            NetworkCapabilities.NET_CAPABILITY_NOT_VPN,
+            NetworkCapabilities.NET_CAPABILITY_VALIDATED,
+            NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL,
+            NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING,
+            NetworkCapabilities.NET_CAPABILITY_FOREGROUND,
+            NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED,
+            NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED,
+            NetworkCapabilities.NET_CAPABILITY_OEM_PAID,
+            NetworkCapabilities.NET_CAPABILITY_MCX,
+            NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY,
+            NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED,
+            NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE,
+            NetworkCapabilities.NET_CAPABILITY_VEHICLE_INTERNAL,
+            NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED,
+            NetworkCapabilities.NET_CAPABILITY_ENTERPRISE,
+            NetworkCapabilities.NET_CAPABILITY_VSIM,
+            NetworkCapabilities.NET_CAPABILITY_BIP,
+            NetworkCapabilities.NET_CAPABILITY_HEAD_UNIT,
+    })
+    public @interface NetCapability { }
+
+    /**
+     * Per Android API guideline 8.15, annotation can't be public APIs. So duplicate
+     * android.net.NetworkAgent.ValidationStatus here. Must update here when new validation status
+     * are added in {@link NetworkAgent}.
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "VALIDATION_STATUS_" }, value = {
+            NetworkAgent.VALIDATION_STATUS_VALID,
+            NetworkAgent.VALIDATION_STATUS_NOT_VALID
+    })
+    public @interface ValidationStatus {}
 }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index f700d79..c7e5aaf 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -5207,16 +5207,6 @@
             "call_composer_picture_server_url_string";
 
     /**
-     * For Android 11, provide a temporary solution for OEMs to use the lower of the two MTU values
-     * for IPv4 and IPv6 if both are sent.
-     * TODO: remove in later release
-     *
-     * @hide
-     */
-    public static final String KEY_USE_LOWER_MTU_VALUE_IF_BOTH_RECEIVED =
-            "use_lower_mtu_value_if_both_received";
-
-    /**
      * Determines the default RTT mode.
      *
      * Upon first boot, when the user has not yet set a value for their preferred RTT mode,
@@ -5961,16 +5951,15 @@
                         "capabilities=eims, retry_interval=1000, maximum_retries=20",
                         "fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|-3|2253|"
                                 + "2254, maximum_retries=0", // No retry for those causes
+                        "capabilities=mms|supl|cbs, retry_interval=2000",
                         "capabilities=internet|enterprise|dun|ims|fota, retry_interval=2000, "
                                 + "backoff=true, maximum_retries=13",
-                        "capabilities=mms|supl|cbs, retry_interval=2000"
                 });
         sDefaults.putStringArray(KEY_MISSED_INCOMING_CALL_SMS_PATTERN_STRING_ARRAY, new String[0]);
         sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false);
         sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, "");
         sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false);
         sDefaults.putString(KEY_CALL_COMPOSER_PICTURE_SERVER_URL_STRING, "");
-        sDefaults.putBoolean(KEY_USE_LOWER_MTU_VALUE_IF_BOTH_RECEIVED, false);
         sDefaults.putBoolean(KEY_USE_ACS_FOR_RCS_BOOL, false);
         sDefaults.putBoolean(KEY_NETWORK_TEMP_NOT_METERED_SUPPORTED_BOOL, true);
         sDefaults.putInt(KEY_DEFAULT_RTT_MODE_INT, 0);
diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java
index 88efe1f..56bf303 100644
--- a/telephony/java/android/telephony/DataFailCause.java
+++ b/telephony/java/android/telephony/DataFailCause.java
@@ -1076,6 +1076,13 @@
      */
     public static final int SERVICE_TEMPORARILY_UNAVAILABLE = 0x10009;
 
+    /**
+     * The request is not supported by the vendor.
+     *
+     * @hide
+     */
+    public static final int REQUEST_NOT_SUPPORTED = 0x1000A;
+
     private static final Map<Integer, String> sFailCauseMap;
     static {
         sFailCauseMap = new HashMap<>();
diff --git a/telephony/java/android/telephony/SignalStrengthUpdateRequest.java b/telephony/java/android/telephony/SignalStrengthUpdateRequest.java
index 2ff4ac5..9cb80f1 100644
--- a/telephony/java/android/telephony/SignalStrengthUpdateRequest.java
+++ b/telephony/java/android/telephony/SignalStrengthUpdateRequest.java
@@ -71,12 +71,7 @@
             @Nullable List<SignalThresholdInfo> signalThresholdInfos,
             boolean isReportingRequestedWhileIdle,
             boolean isSystemThresholdReportingRequestedWhileIdle) {
-        // System app (like Bluetooth) can specify the request to report system thresholds while
-        // device is idle (with permission protection). In this case, the request doesn't need to
-        // provide a non-empty list of SignalThresholdInfo which is only asked for public apps.
-        if (!isSystemThresholdReportingRequestedWhileIdle) {
-            validate(signalThresholdInfos);
-        }
+        validate(signalThresholdInfos, isSystemThresholdReportingRequestedWhileIdle);
 
         mSignalThresholdInfos = signalThresholdInfos;
         mIsReportingRequestedWhileIdle = isReportingRequestedWhileIdle;
@@ -274,8 +269,12 @@
      * Throw IAE if SignalThresholdInfo collection is null or empty,
      * or the SignalMeasurementType for the same RAN in the collection is not unique.
      */
-    private static void validate(Collection<SignalThresholdInfo> infos) {
-        if (infos == null || infos.isEmpty()) {
+    private static void validate(Collection<SignalThresholdInfo> infos,
+            boolean isSystemThresholdReportingRequestedWhileIdle) {
+        // System app (like Bluetooth) can specify the request to report system thresholds while
+        // device is idle (with permission protection). In this case, the request doesn't need to
+        // provide a non-empty list of SignalThresholdInfo which is only asked for public apps.
+        if (infos == null || (infos.isEmpty() && !isSystemThresholdReportingRequestedWhileIdle)) {
             throw new IllegalArgumentException("SignalThresholdInfo collection is null or empty");
         }
 
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index d6d6775..d11ad91 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -498,7 +498,10 @@
      *
      * @return the number of this subscription, or an empty string if one of these requirements is
      * not met
+     * @deprecated use {@link SubscriptionManager#getPhoneNumber(int)} instead, which takes a
+     *             {@link #getSubscriptionId() subscription ID}.
      */
+    @Deprecated
     public String getNumber() {
         return mNumber;
     }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 04e7b7c..88b21e0 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -4791,7 +4791,10 @@
      *     for any API level.
      *     {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      *     for apps targeting SDK API level 29 and below.
+     *
+     * @deprecated use {@link SubscriptionManager#getPhoneNumber(int)} instead.
      */
+    @Deprecated
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges or default SMS app
     @RequiresPermission(anyOf = {
             android.Manifest.permission.READ_PHONE_STATE,
@@ -4864,7 +4867,9 @@
      * @param alphaTag alpha-tagging of the dailing nubmer
      * @param number The dialing number
      * @return true if the operation was executed correctly.
+     * @deprecated use {@link SubscriptionManager#setCarrierPhoneNumber(int, String)} instead.
      */
+    @Deprecated
     public boolean setLine1NumberForDisplay(String alphaTag, String number) {
         return setLine1NumberForDisplay(getSubId(), alphaTag, number);
     }
@@ -4885,6 +4890,10 @@
      */
     public boolean setLine1NumberForDisplay(int subId, String alphaTag, String number) {
         try {
+            // This API is deprecated; call the new API to allow smooth migartion.
+            // The new API doesn't accept null so convert null to empty string.
+            mSubscriptionManager.setCarrierPhoneNumber(subId, (number == null ? "" : number));
+
             ITelephony telephony = getITelephony();
             if (telephony != null)
                 return telephony.setLine1NumberForDisplayForSubscriber(subId, alphaTag, number);
diff --git a/telephony/java/android/telephony/data/DataProfile.java b/telephony/java/android/telephony/data/DataProfile.java
index 93903d2..43ad982 100644
--- a/telephony/java/android/telephony/data/DataProfile.java
+++ b/telephony/java/android/telephony/data/DataProfile.java
@@ -22,9 +22,11 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.net.NetworkCapabilities;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telephony.Annotation.ApnType;
+import android.telephony.Annotation.NetCapability;
 import android.telephony.TelephonyManager;
 import android.telephony.TelephonyManager.NetworkTypeBitMask;
 import android.telephony.data.ApnSetting.AuthType;
@@ -66,7 +68,7 @@
 
     private final @Nullable TrafficDescriptor mTrafficDescriptor;
 
-    private final boolean mPreferred;
+    private boolean mPreferred;
 
     private DataProfile(@NonNull Builder builder) {
         mApnSetting = builder.mApnSetting;
@@ -291,6 +293,16 @@
     }
 
     /**
+     * Set the preferred flag for the data profile.
+     *
+     * @param preferred {@code true} if this data profile is preferred for internet.
+     * @hide
+     */
+    public void setPreferred(boolean preferred) {
+        mPreferred = preferred;
+    }
+
+    /**
      * @return {@code true} if this data profile was used to bring up the last default
      * (i.e internet) data connection successfully, or the one chosen by the user in Settings'
      * APN editor. For one carrier there can be only one profiled preferred.
@@ -315,6 +327,76 @@
         return mTrafficDescriptor;
     }
 
+    /**
+     * Check if this data profile can satisfy certain network capabilities
+     *
+     * @param networkCapabilities The network capabilities. Note that the non-APN-type capabilities
+     * will be ignored.
+     *
+     * @return {@code true} if this data profile can satisfy the given network capabilities.
+     * @hide
+     */
+    public boolean canSatisfy(@NonNull @NetCapability int[] networkCapabilities) {
+        if (mApnSetting != null) {
+            for (int netCap : networkCapabilities) {
+                if (!canSatisfy(netCap)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Check if this data profile can satisfy a certain network capability.
+     *
+     * @param networkCapability The network capability. Note that the non-APN-type capability
+     * will always be satisfied.
+     * @return {@code true} if this data profile can satisfy the given network capability.
+     * @hide
+     */
+    public boolean canSatisfy(@NetCapability int networkCapability) {
+        return mApnSetting != null && mApnSetting.canHandleType(
+                networkCapabilityToApnType(networkCapability));
+    }
+
+    /**
+     * Convert network capability into APN type.
+     *
+     * @param networkCapability Network capability.
+     * @return APN type.
+     * @hide
+     */
+    private static @ApnType int networkCapabilityToApnType(@NetCapability int networkCapability) {
+        switch (networkCapability) {
+            case NetworkCapabilities.NET_CAPABILITY_MMS:
+                return ApnSetting.TYPE_MMS;
+            case NetworkCapabilities.NET_CAPABILITY_SUPL:
+                return ApnSetting.TYPE_SUPL;
+            case NetworkCapabilities.NET_CAPABILITY_DUN:
+                return ApnSetting.TYPE_DUN;
+            case NetworkCapabilities.NET_CAPABILITY_FOTA:
+                return ApnSetting.TYPE_FOTA;
+            case NetworkCapabilities.NET_CAPABILITY_IMS:
+                return ApnSetting.TYPE_IMS;
+            case NetworkCapabilities.NET_CAPABILITY_CBS:
+                return ApnSetting.TYPE_CBS;
+            case NetworkCapabilities.NET_CAPABILITY_XCAP:
+                return ApnSetting.TYPE_XCAP;
+            case NetworkCapabilities.NET_CAPABILITY_EIMS:
+                return ApnSetting.TYPE_EMERGENCY;
+            case NetworkCapabilities.NET_CAPABILITY_INTERNET:
+                return ApnSetting.TYPE_DEFAULT;
+            case NetworkCapabilities.NET_CAPABILITY_MCX:
+                return ApnSetting.TYPE_MCX;
+            case NetworkCapabilities.NET_CAPABILITY_IA:
+                return ApnSetting.TYPE_IA;
+            default:
+                return ApnSetting.TYPE_NONE;
+        }
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/telephony/java/android/telephony/ims/DelegateRegistrationState.java b/telephony/java/android/telephony/ims/DelegateRegistrationState.java
index c00c741..1b10404 100644
--- a/telephony/java/android/telephony/ims/DelegateRegistrationState.java
+++ b/telephony/java/android/telephony/ims/DelegateRegistrationState.java
@@ -117,6 +117,7 @@
     })
     public @interface DeregisteringReason {}
 
+    private ArraySet<String> mRegisteringTags = new ArraySet<>();
     private ArraySet<String> mRegisteredTags = new ArraySet<>();
     private final ArraySet<FeatureTagState> mDeregisteringTags = new ArraySet<>();
     private final ArraySet<FeatureTagState> mDeregisteredTags = new ArraySet<>();
@@ -134,6 +135,20 @@
         }
 
         /**
+         * Add the set of feature tags that are associated with this SipDelegate and
+         * the IMS stack is actively trying to register on the carrier network.
+         *
+         * The feature tags will either move to the registered or deregistered state
+         * depending on the result of the registration.
+         * @param featureTags The IMS media feature tags that are in the progress of registering.
+         * @return The in-progress Builder instance for RegistrationState. ]
+         */
+        public @NonNull Builder addRegisteringFeatureTags(@NonNull Set<String> featureTags) {
+            mState.mRegisteringTags.addAll(featureTags);
+            return this;
+        }
+
+        /**
          * Add a feature tag that is currently included in the current network IMS Registration.
          * @param featureTag The IMS media feature tag included in the current IMS registration.
          * @return The in-progress Builder instance for RegistrationState.
@@ -209,6 +224,17 @@
         mRegisteredTags = (ArraySet<String>) source.readArraySet(null);
         readStateFromParcel(source, mDeregisteringTags);
         readStateFromParcel(source, mDeregisteredTags);
+        mRegisteringTags = (ArraySet<String>) source.readArraySet(null);
+    }
+
+    /**
+     * Get the feature tags that are associated with this SipDelegate that the IMS stack is actively
+     * trying to register on the carrier network.
+     * @return A Set of feature tags associated with this SipDelegate that the IMS service is
+     * currently trying to register on the  carrier network.
+     */
+    public @NonNull Set<String> getRegisteringFeatureTags() {
+        return new ArraySet<>(mRegisteringTags);
     }
 
     /**
@@ -286,6 +312,7 @@
         dest.writeArraySet(mRegisteredTags);
         writeStateToParcel(dest, mDeregisteringTags);
         writeStateToParcel(dest, mDeregisteredTags);
+        dest.writeArraySet(mRegisteringTags);
     }
 
     private void writeStateToParcel(Parcel dest, Set<FeatureTagState> state) {
@@ -311,19 +338,22 @@
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
         DelegateRegistrationState that = (DelegateRegistrationState) o;
-        return mRegisteredTags.equals(that.mRegisteredTags)
+        return mRegisteringTags.equals(that.mRegisteringTags)
+                && mRegisteredTags.equals(that.mRegisteredTags)
                 && mDeregisteringTags.equals(that.mDeregisteringTags)
                 && mDeregisteredTags.equals(that.mDeregisteredTags);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mRegisteredTags, mDeregisteringTags, mDeregisteredTags);
+        return Objects.hash(mRegisteringTags, mRegisteredTags,
+                mDeregisteringTags, mDeregisteredTags);
     }
 
     @Override
     public String toString() {
         return "DelegateRegistrationState{ registered={" + mRegisteredTags
+                + "}, registering={" + mRegisteringTags
                 + "}, deregistering={" + mDeregisteringTags + "}, deregistered={"
                 + mDeregisteredTags + "}}";
     }
diff --git a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
index e4e535d..da5468e 100644
--- a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
@@ -157,8 +157,18 @@
 
     @Test
     public void testStagedSessionShouldCleanUpOnVerificationFailure() throws Exception {
+        // APEX verification
         InstallUtils.commitExpectingFailure(AssertionError.class, "apexd verification failed",
                 Install.single(APEX_WRONG_SHA_V2).setStaged());
+        InstallUtils.commitExpectingFailure(AssertionError.class, "apexd verification failed",
+                Install.multi(APEX_WRONG_SHA_V2, TestApp.A1).setStaged());
+        // APK verification
+        Install.single(TestApp.A2).commit();
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+        InstallUtils.commitExpectingFailure(AssertionError.class, "Downgrade detected",
+                Install.single(TestApp.A1).setStaged());
+        InstallUtils.commitExpectingFailure(AssertionError.class, "Downgrade detected",
+                Install.multi(TestApp.A1, TestApp.B1).setStaged());
     }
 
     @Test
@@ -176,6 +186,12 @@
     }
 
     @Test
+    public void testStagedSessionShouldCleanUpOnOnSuccessMultiPackage_Commit() throws Exception {
+        int sessionId = Install.multi(TestApp.A1, TestApp.Apex2).setStaged().commit();
+        storeSessionId(sessionId);
+    }
+
+    @Test
     public void testStagedInstallationShouldCleanUpOnValidationFailure() throws Exception {
         InstallUtils.commitExpectingFailure(AssertionError.class, "INSTALL_FAILED_INVALID_APK",
                 Install.single(TestApp.AIncompleteSplit).setStaged());
diff --git a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
index 78cf9ac..926bf1b 100644
--- a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
@@ -301,6 +301,18 @@
     }
 
     @Test
+    @LargeTest
+    public void testStagedSessionShouldCleanUpOnOnSuccessMultiPackage() throws Exception {
+        List<String> before = getStagingDirectories();
+        runPhase("testStagedSessionShouldCleanUpOnOnSuccessMultiPackage_Commit");
+        assertThat(getStagingDirectories()).isNotEqualTo(before);
+        getDevice().reboot();
+        runPhase("testStagedSessionShouldCleanUpOnOnSuccess_Verify");
+        List<String> after = getStagingDirectories();
+        assertThat(after).isEqualTo(before);
+    }
+
+    @Test
     public void testStagedInstallationShouldCleanUpOnValidationFailure() throws Exception {
         List<String> before = getStagingDirectories();
         runPhase("testStagedInstallationShouldCleanUpOnValidationFailure");