Merge "Fix CommunalManagerUpdaterTest."
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index c4aec0e..ddaa96d 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -997,8 +997,12 @@
     field public static final String ACTION_PROVISION_FINANCED_DEVICE = "android.app.action.PROVISION_FINANCED_DEVICE";
     field public static final String ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
     field @RequiresPermission(android.Manifest.permission.MANAGE_FACTORY_RESET_PROTECTION) public static final String ACTION_RESET_PROTECTION_POLICY_CHANGED = "android.app.action.RESET_PROTECTION_POLICY_CHANGED";
+    field public static final String ACTION_ROLE_HOLDER_PROVISION_FINALIZATION = "android.app.action.ROLE_HOLDER_PROVISION_FINALIZATION";
+    field public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
+    field public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_PROFILE";
     field public static final String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER";
     field @Deprecated public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE";
+    field public static final String ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER = "android.app.action.UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER";
     field public static final String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME";
     field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI";
     field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL";
@@ -1021,6 +1025,10 @@
     field public static final String REQUIRED_APP_MANAGED_DEVICE = "android.app.REQUIRED_APP_MANAGED_DEVICE";
     field public static final String REQUIRED_APP_MANAGED_PROFILE = "android.app.REQUIRED_APP_MANAGED_PROFILE";
     field public static final String REQUIRED_APP_MANAGED_USER = "android.app.REQUIRED_APP_MANAGED_USER";
+    field public static final int RESULT_DEVICE_OWNER_SET = 123; // 0x7b
+    field public static final int RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_RECOVERABLE_ERROR = 1; // 0x1
+    field public static final int RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_UNRECOVERABLE_ERROR = 2; // 0x2
+    field public static final int RESULT_WORK_PROFILE_CREATED = 122; // 0x7a
     field public static final int STATE_USER_PROFILE_COMPLETE = 4; // 0x4
     field public static final int STATE_USER_PROFILE_FINALIZED = 5; // 0x5
     field public static final int STATE_USER_SETUP_COMPLETE = 2; // 0x2
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index cf95ffe..ee74bb8 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -470,6 +470,105 @@
             = "android.app.action.PROVISION_FINALIZATION";
 
     /**
+     * Activity action: starts the managed profile provisioning flow inside the device management
+     * role holder.
+     *
+     * <p>During the managed profile provisioning flow, the platform-provided provisioning handler
+     * will delegate provisioning to the device management role holder, by firing this intent.
+     * Third-party mobile device management applications attempting to fire this intent will
+     * receive a {@link SecurityException}.
+     *
+     * <p>Device management role holders are required to have a handler for this intent action.
+     *
+     * <p>A result code of {@link Activity#RESULT_OK} implies that managed profile provisioning
+     * finished successfully. If it did not, a result code of {@link Activity#RESULT_CANCELED}
+     * is used instead.
+     *
+     * @see #ACTION_PROVISION_MANAGED_PROFILE
+     *
+     * @hide
+     */
+    // TODO(b/208628038): Uncomment when the permission is in place
+    //  @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_ROLE_HOLDER)
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    @SystemApi
+    public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE =
+            "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_PROFILE";
+
+    /**
+     * Result code that can be returned by the {@link
+     * #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} or {@link
+     * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} intent handlers if a work
+     * profile has been created.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int RESULT_WORK_PROFILE_CREATED = 122;
+
+    /**
+     * Result code that can be returned by the {@link
+     * #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} or {@link
+     * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} intent handlers if the
+     * device owner was set.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int RESULT_DEVICE_OWNER_SET = 123;
+
+    /**
+     * Activity action: starts the trusted source provisioning flow inside the device management
+     * role holder.
+     *
+     * <p>During the trusted source provisioning flow, the platform-provided provisioning handler
+     * will delegate provisioning to the device management role holder, by firing this intent.
+     * Third-party mobile device management applications attempting to fire this intent will
+     * receive a {@link SecurityException}.
+     *
+     * <p>Device management role holders are required to have a handler for this intent action.
+     *
+     * <p>The result codes can be either {@link #RESULT_WORK_PROFILE_CREATED}, {@link
+     * #RESULT_DEVICE_OWNER_SET} or {@link Activity#RESULT_CANCELED} if provisioning failed.
+     *
+     * @see #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE
+     *
+     * @hide
+     */
+    // TODO(b/208628038): Uncomment when the permission is in place
+    //  @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_ROLE_HOLDER)
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    @SystemApi
+    public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE =
+            "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
+
+    /**
+     * Activity action: starts the provisioning finalization flow inside the device management
+     * role holder.
+     *
+     * <p>During the provisioning finalization flow, the platform-provided provisioning handler
+     * will delegate provisioning to the device management role holder, by firing this intent.
+     * Third-party mobile device management applications attempting to fire this intent will
+     * receive a {@link SecurityException}.
+     *
+     * <p>Device management role holders are required to have a handler for this intent action.
+     *
+     * <p>This handler forwards the result from the admin app's {@link
+     * #ACTION_ADMIN_POLICY_COMPLIANCE} handler. Result code {@link Activity#RESULT_CANCELED}
+     * implies the provisioning finalization flow has failed.
+     *
+     * @see #ACTION_PROVISION_FINALIZATION
+     *
+     * @hide
+     */
+    // TODO(b/208628038): Uncomment when the permission is in place
+    //  @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_ROLE_HOLDER)
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    @SystemApi
+    public static final String ACTION_ROLE_HOLDER_PROVISION_FINALIZATION =
+            "android.app.action.ROLE_HOLDER_PROVISION_FINALIZATION";
+
+    /**
      * Action: Bugreport sharing with device owner has been accepted by the user.
      *
      * @hide
@@ -2821,6 +2920,43 @@
             "android.app.action.ADMIN_POLICY_COMPLIANCE";
 
     /**
+     * Activity action: Starts the device management role holder updater.
+     *
+     * <p>The activity must handle the device management role holder update and set the intent
+     * result to either {@link Activity#RESULT_OK} if the update was successful, {@link
+     * #RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_RECOVERABLE_ERROR} if it encounters a problem
+     * that may be solved by relaunching it again, or {@link
+     * #RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_UNRECOVERABLE_ERROR} if it encounters a problem
+     * that will not be solved by relaunching it again.
+     *
+     * @hide
+     */
+    // TODO(b/208628038): Uncomment when the permission is in place
+    //  @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_ROLE_HOLDER)
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    @SystemApi
+    public static final String ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER =
+            "android.app.action.UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER";
+
+    /**
+     * Result code that can be returned by the {@link #ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER}
+     * handler if it encounters a problem that may be solved by relaunching it again.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_RECOVERABLE_ERROR = 1;
+
+    /**
+     * Result code that can be returned by the {@link #ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER}
+     * handler if it encounters a problem that will not be solved by relaunching it again.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_UNRECOVERABLE_ERROR = 2;
+
+    /**
      * Maximum supported password length. Kind-of arbitrary.
      * @hide
      */
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java
index ceab02f..aac23d8 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetector.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java
@@ -103,6 +103,14 @@
     String SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK = "enable_telephony_fallback";
 
     /**
+     * A shell command that dumps a {@link
+     * com.android.server.timezonedetector.MetricsTimeZoneDetectorState} object to stdout for
+     * debugging.
+     * @hide
+     */
+    String SHELL_COMMAND_DUMP_METRICS = "dump_metrics";
+
+    /**
      * A shared utility method to create a {@link ManualTimeZoneSuggestion}.
      *
      * @hide
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index 0cf9f9f..4f2dba75 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -436,6 +436,8 @@
                 return "OPP";
             case HEARING_AID:
                 return "HEARING_AID";
+            case LE_AUDIO:
+                return "LE_AUDIO";
             default:
                 return "UNKNOWN_PROFILE";
         }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index e267db4..7bb290d 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11427,22 +11427,38 @@
                 "night_display_forced_auto_mode_available";
 
         /**
-        * If the NITZ_UPDATE_DIFF time is exceeded then an automatic adjustment
-        * to SystemClock will be allowed even if NITZ_UPDATE_SPACING has not been
-        * exceeded.
-        * @hide
-        */
+         * If UTC time between two NITZ signals is greater than this value then the second signal
+         * cannot be ignored.
+         *
+         * <p>This value is in milliseconds. It is used for telephony-based time and time zone
+         * detection.
+         * @hide
+         */
         @Readable
         public static final String NITZ_UPDATE_DIFF = "nitz_update_diff";
 
         /**
-        * The length of time in milli-seconds that automatic small adjustments to
-        * SystemClock are ignored if NITZ_UPDATE_DIFF is not exceeded.
-        * @hide
-        */
+         * If the elapsed realtime between two NITZ signals is greater than this value then the
+         * second signal cannot be ignored.
+         *
+         * <p>This value is in milliseconds. It is used for telephony-based time and time zone
+         * detection.
+         * @hide
+         */
         @Readable
         public static final String NITZ_UPDATE_SPACING = "nitz_update_spacing";
 
+        /**
+         * If the device connects to a telephony network and was disconnected from a telephony
+         * network for less than this time, a previously received NITZ signal can be restored.
+         *
+         * <p>This value is in milliseconds. It is used for telephony-based time and time zone
+         * detection.
+         * @hide
+         */
+        public static final String NITZ_NETWORK_DISCONNECT_RETENTION =
+                "nitz_network_disconnect_retention";
+
         /** Preferred NTP server. {@hide} */
         @Readable
         public static final String NTP_SERVER = "ntp_server";
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index a45a91e..414a7f1 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -3681,14 +3681,14 @@
                                     if (!mEdgeGlowBottom.isFinished()) {
                                         mEdgeGlowBottom.onRelease();
                                     }
-                                    invalidateTopGlow();
+                                    invalidateEdgeEffects();
                                 } else if (incrementalDeltaY < 0) {
                                     mEdgeGlowBottom.onPullDistance((float) overscroll / getHeight(),
                                             1.f - (float) x / getWidth());
                                     if (!mEdgeGlowTop.isFinished()) {
                                         mEdgeGlowTop.onRelease();
                                     }
-                                    invalidateBottomGlow();
+                                    invalidateEdgeEffects();
                                 }
                             }
                         }
@@ -3728,7 +3728,7 @@
                             if (!mEdgeGlowBottom.isFinished()) {
                                 mEdgeGlowBottom.onRelease();
                             }
-                            invalidateTopGlow();
+                            invalidateEdgeEffects();
                         } else if (rawDeltaY < 0) {
                             mEdgeGlowBottom.onPullDistance(
                                     (float) -overScrollDistance / getHeight(),
@@ -3736,7 +3736,7 @@
                             if (!mEdgeGlowTop.isFinished()) {
                                 mEdgeGlowTop.onRelease();
                             }
-                            invalidateBottomGlow();
+                            invalidateEdgeEffects();
                         }
                     }
                 }
@@ -3782,17 +3782,21 @@
         // First allow releasing existing overscroll effect:
         float consumed = 0;
         if (mEdgeGlowTop.getDistance() != 0) {
-            consumed = mEdgeGlowTop.onPullDistance((float) deltaY / getHeight(),
-                    (float) x / getWidth());
-            if (consumed != 0f) {
-                invalidateTopGlow();
+            if (canScrollUp()) {
+                mEdgeGlowTop.onRelease();
+            } else {
+                consumed = mEdgeGlowTop.onPullDistance((float) deltaY / getHeight(),
+                        (float) x / getWidth());
             }
+            invalidateEdgeEffects();
         } else if (mEdgeGlowBottom.getDistance() != 0) {
-            consumed = -mEdgeGlowBottom.onPullDistance((float) -deltaY / getHeight(),
-                    1f - (float) x / getWidth());
-            if (consumed != 0f) {
-                invalidateBottomGlow();
+            if (canScrollDown()) {
+                mEdgeGlowBottom.onRelease();
+            } else {
+                consumed = -mEdgeGlowBottom.onPullDistance((float) -deltaY / getHeight(),
+                        1f - (float) x / getWidth());
             }
+            invalidateEdgeEffects();
         }
         int pixelsConsumed = Math.round(consumed * getHeight());
         return deltaY - pixelsConsumed;
@@ -3802,30 +3806,16 @@
      * @return <code>true</code> if either the top or bottom edge glow is currently active or
      * <code>false</code> if it has no value to release.
      */
-    private boolean isGlowActive() {
-        return mEdgeGlowBottom.getDistance() != 0 || mEdgeGlowTop.getDistance() != 0;
+    private boolean doesTouchStopStretch() {
+        return (mEdgeGlowBottom.getDistance() != 0 && !canScrollDown())
+                || (mEdgeGlowTop.getDistance() != 0 && !canScrollUp());
     }
 
-    private void invalidateTopGlow() {
+    private void invalidateEdgeEffects() {
         if (!shouldDisplayEdgeEffects()) {
             return;
         }
-        final boolean clipToPadding = getClipToPadding();
-        final int top = clipToPadding ? mPaddingTop : 0;
-        final int left = clipToPadding ? mPaddingLeft : 0;
-        final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth();
-        invalidate(left, top, right, top + mEdgeGlowTop.getMaxHeight());
-    }
-
-    private void invalidateBottomGlow() {
-        if (!shouldDisplayEdgeEffects()) {
-            return;
-        }
-        final boolean clipToPadding = getClipToPadding();
-        final int bottom = clipToPadding ? getHeight() - mPaddingBottom : getHeight();
-        final int left = clipToPadding ? mPaddingLeft : 0;
-        final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth();
-        invalidate(left, bottom - mEdgeGlowBottom.getMaxHeight(), right, bottom);
+        invalidate();
     }
 
     @Override
@@ -4468,7 +4458,7 @@
                 final int edgeY = Math.min(0, scrollY + mFirstPositionDistanceGuess) + translateY;
                 canvas.translate(translateX, edgeY);
                 if (mEdgeGlowTop.draw(canvas)) {
-                    invalidateTopGlow();
+                    invalidateEdgeEffects();
                 }
                 canvas.restoreToCount(restoreCount);
             }
@@ -4482,7 +4472,7 @@
                 canvas.translate(edgeX, edgeY);
                 canvas.rotate(180, width, 0);
                 if (mEdgeGlowBottom.draw(canvas)) {
-                    invalidateBottomGlow();
+                    invalidateEdgeEffects();
                 }
                 canvas.restoreToCount(restoreCount);
             }
@@ -4572,7 +4562,7 @@
                 mActivePointerId = ev.getPointerId(0);
 
                 int motionPosition = findMotionRow(y);
-                if (isGlowActive()) {
+                if (doesTouchStopStretch()) {
                     // Pressed during edge effect, so this is considered the same as a fling catch.
                     touchMode = mTouchMode = TOUCH_MODE_FLING;
                 } else if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
@@ -6579,7 +6569,7 @@
      */
     public void setBottomEdgeEffectColor(@ColorInt int color) {
         mEdgeGlowBottom.setColor(color);
-        invalidateBottomGlow();
+        invalidateEdgeEffects();
     }
 
     /**
@@ -6593,7 +6583,7 @@
      */
     public void setTopEdgeEffectColor(@ColorInt int color) {
         mEdgeGlowTop.setColor(color);
-        invalidateTopGlow();
+        invalidateEdgeEffects();
     }
 
     /**
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index a8bf50e..62585c1 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -1848,7 +1848,7 @@
         if (mHasPendingRestartInputForSetText) {
             final InputMethodManager imm = getInputMethodManager();
             if (imm != null) {
-                imm.invalidateInput(mTextView);
+                imm.restartInput(mTextView);
             }
             mHasPendingRestartInputForSetText = false;
         }
diff --git a/core/proto/android/app/time_zone_detector.proto b/core/proto/android/app/time_zone_detector.proto
index b33ca1d..b52aa82 100644
--- a/core/proto/android/app/time_zone_detector.proto
+++ b/core/proto/android/app/time_zone_detector.proto
@@ -32,16 +32,8 @@
 }
 
 /*
- * An obfuscated and simplified time zone suggestion for metrics use.
- *
- * The suggestion's time zone IDs (which relate to location) are obfuscated by
- * mapping them to an ordinal. When the ordinal is assigned consistently across
- * several objects (i.e. so the same time zone ID is always mapped to the same
- * ordinal), this allows comparisons between those objects. For example, we can
- * answer "did these two suggestions agree?", "does the suggestion match the
- * device's current time zone?", without leaking knowledge of location. Ordinals
- * are also significantly more compact than full IANA TZDB IDs, albeit highly
- * unstable and of limited use.
+ * A generic-form time zone suggestion for metrics use. Required to be a superset of the
+ * MetricsTimeZoneSuggestion proto defined in atoms.proto to ensure binary compatibility.
  */
 message MetricsTimeZoneSuggestion {
   option (android.msg_privacy).dest = DEST_AUTOMATIC;
@@ -55,5 +47,24 @@
   // The ordinals for time zone(s) in the suggestion. Always empty for
   // UNCERTAIN, and can be empty for CERTAIN, for example when the device is in
   // a disputed area / on an ocean.
-  repeated uint32 time_zone_ordinals = 2;
+  //
+  // The suggestion's time zone IDs (which relate to location) are obfuscated by
+  // mapping them to an ordinal. When the ordinal is assigned consistently across
+  // several objects (i.e. so the same time zone ID is always mapped to the same
+  // ordinal), this allows comparisons between those objects. For example, we can
+  // answer "did these two suggestions agree?", "does the suggestion match the
+  // device's current time zone?", without leaking knowledge of location. Ordinals
+  // are also significantly more compact than full IANA TZDB IDs, albeit unstable
+  // and of limited use.
+  repeated int32 time_zone_ordinals = 2;
+
+  // The actual time zone ID(s) in the suggestion. Similar to time_zone_ordinals
+  // but contains the actual string IDs.
+  //
+  // This information is only captured / reported for some devices based on the
+  // value of a server side flag, i.e. it could be enabled for internal testers.
+  // Therefore the list can be empty even when time_zone_ordinals is populated.
+  //
+  // When enabled, see time_zone_ordinals for the expected number of values.
+  repeated string time_zone_ids = 3;
 }
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index db5d49d..b406578 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -672,18 +672,31 @@
     optional SettingProto new_contact_aggregator = 79 [ (android.privacy).dest = DEST_AUTOMATIC ];
     optional SettingProto night_display_forced_auto_mode_available = 80 [ (android.privacy).dest = DEST_AUTOMATIC ];
 
-    message NitzUpdate {
+    message Nitz {
         option (android.msg_privacy).dest = DEST_EXPLICIT;
 
-        // If the NITZ_UPDATE_DIFF time is exceeded then an automatic adjustment to
-        // SystemClock will be allowed even if NITZ_UPDATE_SPACING has not been
-        // exceeded.
-        optional SettingProto diff = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
-        // The length of time in milli-seconds that automatic small adjustments to
-        // SystemClock are ignored if NITZ_UPDATE_DIFF is not exceeded.
-        optional SettingProto spacing = 2 [ (android.privacy).dest = DEST_AUTOMATIC ];
+        // If UTC time between two NITZ signals is greater than this value then the second signal
+        // cannot be ignored.
+        //
+        // This value is in milliseconds. It is used for telephony-based time and time zone
+        // detection.
+        optional SettingProto update_diff = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
+        // If the elapsed realtime between two NITZ signals is greater than this value then the
+        // second signal cannot be ignored.
+        //
+        // This value is in milliseconds. It is used for telephony-based time and time zone
+        // detection.
+        optional SettingProto update_spacing = 2 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
+        // If the device connects to a telephony network and was disconnected from a telephony
+        // network for less than this time, a previously received NITZ signal can be restored.
+        //
+        // This value is in milliseconds. It is used for telephony-based time and time zone
+        // detection.
+        optional SettingProto network_disconnect_retention = 3 [ (android.privacy).dest = DEST_AUTOMATIC ];
     }
-    optional NitzUpdate nitz_update = 81;
+    optional Nitz nitz = 81;
 
     message Notification {
         option (android.msg_privacy).dest = DEST_EXPLICIT;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index 0c3a6b2..5161092 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -95,9 +95,8 @@
 
         // Update bitmap
         val fg = InsetDrawable(overflowBtn?.drawable, overflowIconInset)
-        bitmap = iconFactory.createBadgedIconBitmap(AdaptiveIconDrawable(
-                ColorDrawable(colorAccent), fg),
-            null /* user */, true /* shrinkNonAdaptiveIcons */).icon
+        bitmap = iconFactory.createBadgedIconBitmap(
+                AdaptiveIconDrawable(ColorDrawable(colorAccent), fg)).icon
 
         // Update dot path
         dotPath = PathParser.createPathFromPathData(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
index 932f879..91aff3e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
@@ -189,9 +189,7 @@
             BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon,
                     b.isImportantConversation());
             info.badgeBitmap = badgeBitmapInfo.icon;
-            info.bubbleBitmap = iconFactory.createBadgedIconBitmap(bubbleDrawable,
-                    null /* user */,
-                    true /* shrinkNonAdaptiveIcons */).icon;
+            info.bubbleBitmap = iconFactory.createBadgedIconBitmap(bubbleDrawable).icon;
 
             // Dot color & placement
             Path iconPath = PathParser.createPathFromPathData(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index a163f37..413627d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -390,8 +390,7 @@
                     final ShapeIconFactory factory = new ShapeIconFactory(
                             SplashscreenContentDrawer.this.mContext,
                             scaledIconDpi, mFinalIconSize);
-                    final Bitmap bitmap = factory.createScaledBitmapWithoutShadow(
-                            iconDrawable, true /* shrinkNonAdaptiveIcons */);
+                    final Bitmap bitmap = factory.createScaledBitmapWithoutShadow(iconDrawable);
                     Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
                     createIconDrawable(new BitmapDrawable(bitmap), true);
                 }
diff --git a/packages/Nsd/service/src/com/android/server/NativeDaemonConnector.java b/packages/Nsd/service/src/com/android/server/NativeDaemonConnector.java
index eac767f..02475dd 100644
--- a/packages/Nsd/service/src/com/android/server/NativeDaemonConnector.java
+++ b/packages/Nsd/service/src/com/android/server/NativeDaemonConnector.java
@@ -26,12 +26,10 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.util.LocalLog;
-import android.util.Slog;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
 import com.android.server.power.ShutdownThread;
-import com.google.android.collect.Lists;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
@@ -40,19 +38,19 @@
 import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
-import java.util.concurrent.atomic.AtomicInteger;
+import java.util.LinkedList;
+import java.util.Objects;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
-import java.util.LinkedList;
-import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * Generic connector class for interfacing with a native daemon which uses the
  * {@code libsysutils} FrameworkListener protocol.
  */
-final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdog.Monitor {
+final class NativeDaemonConnector implements Runnable, Handler.Callback {
     private final static boolean VDBG = false;
 
     private final String TAG;
@@ -107,7 +105,7 @@
 
     /**
      * Enable Set debugging mode, which causes messages to also be written to both
-     * {@link Slog} in addition to internal log.
+     * {@link Log} in addition to internal log.
      */
     public void setDebug(boolean debug) {
         mDebug = debug;
@@ -126,7 +124,9 @@
      * calls while holding a lock on the given object.
      */
     public void setWarnIfHeld(Object warnIfHeld) {
-        Preconditions.checkState(mWarnIfHeld == null);
+        if (mWarnIfHeld != null) {
+            throw new IllegalStateException("warnIfHeld is already set.");
+        }
         mWarnIfHeld = Objects.requireNonNull(warnIfHeld);
     }
 
@@ -183,7 +183,7 @@
         // In order to ensure that unprivileged apps aren't able to impersonate native daemons on
         // production devices, even if said native daemons ill-advisedly pick a socket name that
         // starts with __test__, only allow this on debug builds.
-        if (mSocket.startsWith("__test__") && Build.IS_DEBUGGABLE) {
+        if (mSocket.startsWith("__test__") && Build.isDebuggable()) {
             return new LocalSocketAddress(mSocket);
         } else {
             return new LocalSocketAddress(mSocket, LocalSocketAddress.Namespace.RESERVED);
@@ -375,7 +375,7 @@
         try {
             latch.await();
         } catch (InterruptedException e) {
-            Slog.wtf(TAG, "Interrupted while waiting for unsolicited response handling", e);
+            Log.wtf(TAG, "Interrupted while waiting for unsolicited response handling", e);
         }
     }
 
@@ -462,13 +462,13 @@
     public NativeDaemonEvent[] executeForList(long timeoutMs, String cmd, Object... args)
             throws NativeDaemonConnectorException {
         if (mWarnIfHeld != null && Thread.holdsLock(mWarnIfHeld)) {
-            Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName() + " is holding 0x"
+            Log.wtf(TAG, "Calling thread " + Thread.currentThread().getName() + " is holding 0x"
                     + Integer.toHexString(System.identityHashCode(mWarnIfHeld)), new Throwable());
         }
 
         final long startTime = SystemClock.elapsedRealtime();
 
-        final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();
+        final ArrayList<NativeDaemonEvent> events = new ArrayList<>();
 
         final StringBuilder rawBuilder = new StringBuilder();
         final StringBuilder logBuilder = new StringBuilder();
@@ -571,7 +571,7 @@
      */
     public static class Command {
         private String mCmd;
-        private ArrayList<Object> mArguments = Lists.newArrayList();
+        private ArrayList<Object> mArguments = new ArrayList<>();
 
         public Command(String cmd, Object... args) {
             mCmd = cmd;
@@ -586,11 +586,6 @@
         }
     }
 
-    /** {@inheritDoc} */
-    public void monitor() {
-        synchronized (mDaemonLock) { }
-    }
-
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         mLocalLog.dump(fd, pw, args);
         pw.println();
@@ -598,12 +593,12 @@
     }
 
     private void log(String logstring) {
-        if (mDebug) Slog.d(TAG, logstring);
+        if (mDebug) Log.d(TAG, logstring);
         mLocalLog.log(logstring);
     }
 
     private void loge(String logstring) {
-        Slog.e(TAG, logstring);
+        Log.e(TAG, logstring);
         mLocalLog.log(logstring);
     }
 
@@ -659,12 +654,12 @@
                 if (found == null) {
                     // didn't find it - make sure our queue isn't too big before adding
                     while (mPendingCmds.size() >= mMaxCount) {
-                        Slog.e("NativeDaemonConnector.ResponseQueue",
+                        Log.e("NativeDaemonConnector.ResponseQueue",
                                 "more buffered than allowed: " + mPendingCmds.size() +
                                 " >= " + mMaxCount);
                         // let any waiter timeout waiting for this
                         PendingCmd pendingCmd = mPendingCmds.remove();
-                        Slog.e("NativeDaemonConnector.ResponseQueue",
+                        Log.e("NativeDaemonConnector.ResponseQueue",
                                 "Removing request: " + pendingCmd.logCmd + " (" +
                                 pendingCmd.cmdNum + ")");
                     }
@@ -706,7 +701,7 @@
                 result = found.responses.poll(timeoutMs, TimeUnit.MILLISECONDS);
             } catch (InterruptedException e) {}
             if (result == null) {
-                Slog.e("NativeDaemonConnector.ResponseQueue", "Timeout waiting for response");
+                Log.e("NativeDaemonConnector.ResponseQueue", "Timeout waiting for response");
             }
             return result;
         }
diff --git a/packages/Nsd/service/src/com/android/server/NativeDaemonEvent.java b/packages/Nsd/service/src/com/android/server/NativeDaemonEvent.java
index e6feda3..5683694 100644
--- a/packages/Nsd/service/src/com/android/server/NativeDaemonEvent.java
+++ b/packages/Nsd/service/src/com/android/server/NativeDaemonEvent.java
@@ -16,8 +16,7 @@
 
 package com.android.server;
 
-import android.util.Slog;
-import com.google.android.collect.Lists;
+import android.util.Log;
 
 import java.io.FileDescriptor;
 import java.util.ArrayList;
@@ -179,7 +178,7 @@
      * {@link #getMessage()} for any events matching the requested code.
      */
     public static String[] filterMessageList(NativeDaemonEvent[] events, int matchCode) {
-        final ArrayList<String> result = Lists.newArrayList();
+        final ArrayList<String> result = new ArrayList<>();
         for (NativeDaemonEvent event : events) {
             if (event.getCode() == matchCode) {
                 result.add(event.getMessage());
@@ -212,7 +211,7 @@
         int wordEnd = -1;
         boolean quoted = false;
 
-        if (DEBUG_ROUTINE) Slog.e(LOGTAG, "parsing '" + rawEvent + "'");
+        if (DEBUG_ROUTINE) Log.e(LOGTAG, "parsing '" + rawEvent + "'");
         if (rawEvent.charAt(current) == '\"') {
             quoted = true;
             current++;
@@ -240,14 +239,14 @@
             word = word.replace("\\\\", "\\");
             word = word.replace("\\\"", "\"");
 
-            if (DEBUG_ROUTINE) Slog.e(LOGTAG, "found '" + word + "'");
+            if (DEBUG_ROUTINE) Log.e(LOGTAG, "found '" + word + "'");
             parsed.add(word);
 
             // find the beginning of the next word - either of these options
             int nextSpace = rawEvent.indexOf(' ', current);
             int nextQuote = rawEvent.indexOf(" \"", current);
             if (DEBUG_ROUTINE) {
-                Slog.e(LOGTAG, "nextSpace=" + nextSpace + ", nextQuote=" + nextQuote);
+                Log.e(LOGTAG, "nextSpace=" + nextSpace + ", nextQuote=" + nextQuote);
             }
             if (nextQuote > -1 && nextQuote <= nextSpace) {
                 quoted = true;
@@ -259,8 +258,8 @@
                 }
             } // else we just start the next word after the current and read til the end
             if (DEBUG_ROUTINE) {
-                Slog.e(LOGTAG, "next loop - current=" + current +
-                        ", length=" + length + ", quoted=" + quoted);
+                Log.e(LOGTAG, "next loop - current=" + current
+                        + ", length=" + length + ", quoted=" + quoted);
             }
         }
         return parsed.toArray(new String[parsed.size()]);
diff --git a/packages/Nsd/service/src/com/android/server/NsdService.java b/packages/Nsd/service/src/com/android/server/NsdService.java
index 3e02084..76ecea7 100644
--- a/packages/Nsd/service/src/com/android/server/NsdService.java
+++ b/packages/Nsd/service/src/com/android/server/NsdService.java
@@ -19,6 +19,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.net.nsd.INsdManager;
@@ -36,12 +37,10 @@
 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.DumpUtils;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 import com.android.net.module.util.DnsSdTxtRecord;
@@ -228,7 +227,7 @@
                         break;
                     case NsdManager.NATIVE_DAEMON_EVENT:
                     default:
-                        Slog.e(TAG, "Unhandled " + msg);
+                        Log.e(TAG, "Unhandled " + msg);
                         return NOT_HANDLED;
                 }
                 return HANDLED;
@@ -274,7 +273,7 @@
 
             private boolean requestLimitReached(ClientInfo clientInfo) {
                 if (clientInfo.mClientIds.size() >= ClientInfo.MAX_LIMIT) {
-                    if (DBG) Slog.d(TAG, "Exceeded max outstanding requests " + clientInfo);
+                    if (DBG) Log.d(TAG, "Exceeded max outstanding requests " + clientInfo);
                     return true;
                 }
                 return false;
@@ -307,7 +306,7 @@
                         transitionTo(mDisabledState);
                         break;
                     case NsdManager.DISCOVER_SERVICES:
-                        if (DBG) Slog.d(TAG, "Discover services");
+                        if (DBG) Log.d(TAG, "Discover services");
                         args = (ListenerArgs) msg.obj;
                         clientInfo = mClients.get(args.connector);
 
@@ -321,8 +320,8 @@
                         id = getUniqueId();
                         if (discoverServices(id, args.serviceInfo.getServiceType())) {
                             if (DBG) {
-                                Slog.d(TAG, "Discover " + msg.arg2 + " " + id +
-                                        args.serviceInfo.getServiceType());
+                                Log.d(TAG, "Discover " + msg.arg2 + " " + id
+                                        + args.serviceInfo.getServiceType());
                             }
                             storeRequestMap(clientId, id, clientInfo, msg.what);
                             clientInfo.onDiscoverServicesStarted(clientId, args.serviceInfo);
@@ -333,7 +332,7 @@
                         }
                         break;
                     case NsdManager.STOP_DISCOVERY:
-                        if (DBG) Slog.d(TAG, "Stop service discovery");
+                        if (DBG) Log.d(TAG, "Stop service discovery");
                         args = (ListenerArgs) msg.obj;
                         clientInfo = mClients.get(args.connector);
 
@@ -353,7 +352,7 @@
                         }
                         break;
                     case NsdManager.REGISTER_SERVICE:
-                        if (DBG) Slog.d(TAG, "Register service");
+                        if (DBG) Log.d(TAG, "Register service");
                         args = (ListenerArgs) msg.obj;
                         clientInfo = mClients.get(args.connector);
                         if (requestLimitReached(clientInfo)) {
@@ -365,7 +364,7 @@
                         maybeStartDaemon();
                         id = getUniqueId();
                         if (registerService(id, args.serviceInfo)) {
-                            if (DBG) Slog.d(TAG, "Register " + clientId + " " + id);
+                            if (DBG) Log.d(TAG, "Register " + clientId + " " + id);
                             storeRequestMap(clientId, id, clientInfo, msg.what);
                             // Return success after mDns reports success
                         } else {
@@ -375,11 +374,11 @@
                         }
                         break;
                     case NsdManager.UNREGISTER_SERVICE:
-                        if (DBG) Slog.d(TAG, "unregister service");
+                        if (DBG) Log.d(TAG, "unregister service");
                         args = (ListenerArgs) msg.obj;
                         clientInfo = mClients.get(args.connector);
                         if (clientInfo == null) {
-                            Slog.e(TAG, "Unknown connector in unregistration");
+                            Log.e(TAG, "Unknown connector in unregistration");
                             break;
                         }
                         id = clientInfo.mClientIds.get(clientId);
@@ -392,7 +391,7 @@
                         }
                         break;
                     case NsdManager.RESOLVE_SERVICE:
-                        if (DBG) Slog.d(TAG, "Resolve service");
+                        if (DBG) Log.d(TAG, "Resolve service");
                         args = (ListenerArgs) msg.obj;
                         clientInfo = mClients.get(args.connector);
 
@@ -430,7 +429,7 @@
                 ClientInfo clientInfo = mIdToClientInfoMap.get(id);
                 if (clientInfo == null) {
                     String name = NativeResponseCode.nameOf(code);
-                    Slog.e(TAG, String.format("id %d for %s has no client mapping", id, name));
+                    Log.e(TAG, String.format("id %d for %s has no client mapping", id, name));
                     return false;
                 }
 
@@ -441,14 +440,14 @@
                     // SERVICE_FOUND may race with STOP_SERVICE_DISCOVERY,
                     // and we may get in this situation.
                     String name = NativeResponseCode.nameOf(code);
-                    Slog.d(TAG, String.format(
+                    Log.d(TAG, String.format(
                             "Notification %s for listener id %d that is no longer active",
                             name, id));
                     return false;
                 }
                 if (DBG) {
                     String name = NativeResponseCode.nameOf(code);
-                    Slog.d(TAG, String.format("Native daemon message %s: %s", name, raw));
+                    Log.d(TAG, String.format("Native daemon message %s: %s", name, raw));
                 }
                 switch (code) {
                     case NativeResponseCode.SERVICE_FOUND:
@@ -492,7 +491,7 @@
                             ++index;
                         }
                         if (index >= cooked[2].length()) {
-                            Slog.e(TAG, "Invalid service found " + raw);
+                            Log.e(TAG, "Invalid service found " + raw);
                             break;
                         }
                         String name = cooked[2].substring(0, index);
@@ -562,13 +561,13 @@
             char c = s.charAt(i);
             if (c == '\\') {
                 if (++i >= s.length()) {
-                    Slog.e(TAG, "Unexpected end of escape sequence in: " + s);
+                    Log.e(TAG, "Unexpected end of escape sequence in: " + s);
                     break;
                 }
                 c = s.charAt(i);
                 if (c != '.' && c != '\\') {
                     if (i + 2 >= s.length()) {
-                        Slog.e(TAG, "Unexpected end of escape sequence in: " + s);
+                        Log.e(TAG, "Unexpected end of escape sequence in: " + s);
                         break;
                     }
                     c = (char) ((c-'0') * 100 + (s.charAt(i+1)-'0') * 10 + (s.charAt(i+2)-'0'));
@@ -685,7 +684,7 @@
     private boolean isNsdEnabled() {
         boolean ret = mNsdSettings.isEnabled();
         if (DBG) {
-            Slog.d(TAG, "Network service discovery is " + (ret ? "enabled" : "disabled"));
+            Log.d(TAG, "Network service discovery is " + (ret ? "enabled" : "disabled"));
         }
         return ret;
     }
@@ -795,12 +794,12 @@
          */
         public boolean execute(Object... args) {
             if (DBG) {
-                Slog.d(TAG, "mdnssd " + Arrays.toString(args));
+                Log.d(TAG, "mdnssd " + Arrays.toString(args));
             }
             try {
                 mNativeConnector.execute("mdnssd", args);
             } catch (NativeDaemonConnectorException e) {
-                Slog.e(TAG, "Failed to execute mdnssd " + Arrays.toString(args), e);
+                Log.e(TAG, "Failed to execute mdnssd " + Arrays.toString(args), e);
                 return false;
             }
             return true;
@@ -831,7 +830,7 @@
 
     private boolean registerService(int regId, NsdServiceInfo service) {
         if (DBG) {
-            Slog.d(TAG, "registerService: " + regId + " " + service);
+            Log.d(TAG, "registerService: " + regId + " " + service);
         }
         String name = service.getServiceName();
         String type = service.getServiceType();
@@ -880,7 +879,12 @@
 
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+                != PackageManager.PERMISSION_GRANTED) {
+            pw.println("Permission Denial: can't dump " + TAG
+                    + " due to missing android.permission.DUMP permission");
+            return;
+        }
 
         for (ClientInfo client : mClients.values()) {
             pw.println("Client Info");
@@ -909,7 +913,7 @@
 
         private ClientInfo(INsdManagerCallback cb) {
             mCb = cb;
-            if (DBG) Slog.d(TAG, "New client");
+            if (DBG) Log.d(TAG, "New client");
         }
 
         @Override
@@ -943,8 +947,10 @@
                 clientId = mClientIds.keyAt(i);
                 globalId = mClientIds.valueAt(i);
                 mIdToClientInfoMap.remove(globalId);
-                if (DBG) Slog.d(TAG, "Terminating client-ID " + clientId +
-                        " global-ID " + globalId + " type " + mClientRequests.get(clientId));
+                if (DBG) {
+                    Log.d(TAG, "Terminating client-ID " + clientId
+                            + " global-ID " + globalId + " type " + mClientRequests.get(clientId));
+                }
                 switch (mClientRequests.get(clientId)) {
                     case NsdManager.DISCOVER_SERVICES:
                         stopServiceDiscovery(globalId);
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index f637ff8..d73e45e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -19,7 +19,6 @@
 import android.graphics.ColorFilter;
 import android.graphics.ColorMatrix;
 import android.graphics.ColorMatrixColorFilter;
-import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.location.LocationManager;
 import android.media.AudioManager;
@@ -44,6 +43,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.UserIcons;
+import com.android.launcher3.icons.BaseIconFactory.IconOptions;
 import com.android.launcher3.icons.IconFactory;
 import com.android.settingslib.drawable.UserIconDrawable;
 import com.android.settingslib.fuelgauge.BatteryStatus;
@@ -525,9 +525,9 @@
     /** Get the corresponding adaptive icon drawable. */
     public static Drawable getBadgedIcon(Context context, Drawable icon, UserHandle user) {
         try (IconFactory iconFactory = IconFactory.obtain(context)) {
-            final Bitmap iconBmp = iconFactory.createBadgedIconBitmap(icon, user,
-                    true /* shrinkNonAdaptiveIcons */).icon;
-            return new BitmapDrawable(context.getResources(), iconBmp);
+            return iconFactory
+                    .createBadgedIconBitmap(icon, new IconOptions().setUser(user))
+                    .newIcon(context);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java
index 1d8f71e..78ec58b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java
@@ -18,11 +18,13 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.res.Configuration;
 import android.icu.text.ListFormatter;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 import android.text.TextUtils;
@@ -155,9 +157,41 @@
         return set;
     }
 
-    public static void saveInputMethodSubtypeList(PreferenceFragmentCompat context,
+    /**
+     * Save the enabled/disabled input methods and selected subtype states into system settings.
+     *
+     * @param fragment The preference fragment user interact with.
+     * @param resolver The {@link ContentResolver} used to access the database.
+     * @param inputMethodInfos The list of {@link InputMethodInfo} to be checked.
+     * @param hasHardKeyboard {@code true} if the device has the hardware keyboard.
+     */
+    public static void saveInputMethodSubtypeList(PreferenceFragmentCompat fragment,
             ContentResolver resolver, List<InputMethodInfo> inputMethodInfos,
             boolean hasHardKeyboard) {
+        saveInputMethodSubtypeListForUserInternal(
+                fragment, resolver, inputMethodInfos, hasHardKeyboard, UserHandle.myUserId());
+    }
+
+    /**
+     * Save the enabled/disabled input methods and selected subtype states into system settings as
+     * given userId.
+     *
+     * @param fragment The preference fragment user interact with.
+     * @param resolver The {@link ContentResolver} used to access the database.
+     * @param inputMethodInfos The list of {@link InputMethodInfo} to be checked.
+     * @param hasHardKeyboard {@code true} if the device has the hardware keyboard.
+     * @param userId The given userId
+     */
+    public static void saveInputMethodSubtypeListForUser(PreferenceFragmentCompat fragment,
+            ContentResolver resolver, List<InputMethodInfo> inputMethodInfos,
+            boolean hasHardKeyboard, @UserIdInt int userId) {
+        saveInputMethodSubtypeListForUserInternal(
+                fragment, resolver, inputMethodInfos, hasHardKeyboard, userId);
+    }
+
+    private static void saveInputMethodSubtypeListForUserInternal(PreferenceFragmentCompat fragment,
+            ContentResolver resolver, List<InputMethodInfo> inputMethodInfos,
+            boolean hasHardKeyboard, @UserIdInt int userId) {
         String currentInputMethodId = Settings.Secure.getString(resolver,
                 Settings.Secure.DEFAULT_INPUT_METHOD);
         final int selectedInputMethodSubtype = getInputMethodSubtypeSelected(resolver);
@@ -168,7 +202,7 @@
         boolean needsToResetSelectedSubtype = false;
         for (final InputMethodInfo imi : inputMethodInfos) {
             final String imiId = imi.getId();
-            final Preference pref = context.findPreference(imiId);
+            final Preference pref = fragment.findPreference(imiId);
             if (pref == null) {
                 continue;
             }
@@ -184,8 +218,11 @@
             }
             final boolean isCurrentInputMethod = imiId.equals(currentInputMethodId);
             final boolean systemIme = imi.isSystem();
+            // Create context as given userId
+            final Context wrapperContext = userId == UserHandle.myUserId() ? fragment.getActivity()
+                    : fragment.getActivity().createContextAsUser(UserHandle.of(userId), 0);
             if ((!hasHardKeyboard && InputMethodSettingValuesWrapper.getInstance(
-                    context.getActivity()).isAlwaysCheckedIme(imi))
+                    wrapperContext).isAlwaysCheckedIme(imi))
                     || isImeChecked) {
                 if (!enabledIMEsAndSubtypesMap.containsKey(imiId)) {
                     // imiId has just been enabled
@@ -198,7 +235,7 @@
                 for (int i = 0; i < subtypeCount; ++i) {
                     final InputMethodSubtype subtype = imi.getSubtypeAt(i);
                     final String subtypeHashCodeStr = String.valueOf(subtype.hashCode());
-                    final TwoStatePreference subtypePref = (TwoStatePreference) context
+                    final TwoStatePreference subtypePref = (TwoStatePreference) fragment
                             .findPreference(imiId + subtypeHashCodeStr);
                     // In the Configure input method screen which does not have subtype preferences.
                     if (subtypePref == null) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
index 94a0c00..c1ab706 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
@@ -18,6 +18,7 @@
 
 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 
+import android.annotation.UserIdInt;
 import android.app.AlertDialog;
 import android.content.ActivityNotFoundException;
 import android.content.Context;
@@ -75,30 +76,34 @@
     private final OnSavePreferenceListener mOnSaveListener;
     private final InputMethodSettingValuesWrapper mInputMethodSettingValues;
     private final boolean mIsAllowedByOrganization;
+    @UserIdInt
+    private final int mUserId;
 
     private AlertDialog mDialog = null;
 
     /**
      * A preference entry of an input method.
      *
-     * @param context The Context this is associated with.
+     * @param prefContext The Context this preference is associated with.
      * @param imi The {@link InputMethodInfo} of this preference.
      * @param isAllowedByOrganization false if the IME has been disabled by a device or profile
      *     owner.
      * @param onSaveListener The listener called when this preference has been changed and needs
      *     to save the state to shared preference.
+     * @param userId The userId to specify the corresponding user for this preference.
      */
-    public InputMethodPreference(final Context context, final InputMethodInfo imi,
-            final boolean isAllowedByOrganization, final OnSavePreferenceListener onSaveListener) {
-        this(context, imi, imi.loadLabel(context.getPackageManager()), isAllowedByOrganization,
-                onSaveListener);
+    public InputMethodPreference(final Context prefContext, final InputMethodInfo imi,
+            final boolean isAllowedByOrganization, final OnSavePreferenceListener onSaveListener,
+            final @UserIdInt int userId) {
+        this(prefContext, imi, imi.loadLabel(prefContext.getPackageManager()),
+                isAllowedByOrganization, onSaveListener, userId);
     }
 
     @VisibleForTesting
-    InputMethodPreference(final Context context, final InputMethodInfo imi,
+    InputMethodPreference(final Context prefContext, final InputMethodInfo imi,
             final CharSequence title, final boolean isAllowedByOrganization,
-            final OnSavePreferenceListener onSaveListener) {
-        super(context);
+            final OnSavePreferenceListener onSaveListener, final @UserIdInt int userId) {
+        super(prefContext);
         setPersistent(false);
         mImi = imi;
         mIsAllowedByOrganization = isAllowedByOrganization;
@@ -114,7 +119,12 @@
             intent.setClassName(imi.getPackageName(), settingsActivity);
             setIntent(intent);
         }
-        mInputMethodSettingValues = InputMethodSettingValuesWrapper.getInstance(context);
+        // Handle the context by given userId because {@link InputMethodSettingValuesWrapper} is
+        // per-user instance.
+        final Context userAwareContext = userId == UserHandle.myUserId() ? prefContext :
+                getContext().createContextAsUser(UserHandle.of(userId), 0);
+        mInputMethodSettingValues = InputMethodSettingValuesWrapper.getInstance(userAwareContext);
+        mUserId = userId;
         mHasPriorityInSorting = imi.isSystem()
                 && InputMethodAndSubtypeUtil.isValidNonAuxAsciiCapableIme(imi);
         setOnPreferenceClickListener(this);
@@ -130,17 +140,15 @@
         super.onBindViewHolder(holder);
         final Switch switchWidget = getSwitch();
         if (switchWidget != null) {
+            // Avoid default behavior in {@link PrimarySwitchPreference#onBindViewHolder}.
             switchWidget.setOnClickListener(v -> {
-                // no-op, avoid default behavior in {@link PrimarySwitchPreference#onBindViewHolder}
-            });
-            switchWidget.setOnCheckedChangeListener((buttonView, isChecked) -> {
-                // Avoid the invocation after we call {@link PrimarySwitchPreference#setChecked()}
-                // in {@link setCheckedInternal}
-                if (isChecked != isChecked()) {
-                    // Keep switch to previous state because we have to show the dialog first
-                    buttonView.setChecked(!isChecked);
-                    callChangeListener(isChecked());
+                if (!switchWidget.isEnabled()) {
+                    return;
                 }
+                final boolean newValue = !isChecked();
+                // Keep switch to previous state because we have to show the dialog first.
+                switchWidget.setChecked(isChecked());
+                callChangeListener(newValue);
             });
         }
         final ImageView icon = holder.itemView.findViewById(android.R.id.icon);
@@ -187,7 +195,7 @@
             final Intent intent = getIntent();
             if (intent != null) {
                 // Invoke a settings activity of an input method.
-                context.startActivity(intent);
+                context.startActivityAsUser(intent, UserHandle.of(mUserId));
             }
         } catch (final ActivityNotFoundException e) {
             Log.d(TAG, "IME's Settings Activity Not Found", e);
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java
index 13c1b82..39e6dce 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java
@@ -16,13 +16,18 @@
 
 package com.android.settingslib.inputmethod;
 
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
 import android.annotation.UiThread;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.util.Log;
+import android.util.SparseArray;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodManager;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -39,20 +44,39 @@
 public class InputMethodSettingValuesWrapper {
     private static final String TAG = InputMethodSettingValuesWrapper.class.getSimpleName();
 
-    private static volatile InputMethodSettingValuesWrapper sInstance;
+    private static final Object sInstanceMapLock = new Object();
+    /**
+     * Manages mapping between user ID and corresponding singleton
+     * {@link InputMethodSettingValuesWrapper} object.
+     */
+    @GuardedBy("sInstanceMapLock")
+    private static SparseArray<InputMethodSettingValuesWrapper> sInstanceMap = new SparseArray<>();
     private final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>();
     private final ContentResolver mContentResolver;
     private final InputMethodManager mImm;
 
-    public static InputMethodSettingValuesWrapper getInstance(Context context) {
-        if (sInstance == null) {
-            synchronized (TAG) {
-                if (sInstance == null) {
-                    sInstance = new InputMethodSettingValuesWrapper(context);
-                }
+    @AnyThread
+    @NonNull
+    public static InputMethodSettingValuesWrapper getInstance(@NonNull Context context) {
+        final int requestUserId = context.getUserId();
+        InputMethodSettingValuesWrapper valuesWrapper;
+        // First time to create the wrapper.
+        synchronized (sInstanceMapLock) {
+            if (sInstanceMap.size() == 0) {
+                valuesWrapper = new InputMethodSettingValuesWrapper(context);
+                sInstanceMap.put(requestUserId, valuesWrapper);
+                return valuesWrapper;
             }
+            // We have same user context as request.
+            if (sInstanceMap.indexOfKey(requestUserId) >= 0) {
+                return sInstanceMap.get(requestUserId);
+            }
+            // Request by a new user context.
+            valuesWrapper = new InputMethodSettingValuesWrapper(context);
+            sInstanceMap.put(context.getUserId(), valuesWrapper);
         }
-        return sInstance;
+
+        return valuesWrapper;
     }
 
     // Ensure singleton
@@ -64,7 +88,7 @@
 
     public void refreshAllInputMethodAndSubtypes() {
         mMethodList.clear();
-        mMethodList.addAll(mImm.getInputMethodList());
+        mMethodList.addAll(mImm.getInputMethodListAsUser(mContentResolver.getUserId()));
     }
 
     public List<InputMethodInfo> getInputMethodList() {
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodPreferenceTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodPreferenceTest.java
index 9962e1c..1e75014 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodPreferenceTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodPreferenceTest.java
@@ -20,6 +20,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.os.UserHandle;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
 
@@ -112,7 +113,8 @@
                 createInputMethodInfo(systemIme, name),
                 title,
                 true /* isAllowedByOrganization */,
-                p -> {} /* onSavePreferenceListener */);
+                p -> {} /* onSavePreferenceListener */,
+                UserHandle.myUserId());
     }
 
     private static InputMethodInfo createInputMethodInfo(
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index a67b565..6072f68 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1067,14 +1067,17 @@
                 Settings.Global.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE,
                 GlobalSettingsProto.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE);
 
-        final long nitzUpdateToken = p.start(GlobalSettingsProto.NITZ_UPDATE);
+        final long nitzToken = p.start(GlobalSettingsProto.NITZ);
         dumpSetting(s, p,
                 Settings.Global.NITZ_UPDATE_DIFF,
-                GlobalSettingsProto.NitzUpdate.DIFF);
+                GlobalSettingsProto.Nitz.UPDATE_DIFF);
         dumpSetting(s, p,
                 Settings.Global.NITZ_UPDATE_SPACING,
-                GlobalSettingsProto.NitzUpdate.SPACING);
-        p.end(nitzUpdateToken);
+                GlobalSettingsProto.Nitz.UPDATE_SPACING);
+        dumpSetting(s, p,
+                Settings.Global.NITZ_NETWORK_DISCONNECT_RETENTION,
+                GlobalSettingsProto.Nitz.NETWORK_DISCONNECT_RETENTION);
+        p.end(nitzToken);
 
         final long notificationToken = p.start(GlobalSettingsProto.NOTIFICATION);
         dumpSetting(s, p,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index c9c93c4..19ed021 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -384,6 +384,7 @@
                     Settings.Global.NETWORK_WATCHLIST_ENABLED,
                     Settings.Global.NEW_CONTACT_AGGREGATOR,
                     Settings.Global.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE,
+                    Settings.Global.NITZ_NETWORK_DISCONNECT_RETENTION,
                     Settings.Global.NITZ_UPDATE_DIFF,
                     Settings.Global.NITZ_UPDATE_SPACING,
                     Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 4790412..43254aa 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2164,6 +2164,15 @@
     <!-- Content description for media cotnrols progress bar [CHAR_LIMIT=NONE] -->
     <string name="controls_media_seekbar_description"><xliff:g id="elapsed_time" example="1:30">%1$s</xliff:g> of <xliff:g id="total_time" example="3:00">%2$s</xliff:g></string>
 
+    <!-- Description for button in media controls. Pressing button starts playback [CHAR_LIMIT=NONE] -->
+    <string name="controls_media_button_play">Play</string>
+    <!-- Description for button in media controls. Pressing button pauses playback [CHAR_LIMIT=NONE] -->
+    <string name="controls_media_button_pause">Pause</string>
+    <!-- Description for button in media controls. Pressing button goes to previous track [CHAR_LIMIT=NONE] -->
+    <string name="controls_media_button_prev">Previous track</string>
+    <!-- Description for button in media controls. Pressing button goes to next track [CHAR_LIMIT=NONE] -->
+    <string name="controls_media_button_next">Next track</string>
+
     <!-- Title for Smartspace recommendation card within media controls. The "Play" means the action to play a media [CHAR_LIMIT=10] -->
     <string name="controls_media_smartspace_rec_title">Play</string>
     <!-- Description for Smartspace recommendation card within media controls [CHAR_LIMIT=NONE] -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
index 9574101..b611c96 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
@@ -16,28 +16,31 @@
 
 package com.android.systemui.flags
 
+import android.annotation.BoolRes
+import android.annotation.IntegerRes
+import android.annotation.StringRes
 import android.os.Parcel
 import android.os.Parcelable
 
-interface Flag<T> : Parcelable {
+interface Flag<T> {
     val id: Int
+}
+
+interface ParcelableFlag<T> : Flag<T>, Parcelable {
     val default: T
-    val resourceOverride: Int
-
     override fun describeContents() = 0
+}
 
-    fun hasResourceOverride(): Boolean {
-        return resourceOverride != -1
-    }
+interface ResourceFlag<T> : Flag<T> {
+    val resourceId: Int
 }
 
 // Consider using the "parcelize" kotlin library.
 
 data class BooleanFlag @JvmOverloads constructor(
     override val id: Int,
-    override val default: Boolean = false,
-    override val resourceOverride: Int = -1
-) : Flag<Boolean> {
+    override val default: Boolean = false
+) : ParcelableFlag<Boolean> {
 
     companion object {
         @JvmField
@@ -58,11 +61,15 @@
     }
 }
 
+data class ResourceBooleanFlag constructor(
+    override val id: Int,
+    @BoolRes override val resourceId: Int
+) : ResourceFlag<Boolean>
+
 data class StringFlag @JvmOverloads constructor(
     override val id: Int,
-    override val default: String = "",
-    override val resourceOverride: Int = -1
-) : Flag<String> {
+    override val default: String = ""
+) : ParcelableFlag<String> {
     companion object {
         @JvmField
         val CREATOR = object : Parcelable.Creator<StringFlag> {
@@ -82,11 +89,15 @@
     }
 }
 
+data class ResourceStringFlag constructor(
+    override val id: Int,
+    @StringRes override val resourceId: Int
+) : ResourceFlag<String>
+
 data class IntFlag @JvmOverloads constructor(
     override val id: Int,
-    override val default: Int = 0,
-    override val resourceOverride: Int = -1
-) : Flag<Int> {
+    override val default: Int = 0
+) : ParcelableFlag<Int> {
 
     companion object {
         @JvmField
@@ -107,11 +118,15 @@
     }
 }
 
+data class ResourceIntFlag constructor(
+    override val id: Int,
+    @IntegerRes override val resourceId: Int
+) : ResourceFlag<Int>
+
 data class LongFlag @JvmOverloads constructor(
     override val id: Int,
-    override val default: Long = 0,
-    override val resourceOverride: Int = -1
-) : Flag<Long> {
+    override val default: Long = 0
+) : ParcelableFlag<Long> {
 
     companion object {
         @JvmField
@@ -134,9 +149,8 @@
 
 data class FloatFlag @JvmOverloads constructor(
     override val id: Int,
-    override val default: Float = 0f,
-    override val resourceOverride: Int = -1
-) : Flag<Float> {
+    override val default: Float = 0f
+) : ParcelableFlag<Float> {
 
     companion object {
         @JvmField
@@ -157,11 +171,15 @@
     }
 }
 
+data class ResourceFloatFlag constructor(
+    override val id: Int,
+    override val resourceId: Int
+) : ResourceFlag<Int>
+
 data class DoubleFlag @JvmOverloads constructor(
     override val id: Int,
-    override val default: Double = 0.0,
-    override val resourceOverride: Int = -1
-) : Flag<Double> {
+    override val default: Double = 0.0
+) : ParcelableFlag<Double> {
 
     companion object {
         @JvmField
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
index e61cb5c..b2ca2d7 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
@@ -60,7 +60,7 @@
                     object : BroadcastReceiver() {
                         override fun onReceive(context: Context, intent: Intent) {
                             val extras: Bundle? = getResultExtras(false)
-                            val listOfFlags: java.util.ArrayList<Flag<*>>? =
+                            val listOfFlags: java.util.ArrayList<ParcelableFlag<*>>? =
                                 extras?.getParcelableArrayList(FIELD_FLAGS)
                             if (listOfFlags != null) {
                                 completer.set(listOfFlags)
@@ -108,6 +108,10 @@
         }
     }
 
+    override fun isEnabled(flag: ResourceBooleanFlag): Boolean {
+        throw RuntimeException("Not implemented in FlagManager")
+    }
+
     override fun addListener(listener: FlagReader.Listener) {
         synchronized(listeners) {
             val registerNeeded = listeners.isEmpty()
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagReader.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagReader.kt
index 91a3912..26c6a4b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagReader.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagReader.kt
@@ -24,6 +24,8 @@
         return flag.default
     }
 
+    fun isEnabled(flag: ResourceBooleanFlag): Boolean
+
     /** Returns a boolean value for the given flag.  */
     fun isEnabled(id: Int, def: Boolean): Boolean {
         return def
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 a319b40..3cd090e 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
@@ -270,7 +270,7 @@
                 mOpeningLeashes.add(openingTasks.get(i).getLeash());
                 // We are receiving new opening tasks, so convert to onTasksAppeared.
                 final RemoteAnimationTargetCompat target = new RemoteAnimationTargetCompat(
-                        openingTasks.get(i), layer, mInfo, t);
+                        openingTasks.get(i), layer, info, t);
                 mLeashMap.put(mOpeningLeashes.get(i), target.leash.mSurfaceControl);
                 t.reparent(target.leash.mSurfaceControl, mInfo.getRootLeash());
                 t.setLayer(target.leash.mSurfaceControl, layer);
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
index b6be6ed..e46b6f1 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.unfold.config.UnfoldTransitionConfig
 import com.android.systemui.unfold.progress.FixedTimingTransitionProgressProvider
 import com.android.systemui.unfold.progress.PhysicsBasedUnfoldTransitionProgressProvider
+import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider
 import com.android.systemui.unfold.updates.DeviceFoldStateProvider
 import com.android.systemui.unfold.updates.hinge.EmptyHingeAngleProvider
 import com.android.systemui.unfold.updates.hinge.HingeSensorAngleProvider
@@ -62,7 +63,7 @@
         mainExecutor
     )
 
-    return if (config.isHingeAngleEnabled) {
+    val unfoldTransitionProgressProvider = if (config.isHingeAngleEnabled) {
         PhysicsBasedUnfoldTransitionProgressProvider(
             mainHandler,
             foldStateProvider
@@ -70,6 +71,10 @@
     } else {
         FixedTimingTransitionProgressProvider(foldStateProvider)
     }
+    return ScaleAwareTransitionProgressProvider(
+            unfoldTransitionProgressProvider,
+            context.contentResolver
+    )
 }
 
 fun createConfig(context: Context): UnfoldTransitionConfig =
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
index e072d41..58d7dfb 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
@@ -2,7 +2,6 @@
 
 import android.content.Context
 import android.os.RemoteException
-import android.util.Log
 import android.view.IRotationWatcher
 import android.view.IWindowManager
 import android.view.Surface
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
new file mode 100644
index 0000000..df9078a
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
@@ -0,0 +1,50 @@
+package com.android.systemui.unfold.util
+
+import android.animation.ValueAnimator
+import android.content.ContentResolver
+import android.database.ContentObserver
+import android.provider.Settings
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+
+/** Wraps [UnfoldTransitionProgressProvider] to disable transitions when animations are disabled. */
+class ScaleAwareTransitionProgressProvider(
+    unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider,
+    private val contentResolver: ContentResolver
+) : UnfoldTransitionProgressProvider {
+
+    private val scopedUnfoldTransitionProgressProvider =
+            ScopedUnfoldTransitionProgressProvider(unfoldTransitionProgressProvider)
+
+    private val animatorDurationScaleObserver = object : ContentObserver(null) {
+        override fun onChange(selfChange: Boolean) {
+            onAnimatorScaleChanged()
+        }
+    }
+
+    init {
+        contentResolver.registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE),
+                /* notifyForDescendants= */ false,
+                animatorDurationScaleObserver)
+        onAnimatorScaleChanged()
+    }
+
+    private fun onAnimatorScaleChanged() {
+        val animationsEnabled = ValueAnimator.areAnimatorsEnabled()
+        scopedUnfoldTransitionProgressProvider.setReadyToHandleTransition(animationsEnabled)
+    }
+
+    override fun addCallback(listener: TransitionProgressListener) {
+        scopedUnfoldTransitionProgressProvider.addCallback(listener)
+    }
+
+    override fun removeCallback(listener: TransitionProgressListener) {
+        scopedUnfoldTransitionProgressProvider.removeCallback(listener)
+    }
+
+    override fun destroy() {
+        contentResolver.unregisterContentObserver(animatorDurationScaleObserver)
+        scopedUnfoldTransitionProgressProvider.destroy()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java
index 3ebfb51..9b72655 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java
@@ -22,12 +22,11 @@
 
 import com.android.systemui.communal.conditions.CommunalCondition;
 import com.android.systemui.communal.conditions.CommunalSettingCondition;
-import com.android.systemui.communal.conditions.CommunalTrustedNetworkCondition;
 import com.android.systemui.idle.AmbientLightModeMonitor;
 import com.android.systemui.idle.LightSensorEventsDebounceAlgorithm;
 import com.android.systemui.idle.dagger.IdleViewComponent;
 
-import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -73,9 +72,7 @@
     @ElementsIntoSet
     @Named(COMMUNAL_CONDITIONS)
     static Set<CommunalCondition> provideCommunalConditions(
-            CommunalSettingCondition communalSettingCondition,
-            CommunalTrustedNetworkCondition communalTrustedNetworkCondition) {
-        return new HashSet<>(
-                Arrays.asList(communalSettingCondition, communalTrustedNetworkCondition));
+            CommunalSettingCondition communalSettingCondition) {
+        return new HashSet<>(Collections.singletonList(communalSettingCondition));
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index 0ee47a7..3f00b87 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -22,6 +22,7 @@
 import static com.android.systemui.flags.FlagManager.FIELD_ID;
 import static com.android.systemui.flags.FlagManager.FIELD_VALUE;
 
+import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -30,7 +31,6 @@
 import android.os.Bundle;
 import android.util.Log;
 
-import androidx.annotation.BoolRes;
 import androidx.annotation.NonNull;
 
 import com.android.systemui.Dumpable;
@@ -89,16 +89,18 @@
     public boolean isEnabled(BooleanFlag flag) {
         int id = flag.getId();
         if (!mBooleanFlagCache.containsKey(id)) {
-            boolean def = flag.getDefault();
-            if (flag.hasResourceOverride()) {
-                try {
-                    def = isEnabledInOverlay(flag.getResourceOverride());
-                } catch (Resources.NotFoundException e) {
-                    // no-op
-                }
-            }
+            mBooleanFlagCache.put(id, isEnabled(id, flag.getDefault()));
+        }
 
-            mBooleanFlagCache.put(id, isEnabled(id, def));
+        return mBooleanFlagCache.get(id);
+    }
+
+    @Override
+    public boolean isEnabled(ResourceBooleanFlag flag) {
+        int id = flag.getId();
+        if (!mBooleanFlagCache.containsKey(id)) {
+            mBooleanFlagCache.put(
+                    id, isEnabled(id, mResources.getBoolean(flag.getResourceId())));
         }
 
         return mBooleanFlagCache.get(id);
@@ -111,6 +113,7 @@
         return result == null ? defaultValue : result;
     }
 
+
     /** Returns the stored value or null if not set. */
     private Boolean isEnabledInternal(int id) {
         try {
@@ -121,10 +124,6 @@
         return null;
     }
 
-    private boolean isEnabledInOverlay(@BoolRes int resId) {
-        return mResources.getBoolean(resId);
-    }
-
     /** Set whether a given {@link BooleanFlag} is enabled or not. */
     public void setEnabled(int id, boolean value) {
         Boolean currentValue = isEnabledInternal(id);
@@ -185,9 +184,19 @@
             } else if (ACTION_GET_FLAGS.equals(action)) {
                 Map<Integer, Flag<?>> knownFlagMap = Flags.collectFlags();
                 ArrayList<Flag<?>> flags = new ArrayList<>(knownFlagMap.values());
+
+                // Convert all flags to parcelable flags.
+                ArrayList<ParcelableFlag<?>> pFlags = new ArrayList<>();
+                for (Flag<?> f : flags) {
+                    ParcelableFlag<?> pf = toParcelableFlag(f);
+                    if (pf != null) {
+                        pFlags.add(pf);
+                    }
+                }
+
                 Bundle extras =  getResultExtras(true);
                 if (extras != null) {
-                    extras.putParcelableArrayList(FIELD_FLAGS, flags);
+                    extras.putParcelableArrayList(FIELD_FLAGS, pFlags);
                 }
             }
         }
@@ -215,6 +224,25 @@
                 setEnabled(id, extras.getBoolean(FIELD_VALUE));
             }
         }
+
+        /**
+         * Ensures that the data we send to the app reflects the current state of the flags.
+         *
+         * Also converts an non-parcelable versions of the flags to their parcelable versions.
+         */
+        @Nullable
+        private ParcelableFlag<?> toParcelableFlag(Flag<?> f) {
+            if (f instanceof BooleanFlag) {
+                return new BooleanFlag(f.getId(), isEnabled((BooleanFlag) f));
+            }
+            if (f instanceof ResourceBooleanFlag) {
+                return new BooleanFlag(f.getId(), isEnabled((ResourceBooleanFlag) f));
+            }
+
+            // TODO: add support for other flag types.
+            Log.w(TAG, "Unsupported Flag Type. Please file a bug.");
+            return null;
+        }
     };
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
index bd6cb66..5b6404f 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
@@ -16,12 +16,14 @@
 
 package com.android.systemui.flags;
 
+import android.content.res.Resources;
 import android.util.SparseBooleanArray;
 
 import androidx.annotation.NonNull;
 
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 
 import java.io.FileDescriptor;
@@ -37,9 +39,11 @@
  */
 @SysUISingleton
 public class FeatureFlagsRelease implements FeatureFlags, Dumpable {
-    SparseBooleanArray mAccessedFlags = new SparseBooleanArray();
+    private final Resources mResources;
+    SparseBooleanArray mFlagCache = new SparseBooleanArray();
     @Inject
-    public FeatureFlagsRelease(DumpManager dumpManager) {
+    public FeatureFlagsRelease(@Main Resources resources, DumpManager dumpManager) {
+        mResources = resources;
         dumpManager.registerDumpable("SysUIFlags", this);
     }
 
@@ -55,18 +59,28 @@
     }
 
     @Override
+    public boolean isEnabled(ResourceBooleanFlag flag) {
+        int cacheIndex = mFlagCache.indexOfKey(flag.getId());
+        if (cacheIndex < 0) {
+            return isEnabled(flag.getId(), mResources.getBoolean(flag.getResourceId()));
+        }
+
+        return mFlagCache.valueAt(cacheIndex);
+    }
+
+    @Override
     public boolean isEnabled(int key, boolean defaultValue) {
-        mAccessedFlags.append(key, defaultValue);
+        mFlagCache.append(key, defaultValue);
         return defaultValue;
     }
 
     @Override
     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("can override: false");
-        int size = mAccessedFlags.size();
+        int size = mFlagCache.size();
         for (int i = 0; i < size; i++) {
-            pw.println("  sysui_flag_" + mAccessedFlags.keyAt(i)
-                    + ": " + mAccessedFlags.valueAt(i));
+            pw.println("  sysui_flag_" + mFlagCache.keyAt(i)
+                    + ": " + mFlagCache.valueAt(i));
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 458cdc1f..97533c9 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -61,8 +61,8 @@
     public static final BooleanFlag NEW_UNLOCK_SWIPE_ANIMATION =
             new BooleanFlag(202, true);
 
-    public static final BooleanFlag CHARGING_RIPPLE =
-            new BooleanFlag(203, false, R.bool.flag_charging_ripple);
+    public static final ResourceBooleanFlag CHARGING_RIPPLE =
+            new ResourceBooleanFlag(203, R.bool.flag_charging_ripple);
 
     /***************************************/
     // 300 - power menu
@@ -77,8 +77,8 @@
     public static final BooleanFlag SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED =
             new BooleanFlag(401, false);
 
-    public static final BooleanFlag SMARTSPACE =
-            new BooleanFlag(402, false, R.bool.flag_smartspace);
+    public static final ResourceBooleanFlag SMARTSPACE =
+            new ResourceBooleanFlag(402, R.bool.flag_smartspace);
 
     /***************************************/
     // 500 - quick settings
@@ -88,11 +88,11 @@
     public static final BooleanFlag COMBINED_QS_HEADERS =
             new BooleanFlag(501, false);
 
-    public static final BooleanFlag PEOPLE_TILE =
-            new BooleanFlag(502, false, R.bool.flag_conversations);
+    public static final ResourceBooleanFlag PEOPLE_TILE =
+            new ResourceBooleanFlag(502, R.bool.flag_conversations);
 
-    public static final BooleanFlag QS_USER_DETAIL_SHORTCUT =
-            new BooleanFlag(503, false, R.bool.flag_lockscreen_qs_user_detail_shortcut);
+    public static final ResourceBooleanFlag QS_USER_DETAIL_SHORTCUT =
+            new ResourceBooleanFlag(503, R.bool.flag_lockscreen_qs_user_detail_shortcut);
 
     /***************************************/
     // 600- status bar
@@ -115,12 +115,13 @@
 
     /***************************************/
     // 800 - general visual/theme
-    public static final BooleanFlag MONET =
-            new BooleanFlag(800, true, R.bool.flag_monet);
+    public static final ResourceBooleanFlag MONET =
+            new ResourceBooleanFlag(800, R.bool.flag_monet);
 
     /***************************************/
     // 900 - media
     public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, false);
+    public static final BooleanFlag MEDIA_SESSION_ACTIONS = new BooleanFlag(901, false);
 
     // Pay no attention to the reflection behind the curtain.
     // ========================== Curtain ==========================
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index b85f1072..e921ad29c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -862,8 +862,23 @@
 
 @VisibleForTesting
 internal object MediaPlayerData {
-    private val EMPTY = MediaData(-1, false, 0, null, null, null, null, null,
-            emptyList(), emptyList(), "INVALID", null, null, null, true, null)
+    private val EMPTY = MediaData(
+            userId = -1,
+            initialized = false,
+            backgroundColor = 0,
+            app = null,
+            appIcon = null,
+            artist = null,
+            song = null,
+            artwork = null,
+            actions = emptyList(),
+            actionsToShowInCompact = emptyList(),
+            packageName = "INVALID",
+            token = null,
+            clickIntent = null,
+            device = null,
+            active = true,
+            resumeAction = null)
     // Whether should prioritize Smartspace card.
     internal var shouldPrioritizeSs: Boolean = false
         private set
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index f66eb5b..63555bb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -62,6 +62,7 @@
 import com.android.systemui.util.time.SystemClock;
 
 import java.net.URISyntaxException;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.Executor;
 
@@ -122,6 +123,8 @@
     private MediaCarouselController mMediaCarouselController;
     private final MediaOutputDialogFactory mMediaOutputDialogFactory;
     private final FalsingManager mFalsingManager;
+    private final MediaFlags mMediaFlags;
+
     // Used for swipe-to-dismiss logging.
     protected boolean mIsImpressed = false;
     private SystemClock mSystemClock;
@@ -138,7 +141,7 @@
             SeekBarViewModel seekBarViewModel, Lazy<MediaDataManager> lazyMediaDataManager,
             KeyguardDismissUtil keyguardDismissUtil, MediaOutputDialogFactory
             mediaOutputDialogFactory, MediaCarouselController mediaCarouselController,
-            FalsingManager falsingManager, SystemClock systemClock) {
+            FalsingManager falsingManager, MediaFlags mediaFlags, SystemClock systemClock) {
         mContext = context;
         mBackgroundExecutor = backgroundExecutor;
         mActivityStarter = activityStarter;
@@ -149,8 +152,8 @@
         mMediaOutputDialogFactory = mediaOutputDialogFactory;
         mMediaCarouselController = mediaCarouselController;
         mFalsingManager = falsingManager;
+        mMediaFlags = mediaFlags;
         mSystemClock = systemClock;
-
         loadDimens();
 
         mSeekBarViewModel.setLogSmartspaceClick(() -> {
@@ -426,33 +429,61 @@
         deviceName.setText(deviceString);
         seamlessView.setContentDescription(deviceString);
 
-        List<Integer> actionsWhenCollapsed = data.getActionsToShowInCompact();
-        // Media controls
-        int i = 0;
+        // Media action buttons
         List<MediaAction> actionIcons = data.getActions();
+        List<Integer> actionsWhenCollapsed = data.getActionsToShowInCompact();
+
+        if (mMediaFlags.areMediaSessionActionsEnabled() && data.getSemanticActions() != null) {
+            // Use PlaybackState actions instead
+            MediaButton semanticActions = data.getSemanticActions();
+
+            actionIcons = new ArrayList<MediaAction>();
+            actionIcons.add(semanticActions.getStartCustom());
+            actionIcons.add(semanticActions.getPrevOrCustom());
+            actionIcons.add(semanticActions.getPlayOrPause());
+            actionIcons.add(semanticActions.getNextOrCustom());
+            actionIcons.add(semanticActions.getEndCustom());
+
+            actionsWhenCollapsed = new ArrayList<Integer>();
+            actionsWhenCollapsed.add(1);
+            actionsWhenCollapsed.add(2);
+            actionsWhenCollapsed.add(3);
+        }
+
+        int i = 0;
         for (; i < actionIcons.size() && i < ACTION_IDS.length; i++) {
             int actionId = ACTION_IDS[i];
+            boolean visibleInCompat = actionsWhenCollapsed.contains(i);
             final ImageButton button = mPlayerViewHolder.getAction(actionId);
             MediaAction mediaAction = actionIcons.get(i);
-            button.setImageIcon(mediaAction.getIcon());
-            button.setContentDescription(mediaAction.getContentDescription());
-            Runnable action = mediaAction.getAction();
+            if (mediaAction != null) {
+                button.setImageIcon(mediaAction.getIcon());
+                button.setContentDescription(mediaAction.getContentDescription());
+                Runnable action = mediaAction.getAction();
 
-            if (action == null) {
-                button.setEnabled(false);
+                if (action == null) {
+                    button.setEnabled(false);
+                } else {
+                    button.setEnabled(true);
+                    button.setOnClickListener(v -> {
+                        if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+                            logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
+                                    /* isRecommendationCard */ false);
+                            action.run();
+                        }
+                    });
+                }
+                setVisibleAndAlpha(collapsedSet, actionId, visibleInCompat);
+                setVisibleAndAlpha(expandedSet, actionId, true /*visible */);
             } else {
-                button.setEnabled(true);
-                button.setOnClickListener(v -> {
-                    if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
-                        logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
-                                /* isRecommendationCard */ false);
-                        action.run();
-                    }
-                });
+                button.setImageIcon(null);
+                button.setContentDescription(null);
+                button.setEnabled(false);
+                setVisibleAndAlpha(collapsedSet, actionId, visibleInCompat);
+                // for expanded layout, set as INVISIBLE so that we still reserve space in the UI
+                expandedSet.setVisibility(actionId, ConstraintSet.INVISIBLE);
+                expandedSet.setAlpha(actionId, 0.0f);
             }
-            boolean visibleInCompat = actionsWhenCollapsed.contains(i);
-            setVisibleAndAlpha(collapsedSet, actionId, visibleInCompat);
-            setVisibleAndAlpha(expandedSet, actionId, true /*visible */);
         }
 
         // Hide any unused buttons
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
index bda07f4..4b8dfde 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
@@ -47,7 +47,7 @@
      */
     val artwork: Icon?,
     /**
-     * List of actions that can be performed on the player: prev, next, play, pause, etc.
+     * List of generic action buttons for the media player, based on notification actions
      */
     val actions: List<MediaAction>,
     /**
@@ -55,6 +55,11 @@
      */
     val actionsToShowInCompact: List<Int>,
     /**
+     * Semantic actions buttons, based on the PlaybackState of the media session.
+     * If present, these actions will be preferred in the UI over [actions]
+     */
+    val semanticActions: MediaButton? = null,
+    /**
      * Package name of the app that's posting the media.
      */
     val packageName: String,
@@ -125,6 +130,32 @@
     }
 }
 
+/**
+ * Contains [MediaAction] objects which represent specific buttons in the UI
+ */
+data class MediaButton(
+    /**
+     * Play/pause button
+     */
+    var playOrPause: MediaAction? = null,
+    /**
+     * Next button, or custom action
+     */
+    var nextOrCustom: MediaAction? = null,
+    /**
+     * Previous button, or custom action
+     */
+    var prevOrCustom: MediaAction? = null,
+    /**
+     * First custom action space
+     */
+    var startCustom: MediaAction? = null,
+    /**
+     * Last custom action space
+     */
+    var endCustom: MediaAction? = null
+)
+
 /** State of a media action. */
 data class MediaAction(
     val icon: Icon?,
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 7c0f7fc..49a63c3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -38,6 +38,7 @@
 import android.media.MediaMetadata
 import android.media.session.MediaController
 import android.media.session.MediaSession
+import android.media.session.PlaybackState
 import android.net.Uri
 import android.os.Parcelable
 import android.os.UserHandle
@@ -45,6 +46,7 @@
 import android.service.notification.StatusBarNotification
 import android.text.TextUtils
 import android.util.Log
+import androidx.media.utils.MediaConstants
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.Dumpable
 import com.android.systemui.R
@@ -80,8 +82,24 @@
 private const val DEBUG = true
 private const val EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY = "dismiss_intent"
 
-private val LOADING = MediaData(-1, false, 0, null, null, null, null, null,
-        emptyList(), emptyList(), "INVALID", null, null, null, true, null)
+private val LOADING = MediaData(
+        userId = -1,
+        initialized = false,
+        backgroundColor = 0,
+        app = null,
+        appIcon = null,
+        artist = null,
+        song = null,
+        artwork = null,
+        actions = emptyList(),
+        actionsToShowInCompact = emptyList(),
+        packageName = "INVALID",
+        token = null,
+        clickIntent = null,
+        device = null,
+        active = true,
+        resumeAction = null)
+
 @VisibleForTesting
 internal val EMPTY_SMARTSPACE_MEDIA_DATA = SmartspaceMediaData("INVALID", false, false,
     "INVALID", null, emptyList(), null, 0, 0)
@@ -112,7 +130,8 @@
     private var useMediaResumption: Boolean,
     private val useQsMediaPlayer: Boolean,
     private val systemClock: SystemClock,
-    private val tunerService: TunerService
+    private val tunerService: TunerService,
+    private val mediaFlags: MediaFlags,
 ) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener {
 
     companion object {
@@ -127,6 +146,10 @@
         // Maximum number of actions allowed in compact view
         @JvmField
         val MAX_COMPACT_ACTIONS = 3
+
+        /** Maximum number of [PlaybackState.CustomAction] buttons supported */
+        @JvmField
+        val MAX_CUSTOM_ACTIONS = 4
     }
 
     private val themeText = com.android.settingslib.Utils.getColorAttr(context,
@@ -182,12 +205,13 @@
         activityStarter: ActivityStarter,
         smartspaceMediaDataProvider: SmartspaceMediaDataProvider,
         clock: SystemClock,
-        tunerService: TunerService
+        tunerService: TunerService,
+        mediaFlags: MediaFlags
     ) : this(context, backgroundExecutor, foregroundExecutor, mediaControllerFactory,
             broadcastDispatcher, dumpManager, mediaTimeoutListener, mediaResumeListener,
             mediaSessionBasedFilter, mediaDeviceManager, mediaDataCombineLatest, mediaDataFilter,
             activityStarter, smartspaceMediaDataProvider, Utils.useMediaResumption(context),
-            Utils.useQsMediaPlayer(context), clock, tunerService)
+            Utils.useQsMediaPlayer(context), clock, tunerService, mediaFlags)
 
     private val appChangeReceiver = object : BroadcastReceiver() {
         override fun onReceive(context: Context, intent: Intent) {
@@ -522,7 +546,7 @@
         foregroundExecutor.execute {
             onMediaDataLoaded(packageName, null, MediaData(userId, true, bgColor, appName,
                     null, desc.subtitle, desc.title, artworkIcon, listOf(mediaAction), listOf(0),
-                    packageName, token, appIntent, device = null, active = false,
+                    null, packageName, token, appIntent, device = null, active = false,
                     resumeAction = resumeAction, resumption = true, notificationKey = packageName,
                     hasCheckedForResume = true, lastActive = lastActive))
         }
@@ -594,15 +618,55 @@
         }
 
         // Control buttons
+        // If flag is enabled and controller has a PlaybackState, create actions from session info
+        // Otherwise, use the notification actions
+        var actionIcons: List<MediaAction> = emptyList()
+        var actionsToShowCollapsed: List<Int> = emptyList()
+        var semanticActions: MediaButton? = null
+        if (mediaFlags.areMediaSessionActionsEnabled() && mediaController.playbackState != null) {
+            semanticActions = createActionsFromState(sbn.packageName, mediaController)
+        } else {
+            val actions = createActionsFromNotification(sbn)
+            actionIcons = actions.first
+            actionsToShowCollapsed = actions.second
+        }
+
+        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 {
+            val resumeAction: Runnable? = mediaEntries[key]?.resumeAction
+            val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true
+            val active = mediaEntries[key]?.active ?: true
+            onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, bgColor, app,
+                    smallIcon, artist, song, artWorkIcon, actionIcons, actionsToShowCollapsed,
+                    semanticActions, sbn.packageName, token, notif.contentIntent, null,
+                    active, resumeAction = resumeAction, playbackLocation = playbackLocation,
+                    notificationKey = key, hasCheckedForResume = hasCheckedForResume,
+                    isPlaying = isPlaying, isClearable = sbn.isClearable(),
+                    lastActive = lastActive))
+        }
+    }
+
+    /**
+     * Generate action buttons based on notification actions
+     */
+    private fun createActionsFromNotification(sbn: StatusBarNotification):
+            Pair<List<MediaAction>, List<Int>> {
+        val notif = sbn.notification
         val actionIcons: MutableList<MediaAction> = ArrayList()
         val actions = notif.actions
         var actionsToShowCollapsed = notif.extras.getIntArray(
-                Notification.EXTRA_COMPACT_ACTIONS)?.toMutableList() ?: mutableListOf<Int>()
+            Notification.EXTRA_COMPACT_ACTIONS)?.toMutableList() ?: mutableListOf()
         if (actionsToShowCollapsed.size > MAX_COMPACT_ACTIONS) {
-            Log.e(TAG, "Too many compact actions for $key, limiting to first $MAX_COMPACT_ACTIONS")
+            Log.e(TAG, "Too many compact actions for ${sbn.key}," +
+                "limiting to first $MAX_COMPACT_ACTIONS")
             actionsToShowCollapsed = actionsToShowCollapsed.subList(0, MAX_COMPACT_ACTIONS)
         }
-        // TODO: b/153736623 look into creating actions when this isn't a media style notification
 
         if (actions != null) {
             for ((index, action) in actions.withIndex()) {
@@ -631,32 +695,150 @@
                     action.getIcon()
                 }.setTint(themeText)
                 val mediaAction = MediaAction(
-                        mediaActionIcon,
-                        runnable,
-                        action.title)
+                    mediaActionIcon,
+                    runnable,
+                    action.title)
                 actionIcons.add(mediaAction)
             }
         }
+        return Pair(actionIcons, actionsToShowCollapsed)
+    }
 
-        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 {
-            val resumeAction: Runnable? = mediaEntries[key]?.resumeAction
-            val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true
-            val active = mediaEntries[key]?.active ?: true
-            onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, bgColor, app,
-                    smallIcon, artist, song, artWorkIcon, actionIcons,
-                    actionsToShowCollapsed, sbn.packageName, token, notif.contentIntent, null,
-                    active, resumeAction = resumeAction, playbackLocation = playbackLocation,
-                    notificationKey = key, hasCheckedForResume = hasCheckedForResume,
-                    isPlaying = isPlaying, isClearable = sbn.isClearable(),
-                    lastActive = lastActive))
+    /**
+     * Generates action button info for this media session based on the PlaybackState
+     *
+     * @param packageName Package name for the media app
+     * @param controller MediaController for the current session
+     * @return a Pair consisting of a list of media actions, and a list of ints representing which
+     *      of those actions should be shown in the compact player
+     */
+    private fun createActionsFromState(packageName: String, controller: MediaController):
+            MediaButton? {
+        val actions = MediaButton()
+        controller.playbackState?.let { state ->
+            // First, check for standard actions
+            actions.playOrPause = if (isPlayingState(state.state)) {
+                getStandardAction(controller, state.actions, PlaybackState.ACTION_PAUSE)
+            } else {
+                getStandardAction(controller, state.actions, PlaybackState.ACTION_PLAY)
+            }
+            val prevButton = getStandardAction(controller, state.actions,
+                    PlaybackState.ACTION_SKIP_TO_PREVIOUS)
+            val nextButton = getStandardAction(controller, state.actions,
+                    PlaybackState.ACTION_SKIP_TO_NEXT)
+
+            // Then, check for custom actions
+            val customActions = MutableList<MediaAction?>(4) { null }
+            var customCount = 0
+            for (i in 0..MAX_CUSTOM_ACTIONS) {
+                getCustomAction(state, packageName, controller, customCount)?.let {
+                    customActions[customCount++] = it
+                }
+            }
+
+            // Finally, assign the remaining button slots: C A play/pause B D
+            // A = previous, else custom action (if not reserved)
+            // B = next, else custom action (if not reserved)
+            // C and D are always custom actions
+            val reservePrev = controller.extras?.getBoolean(
+                    MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV) == true
+            val reserveNext = controller.extras?.getBoolean(
+                    MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT) == true
+            var customIdx = 0
+
+            actions.prevOrCustom = if (prevButton != null) {
+                prevButton
+            } else if (!reservePrev) {
+                customActions[customIdx++]
+            } else {
+                null
+            }
+
+            actions.nextOrCustom = if (nextButton != null) {
+                nextButton
+            } else if (!reserveNext) {
+                customActions[customIdx++]
+            } else {
+                null
+            }
+
+            actions.startCustom = customActions[customIdx++]
+            actions.endCustom = customActions[customIdx++]
         }
+        return actions
+    }
+
+    /**
+     * Get a [MediaAction] representing one of
+     * - [PlaybackState.ACTION_PLAY]
+     * - [PlaybackState.ACTION_PAUSE]
+     * - [PlaybackState.ACTION_SKIP_TO_PREVIOUS]
+     * - [PlaybackState.ACTION_SKIP_TO_NEXT]
+     */
+    private fun getStandardAction(
+        controller: MediaController,
+        stateActions: Long,
+        action: Long
+    ): MediaAction? {
+        if (stateActions and action == 0L) {
+            return null
+        }
+
+        return when (action) {
+            PlaybackState.ACTION_PLAY -> {
+                MediaAction(
+                    Icon.createWithResource(context, com.android.internal.R.drawable.ic_media_play),
+                    { controller.transportControls.play() },
+                    context.getString(R.string.controls_media_button_play)
+                )
+            }
+            PlaybackState.ACTION_PAUSE -> {
+                MediaAction(
+                    Icon.createWithResource(context,
+                        com.android.internal.R.drawable.ic_media_pause),
+                    { controller.transportControls.pause() },
+                    context.getString(R.string.controls_media_button_pause)
+                )
+            }
+            PlaybackState.ACTION_SKIP_TO_PREVIOUS -> {
+                MediaAction(
+                    Icon.createWithResource(context,
+                        com.android.internal.R.drawable.ic_media_previous),
+                    { controller.transportControls.skipToPrevious() },
+                    context.getString(R.string.controls_media_button_prev)
+                )
+            }
+            PlaybackState.ACTION_SKIP_TO_NEXT -> {
+                MediaAction(
+                    Icon.createWithResource(context, com.android.internal.R.drawable.ic_media_next),
+                    { controller.transportControls.skipToNext() },
+                    context.getString(R.string.controls_media_button_next)
+                )
+            }
+            else -> null
+        }
+    }
+
+    /**
+     * Get a [MediaAction] representing a [PlaybackState.CustomAction]
+     */
+    private fun getCustomAction(
+        state: PlaybackState,
+        packageName: String,
+        controller: MediaController,
+        index: Int
+    ): MediaAction? {
+        if (state.customActions.size <= index || state.customActions[index] == null) {
+            if (DEBUG) { Log.d(TAG, "not enough actions or action was null at $index") }
+            return null
+        }
+
+        val it = state.customActions[index]
+        return MediaAction(
+            Icon.createWithResource(packageName, it.icon),
+            { controller.transportControls.sendCustomAction(it, it.extras) },
+            it.name
+        )
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt
new file mode 100644
index 0000000..b4a4b42
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.media
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import javax.inject.Inject
+
+@SysUISingleton
+class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) {
+    /**
+     * Check whether media control actions should be based on PlaybackState instead of notification
+     */
+    fun areMediaSessionActionsEnabled(): Boolean {
+        return featureFlags.isEnabled(Flags.MEDIA_SESSION_ACTIONS)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 5afefa1..ab48a28 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -15,6 +15,8 @@
  */
 package com.android.systemui.navigationbar.gestural;
 
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
+
 import static com.android.systemui.classifier.Classifier.BACK_GESTURE;
 
 import android.app.ActivityManager;
@@ -544,7 +546,8 @@
         layoutParams.accessibilityTitle = mContext.getString(R.string.nav_bar_edge_panel);
         layoutParams.windowAnimations = 0;
         layoutParams.privateFlags |=
-                WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+                (WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
+                | PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION);
         layoutParams.setTitle(TAG + mContext.getDisplayId());
         layoutParams.setFitInsetsTypes(0 /* types */);
         layoutParams.setTrustedOverlay();
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
index a16b92f..097cf35 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
@@ -33,6 +33,7 @@
 import static android.util.TypedValue.COMPLEX_UNIT_DIP;
 import static android.util.TypedValue.COMPLEX_UNIT_PX;
 
+import static com.android.launcher3.icons.FastBitmapDrawable.getDisabledColorFilter;
 import static com.android.systemui.people.PeopleSpaceUtils.STARRED_CONTACT;
 import static com.android.systemui.people.PeopleSpaceUtils.VALID_CONTACT;
 import static com.android.systemui.people.PeopleSpaceUtils.convertDrawableToBitmap;
@@ -45,8 +46,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
 import android.graphics.ImageDecoder;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
@@ -75,7 +74,6 @@
 import androidx.core.math.MathUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.launcher3.icons.FastBitmapDrawable;
 import com.android.systemui.R;
 import com.android.systemui.people.widget.LaunchConversationActivity;
 import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
@@ -339,8 +337,9 @@
             views = new RemoteViews(mContext.getPackageName(),
                     R.layout.people_tile_suppressed_layout);
         }
-        Drawable appIcon = mContext.getDrawable(R.drawable.ic_conversation_icon);
-        Bitmap disabledBitmap = convertDrawableToDisabledBitmap(appIcon);
+        Drawable appIcon = mContext.getDrawable(R.drawable.ic_conversation_icon).mutate();
+        appIcon.setColorFilter(getDisabledColorFilter());
+        Bitmap disabledBitmap = convertDrawableToBitmap(appIcon);
         views.setImageViewBitmap(R.id.icon, disabledBitmap);
         return views;
     }
@@ -1262,8 +1261,9 @@
             Context context, PeopleSpaceTile tile, int maxAvatarSize, boolean hasNewStory) {
         Icon icon = tile.getUserIcon();
         if (icon == null) {
-            Drawable placeholder = context.getDrawable(R.drawable.ic_avatar_with_badge);
-            return convertDrawableToDisabledBitmap(placeholder);
+            Drawable placeholder = context.getDrawable(R.drawable.ic_avatar_with_badge).mutate();
+            placeholder.setColorFilter(getDisabledColorFilter());
+            return convertDrawableToBitmap(placeholder);
         }
         PeopleStoryIconFactory storyIcon = new PeopleStoryIconFactory(context,
                 context.getPackageManager(),
@@ -1276,10 +1276,7 @@
                 hasNewStory);
 
         if (isDndBlockingTileData(tile)) {
-            // If DND is blocking the conversation, then display the icon in grayscale.
-            ColorMatrix colorMatrix = new ColorMatrix();
-            colorMatrix.setSaturation(0);
-            personDrawable.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
+            personDrawable.setColorFilter(getDisabledColorFilter());
         }
 
         return convertDrawableToBitmap(personDrawable);
@@ -1375,11 +1372,4 @@
             mAvatarSize = avatarSize;
         }
     }
-
-    private static Bitmap convertDrawableToDisabledBitmap(Drawable icon) {
-        Bitmap appIconAsBitmap = convertDrawableToBitmap(icon);
-        FastBitmapDrawable drawable = new FastBitmapDrawable(appIconAsBitmap);
-        drawable.setIsDisabled(true);
-        return convertDrawableToBitmap(drawable);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 706f423..6f63a08 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -86,6 +86,7 @@
 import com.android.wifitrackerlib.MergedCarrierEntry;
 import com.android.wifitrackerlib.WifiEntry;
 
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -98,8 +99,10 @@
 
 import javax.inject.Inject;
 
-public class InternetDialogController implements WifiEntry.DisconnectCallback,
-        AccessPointController.AccessPointCallback {
+/**
+ * Controller for Internet Dialog.
+ */
+public class InternetDialogController implements AccessPointController.AccessPointCallback {
 
     private static final String TAG = "InternetDialogController";
     private static final String ACTION_NETWORK_PROVIDER_SETTINGS =
@@ -881,20 +884,6 @@
             return;
         }
 
-        boolean hasConnectedWifi = false;
-        final int accessPointSize = accessPoints.size();
-        for (int i = 0; i < accessPointSize; i++) {
-            WifiEntry wifiEntry = accessPoints.get(i);
-            if (wifiEntry.isDefaultNetwork() && wifiEntry.hasInternetAccess()) {
-                mConnectedEntry = wifiEntry;
-                hasConnectedWifi = true;
-                break;
-            }
-        }
-        if (!hasConnectedWifi) {
-            mConnectedEntry = null;
-        }
-
         int count = MAX_WIFI_ENTRY_COUNT;
         if (mHasEthernet) {
             count -= 1;
@@ -902,15 +891,22 @@
         if (hasActiveSubId()) {
             count -= 1;
         }
-        if (hasConnectedWifi) {
-            count -= 1;
+        if (count > accessPoints.size()) {
+            count = accessPoints.size();
         }
-        final List<WifiEntry> wifiEntries = accessPoints.stream()
-                .filter(wifiEntry -> (!wifiEntry.isDefaultNetwork()
-                        || !wifiEntry.hasInternetAccess()))
-                .limit(count)
-                .collect(Collectors.toList());
-        mWifiEntriesCount = wifiEntries == null ? 0 : wifiEntries.size();
+
+        WifiEntry connectedEntry = null;
+        final List<WifiEntry> wifiEntries = new ArrayList<>();
+        for (int i = 0; i < count; i++) {
+            WifiEntry entry = accessPoints.get(i);
+            if (connectedEntry == null && entry.isDefaultNetwork() && entry.hasInternetAccess()) {
+                connectedEntry = entry;
+            } else {
+                wifiEntries.add(entry);
+            }
+        }
+        mConnectedEntry = connectedEntry;
+        mWifiEntriesCount = wifiEntries.size();
 
         if (mCallback != null) {
             mCallback.onAccessPointsChanged(wifiEntries, mConnectedEntry);
@@ -921,10 +917,6 @@
     public void onSettingsActivityTriggered(Intent settingsIntent) {
     }
 
-    @Override
-    public void onDisconnectResult(int status) {
-    }
-
     private class InternetTelephonyCallback extends TelephonyCallback implements
             TelephonyCallback.DataConnectionStateListener,
             TelephonyCallback.DisplayInfoListener,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.java
deleted file mode 100644
index ac8b47d..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.policy;
-
-import android.app.StatusBarManager;
-import android.content.Context;
-import android.content.res.Configuration;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.qs.QSFragment;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.phone.StatusBar;
-
-import javax.inject.Inject;
-
-/**
- * Let {@link RemoteInputView} to control the visibility of QuickSetting.
- */
-@SysUISingleton
-public class RemoteInputQuickSettingsDisabler
-        implements ConfigurationController.ConfigurationListener {
-
-    private Context mContext;
-    @VisibleForTesting boolean mRemoteInputActive;
-    @VisibleForTesting boolean misLandscape;
-    private int mLastOrientation;
-    private final CommandQueue mCommandQueue;
-
-    @Inject
-    public RemoteInputQuickSettingsDisabler(Context context,
-            ConfigurationController configController, CommandQueue commandQueue) {
-        mContext = context;
-        mCommandQueue = commandQueue;
-        mLastOrientation = mContext.getResources().getConfiguration().orientation;
-        configController.addCallback(this);
-    }
-
-    public int adjustDisableFlags(int state) {
-        if (mRemoteInputActive && misLandscape) {
-            state |= StatusBarManager.DISABLE2_QUICK_SETTINGS;
-        }
-
-        return state;
-    }
-
-    public void setRemoteInputActive(boolean active){
-        if(mRemoteInputActive != active){
-            mRemoteInputActive = active;
-            recomputeDisableFlags();
-        }
-    }
-
-    @Override
-    public void onConfigChanged(Configuration newConfig) {
-        if (newConfig.orientation != mLastOrientation) {
-            misLandscape = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE;
-            mLastOrientation = newConfig.orientation;
-            recomputeDisableFlags();
-        }
-    }
-
-    /**
-     * Reapplies the disable flags. Then the method adjustDisableFlags in this class will be invoked
-     * in {@link QSFragment#disable(int, int, boolean)} and
-     * {@link StatusBar#disable(int, int, boolean)}
-     * to modify the disable flags according to the status of mRemoteInputActive and misLandscape.
-     */
-    private void recomputeDisableFlags() {
-        mCommandQueue.recomputeDisableFlags(mContext.getDisplayId(), true);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.kt
new file mode 100644
index 0000000..31ef2f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.policy
+
+import android.app.StatusBarManager
+import android.content.Context
+import android.content.res.Configuration
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.util.Utils
+import javax.inject.Inject
+
+/**
+ * Controls whether the disable flag [StatusBarManager.DISABLE2_QUICK_SETTINGS] should be set.
+ * This would happen when a [RemoteInputView] is active, the device is in landscape and not using
+ * split shade.
+ */
+@SysUISingleton
+class RemoteInputQuickSettingsDisabler @Inject constructor(
+    private val context: Context,
+    private val commandQueue: CommandQueue,
+    configController: ConfigurationController
+) : ConfigurationController.ConfigurationListener {
+
+    private var remoteInputActive = false
+    private var isLandscape: Boolean
+    private var shouldUseSplitNotificationShade: Boolean
+
+    init {
+        isLandscape =
+            context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
+        shouldUseSplitNotificationShade = Utils.shouldUseSplitNotificationShade(context.resources)
+        configController.addCallback(this)
+    }
+
+    fun adjustDisableFlags(state: Int): Int {
+        var mutableState = state
+        if (remoteInputActive &&
+            isLandscape &&
+            !shouldUseSplitNotificationShade
+        ) {
+            mutableState = state or StatusBarManager.DISABLE2_QUICK_SETTINGS
+        }
+        return mutableState
+    }
+
+    fun setRemoteInputActive(active: Boolean) {
+        if (remoteInputActive != active) {
+            remoteInputActive = active
+            recomputeDisableFlags()
+        }
+    }
+
+    override fun onConfigChanged(newConfig: Configuration) {
+        var needToRecompute = false
+
+        val newIsLandscape = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE
+        if (newIsLandscape != isLandscape) {
+            isLandscape = newIsLandscape
+            needToRecompute = true
+        }
+
+        val newSplitShadeFlag = Utils.shouldUseSplitNotificationShade(context.resources)
+        if (newSplitShadeFlag != shouldUseSplitNotificationShade) {
+            shouldUseSplitNotificationShade = newSplitShadeFlag
+            needToRecompute = true
+        }
+        if (needToRecompute) {
+            recomputeDisableFlags()
+        }
+    }
+
+    /**
+     * Called in order to trigger a refresh of the disable flags after a relevant configuration
+     * change or when a [RemoteInputView] has changed its active state. The method
+     * [adjustDisableFlags] will be invoked to modify the disable flags according to
+     * [remoteInputActive], [isLandscape] and [shouldUseSplitNotificationShade].
+     */
+    private fun recomputeDisableFlags() {
+        commandQueue.recomputeDisableFlags(context.displayId, true)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
index 8b394bf..97fce51 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
@@ -30,8 +30,6 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.UserHandle;
@@ -42,6 +40,7 @@
 import android.widget.TextView;
 
 import com.android.internal.R;
+import com.android.launcher3.icons.BaseIconFactory.IconOptions;
 import com.android.launcher3.icons.IconFactory;
 import com.android.settingslib.applications.ApplicationsState;
 import com.android.settingslib.applications.ApplicationsState.AppEntry;
@@ -280,9 +279,14 @@
         final ApplicationInfo appInfo = appEntry.info;
         UserHandle user = UserHandle.getUserHandleForUid(appInfo.uid);
         IconFactory iconFactory = IconFactory.obtain(context);
-        Bitmap iconBmp = iconFactory.createBadgedIconBitmap(
-                appInfo.loadUnbadgedIcon(packageManager), user, true).icon;
-        return new BitmapDrawable(context.getResources(), iconBmp);
+        try {
+            return iconFactory.createBadgedIconBitmap(
+                        appInfo.loadUnbadgedIcon(packageManager),
+                        new IconOptions().setUser(user))
+                    .newIcon(context);
+        } finally {
+            iconFactory.recycle();
+        }
     }
 
     private static boolean showApplicationIcon(ApplicationInfo appInfo,
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index 51de1321..e6fc49f 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.unfold
 
+import android.animation.ValueAnimator
 import android.content.Context
 import android.graphics.PixelFormat
 import android.hardware.devicestate.DeviceStateManager
@@ -111,7 +112,7 @@
         Trace.beginSection("UnfoldLightRevealOverlayAnimation#onScreenTurningOn")
         try {
             // Add the view only if we are unfolding and this is the first screen on
-            if (!isFolded && !isUnfoldHandled) {
+            if (!isFolded && !isUnfoldHandled && ValueAnimator.areAnimatorsEnabled()) {
                 addView(onOverlayReady)
                 isUnfoldHandled = true
             } else {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.java
index 475dde2..dea42f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.java
@@ -18,15 +18,14 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
 
-import android.content.Context;
+import android.content.res.Resources;
 
 import androidx.test.filters.SmallTest;
 
@@ -51,13 +50,14 @@
 public class FeatureFlagsReleaseTest extends SysuiTestCase {
     FeatureFlagsRelease mFeatureFlagsRelease;
 
+    @Mock private Resources mResources;
     @Mock private DumpManager mDumpManager;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
 
-        mFeatureFlagsRelease = new FeatureFlagsRelease(mDumpManager);
+        mFeatureFlagsRelease = new FeatureFlagsRelease(mResources, mDumpManager);
     }
 
     @After
@@ -68,15 +68,34 @@
     }
 
     @Test
+    public void testBooleanResourceFlag() {
+        int flagId = 213;
+        int flagResourceId = 3;
+        ResourceBooleanFlag flag = new ResourceBooleanFlag(flagId, flagResourceId);
+        when(mResources.getBoolean(flagResourceId)).thenReturn(true);
+
+        assertThat(mFeatureFlagsRelease.isEnabled(flag)).isTrue();
+    }
+
+    @Test
     public void testDump() {
+        int flagIdA = 213;
+        int flagIdB = 18;
+        int flagResourceId = 3;
+        BooleanFlag flagA = new BooleanFlag(flagIdA, true);
+        ResourceBooleanFlag flagB = new ResourceBooleanFlag(flagIdB, flagResourceId);
+        when(mResources.getBoolean(flagResourceId)).thenReturn(true);
+
         // WHEN the flags have been accessed
-        assertFalse(mFeatureFlagsRelease.isEnabled(1, false));
-        assertTrue(mFeatureFlagsRelease.isEnabled(2, true));
+        assertThat(mFeatureFlagsRelease.isEnabled(1, false)).isFalse();
+        assertThat(mFeatureFlagsRelease.isEnabled(flagA)).isTrue();
+        assertThat(mFeatureFlagsRelease.isEnabled(flagB)).isTrue();
 
         // THEN the dump contains the flags and the default values
         String dump = dumpToString();
         assertThat(dump).contains(" sysui_flag_1: false\n");
-        assertThat(dump).contains(" sysui_flag_2: true\n");
+        assertThat(dump).contains(" sysui_flag_" + flagIdA + ": true\n");
+        assertThat(dump).contains(" sysui_flag_" + flagIdB + ": true\n");
     }
 
     private String dumpToString() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index 41ce941..7cc0172 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -19,6 +19,7 @@
 import android.content.Intent
 import android.graphics.Color
 import android.graphics.drawable.GradientDrawable
+import android.graphics.drawable.Icon
 import android.graphics.drawable.RippleDrawable
 import android.media.MediaMetadata
 import android.media.session.MediaSession
@@ -97,6 +98,7 @@
     @Mock private lateinit var mediaOutputDialogFactory: MediaOutputDialogFactory
     @Mock private lateinit var mediaCarouselController: MediaCarouselController
     @Mock private lateinit var falsingManager: FalsingManager
+    @Mock private lateinit var mediaFlags: MediaFlags
     private lateinit var appIcon: ImageView
     private lateinit var albumView: ImageView
     private lateinit var titleText: TextView
@@ -123,6 +125,7 @@
     private lateinit var session: MediaSession
     private val device = MediaDeviceData(true, null, DEVICE_NAME)
     private val disabledDevice = MediaDeviceData(false, null, "Disabled Device")
+    private lateinit var mediaData: MediaData
     private val clock = FakeSystemClock()
 
     @JvmField @Rule val mockito = MockitoJUnit.rule()
@@ -135,7 +138,7 @@
 
         player = MediaControlPanel(context, bgExecutor, activityStarter, mediaViewController,
             seekBarViewModel, Lazy { mediaDataManager }, keyguardDismissUtil,
-            mediaOutputDialogFactory, mediaCarouselController, falsingManager, clock)
+            mediaOutputDialogFactory, mediaCarouselController, falsingManager, mediaFlags, clock)
         whenever(seekBarViewModel.progress).thenReturn(seekBarData)
 
         // Mock out a view holder for the player to attach to.
@@ -165,14 +168,19 @@
         whenever(holder.totalTimeView).thenReturn(totalTimeView)
         action0 = ImageButton(context)
         whenever(holder.action0).thenReturn(action0)
+        whenever(holder.getAction(R.id.action0)).thenReturn(action0)
         action1 = ImageButton(context)
         whenever(holder.action1).thenReturn(action1)
+        whenever(holder.getAction(R.id.action1)).thenReturn(action1)
         action2 = ImageButton(context)
         whenever(holder.action2).thenReturn(action2)
+        whenever(holder.getAction(R.id.action2)).thenReturn(action2)
         action3 = ImageButton(context)
         whenever(holder.action3).thenReturn(action3)
+        whenever(holder.getAction(R.id.action3)).thenReturn(action3)
         action4 = ImageButton(context)
         whenever(holder.action4).thenReturn(action4)
+        whenever(holder.getAction(R.id.action4)).thenReturn(action4)
         whenever(holder.longPressText).thenReturn(longPressText)
         whenever(longPressText.handler).thenReturn(handler)
         settings = View(context)
@@ -200,6 +208,26 @@
             setPlaybackState(playbackBuilder.build())
         }
         session.setActive(true)
+
+        mediaData = MediaData(
+                userId = USER_ID,
+                initialized = true,
+                backgroundColor = BG_COLOR,
+                app = APP,
+                appIcon = null,
+                artist = ARTIST,
+                song = TITLE,
+                artwork = null,
+                actions = emptyList(),
+                actionsToShowInCompact = emptyList(),
+                packageName = PACKAGE,
+                token = session.sessionToken,
+                clickIntent = null,
+                device = device,
+                active = true,
+                resumeAction = null)
+
+        whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(false)
     }
 
     @After
@@ -210,18 +238,50 @@
 
     @Test
     fun bindWhenUnattached() {
-        val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-                emptyList(), PACKAGE, null, null, device, true, null)
+        val state = mediaData.copy(token = null)
         player.bindPlayer(state, PACKAGE)
         assertThat(player.isPlaying()).isFalse()
     }
 
     @Test
+    fun bindSemanticActions() {
+        whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true)
+        val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play)
+        val semanticActions = MediaButton(
+                playOrPause = MediaAction(icon, Runnable {}, "play"),
+                nextOrCustom = MediaAction(icon, Runnable {}, "next"),
+                startCustom = MediaAction(icon, null, "custom 1"),
+                endCustom = MediaAction(icon, null, "custom 2")
+        )
+        val state = mediaData.copy(semanticActions = semanticActions)
+
+        player.attachPlayer(holder)
+        player.bindPlayer(state, PACKAGE)
+
+        verify(expandedSet).setVisibility(R.id.action0, ConstraintSet.VISIBLE)
+        assertThat(action0.contentDescription).isEqualTo("custom 1")
+        assertThat(action0.isEnabled()).isFalse()
+
+        verify(expandedSet).setVisibility(R.id.action1, ConstraintSet.INVISIBLE)
+        assertThat(action1.isEnabled()).isFalse()
+
+        verify(expandedSet).setVisibility(R.id.action2, ConstraintSet.VISIBLE)
+        assertThat(action2.isEnabled()).isTrue()
+        assertThat(action2.contentDescription).isEqualTo("play")
+
+        verify(expandedSet).setVisibility(R.id.action3, ConstraintSet.VISIBLE)
+        assertThat(action3.isEnabled()).isTrue()
+        assertThat(action3.contentDescription).isEqualTo("next")
+
+        verify(expandedSet).setVisibility(R.id.action4, ConstraintSet.VISIBLE)
+        assertThat(action4.contentDescription).isEqualTo("custom 2")
+        assertThat(action4.isEnabled()).isFalse()
+    }
+
+    @Test
     fun bindText() {
         player.attachPlayer(holder)
-        val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-                emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null)
-        player.bindPlayer(state, PACKAGE)
+        player.bindPlayer(mediaData, PACKAGE)
         assertThat(titleText.getText()).isEqualTo(TITLE)
         assertThat(artistText.getText()).isEqualTo(ARTIST)
     }
@@ -229,9 +289,7 @@
     @Test
     fun bindDevice() {
         player.attachPlayer(holder)
-        val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-                emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null)
-        player.bindPlayer(state, PACKAGE)
+        player.bindPlayer(mediaData, PACKAGE)
         assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME)
         assertThat(seamless.contentDescription).isEqualTo(DEVICE_NAME)
         assertThat(seamless.isEnabled()).isTrue()
@@ -242,8 +300,7 @@
         seamless.id = 1
         val fallbackString = context.getString(R.string.media_seamless_other_device)
         player.attachPlayer(holder)
-        val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-                emptyList(), PACKAGE, session.getSessionToken(), null, disabledDevice, true, null)
+        val state = mediaData.copy(device = disabledDevice)
         player.bindPlayer(state, PACKAGE)
         assertThat(seamless.isEnabled()).isFalse()
         assertThat(seamlessText.getText()).isEqualTo(fallbackString)
@@ -254,8 +311,7 @@
     fun bindNullDevice() {
         val fallbackString = context.getResources().getString(R.string.media_seamless_other_device)
         player.attachPlayer(holder)
-        val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-                emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null)
+        val state = mediaData.copy(device = null)
         player.bindPlayer(state, PACKAGE)
         assertThat(seamless.isEnabled()).isTrue()
         assertThat(seamlessText.getText()).isEqualTo(fallbackString)
@@ -265,9 +321,7 @@
     @Test
     fun bindDeviceResumptionPlayer() {
         player.attachPlayer(holder)
-        val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-                emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null,
-                resumption = true)
+        val state = mediaData.copy(resumption = true)
         player.bindPlayer(state, PACKAGE)
         assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME)
         assertThat(seamless.isEnabled()).isFalse()
@@ -323,9 +377,7 @@
     fun dismissButtonClick() {
         val mediaKey = "key for dismissal"
         player.attachPlayer(holder)
-        val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-                emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null,
-                notificationKey = KEY)
+        val state = mediaData.copy(notificationKey = KEY)
         player.bindPlayer(state, mediaKey)
 
         assertThat(dismiss.isEnabled).isEqualTo(true)
@@ -337,9 +389,7 @@
     fun dismissButtonDisabled() {
         val mediaKey = "key for dismissal"
         player.attachPlayer(holder)
-        val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-                emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null,
-                isClearable = false, notificationKey = KEY)
+        val state = mediaData.copy(isClearable = false, notificationKey = KEY)
         player.bindPlayer(state, mediaKey)
 
         assertThat(dismiss.isEnabled).isEqualTo(false)
@@ -351,9 +401,7 @@
         whenever(mediaDataManager.dismissMediaData(eq(mediaKey), anyLong())).thenReturn(false)
 
         player.attachPlayer(holder)
-        val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-                emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null,
-                notificationKey = KEY)
+        val state = mediaData.copy(notificationKey = KEY)
         player.bindPlayer(state, mediaKey)
 
         assertThat(dismiss.isEnabled).isEqualTo(true)
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 09c83e5..7a487b8 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,9 @@
         mManager = new MediaDataCombineLatest();
         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,
+        mMediaData = new MediaData(
+                USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null,
+                new ArrayList<>(), new ArrayList<>(), null, 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/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
index 5a3c43c..6b203bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
@@ -96,11 +96,24 @@
         setUser(USER_MAIN)
 
         // Set up test media data
-        dataMain = MediaData(USER_MAIN, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-            emptyList(), PACKAGE, null, null, device, true, null)
-
-        dataGuest = MediaData(USER_GUEST, true, BG_COLOR, APP, null, ARTIST, TITLE, null,
-            emptyList(), emptyList(), PACKAGE, null, null, device, true, null)
+        dataMain = MediaData(
+                userId = USER_MAIN,
+                initialized = true,
+                backgroundColor = BG_COLOR,
+                app = APP,
+                appIcon = null,
+                artist = ARTIST,
+                song = TITLE,
+                artwork = null,
+                actions = emptyList(),
+                actionsToShowInCompact = emptyList(),
+                packageName = PACKAGE,
+                token = null,
+                clickIntent = null,
+                device = device,
+                active = true,
+                resumeAction = null)
+        dataGuest = dataMain.copy(userId = USER_GUEST)
 
         `when`(smartspaceData.targetId).thenReturn(SMARTSPACE_KEY)
         `when`(smartspaceData.isActive).thenReturn(true)
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 e2019e0..d0b957c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -11,12 +11,15 @@
 import android.media.MediaMetadata
 import android.media.session.MediaController
 import android.media.session.MediaSession
+import android.media.session.PlaybackState
 import android.os.Bundle
 import android.provider.Settings
 import android.service.notification.StatusBarNotification
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
+import androidx.media.utils.MediaConstants
 import androidx.test.filters.SmallTest
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dump.DumpManager
@@ -67,6 +70,7 @@
     @JvmField @Rule val mockito = MockitoJUnit.rule()
     @Mock lateinit var mediaControllerFactory: MediaControllerFactory
     @Mock lateinit var controller: MediaController
+    @Mock lateinit var transportControls: MediaController.TransportControls
     @Mock lateinit var playbackInfo: MediaController.PlaybackInfo
     lateinit var session: MediaSession
     lateinit var metadataBuilder: MediaMetadata.Builder
@@ -87,6 +91,7 @@
     @Mock lateinit var mediaSmartspaceTarget: SmartspaceTarget
     @Mock private lateinit var mediaRecommendationItem: SmartspaceAction
     @Mock private lateinit var mediaSmartspaceBaseAction: SmartspaceAction
+    @Mock private lateinit var mediaFlags: MediaFlags
     lateinit var mediaDataManager: MediaDataManager
     lateinit var mediaNotification: StatusBarNotification
     @Captor lateinit var mediaDataCaptor: ArgumentCaptor<MediaData>
@@ -122,7 +127,8 @@
             useMediaResumption = true,
             useQsMediaPlayer = true,
             systemClock = clock,
-            tunerService = tunerService
+            tunerService = tunerService,
+            mediaFlags = mediaFlags
         )
         verify(tunerService).addTunable(capture(tunableCaptor),
                 eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
@@ -140,6 +146,7 @@
             putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
         }
         whenever(mediaControllerFactory.create(eq(session.sessionToken))).thenReturn(controller)
+        whenever(controller.transportControls).thenReturn(transportControls)
         whenever(controller.playbackInfo).thenReturn(playbackInfo)
         whenever(playbackInfo.playbackType).thenReturn(
                 MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL)
@@ -161,6 +168,7 @@
         whenever(mediaSmartspaceTarget.featureType).thenReturn(SmartspaceTarget.FEATURE_MEDIA)
         whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf(mediaRecommendationItem))
         whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(1234L)
+        whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(false)
     }
 
     @After
@@ -583,4 +591,157 @@
         assertThat(mediaDataCaptor.value.actionsToShowInCompact.size).isEqualTo(
                 MediaDataManager.MAX_COMPACT_ACTIONS)
     }
+
+    @Test
+    fun testPlaybackActions_noState_usesNotification() {
+        val desc = "Notification Action"
+        whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true)
+        whenever(controller.playbackState).thenReturn(null)
+
+        val notifWithAction = SbnBuilder().run {
+            setPkg(PACKAGE_NAME)
+            modifyNotification(context).also {
+                it.setSmallIcon(android.R.drawable.ic_media_pause)
+                it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+                it.addAction(android.R.drawable.ic_media_play, desc, null)
+            }
+            build()
+        }
+        mediaDataManager.onNotificationAdded(KEY, notifWithAction)
+
+        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
+                eq(0))
+
+        assertThat(mediaDataCaptor.value!!.semanticActions).isNull()
+        assertThat(mediaDataCaptor.value!!.actions).hasSize(1)
+        assertThat(mediaDataCaptor.value!!.actions[0]!!.contentDescription).isEqualTo(desc)
+    }
+
+    @Test
+    fun testPlaybackActions_hasPrevNext() {
+        val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
+        whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true)
+        val stateActions = PlaybackState.ACTION_PLAY or
+                PlaybackState.ACTION_SKIP_TO_PREVIOUS or
+                PlaybackState.ACTION_SKIP_TO_NEXT
+        val stateBuilder = PlaybackState.Builder()
+                .setActions(stateActions)
+        customDesc.forEach {
+            stateBuilder.addCustomAction("action: $it", it, android.R.drawable.ic_media_pause)
+        }
+        whenever(controller.playbackState).thenReturn(stateBuilder.build())
+
+        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
+                eq(0))
+
+        assertThat(mediaDataCaptor.value!!.semanticActions).isNotNull()
+        val actions = mediaDataCaptor.value!!.semanticActions!!
+
+        assertThat(actions.playOrPause).isNotNull()
+        assertThat(actions.playOrPause!!.contentDescription).isEqualTo(
+                context.getString(R.string.controls_media_button_play))
+        actions.playOrPause!!.action!!.run()
+        verify(transportControls).play()
+
+        assertThat(actions.prevOrCustom).isNotNull()
+        assertThat(actions.prevOrCustom!!.contentDescription).isEqualTo(
+                context.getString(R.string.controls_media_button_prev))
+        actions.prevOrCustom!!.action!!.run()
+        verify(transportControls).skipToPrevious()
+
+        assertThat(actions.nextOrCustom).isNotNull()
+        assertThat(actions.nextOrCustom!!.contentDescription).isEqualTo(
+                context.getString(R.string.controls_media_button_next))
+        actions.nextOrCustom!!.action!!.run()
+        verify(transportControls).skipToNext()
+
+        assertThat(actions.startCustom).isNotNull()
+        assertThat(actions.startCustom!!.contentDescription).isEqualTo(customDesc[0])
+
+        assertThat(actions.endCustom).isNotNull()
+        assertThat(actions.endCustom!!.contentDescription).isEqualTo(customDesc[1])
+    }
+
+    @Test
+    fun testPlaybackActions_noPrevNext_usesCustom() {
+        val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
+        whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true)
+        val stateActions = PlaybackState.ACTION_PLAY
+        val stateBuilder = PlaybackState.Builder()
+                .setActions(stateActions)
+        customDesc.forEach {
+            stateBuilder.addCustomAction("action: $it", it, android.R.drawable.ic_media_pause)
+        }
+        whenever(controller.playbackState).thenReturn(stateBuilder.build())
+
+        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
+                eq(0))
+
+        assertThat(mediaDataCaptor.value!!.semanticActions).isNotNull()
+        val actions = mediaDataCaptor.value!!.semanticActions!!
+
+        assertThat(actions.playOrPause).isNotNull()
+        assertThat(actions.playOrPause!!.contentDescription).isEqualTo(
+                context.getString(R.string.controls_media_button_play))
+
+        assertThat(actions.prevOrCustom).isNotNull()
+        assertThat(actions.prevOrCustom!!.contentDescription).isEqualTo(customDesc[0])
+
+        assertThat(actions.nextOrCustom).isNotNull()
+        assertThat(actions.nextOrCustom!!.contentDescription).isEqualTo(customDesc[1])
+
+        assertThat(actions.startCustom).isNotNull()
+        assertThat(actions.startCustom!!.contentDescription).isEqualTo(customDesc[2])
+
+        assertThat(actions.endCustom).isNotNull()
+        assertThat(actions.endCustom!!.contentDescription).isEqualTo(customDesc[3])
+    }
+
+    @Test
+    fun testPlaybackActions_reservedSpace() {
+        val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
+        whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true)
+        val stateActions = PlaybackState.ACTION_PLAY
+        val stateBuilder = PlaybackState.Builder()
+                .setActions(stateActions)
+        customDesc.forEach {
+            stateBuilder.addCustomAction("action: $it", it, android.R.drawable.ic_media_pause)
+        }
+        val extras = Bundle().apply {
+            putBoolean(MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true)
+            putBoolean(MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true)
+        }
+        whenever(controller.playbackState).thenReturn(stateBuilder.build())
+        whenever(controller.extras).thenReturn(extras)
+
+        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
+                eq(0))
+
+        assertThat(mediaDataCaptor.value!!.semanticActions).isNotNull()
+        val actions = mediaDataCaptor.value!!.semanticActions!!
+
+        assertThat(actions.playOrPause).isNotNull()
+        assertThat(actions.playOrPause!!.contentDescription).isEqualTo(
+                context.getString(R.string.controls_media_button_play))
+
+        assertThat(actions.prevOrCustom).isNull()
+        assertThat(actions.nextOrCustom).isNull()
+
+        assertThat(actions.startCustom).isNotNull()
+        assertThat(actions.startCustom!!.contentDescription).isEqualTo(customDesc[0])
+
+        assertThat(actions.endCustom).isNotNull()
+        assertThat(actions.endCustom!!.contentDescription).isEqualTo(customDesc[1])
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
index 7dadbad..3d59497 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
@@ -102,9 +102,24 @@
         // Create a media sesssion and notification for testing.
         session = MediaSession(context, SESSION_KEY)
 
-        mediaData = MediaData(USER_ID, true, 0, PACKAGE, null, null, SESSION_TITLE, null,
-            emptyList(), emptyList(), PACKAGE, session.sessionToken, clickIntent = null,
-            device = null, active = true, resumeAction = null)
+        mediaData = MediaData(
+                userId = USER_ID,
+                initialized = true,
+                backgroundColor = 0,
+                app = PACKAGE,
+                appIcon = null,
+                artist = null,
+                song = SESSION_TITLE,
+                artwork = null,
+                actions = emptyList(),
+                actionsToShowInCompact = emptyList(),
+                packageName = PACKAGE,
+                token = session.sessionToken,
+                clickIntent = null,
+                device = null,
+                active = true,
+                resumeAction = null)
+
         whenever(controllerFactory.create(session.sessionToken))
                 .thenReturn(controller)
     }
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 421f9be..ceeb0db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
@@ -163,8 +163,27 @@
         isPlaying: Boolean?,
         location: Int,
         resumption: Boolean
-    ) =
-        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)
+    ) = MediaData(
+        userId = 0,
+        initialized = false,
+        backgroundColor = 0,
+        app = app,
+        appIcon = null,
+        artist = null,
+        song = null,
+        artwork = null,
+        actions = emptyList(),
+        actionsToShowInCompact = emptyList(),
+        packageName = "package: $app",
+        token = null,
+        clickIntent = null,
+        device = null,
+        active = true,
+        resumeAction = null,
+        playbackLocation = location,
+        resumption = resumption,
+        notificationKey = "key: $app",
+        hasCheckedForResume = false,
+        isPlaying = isPlaying
+    )
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
index de2235d..8c2fed5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
@@ -95,13 +95,26 @@
             setPlaybackState(playbackBuilder.build())
         }
         session.setActive(true)
-        mediaData = MediaData(USER_ID, true, 0, PACKAGE, null, null, SESSION_TITLE, null,
-            emptyList(), emptyList(), PACKAGE, session.sessionToken, clickIntent = null,
-            device = null, active = true, resumeAction = null)
 
-        resumeData = MediaData(USER_ID, true, 0, PACKAGE, null, null, SESSION_TITLE, null,
-                emptyList(), emptyList(), PACKAGE, null, clickIntent = null,
-                device = null, active = false, resumeAction = null, resumption = true)
+        mediaData = MediaData(
+                userId = USER_ID,
+                initialized = true,
+                backgroundColor = 0,
+                app = PACKAGE,
+                appIcon = null,
+                artist = null,
+                song = SESSION_TITLE,
+                artwork = null,
+                actions = emptyList(),
+                actionsToShowInCompact = emptyList(),
+                packageName = PACKAGE,
+                token = session.sessionToken,
+                clickIntent = null,
+                device = null,
+                active = true,
+                resumeAction = null)
+
+        resumeData = mediaData.copy(token = null, active = false, resumption = true)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index c1562c1..6f4e619 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -185,8 +185,8 @@
     protected Fragment instantiate(Context context, String className, Bundle arguments) {
         CommandQueue commandQueue = new CommandQueue(context);
         return new QSFragment(
-                new RemoteInputQuickSettingsDisabler(context, mock(ConfigurationController.class),
-                        commandQueue),
+                new RemoteInputQuickSettingsDisabler(context, commandQueue,
+                        mock(ConfigurationController.class)),
                 mock(QSTileHost.class),
                 mock(StatusBarStateController.class),
                 commandQueue,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index ca8903b..95e7a98c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -1,5 +1,7 @@
 package com.android.systemui.qs.tiles.dialog;
 
+import static android.provider.Settings.Global.AIRPLANE_MODE_ON;
+
 import static com.android.systemui.qs.tiles.dialog.InternetDialogController.TOAST_PARAMS_HORIZONTAL_WEIGHT;
 import static com.android.systemui.qs.tiles.dialog.InternetDialogController.TOAST_PARAMS_VERTICAL_WEIGHT;
 
@@ -19,7 +21,6 @@
 import static org.mockito.Mockito.when;
 
 import android.animation.Animator;
-import android.content.Context;
 import android.content.Intent;
 import android.graphics.PixelFormat;
 import android.graphics.drawable.Drawable;
@@ -37,7 +38,6 @@
 import android.view.View;
 import android.view.WindowManager;
 
-import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.UiEventLogger;
@@ -47,7 +47,6 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.connectivity.AccessPointController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -70,7 +69,6 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.Executor;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -78,8 +76,6 @@
 public class InternetDialogControllerTest extends SysuiTestCase {
 
     private static final int SUB_ID = 1;
-    private static final String CONNECTED_TITLE = "Connected Wi-Fi Title";
-    private static final String CONNECTED_SUMMARY = "Connected Wi-Fi Summary";
 
     //SystemUIToast
     private static final int GRAVITY_FLAGS = Gravity.FILL_HORIZONTAL | Gravity.FILL_VERTICAL;
@@ -142,7 +138,7 @@
     private DialogLaunchAnimator mDialogLaunchAnimator;
 
     private TestableResources mTestableResources;
-    private MockInternetDialogController mInternetDialogController;
+    private InternetDialogController mInternetDialogController;
     private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
     private List<WifiEntry> mAccessPoints = new ArrayList<>();
     private List<WifiEntry> mWifiEntries = new ArrayList<>();
@@ -170,7 +166,7 @@
         when(mSystemUIToast.getGravity()).thenReturn(GRAVITY_FLAGS);
         when(mSystemUIToast.getInAnimation()).thenReturn(mAnimator);
 
-        mInternetDialogController = new MockInternetDialogController(mContext,
+        mInternetDialogController = new InternetDialogController(mContext,
                 mock(UiEventLogger.class), mock(ActivityStarter.class), mAccessPointController,
                 mSubscriptionManager, mTelephonyManager, mWifiManager,
                 mock(ConnectivityManager.class), mHandler, mExecutor, mBroadcastDispatcher,
@@ -225,7 +221,7 @@
 
     @Test
     public void getDialogTitleText_withAirplaneModeOn_returnAirplaneMode() {
-        mInternetDialogController.setAirplaneModeEnabled(true);
+        fakeAirplaneModeEnabled(true);
 
         assertTrue(TextUtils.equals(mInternetDialogController.getDialogTitleText(),
                 getResourcesString("airplane_mode")));
@@ -233,7 +229,7 @@
 
     @Test
     public void getDialogTitleText_withAirplaneModeOff_returnInternet() {
-        mInternetDialogController.setAirplaneModeEnabled(false);
+        fakeAirplaneModeEnabled(false);
 
         assertTrue(TextUtils.equals(mInternetDialogController.getDialogTitleText(),
                 getResourcesString("quick_settings_internet_label")));
@@ -241,14 +237,14 @@
 
     @Test
     public void getSubtitleText_withAirplaneModeOn_returnNull() {
-        mInternetDialogController.setAirplaneModeEnabled(true);
+        fakeAirplaneModeEnabled(true);
 
         assertThat(mInternetDialogController.getSubtitleText(false)).isNull();
     }
 
     @Test
     public void getSubtitleText_withWifiOff_returnWifiIsOff() {
-        mInternetDialogController.setAirplaneModeEnabled(false);
+        fakeAirplaneModeEnabled(false);
         when(mWifiManager.isWifiEnabled()).thenReturn(false);
 
         assertThat(mInternetDialogController.getSubtitleText(false))
@@ -263,7 +259,7 @@
 
     @Test
     public void getSubtitleText_withNoWifiEntry_returnSearchWifi() {
-        mInternetDialogController.setAirplaneModeEnabled(false);
+        fakeAirplaneModeEnabled(false);
         when(mWifiManager.isWifiEnabled()).thenReturn(true);
         mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
 
@@ -280,7 +276,7 @@
     @Test
     public void getSubtitleText_withWifiEntry_returnTapToConnect() {
         // The preconditions WiFi Entries is already in setUp()
-        mInternetDialogController.setAirplaneModeEnabled(false);
+        fakeAirplaneModeEnabled(false);
         when(mWifiManager.isWifiEnabled()).thenReturn(true);
 
         assertThat(mInternetDialogController.getSubtitleText(false))
@@ -295,7 +291,7 @@
 
     @Test
     public void getSubtitleText_deviceLockedWithWifiOn_returnUnlockToViewNetworks() {
-        mInternetDialogController.setAirplaneModeEnabled(false);
+        fakeAirplaneModeEnabled(false);
         when(mWifiManager.isWifiEnabled()).thenReturn(true);
         when(mKeyguardStateController.isUnlocked()).thenReturn(false);
 
@@ -305,7 +301,7 @@
 
     @Test
     public void getSubtitleText_withNoService_returnNoNetworksAvailable() {
-        mInternetDialogController.setAirplaneModeEnabled(false);
+        fakeAirplaneModeEnabled(false);
         when(mWifiManager.isWifiEnabled()).thenReturn(true);
         mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
 
@@ -319,7 +315,7 @@
 
     @Test
     public void getSubtitleText_withMobileDataDisabled_returnNoOtherAvailable() {
-        mInternetDialogController.setAirplaneModeEnabled(false);
+        fakeAirplaneModeEnabled(false);
         when(mWifiManager.isWifiEnabled()).thenReturn(true);
         mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
 
@@ -420,7 +416,7 @@
     @Test
     public void onAccessPointsChanged_oneConnectedEntry_callbackConnectedEntryOnly() {
         reset(mInternetDialogCallback);
-        mInternetDialogController.setAirplaneModeEnabled(true);
+        fakeAirplaneModeEnabled(true);
         mAccessPoints.clear();
         mAccessPoints.add(mConnectedEntry);
 
@@ -433,7 +429,7 @@
     @Test
     public void onAccessPointsChanged_noConnectedEntryAndOneOther_callbackWifiEntriesOnly() {
         reset(mInternetDialogCallback);
-        mInternetDialogController.setAirplaneModeEnabled(true);
+        fakeAirplaneModeEnabled(true);
         mAccessPoints.clear();
         mAccessPoints.add(mWifiEntry1);
 
@@ -448,7 +444,7 @@
     @Test
     public void onAccessPointsChanged_oneConnectedEntryAndOneOther_callbackCorrectly() {
         reset(mInternetDialogCallback);
-        mInternetDialogController.setAirplaneModeEnabled(true);
+        fakeAirplaneModeEnabled(true);
         mAccessPoints.clear();
         mAccessPoints.add(mConnectedEntry);
         mAccessPoints.add(mWifiEntry1);
@@ -463,7 +459,7 @@
     @Test
     public void onAccessPointsChanged_oneConnectedEntryAndTwoOthers_callbackCorrectly() {
         reset(mInternetDialogCallback);
-        mInternetDialogController.setAirplaneModeEnabled(true);
+        fakeAirplaneModeEnabled(true);
         mAccessPoints.clear();
         mAccessPoints.add(mConnectedEntry);
         mAccessPoints.add(mWifiEntry1);
@@ -480,7 +476,7 @@
     @Test
     public void onAccessPointsChanged_oneConnectedEntryAndThreeOthers_callbackCutMore() {
         reset(mInternetDialogCallback);
-        mInternetDialogController.setAirplaneModeEnabled(true);
+        fakeAirplaneModeEnabled(true);
         mAccessPoints.clear();
         mAccessPoints.add(mConnectedEntry);
         mAccessPoints.add(mWifiEntry1);
@@ -497,7 +493,7 @@
 
         // Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one.
         reset(mInternetDialogCallback);
-        mInternetDialogController.setAirplaneModeEnabled(false);
+        fakeAirplaneModeEnabled(false);
 
         mInternetDialogController.onAccessPointsChanged(mAccessPoints);
 
@@ -508,7 +504,7 @@
     @Test
     public void onAccessPointsChanged_oneConnectedEntryAndFourOthers_callbackCutMore() {
         reset(mInternetDialogCallback);
-        mInternetDialogController.setAirplaneModeEnabled(true);
+        fakeAirplaneModeEnabled(true);
         mAccessPoints.clear();
         mAccessPoints.add(mConnectedEntry);
         mAccessPoints.add(mWifiEntry1);
@@ -526,7 +522,7 @@
 
         // Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one.
         reset(mInternetDialogCallback);
-        mInternetDialogController.setAirplaneModeEnabled(false);
+        fakeAirplaneModeEnabled(false);
 
         mInternetDialogController.onAccessPointsChanged(mAccessPoints);
 
@@ -537,7 +533,7 @@
     @Test
     public void onAccessPointsChanged_fourWifiEntries_callbackCutMore() {
         reset(mInternetDialogCallback);
-        mInternetDialogController.setAirplaneModeEnabled(true);
+        fakeAirplaneModeEnabled(true);
         mAccessPoints.clear();
         mAccessPoints.add(mWifiEntry1);
         mAccessPoints.add(mWifiEntry2);
@@ -566,7 +562,7 @@
 
         // Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one.
         reset(mInternetDialogCallback);
-        mInternetDialogController.setAirplaneModeEnabled(false);
+        fakeAirplaneModeEnabled(false);
 
         mInternetDialogController.onAccessPointsChanged(mAccessPoints);
 
@@ -576,6 +572,23 @@
     }
 
     @Test
+    public void onAccessPointsChanged_wifiIsDefaultButNoInternetAccess_putIntoWifiEntries() {
+        reset(mInternetDialogCallback);
+        mAccessPoints.clear();
+        when(mWifiEntry1.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_CONNECTED);
+        when(mWifiEntry1.isDefaultNetwork()).thenReturn(true);
+        when(mWifiEntry1.hasInternetAccess()).thenReturn(false);
+        mAccessPoints.add(mWifiEntry1);
+
+        mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+        mWifiEntries.clear();
+        mWifiEntries.add(mWifiEntry1);
+        verify(mInternetDialogCallback)
+                .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */);
+    }
+
+    @Test
     public void setMergedCarrierWifiEnabledIfNeed_carrierProvisionsEnabled_doNothing() {
         when(mCarrierConfigTracker.getCarrierProvisionsWifiMergedNetworksBool(SUB_ID))
                 .thenReturn(true);
@@ -641,38 +654,7 @@
                 mContext.getPackageName());
     }
 
-    private class MockInternetDialogController extends InternetDialogController {
-
-        private GlobalSettings mGlobalSettings;
-        private boolean mIsAirplaneModeOn;
-
-        MockInternetDialogController(Context context, UiEventLogger uiEventLogger,
-                ActivityStarter starter, AccessPointController accessPointController,
-                SubscriptionManager subscriptionManager, TelephonyManager telephonyManager,
-                @Nullable WifiManager wifiManager, ConnectivityManager connectivityManager,
-                @Main Handler handler, @Main Executor mainExecutor,
-                BroadcastDispatcher broadcastDispatcher,
-                KeyguardUpdateMonitor keyguardUpdateMonitor, GlobalSettings globalSettings,
-                KeyguardStateController keyguardStateController, WindowManager windowManager,
-                ToastFactory toastFactory, Handler workerHandler,
-                CarrierConfigTracker carrierConfigTracker,
-                LocationController locationController,
-                DialogLaunchAnimator dialogLaunchAnimator) {
-            super(context, uiEventLogger, starter, accessPointController, subscriptionManager,
-                    telephonyManager, wifiManager, connectivityManager, handler, mainExecutor,
-                    broadcastDispatcher, keyguardUpdateMonitor, globalSettings,
-                    keyguardStateController, windowManager, toastFactory, workerHandler,
-                    carrierConfigTracker, locationController, dialogLaunchAnimator);
-            mGlobalSettings = globalSettings;
-        }
-
-        @Override
-        boolean isAirplaneModeEnabled() {
-            return mIsAirplaneModeOn;
-        }
-
-        public void setAirplaneModeEnabled(boolean enabled) {
-            mIsAirplaneModeOn = enabled;
-        }
+    private void fakeAirplaneModeEnabled(boolean enabled) {
+        when(mGlobalSettings.getInt(eq(AIRPLANE_MODE_ON), anyInt())).thenReturn(enabled ? 1 : 0);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.java
deleted file mode 100644
index b359b9c..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.statusbar.policy;
-
-import static junit.framework.TestCase.assertTrue;
-
-import static org.junit.Assert.assertFalse;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-import android.content.res.Configuration;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class RemoteInputQuickSettingsDisablerTest extends SysuiTestCase {
-
-    @Mock
-    private CommandQueue mCommandQueue;
-    private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        mRemoteInputQuickSettingsDisabler = new RemoteInputQuickSettingsDisabler(mContext,
-                mock(ConfigurationController.class), mCommandQueue);
-    }
-
-    @Test
-    public void shouldEnableQuickSetting_afterDeactiviate() {
-        mRemoteInputQuickSettingsDisabler.setRemoteInputActive(Boolean.TRUE);
-        mRemoteInputQuickSettingsDisabler.setRemoteInputActive(Boolean.FALSE);
-        assertFalse(mRemoteInputQuickSettingsDisabler.mRemoteInputActive);
-        verify(mCommandQueue, atLeastOnce()).recomputeDisableFlags(anyInt(), anyBoolean());
-    }
-
-    @Test
-    public void shouldDisableQuickSetting_afteActiviate() {
-        mRemoteInputQuickSettingsDisabler.setRemoteInputActive(Boolean.FALSE);
-        mRemoteInputQuickSettingsDisabler.setRemoteInputActive(Boolean.TRUE);
-        assertTrue(mRemoteInputQuickSettingsDisabler.mRemoteInputActive);
-        verify(mCommandQueue, atLeastOnce()).recomputeDisableFlags(anyInt(), anyBoolean());
-    }
-
-    @Test
-    public void testChangeToLandscape() {
-        Configuration c = new Configuration(mContext.getResources().getConfiguration());
-        c.orientation = Configuration.ORIENTATION_PORTRAIT;
-        mRemoteInputQuickSettingsDisabler.onConfigChanged(c);
-        c.orientation = Configuration.ORIENTATION_LANDSCAPE;
-        mRemoteInputQuickSettingsDisabler.onConfigChanged(c);
-        assertTrue(mRemoteInputQuickSettingsDisabler.misLandscape);
-        verify(mCommandQueue, atLeastOnce()).recomputeDisableFlags(anyInt(), anyBoolean());
-    }
-
-    @Test
-    public void testChangeToPortrait() {
-        Configuration c = new Configuration(mContext.getResources().getConfiguration());
-        c.orientation = Configuration.ORIENTATION_LANDSCAPE;
-        mRemoteInputQuickSettingsDisabler.onConfigChanged(c);
-        c.orientation = Configuration.ORIENTATION_PORTRAIT;
-        mRemoteInputQuickSettingsDisabler.onConfigChanged(c);
-        assertFalse(mRemoteInputQuickSettingsDisabler.misLandscape);
-        verify(mCommandQueue, atLeastOnce()).recomputeDisableFlags(anyInt(), anyBoolean());
-    }
-
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt
new file mode 100644
index 0000000..1ab0582
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.statusbar.policy
+
+import android.app.StatusBarManager
+import android.content.res.Configuration
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.CommandQueue
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class RemoteInputQuickSettingsDisablerTest : SysuiTestCase() {
+
+    @Mock lateinit var commandQueue: CommandQueue
+    private lateinit var remoteInputQuickSettingsDisabler: RemoteInputQuickSettingsDisabler
+    private lateinit var configuration: Configuration
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        remoteInputQuickSettingsDisabler = RemoteInputQuickSettingsDisabler(
+            mContext,
+            commandQueue, Mockito.mock(ConfigurationController::class.java)
+        )
+        configuration = Configuration(mContext.resources.configuration)
+
+        // Default these conditions to what they need to be to disable QS.
+        mContext.orCreateTestableResources
+            .addOverride(R.bool.config_use_split_notification_shade, /* value= */false)
+        remoteInputQuickSettingsDisabler.setRemoteInputActive(true)
+        configuration.orientation = Configuration.ORIENTATION_LANDSCAPE
+        remoteInputQuickSettingsDisabler.onConfigChanged(configuration)
+    }
+
+    @Test
+    fun whenRemoteInputActiveAndLandscapeAndNotSplitShade_shouldDisableQs() {
+        assertThat(
+            shouldDisableQs(
+                remoteInputQuickSettingsDisabler.adjustDisableFlags(0)
+            )
+        )
+            .isTrue()
+    }
+
+    @Test
+    fun whenRemoteInputNotActive_shouldNotDisableQs() {
+        remoteInputQuickSettingsDisabler.setRemoteInputActive(false)
+
+        assertThat(
+            shouldDisableQs(
+                remoteInputQuickSettingsDisabler.adjustDisableFlags(0)
+            )
+        )
+            .isFalse()
+    }
+
+    @Test
+    fun whenSplitShadeEnabled_shouldNotDisableQs() {
+        mContext.orCreateTestableResources
+            .addOverride(R.bool.config_use_split_notification_shade, /* value= */true)
+        remoteInputQuickSettingsDisabler.onConfigChanged(configuration)
+
+        assertThat(
+            shouldDisableQs(
+                remoteInputQuickSettingsDisabler.adjustDisableFlags(0)
+            )
+        )
+            .isFalse()
+    }
+
+    @Test
+    fun whenPortrait_shouldNotDisableQs() {
+        configuration.orientation = Configuration.ORIENTATION_PORTRAIT
+        remoteInputQuickSettingsDisabler.onConfigChanged(configuration)
+
+        assertThat(
+            shouldDisableQs(
+                remoteInputQuickSettingsDisabler.adjustDisableFlags(0)
+            )
+        )
+            .isFalse()
+    }
+
+    @Test
+    fun whenRemoteInputChanges_recomputeTriggered() {
+        remoteInputQuickSettingsDisabler.setRemoteInputActive(false)
+
+        verify(commandQueue, atLeastOnce()).recomputeDisableFlags(
+            anyInt(), anyBoolean()
+        )
+    }
+
+    @Test
+    fun whenConfigChanges_recomputeTriggered() {
+        configuration.orientation = Configuration.ORIENTATION_PORTRAIT
+        remoteInputQuickSettingsDisabler.onConfigChanged(configuration)
+
+        verify(commandQueue, atLeastOnce()).recomputeDisableFlags(
+            anyInt(), anyBoolean()
+        )
+    }
+
+    private fun shouldDisableQs(state: Int): Boolean {
+        return state and StatusBarManager.DISABLE2_QUICK_SETTINGS != 0
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
new file mode 100644
index 0000000..db7a8516
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
@@ -0,0 +1,139 @@
+/*
+ * 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.unfold.util
+
+import android.animation.ValueAnimator
+import android.content.ContentResolver
+import android.database.ContentObserver
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.util.mockito.any
+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.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() {
+
+    @Mock
+    lateinit var contentResolver: ContentResolver
+
+    @Mock
+    lateinit var sourceProvider: UnfoldTransitionProgressProvider
+
+    @Mock
+    lateinit var sinkProvider: TransitionProgressListener
+
+    lateinit var progressProvider: ScaleAwareTransitionProgressProvider
+
+    private val sourceProviderListenerCaptor =
+            ArgumentCaptor.forClass(TransitionProgressListener::class.java)
+
+    private val animatorDurationScaleListenerCaptor =
+            ArgumentCaptor.forClass(ContentObserver::class.java)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        progressProvider = ScaleAwareTransitionProgressProvider(
+                sourceProvider,
+                contentResolver
+        )
+
+        verify(sourceProvider).addCallback(sourceProviderListenerCaptor.capture())
+        verify(contentResolver).registerContentObserver(any(), any(),
+                animatorDurationScaleListenerCaptor.capture())
+
+        progressProvider.addCallback(sinkProvider)
+    }
+
+    @Test
+    fun onTransitionStarted_animationsEnabled_eventReceived() {
+        setAnimationsEnabled(true)
+
+        source.onTransitionStarted()
+
+        verify(sinkProvider).onTransitionStarted()
+    }
+
+    @Test
+    fun onTransitionStarted_animationsNotEnabled_eventNotReceived() {
+        setAnimationsEnabled(false)
+
+        source.onTransitionStarted()
+
+        verifyNoMoreInteractions(sinkProvider)
+    }
+
+    @Test
+    fun onTransitionEnd_animationsEnabled_eventReceived() {
+        setAnimationsEnabled(true)
+
+        source.onTransitionFinished()
+
+        verify(sinkProvider).onTransitionFinished()
+    }
+
+    @Test
+    fun onTransitionEnd_animationsNotEnabled_eventNotReceived() {
+        setAnimationsEnabled(false)
+
+        source.onTransitionFinished()
+
+        verifyNoMoreInteractions(sinkProvider)
+    }
+
+    @Test
+    fun onTransitionProgress_animationsEnabled_eventReceived() {
+        setAnimationsEnabled(true)
+
+        source.onTransitionProgress(42f)
+
+        verify(sinkProvider).onTransitionProgress(42f)
+    }
+
+    @Test
+    fun onTransitionProgress_animationsNotEnabled_eventNotReceived() {
+        setAnimationsEnabled(false)
+
+        source.onTransitionProgress(42f)
+
+        verifyNoMoreInteractions(sinkProvider)
+    }
+
+    private fun setAnimationsEnabled(enabled: Boolean) {
+        val durationScale = if (enabled) {
+            1f
+        } else {
+            0f
+        }
+        ValueAnimator.setDurationScale(durationScale)
+        animatorDurationScaleListenerCaptor.value.dispatchChange(/* selfChange= */false)
+    }
+
+    private val source: TransitionProgressListener
+        get() = sourceProviderListenerCaptor.value
+}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 049018cf..e0cd472 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -406,9 +406,8 @@
 
         @Override
         public List<AssociationInfo> getAssociations(String packageName, int userId) {
-            final int callingUid = getCallingUserId();
             if (!checkCallerCanManageAssociationsForPackage(getContext(), userId, packageName)) {
-                throw new SecurityException("Caller (uid=" + callingUid + ") does not have "
+                throw new SecurityException("Caller (uid=" + getCallingUid() + ") does not have "
                         + "permissions to get associations for u" + userId + "/" + packageName);
             }
 
diff --git a/services/companion/java/com/android/server/companion/PermissionsUtils.java b/services/companion/java/com/android/server/companion/PermissionsUtils.java
index 0d38fa3..45097f0 100644
--- a/services/companion/java/com/android/server/companion/PermissionsUtils.java
+++ b/services/companion/java/com/android/server/companion/PermissionsUtils.java
@@ -154,14 +154,14 @@
     }
 
     static boolean checkCallerCanManageCompanionDevice(@NonNull Context context) {
-        if (getCallingUserId() == SYSTEM_UID) return true;
+        if (getCallingUid() == SYSTEM_UID) return true;
 
         return context.checkCallingPermission(MANAGE_COMPANION_DEVICES) == PERMISSION_GRANTED;
     }
 
     static void enforceCallerCanManagerCompanionDevice(@NonNull Context context,
             @Nullable String message) {
-        if (getCallingUserId() == SYSTEM_UID) return;
+        if (getCallingUid() == SYSTEM_UID) return;
 
         context.enforceCallingPermission(MANAGE_COMPANION_DEVICES, message);
     }
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 6a26bea..b47ea4f 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -551,9 +551,9 @@
                 logd("canBeSpatialized false due to usage:" + attributes.getUsage());
                 return false;
         }
-        AudioDeviceAttributes[] devices =
-                // going through adapter to take advantage of routing cache
-                (AudioDeviceAttributes[]) mASA.getDevicesForAttributes(attributes).toArray();
+        AudioDeviceAttributes[] devices = new AudioDeviceAttributes[1];
+        // going through adapter to take advantage of routing cache
+        mASA.getDevicesForAttributes(attributes).toArray(devices);
         final boolean able = AudioSystem.canBeSpatialized(attributes, format, devices);
         logd("canBeSpatialized returning " + able);
         return able;
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 641f24f..e8922eb 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -251,20 +251,29 @@
 
             if (priorUserStates != null) {
                 synchronized (mPm.mLock) {
-                    for (int i = 0; i < allUsers.length; i++) {
-                        TempUserState priorUserState = priorUserStates.get(allUsers[i]);
-                        int enabledState = priorUserState.enabledState;
-                        PackageSetting pkgSetting = mPm.getPackageSettingForMutation(packageName);
-                        pkgSetting.setEnabled(enabledState, allUsers[i],
-                                priorUserState.lastDisableAppCaller);
-
+                    PackageSetting pkgSetting = mPm.getPackageSettingForMutation(packageName);
+                    if (pkgSetting != null) {
                         AndroidPackage aPkg = pkgSetting.getPkg();
                         boolean pkgEnabled = aPkg != null && aPkg.isEnabled();
-                        if (!reEnableStub && priorUserState.installed
-                                && ((enabledState == COMPONENT_ENABLED_STATE_DEFAULT && pkgEnabled)
-                                || enabledState == COMPONENT_ENABLED_STATE_ENABLED)) {
-                            reEnableStub = true;
+                        for (int i = 0; i < allUsers.length; i++) {
+                            TempUserState priorUserState = priorUserStates.get(allUsers[i]);
+                            int enabledState = priorUserState.enabledState;
+                            pkgSetting.setEnabled(enabledState, allUsers[i],
+                                    priorUserState.lastDisableAppCaller);
+                            if (!reEnableStub && priorUserState.installed
+                                    && (
+                                    (enabledState == COMPONENT_ENABLED_STATE_DEFAULT && pkgEnabled)
+                                            || enabledState == COMPONENT_ENABLED_STATE_ENABLED)) {
+                                reEnableStub = true;
+                            }
                         }
+                    } else {
+                        // This should not happen. If priorUserStates != null, we are uninstalling
+                        // an update of a system app. In that case, mPm.mSettings.getPackageLpr()
+                        // should return a non-null value for the target packageName because
+                        // restoreDisabledSystemPackageLIF() is called during deletePackageLIF().
+                        Slog.w(TAG, "Missing PackageSetting after uninstalling the update for"
+                                + " system app: " + packageName + ". This should not happen.");
                     }
                     mPm.mSettings.writeAllUsersPackageRestrictionsLPr();
                 }
diff --git a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java b/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
index 509702f..dfa6c66 100644
--- a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
@@ -126,13 +126,13 @@
         return null;
     }
 
-    public OverlayConfig setUpSystemPackages(
+    public OverlayConfig initPackages(
             WatchedArrayMap<String, PackageSetting> packageSettings, int[] userIds,
             long startTime) {
         PackageParser2 packageParser = mPm.mInjector.getScanningCachingPackageParser();
 
         ExecutorService executorService = ParallelPackageParser.makeExecutorService();
-        // Prepare apex package info before scanning APKs, these information are needed when
+        // Prepare apex package info before scanning APKs, this information is needed when
         // scanning apk in apex.
         mPm.mApexManager.scanApexPackagesTraced(packageParser, executorService);
 
@@ -289,7 +289,7 @@
             long currentTime, PackageParser2 packageParser, ExecutorService executorService) {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
         try {
-            mInstallPackageHelper.installSystemPackagesFromDir(scanDir, parseFlags, scanFlags,
+            mInstallPackageHelper.installPackagesFromDir(scanDir, parseFlags, scanFlags,
                     currentTime, packageParser, executorService);
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index d2087ee..80c2a7e 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -3720,7 +3720,7 @@
     }
 
     @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
-    public void installSystemPackagesFromDir(File scanDir, int parseFlags, int scanFlags,
+    public void installPackagesFromDir(File scanDir, int parseFlags, int scanFlags,
             long currentTime, PackageParser2 packageParser, ExecutorService executorService) {
         final File[] files = scanDir.listFiles();
         if (ArrayUtils.isEmpty(files)) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index fe5bce13..0c78ab8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1975,7 +1975,7 @@
                     mIsEngBuild, mIsUserDebugBuild, mIncrementalVersion);
 
             final int[] userIds = mUserManager.getUserIds();
-            mOverlayConfig = mInitAndSystemPackageHelper.setUpSystemPackages(packageSettings,
+            mOverlayConfig = mInitAndSystemPackageHelper.initPackages(packageSettings,
                     userIds, startTime);
 
             // Resolve the storage manager.
diff --git a/services/core/java/com/android/server/pm/permission/Permission.java b/services/core/java/com/android/server/pm/permission/Permission.java
index 75f3725..041c4fe 100644
--- a/services/core/java/com/android/server/pm/permission/Permission.java
+++ b/services/core/java/com/android/server/pm/permission/Permission.java
@@ -431,6 +431,8 @@
                 }
             }
         }
+        boolean wasNonInternal = permission != null && permission.mType != TYPE_CONFIG
+                && !permission.isInternal();
         boolean wasNonRuntime = permission != null && permission.mType != TYPE_CONFIG
                 && !permission.isRuntime();
         if (permission == null) {
@@ -476,10 +478,10 @@
             r.append("DUP:");
             r.append(permissionInfo.name);
         }
-        if ((permission.isInternal() && ownerChanged)
+        if ((permission.isInternal() && (ownerChanged || wasNonInternal))
                 || (permission.isRuntime() && (ownerChanged || wasNonRuntime))) {
             // If this is an internal/runtime permission and the owner has changed, or this wasn't a
-            // runtime permission, then permission state should be cleaned up.
+            // internal/runtime permission, then permission state should be cleaned up.
             permission.mDefinitionChanged = true;
         }
         if (PackageManagerService.DEBUG_PACKAGE_SCANNING && r != null) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 5c15a84..298f102 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -523,6 +523,7 @@
     private boolean mPendingKeyguardOccluded;
     private boolean mKeyguardOccludedChanged;
 
+    private ActivityTaskManagerInternal.SleepTokenAcquirer mScreenOffSleepTokenAcquirer;
     Intent mHomeIntent;
     Intent mCarDockIntent;
     Intent mDeskDockIntent;
@@ -1852,6 +1853,9 @@
                 new AccessibilityShortcutController(mContext, new Handler(), mCurrentUserId);
         mLogger = new MetricsLogger();
 
+        mScreenOffSleepTokenAcquirer = mActivityTaskManagerInternal
+                .createSleepTokenAcquirer("ScreenOff");
+
         Resources res = mContext.getResources();
         mWakeOnDpadKeyPress =
                 res.getBoolean(com.android.internal.R.bool.config_wakeOnDpadKeyPress);
@@ -4521,6 +4525,7 @@
         if (DEBUG_WAKEUP) Slog.i(TAG, "Display" + displayId + " turned off...");
 
         if (displayId == DEFAULT_DISPLAY) {
+            updateScreenOffSleepToken(true);
             mRequestedOrSleepingDefaultDisplay = false;
             mDefaultDisplayPolicy.screenTurnedOff();
             synchronized (mLock) {
@@ -4553,6 +4558,7 @@
         if (displayId == DEFAULT_DISPLAY) {
             Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "screenTurningOn",
                     0 /* cookie */);
+            updateScreenOffSleepToken(false);
             mDefaultDisplayPolicy.screenTurnedOn(screenOnListener);
             mBootAnimationDismissable = false;
 
@@ -5073,6 +5079,15 @@
         }
     }
 
+    // TODO (multidisplay): Support multiple displays in WindowManagerPolicy.
+    private void updateScreenOffSleepToken(boolean acquire) {
+        if (acquire) {
+            mScreenOffSleepTokenAcquirer.acquire(DEFAULT_DISPLAY);
+        } else {
+            mScreenOffSleepTokenAcquirer.release(DEFAULT_DISPLAY);
+        }
+    }
+
     /** {@inheritDoc} */
     @Override
     public void enableScreenAfterBoot() {
diff --git a/services/core/java/com/android/server/timedetector/ServerFlags.java b/services/core/java/com/android/server/timedetector/ServerFlags.java
index d24a3df..cf0e350 100644
--- a/services/core/java/com/android/server/timedetector/ServerFlags.java
+++ b/services/core/java/com/android/server/timedetector/ServerFlags.java
@@ -66,6 +66,7 @@
             KEY_TIME_DETECTOR_LOWER_BOUND_MILLIS_OVERRIDE,
             KEY_TIME_DETECTOR_ORIGIN_PRIORITIES_OVERRIDE,
             KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED,
+            KEY_ENHANCED_METRICS_COLLECTION_ENABLED,
     })
     @Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER })
     @Retention(RetentionPolicy.SOURCE)
@@ -156,12 +157,18 @@
             "time_detector_origin_priorities_override";
 
     /**
-     * The key to override the time detector lower bound configuration. The values is the number of
+     * The key to override the time detector lower bound configuration. The value is the number of
      * milliseconds since the beginning of the Unix epoch.
      */
     public static final @DeviceConfigKey String KEY_TIME_DETECTOR_LOWER_BOUND_MILLIS_OVERRIDE =
             "time_detector_lower_bound_millis_override";
 
+    /**
+     * The key to allow extra metrics / telemetry information to be collected from internal testers.
+     */
+    public static final @DeviceConfigKey String KEY_ENHANCED_METRICS_COLLECTION_ENABLED =
+            "enhanced_metrics_collection_enabled";
+
     @GuardedBy("mListeners")
     private final ArrayMap<ConfigurationChangeListener, Set<String>> mListeners = new ArrayMap<>();
 
diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
index 65f077e..2291777 100644
--- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
@@ -40,6 +40,7 @@
     private final boolean mTelephonyDetectionSupported;
     private final boolean mGeoDetectionSupported;
     private final boolean mTelephonyFallbackSupported;
+    private final boolean mEnhancedMetricsCollectionEnabled;
     private final boolean mAutoDetectionEnabledSetting;
     private final @UserIdInt int mUserId;
     private final boolean mUserConfigAllowed;
@@ -50,6 +51,7 @@
         mTelephonyDetectionSupported = builder.mTelephonyDetectionSupported;
         mGeoDetectionSupported = builder.mGeoDetectionSupported;
         mTelephonyFallbackSupported = builder.mTelephonyFallbackSupported;
+        mEnhancedMetricsCollectionEnabled = builder.mEnhancedMetricsCollectionEnabled;
         mAutoDetectionEnabledSetting = builder.mAutoDetectionEnabledSetting;
 
         mUserId = builder.mUserId;
@@ -81,6 +83,15 @@
         return mTelephonyFallbackSupported;
     }
 
+    /**
+     * Returns {@code true} if the device can collect / report extra metrics information for QA
+     * / testers. These metrics might involve logging more expensive or more revealing data that
+     * would not be collected from the set of public users.
+     */
+    public boolean isEnhancedMetricsCollectionEnabled() {
+        return mEnhancedMetricsCollectionEnabled;
+    }
+
     /** Returns the value of the auto time zone detection enabled setting. */
     public boolean getAutoDetectionEnabledSetting() {
         return mAutoDetectionEnabledSetting;
@@ -227,6 +238,7 @@
                 && mTelephonyDetectionSupported == that.mTelephonyDetectionSupported
                 && mGeoDetectionSupported == that.mGeoDetectionSupported
                 && mTelephonyFallbackSupported == that.mTelephonyFallbackSupported
+                && mEnhancedMetricsCollectionEnabled == that.mEnhancedMetricsCollectionEnabled
                 && mAutoDetectionEnabledSetting == that.mAutoDetectionEnabledSetting
                 && mLocationEnabledSetting == that.mLocationEnabledSetting
                 && mGeoDetectionEnabledSetting == that.mGeoDetectionEnabledSetting;
@@ -235,7 +247,8 @@
     @Override
     public int hashCode() {
         return Objects.hash(mUserId, mUserConfigAllowed, mTelephonyDetectionSupported,
-                mGeoDetectionSupported, mTelephonyFallbackSupported, mAutoDetectionEnabledSetting,
+                mGeoDetectionSupported, mTelephonyFallbackSupported,
+                mEnhancedMetricsCollectionEnabled, mAutoDetectionEnabledSetting,
                 mLocationEnabledSetting, mGeoDetectionEnabledSetting);
     }
 
@@ -247,6 +260,7 @@
                 + ", mTelephonyDetectionSupported=" + mTelephonyDetectionSupported
                 + ", mGeoDetectionSupported=" + mGeoDetectionSupported
                 + ", mTelephonyFallbackSupported=" + mTelephonyFallbackSupported
+                + ", mEnhancedMetricsCollectionEnabled=" + mEnhancedMetricsCollectionEnabled
                 + ", mAutoDetectionEnabledSetting=" + mAutoDetectionEnabledSetting
                 + ", mLocationEnabledSetting=" + mLocationEnabledSetting
                 + ", mGeoDetectionEnabledSetting=" + mGeoDetectionEnabledSetting
@@ -264,6 +278,7 @@
         private boolean mTelephonyDetectionSupported;
         private boolean mGeoDetectionSupported;
         private boolean mTelephonyFallbackSupported;
+        private boolean mEnhancedMetricsCollectionEnabled;
         private boolean mAutoDetectionEnabledSetting;
         private boolean mLocationEnabledSetting;
         private boolean mGeoDetectionEnabledSetting;
@@ -284,6 +299,7 @@
             this.mTelephonyDetectionSupported = toCopy.mTelephonyDetectionSupported;
             this.mTelephonyFallbackSupported = toCopy.mTelephonyFallbackSupported;
             this.mGeoDetectionSupported = toCopy.mGeoDetectionSupported;
+            this.mEnhancedMetricsCollectionEnabled = toCopy.mEnhancedMetricsCollectionEnabled;
             this.mAutoDetectionEnabledSetting = toCopy.mAutoDetectionEnabledSetting;
             this.mLocationEnabledSetting = toCopy.mLocationEnabledSetting;
             this.mGeoDetectionEnabledSetting = toCopy.mGeoDetectionEnabledSetting;
@@ -323,6 +339,14 @@
         }
 
         /**
+         * Sets the value for enhanced metrics collection.
+         */
+        public Builder setEnhancedMetricsCollectionEnabled(boolean enabled) {
+            mEnhancedMetricsCollectionEnabled = enabled;
+            return this;
+        }
+
+        /**
          * Sets the value of the automatic time zone detection enabled setting for this device.
          */
         public Builder setAutoDetectionEnabledSetting(boolean enabled) {
diff --git a/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java b/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
index f156f8c..ecac267 100644
--- a/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
+++ b/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
@@ -34,11 +34,13 @@
  * A class that provides time zone detector state information for metrics.
  *
  * <p>
- * Regarding time zone ID ordinals:
+ * Regarding the use of time zone ID ordinals in metrics / telemetry:
  * <p>
- * We don't want to leak user location information by reporting time zone IDs. Instead, time zone
- * IDs are consistently identified within a given instance of this class by a numeric ID. This
- * allows comparison of IDs without revealing what those IDs are.
+ * For general metrics, we don't want to leak user location information by reporting time zone
+ * IDs. Instead, time zone IDs are consistently identified within a given instance of this class by
+ * a numeric ID (ordinal). This allows comparison of IDs without revealing what those IDs are.
+ * See {@link #isEnhancedMetricsCollectionEnabled()} for the setting that enables actual IDs to be
+ * collected.
  */
 public final class MetricsTimeZoneDetectorState {
 
@@ -54,6 +56,7 @@
 
     @NonNull private final ConfigurationInternal mConfigurationInternal;
     private final int mDeviceTimeZoneIdOrdinal;
+    @Nullable private final String mDeviceTimeZoneId;
     @Nullable private final MetricsTimeZoneSuggestion mLatestManualSuggestion;
     @Nullable private final MetricsTimeZoneSuggestion mLatestTelephonySuggestion;
     @Nullable private final MetricsTimeZoneSuggestion mLatestGeolocationSuggestion;
@@ -61,11 +64,13 @@
     private MetricsTimeZoneDetectorState(
             @NonNull ConfigurationInternal configurationInternal,
             int deviceTimeZoneIdOrdinal,
+            @Nullable String deviceTimeZoneId,
             @Nullable MetricsTimeZoneSuggestion latestManualSuggestion,
             @Nullable MetricsTimeZoneSuggestion latestTelephonySuggestion,
             @Nullable MetricsTimeZoneSuggestion latestGeolocationSuggestion) {
         mConfigurationInternal = Objects.requireNonNull(configurationInternal);
         mDeviceTimeZoneIdOrdinal = deviceTimeZoneIdOrdinal;
+        mDeviceTimeZoneId = deviceTimeZoneId;
         mLatestManualSuggestion = latestManualSuggestion;
         mLatestTelephonySuggestion = latestTelephonySuggestion;
         mLatestGeolocationSuggestion = latestGeolocationSuggestion;
@@ -83,18 +88,24 @@
             @Nullable TelephonyTimeZoneSuggestion latestTelephonySuggestion,
             @Nullable GeolocationTimeZoneSuggestion latestGeolocationSuggestion) {
 
+        boolean includeZoneIds = configurationInternal.isEnhancedMetricsCollectionEnabled();
+        String metricDeviceTimeZoneId = includeZoneIds ? deviceTimeZoneId : null;
         int deviceTimeZoneIdOrdinal =
                 tzIdOrdinalGenerator.ordinal(Objects.requireNonNull(deviceTimeZoneId));
         MetricsTimeZoneSuggestion latestCanonicalManualSuggestion =
-                createMetricsTimeZoneSuggestion(tzIdOrdinalGenerator, latestManualSuggestion);
+                createMetricsTimeZoneSuggestion(
+                        tzIdOrdinalGenerator, latestManualSuggestion, includeZoneIds);
         MetricsTimeZoneSuggestion latestCanonicalTelephonySuggestion =
-                createMetricsTimeZoneSuggestion(tzIdOrdinalGenerator, latestTelephonySuggestion);
+                createMetricsTimeZoneSuggestion(
+                        tzIdOrdinalGenerator, latestTelephonySuggestion, includeZoneIds);
         MetricsTimeZoneSuggestion latestCanonicalGeolocationSuggestion =
-                createMetricsTimeZoneSuggestion(tzIdOrdinalGenerator, latestGeolocationSuggestion);
+                createMetricsTimeZoneSuggestion(
+                        tzIdOrdinalGenerator, latestGeolocationSuggestion, includeZoneIds);
 
         return new MetricsTimeZoneDetectorState(
-                configurationInternal, deviceTimeZoneIdOrdinal, latestCanonicalManualSuggestion,
-                latestCanonicalTelephonySuggestion, latestCanonicalGeolocationSuggestion);
+                configurationInternal, deviceTimeZoneIdOrdinal, metricDeviceTimeZoneId,
+                latestCanonicalManualSuggestion, latestCanonicalTelephonySuggestion,
+                latestCanonicalGeolocationSuggestion);
     }
 
     /** Returns true if the device supports telephony time zone detection. */
@@ -112,6 +123,11 @@
         return mConfigurationInternal.isTelephonyFallbackSupported();
     }
 
+    /** Returns true if enhanced metric collection is enabled. */
+    public boolean isEnhancedMetricsCollectionEnabled() {
+        return mConfigurationInternal.isEnhancedMetricsCollectionEnabled();
+    }
+
     /** Returns true if user's location can be used generally. */
     public boolean getUserLocationEnabledSetting() {
         return mConfigurationInternal.getLocationEnabledSetting();
@@ -142,7 +158,7 @@
     }
 
     /**
-     * Returns the ordinal for the device's currently set time zone ID.
+     * Returns the ordinal for the device's current time zone ID.
      * See {@link MetricsTimeZoneDetectorState} for information about ordinals.
      */
     public int getDeviceTimeZoneIdOrdinal() {
@@ -150,6 +166,16 @@
     }
 
     /**
+     * Returns the device's current time zone ID. This will only be populated if {@link
+     * #isEnhancedMetricsCollectionEnabled()} is {@code true}. See {@link
+     * MetricsTimeZoneDetectorState} for details.
+     */
+    @Nullable
+    public String getDeviceTimeZoneId() {
+        return mDeviceTimeZoneId;
+    }
+
+    /**
      * Returns a canonical form of the last manual suggestion received.
      */
     @Nullable
@@ -183,6 +209,7 @@
         }
         MetricsTimeZoneDetectorState that = (MetricsTimeZoneDetectorState) o;
         return mDeviceTimeZoneIdOrdinal == that.mDeviceTimeZoneIdOrdinal
+                && Objects.equals(mDeviceTimeZoneId, that.mDeviceTimeZoneId)
                 && mConfigurationInternal.equals(that.mConfigurationInternal)
                 && Objects.equals(mLatestManualSuggestion, that.mLatestManualSuggestion)
                 && Objects.equals(mLatestTelephonySuggestion, that.mLatestTelephonySuggestion)
@@ -191,7 +218,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mConfigurationInternal, mDeviceTimeZoneIdOrdinal,
+        return Objects.hash(mConfigurationInternal, mDeviceTimeZoneIdOrdinal, mDeviceTimeZoneId,
                 mLatestManualSuggestion, mLatestTelephonySuggestion, mLatestGeolocationSuggestion);
     }
 
@@ -200,6 +227,7 @@
         return "MetricsTimeZoneDetectorState{"
                 + "mConfigurationInternal=" + mConfigurationInternal
                 + ", mDeviceTimeZoneIdOrdinal=" + mDeviceTimeZoneIdOrdinal
+                + ", mDeviceTimeZoneId=" + mDeviceTimeZoneId
                 + ", mLatestManualSuggestion=" + mLatestManualSuggestion
                 + ", mLatestTelephonySuggestion=" + mLatestTelephonySuggestion
                 + ", mLatestGeolocationSuggestion=" + mLatestGeolocationSuggestion
@@ -209,34 +237,40 @@
     @Nullable
     private static MetricsTimeZoneSuggestion createMetricsTimeZoneSuggestion(
             @NonNull OrdinalGenerator<String> zoneIdOrdinalGenerator,
-            @NonNull ManualTimeZoneSuggestion manualSuggestion) {
+            @NonNull ManualTimeZoneSuggestion manualSuggestion,
+            boolean includeFullZoneIds) {
         if (manualSuggestion == null) {
             return null;
         }
 
-        int zoneIdOrdinal = zoneIdOrdinalGenerator.ordinal(manualSuggestion.getZoneId());
-        return MetricsTimeZoneSuggestion.createCertain(
-                new int[] { zoneIdOrdinal });
+        String suggestionZoneId = manualSuggestion.getZoneId();
+        String[] metricZoneIds = includeFullZoneIds ? new String[] { suggestionZoneId } : null;
+        int[] zoneIdOrdinals = new int[] { zoneIdOrdinalGenerator.ordinal(suggestionZoneId) };
+        return MetricsTimeZoneSuggestion.createCertain(metricZoneIds, zoneIdOrdinals);
     }
 
     @Nullable
     private static MetricsTimeZoneSuggestion createMetricsTimeZoneSuggestion(
             @NonNull OrdinalGenerator<String> zoneIdOrdinalGenerator,
-            @NonNull TelephonyTimeZoneSuggestion telephonySuggestion) {
+            @NonNull TelephonyTimeZoneSuggestion telephonySuggestion,
+            boolean includeFullZoneIds) {
         if (telephonySuggestion == null) {
             return null;
         }
-        if (telephonySuggestion.getZoneId() == null) {
+        String suggestionZoneId = telephonySuggestion.getZoneId();
+        if (suggestionZoneId == null) {
             return MetricsTimeZoneSuggestion.createUncertain();
         }
-        int zoneIdOrdinal = zoneIdOrdinalGenerator.ordinal(telephonySuggestion.getZoneId());
-        return MetricsTimeZoneSuggestion.createCertain(new int[] { zoneIdOrdinal });
+        String[] metricZoneIds = includeFullZoneIds ? new String[] { suggestionZoneId } : null;
+        int[] zoneIdOrdinals = new int[] { zoneIdOrdinalGenerator.ordinal(suggestionZoneId) };
+        return MetricsTimeZoneSuggestion.createCertain(metricZoneIds, zoneIdOrdinals);
     }
 
     @Nullable
     private static MetricsTimeZoneSuggestion createMetricsTimeZoneSuggestion(
             @NonNull OrdinalGenerator<String> zoneIdOrdinalGenerator,
-            @Nullable GeolocationTimeZoneSuggestion geolocationSuggestion) {
+            @Nullable GeolocationTimeZoneSuggestion geolocationSuggestion,
+            boolean includeFullZoneIds) {
         if (geolocationSuggestion == null) {
             return null;
         }
@@ -245,7 +279,9 @@
         if (zoneIds == null) {
             return MetricsTimeZoneSuggestion.createUncertain();
         }
-        return MetricsTimeZoneSuggestion.createCertain(zoneIdOrdinalGenerator.ordinals(zoneIds));
+        String[] metricZoneIds = includeFullZoneIds ? zoneIds.toArray(new String[0]) : null;
+        int[] zoneIdOrdinals = zoneIdOrdinalGenerator.ordinals(zoneIds);
+        return MetricsTimeZoneSuggestion.createCertain(metricZoneIds, zoneIdOrdinals);
     }
 
     /**
@@ -254,33 +290,49 @@
      * MetricsTimeZoneSuggestion proto definition.
      */
     public static final class MetricsTimeZoneSuggestion {
-        @Nullable
-        private final int[] mZoneIdOrdinals;
+        @Nullable private final String[] mZoneIds;
+        @Nullable private final int[] mZoneIdOrdinals;
 
-        MetricsTimeZoneSuggestion(@Nullable int[] zoneIdOrdinals) {
+        private MetricsTimeZoneSuggestion(
+                @Nullable String[] zoneIds, @Nullable int[] zoneIdOrdinals) {
+            mZoneIds = zoneIds;
             mZoneIdOrdinals = zoneIdOrdinals;
         }
 
         @NonNull
         static MetricsTimeZoneSuggestion createUncertain() {
-            return new MetricsTimeZoneSuggestion(null);
+            return new MetricsTimeZoneSuggestion(null, null);
         }
 
         @NonNull
         static MetricsTimeZoneSuggestion createCertain(
-                @NonNull int[] zoneIdOrdinals) {
-            return new MetricsTimeZoneSuggestion(zoneIdOrdinals);
+                @Nullable String[] zoneIds, @NonNull int[] zoneIdOrdinals) {
+            return new MetricsTimeZoneSuggestion(zoneIds, zoneIdOrdinals);
         }
 
         public boolean isCertain() {
             return mZoneIdOrdinals != null;
         }
 
+        /**
+         * Returns ordinals for the time zone IDs contained in the suggestion.
+         * See {@link MetricsTimeZoneDetectorState} for information about ordinals.
+         */
         @Nullable
         public int[] getZoneIdOrdinals() {
             return mZoneIdOrdinals;
         }
 
+        /**
+         * Returns the time zone IDs contained in the suggestion. This will only be populated if
+         * {@link #isEnhancedMetricsCollectionEnabled()} is {@code true}. See {@link
+         * MetricsTimeZoneDetectorState} for details.
+         */
+        @Nullable
+        public String[] getZoneIds() {
+            return mZoneIds;
+        }
+
         @Override
         public boolean equals(Object o) {
             if (this == o) {
@@ -290,18 +342,22 @@
                 return false;
             }
             MetricsTimeZoneSuggestion that = (MetricsTimeZoneSuggestion) o;
-            return Arrays.equals(mZoneIdOrdinals, that.mZoneIdOrdinals);
+            return Arrays.equals(mZoneIdOrdinals, that.mZoneIdOrdinals)
+                    && Arrays.equals(mZoneIds, that.mZoneIds);
         }
 
         @Override
         public int hashCode() {
-            return Arrays.hashCode(mZoneIdOrdinals);
+            int result = Arrays.hashCode(mZoneIds);
+            result = 31 * result + Arrays.hashCode(mZoneIdOrdinals);
+            return result;
         }
 
         @Override
         public String toString() {
             return "MetricsTimeZoneSuggestion{"
                     + "mZoneIdOrdinals=" + Arrays.toString(mZoneIdOrdinals)
+                    + ", mZoneIds=" + Arrays.toString(mZoneIds)
                     + '}';
         }
     }
diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
index 02ea433..4612f65 100644
--- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
@@ -62,6 +62,7 @@
     private static final Set<String> CONFIGURATION_INTERNAL_SERVER_FLAGS_KEYS_TO_WATCH =
             Collections.unmodifiableSet(new ArraySet<>(new String[] {
                     ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED,
+                    ServerFlags.KEY_ENHANCED_METRICS_COLLECTION_ENABLED,
                     ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT,
                     ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE,
                     ServerFlags.KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED,
@@ -296,6 +297,7 @@
                         isTelephonyTimeZoneDetectionFeatureSupported())
                 .setGeoDetectionFeatureSupported(isGeoTimeZoneDetectionFeatureSupported())
                 .setTelephonyFallbackSupported(isTelephonyFallbackSupported())
+                .setEnhancedMetricsCollectionEnabled(isEnhancedMetricsCollectionEnabled())
                 .setAutoDetectionEnabledSetting(getAutoDetectionEnabledSetting())
                 .setUserConfigAllowed(isUserConfigAllowed(userId))
                 .setLocationEnabledSetting(getLocationEnabledSetting(userId))
@@ -400,6 +402,17 @@
                 defaultEnabled);
     }
 
+    /**
+     * Returns {@code true} if extra metrics / telemetry information can be collected. Used for
+     * internal testers.
+     */
+    private boolean isEnhancedMetricsCollectionEnabled() {
+        final boolean defaultEnabled = false;
+        return mServerFlags.getBoolean(
+                ServerFlags.KEY_ENHANCED_METRICS_COLLECTION_ENABLED,
+                defaultEnabled);
+    }
+
     @Override
     @NonNull
     public synchronized String getPrimaryLocationTimeZoneProviderPackageName() {
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index 14784cf..f75608e 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -364,6 +364,13 @@
         }
     }
 
+    @NonNull
+    MetricsTimeZoneDetectorState generateMetricsState() {
+        enforceManageTimeZoneDetectorPermission();
+
+        return mTimeZoneDetectorStrategy.generateMetricsState();
+    }
+
     @Override
     protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
             @Nullable String[] args) {
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
index 2b912ad..8535b3d 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.timezonedetector;
 
+import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_DUMP_METRICS;
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK;
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED;
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_ENABLED;
@@ -28,6 +29,7 @@
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE;
 import static android.provider.DeviceConfig.NAMESPACE_SYSTEM_TIME;
 
+import static com.android.server.timedetector.ServerFlags.KEY_ENHANCED_METRICS_COLLECTION_ENABLED;
 import static com.android.server.timedetector.ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED;
 import static com.android.server.timedetector.ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT;
 import static com.android.server.timedetector.ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE;
@@ -80,6 +82,8 @@
                 return runSuggestTelephonyTimeZone();
             case SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK:
                 return runEnableTelephonyFallback();
+            case SHELL_COMMAND_DUMP_METRICS:
+                return runDumpMetrics();
             default: {
                 return handleDefaultCommands(cmd);
             }
@@ -168,14 +172,22 @@
             pw.println("Suggestion " + suggestion + " injected.");
             return 0;
         } catch (RuntimeException e) {
-            pw.println(e.toString());
+            pw.println(e);
             return 1;
         }
     }
 
     private int runEnableTelephonyFallback() {
         mInterface.enableTelephonyFallback();
-        return 1;
+        return 0;
+    }
+
+    private int runDumpMetrics() {
+        final PrintWriter pw = getOutPrintWriter();
+        MetricsTimeZoneDetectorState metricsState = mInterface.generateMetricsState();
+        pw.println("MetricsTimeZoneDetectorState:");
+        pw.println(metricsState.toString());
+        return 0;
     }
 
     @Override
@@ -208,10 +220,10 @@
         pw.println();
         pw.printf("  %s <geolocation suggestion opts>\n",
                 SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE);
-        pw.printf("  %s <manual suggestion opts>\n",
-                SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE);
-        pw.printf("  %s <telephony suggestion opts>\n",
-                SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE);
+        pw.printf("  %s <manual suggestion opts>\n", SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE);
+        pw.printf("  %s <telephony suggestion opts>\n", SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE);
+        pw.printf("  %s\n", SHELL_COMMAND_DUMP_METRICS);
+        pw.printf("    Dumps the service metrics to stdout for inspection.\n");
         pw.println();
         GeolocationTimeZoneSuggestion.printCommandLineOpts(pw);
         pw.println();
@@ -235,6 +247,8 @@
         pw.printf("  %s\n", KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED);
         pw.printf("    Used to enable / disable support for telephony detection fallback. Also see"
                 + " the %s command.\n", SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK);
+        pw.printf("  %s\n", KEY_ENHANCED_METRICS_COLLECTION_ENABLED);
+        pw.printf("    Used to increase the detail of metrics collected / reported.\n");
         pw.println();
         pw.printf("[*] To be enabled, the user must still have location = on / auto time zone"
                 + " detection = on.\n");
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index e38e9c1..7fa9861 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -126,6 +126,35 @@
     }
 
     /**
+     * Sleep tokens cause the activity manager to put the top activity to sleep.
+     * They are used by components such as dreams that may hide and block interaction
+     * with underlying activities.
+     * The Acquirer provides an interface that encapsulates the underlying work, so the user does
+     * not need to handle the token by him/herself.
+     */
+    public interface SleepTokenAcquirer {
+
+        /**
+         * Acquires a sleep token.
+         * @param displayId The display to apply to.
+         */
+        void acquire(int displayId);
+
+        /**
+         * Releases the sleep token.
+         * @param displayId The display to apply to.
+         */
+        void release(int displayId);
+    }
+
+    /**
+     * Creates a sleep token acquirer for the specified display with the specified tag.
+     *
+     * @param tag A string identifying the purpose (eg. "Dream").
+     */
+    public abstract SleepTokenAcquirer createSleepTokenAcquirer(@NonNull String tag);
+
+    /**
      * Returns home activity for the specified user.
      *
      * @param userId ID of the user or {@link android.os.UserHandle#USER_ALL}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index e4ed04de..0a85ba1 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4544,25 +4544,17 @@
                 reason);
     }
 
-    /**
-     * Sleep tokens cause the activity manager to put the top activity to sleep.
-     * They are used by components such as dreams that may hide and block interaction
-     * with underlying activities.
-     */
-    final class SleepTokenAcquirer {
+    final class SleepTokenAcquirerImpl implements ActivityTaskManagerInternal.SleepTokenAcquirer {
         private final String mTag;
         private final SparseArray<RootWindowContainer.SleepToken> mSleepTokens =
                 new SparseArray<>();
 
-        SleepTokenAcquirer(@NonNull String tag) {
+        SleepTokenAcquirerImpl(@NonNull String tag) {
             mTag = tag;
         }
 
-        /**
-         * Acquires a sleep token.
-         * @param displayId The display to apply to.
-         */
-        void acquire(int displayId) {
+        @Override
+        public void acquire(int displayId) {
             synchronized (mGlobalLock) {
                 if (!mSleepTokens.contains(displayId)) {
                     mSleepTokens.append(displayId,
@@ -4572,11 +4564,8 @@
             }
         }
 
-        /**
-         * Releases the sleep token.
-         * @param displayId The display to apply to.
-         */
-        void release(int displayId) {
+        @Override
+        public void release(int displayId) {
             synchronized (mGlobalLock) {
                 final RootWindowContainer.SleepToken token = mSleepTokens.get(displayId);
                 if (token != null) {
@@ -5266,6 +5255,12 @@
 
     final class LocalService extends ActivityTaskManagerInternal {
         @Override
+        public SleepTokenAcquirer createSleepTokenAcquirer(@NonNull String tag) {
+            Objects.requireNonNull(tag);
+            return new SleepTokenAcquirerImpl(tag);
+        }
+
+        @Override
         public ComponentName getHomeActivityForUser(int userId) {
             synchronized (mGlobalLock) {
                 final ActivityRecord homeActivity =
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 2e318bc..0956580 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -664,7 +664,7 @@
     /** All tokens used to put activities on this root task to sleep (including mOffToken) */
     final ArrayList<RootWindowContainer.SleepToken> mAllSleepTokens = new ArrayList<>();
     /** The token acquirer to put root tasks on the display to sleep */
-    private final ActivityTaskManagerService.SleepTokenAcquirer mOffTokenAcquirer;
+    private final ActivityTaskManagerInternal.SleepTokenAcquirer mOffTokenAcquirer;
 
     private boolean mSleeping;
 
@@ -5481,14 +5481,6 @@
         return mMetricsLogger;
     }
 
-    void acquireScreenOffToken(boolean acquire) {
-        if (acquire) {
-            mOffTokenAcquirer.acquire(mDisplayId);
-        } else {
-            mOffTokenAcquirer.release(mDisplayId);
-        }
-    }
-
     void onDisplayChanged() {
         mDisplay.getRealSize(mTmpDisplaySize);
         setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y);
@@ -5500,9 +5492,9 @@
         final int displayState = mDisplayInfo.state;
         if (displayId != DEFAULT_DISPLAY) {
             if (displayState == Display.STATE_OFF) {
-                acquireScreenOffToken(true /* acquire */);
+                mOffTokenAcquirer.acquire(mDisplayId);
             } else if (displayState == Display.STATE_ON) {
-                acquireScreenOffToken(false /* acquire */);
+                mOffTokenAcquirer.release(mDisplayId);
             }
             ProtoLog.v(WM_DEBUG_LAYER_MIRRORING,
                     "Display %d state is now (%d), so update layer mirroring?",
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 7a2a311..71ab5b6 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -752,10 +752,6 @@
 
     public void setAwake(boolean awake) {
         mAwake = awake;
-        // The screen off token for non-default display is controlled by DisplayContent.
-        if (mDisplayContent.isDefaultDisplay) {
-            mDisplayContent.acquireScreenOffToken(!awake);
-        }
     }
 
     public boolean isAwake() {
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 3200473..4684cb1 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -508,10 +508,10 @@
         mDisplayContent.setLayoutNeeded();
 
         if (useShellTransitions) {
-            final boolean wasInTransition = mDisplayContent.inTransition();
+            final boolean wasCollecting = mDisplayContent.mTransitionController.isCollecting();
             mDisplayContent.requestChangeTransitionIfNeeded(
                     ActivityInfo.CONFIG_WINDOW_CONFIGURATION);
-            if (wasInTransition) {
+            if (wasCollecting) {
                 // Use remote-rotation infra since the transition has already been requested
                 // TODO(shell-transitions): Remove this once lifecycle management can cover all
                 //                          rotation cases.
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index cabe414..baf7f87 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -75,14 +75,14 @@
     private final SparseArray<KeyguardDisplayState> mDisplayStates = new SparseArray<>();
     private final ActivityTaskManagerService mService;
     private RootWindowContainer mRootWindowContainer;
-    private final ActivityTaskManagerService.SleepTokenAcquirer mSleepTokenAcquirer;
+    private final ActivityTaskManagerInternal.SleepTokenAcquirer mSleepTokenAcquirer;
 
 
     KeyguardController(ActivityTaskManagerService service,
             ActivityTaskSupervisor taskSupervisor) {
         mService = service;
         mTaskSupervisor = taskSupervisor;
-        mSleepTokenAcquirer = mService.new SleepTokenAcquirer(KEYGUARD_SLEEP_TOKEN_TAG);
+        mSleepTokenAcquirer = mService.new SleepTokenAcquirerImpl(KEYGUARD_SLEEP_TOKEN_TAG);
     }
 
     void setWindowManager(WindowManagerService windowManager) {
@@ -518,10 +518,10 @@
 
         private boolean mRequestDismissKeyguard;
         private final ActivityTaskManagerService mService;
-        private final ActivityTaskManagerService.SleepTokenAcquirer mSleepTokenAcquirer;
+        private final ActivityTaskManagerInternal.SleepTokenAcquirer mSleepTokenAcquirer;
 
         KeyguardDisplayState(ActivityTaskManagerService service, int displayId,
-                ActivityTaskManagerService.SleepTokenAcquirer acquirer) {
+                ActivityTaskManagerInternal.SleepTokenAcquirer acquirer) {
             mService = service;
             mDisplayId = displayId;
             mSleepTokenAcquirer = acquirer;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 117b22a..c7b13eb 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -225,7 +225,7 @@
     private static final String DISPLAY_OFF_SLEEP_TOKEN_TAG = "Display-off";
 
     /** The token acquirer to put root tasks on the displays to sleep */
-    final ActivityTaskManagerService.SleepTokenAcquirer mDisplayOffTokenAcquirer;
+    final ActivityTaskManagerInternal.SleepTokenAcquirer mDisplayOffTokenAcquirer;
 
     /**
      * The modes which affect which tasks are returned when calling
@@ -470,7 +470,7 @@
         mService = service.mAtmService;
         mTaskSupervisor = mService.mTaskSupervisor;
         mTaskSupervisor.mRootWindowContainer = this;
-        mDisplayOffTokenAcquirer = mService.new SleepTokenAcquirer(DISPLAY_OFF_SLEEP_TOKEN_TAG);
+        mDisplayOffTokenAcquirer = mService.new SleepTokenAcquirerImpl(DISPLAY_OFF_SLEEP_TOKEN_TAG);
     }
 
     boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 978eb1d..f72f2cc 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -81,6 +81,7 @@
 
     header_libs: [
         "bionic_libc_platform_headers",
+        "bpf_connectivity_headers",
     ],
 }
 
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
index 6ee6020c..a963785 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
@@ -49,6 +49,7 @@
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(true)
                 .setTelephonyFallbackSupported(false)
+                .setEnhancedMetricsCollectionEnabled(false)
                 .setAutoDetectionEnabledSetting(true)
                 .setLocationEnabledSetting(true)
                 .setGeoDetectionEnabledSetting(true)
@@ -112,6 +113,7 @@
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(true)
                 .setTelephonyFallbackSupported(false)
+                .setEnhancedMetricsCollectionEnabled(false)
                 .setAutoDetectionEnabledSetting(true)
                 .setLocationEnabledSetting(true)
                 .setGeoDetectionEnabledSetting(true)
@@ -177,6 +179,7 @@
                 .setTelephonyDetectionFeatureSupported(false)
                 .setGeoDetectionFeatureSupported(false)
                 .setTelephonyFallbackSupported(false)
+                .setEnhancedMetricsCollectionEnabled(false)
                 .setAutoDetectionEnabledSetting(true)
                 .setLocationEnabledSetting(true)
                 .setGeoDetectionEnabledSetting(true)
@@ -240,6 +243,7 @@
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(false)
                 .setTelephonyFallbackSupported(false)
+                .setEnhancedMetricsCollectionEnabled(false)
                 .setAutoDetectionEnabledSetting(true)
                 .setLocationEnabledSetting(true)
                 .setGeoDetectionEnabledSetting(true)
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
new file mode 100644
index 0000000..9029ac5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
@@ -0,0 +1,170 @@
+/*
+ * 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.timezonedetector;
+
+import static com.android.server.timezonedetector.MetricsTimeZoneDetectorState.DETECTION_MODE_GEO;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.UserIdInt;
+import android.app.timezonedetector.ManualTimeZoneSuggestion;
+import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
+
+import com.android.server.timezonedetector.MetricsTimeZoneDetectorState.MetricsTimeZoneSuggestion;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.function.Function;
+
+/** Tests for {@link MetricsTimeZoneDetectorState}. */
+public class MetricsTimeZoneDetectorStateTest {
+
+    private static final @UserIdInt int ARBITRARY_USER_ID = 1;
+    private static final @ElapsedRealtimeLong long ARBITRARY_ELAPSED_REALTIME_MILLIS = 1234L;
+    private static final String DEVICE_TIME_ZONE_ID = "DeviceTimeZoneId";
+
+    private static final ManualTimeZoneSuggestion MANUAL_TIME_ZONE_SUGGESTION =
+            new ManualTimeZoneSuggestion("ManualTimeZoneId");
+
+    private static final TelephonyTimeZoneSuggestion TELEPHONY_TIME_ZONE_SUGGESTION =
+            new TelephonyTimeZoneSuggestion.Builder(0)
+                    .setZoneId("TelephonyZoneId")
+                    .setMatchType(TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY)
+                    .setQuality(TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE)
+                    .build();
+
+    private static final GeolocationTimeZoneSuggestion GEOLOCATION_TIME_ZONE_SUGGESTION =
+            GeolocationTimeZoneSuggestion.createCertainSuggestion(
+                    ARBITRARY_ELAPSED_REALTIME_MILLIS,
+                    Arrays.asList("GeoTimeZoneId1", "GeoTimeZoneId2"));
+
+    private final OrdinalGenerator<String> mOrdinalGenerator =
+            new OrdinalGenerator<>(Function.identity());
+
+    @Test
+    public void enhancedMetricsCollectionEnabled() {
+        final boolean enhancedMetricsCollectionEnabled = true;
+        ConfigurationInternal configurationInternal =
+                createConfigurationInternal(enhancedMetricsCollectionEnabled);
+
+        // Create the object.
+        MetricsTimeZoneDetectorState metricsTimeZoneDetectorState =
+                MetricsTimeZoneDetectorState.create(mOrdinalGenerator, configurationInternal,
+                        DEVICE_TIME_ZONE_ID, MANUAL_TIME_ZONE_SUGGESTION,
+                        TELEPHONY_TIME_ZONE_SUGGESTION, GEOLOCATION_TIME_ZONE_SUGGESTION);
+
+        // Assert the content.
+        assertCommonConfiguration(configurationInternal, metricsTimeZoneDetectorState);
+
+        assertEquals(DEVICE_TIME_ZONE_ID, metricsTimeZoneDetectorState.getDeviceTimeZoneId());
+        MetricsTimeZoneSuggestion expectedManualSuggestion =
+                MetricsTimeZoneSuggestion.createCertain(
+                        new String[] { MANUAL_TIME_ZONE_SUGGESTION.getZoneId() },
+                        new int[] { 1 });
+        assertEquals(expectedManualSuggestion,
+                metricsTimeZoneDetectorState.getLatestManualSuggestion());
+
+        MetricsTimeZoneSuggestion expectedTelephonySuggestion =
+                MetricsTimeZoneSuggestion.createCertain(
+                        new String[] { TELEPHONY_TIME_ZONE_SUGGESTION.getZoneId() },
+                        new int[] { 2 });
+        assertEquals(expectedTelephonySuggestion,
+                metricsTimeZoneDetectorState.getLatestTelephonySuggestion());
+
+        MetricsTimeZoneSuggestion expectedGeoSuggestion =
+                MetricsTimeZoneSuggestion.createCertain(
+                        GEOLOCATION_TIME_ZONE_SUGGESTION.getZoneIds().toArray(new String[0]),
+                        new int[] { 3, 4 });
+        assertEquals(expectedGeoSuggestion,
+                metricsTimeZoneDetectorState.getLatestGeolocationSuggestion());
+    }
+
+    @Test
+    public void enhancedMetricsCollectionDisabled() {
+        final boolean enhancedMetricsCollectionEnabled = false;
+        ConfigurationInternal configurationInternal =
+                createConfigurationInternal(enhancedMetricsCollectionEnabled);
+
+        // Create the object.
+        MetricsTimeZoneDetectorState metricsTimeZoneDetectorState =
+                MetricsTimeZoneDetectorState.create(mOrdinalGenerator, configurationInternal,
+                        DEVICE_TIME_ZONE_ID, MANUAL_TIME_ZONE_SUGGESTION,
+                        TELEPHONY_TIME_ZONE_SUGGESTION, GEOLOCATION_TIME_ZONE_SUGGESTION);
+
+        // Assert the content.
+        assertCommonConfiguration(configurationInternal, metricsTimeZoneDetectorState);
+
+        // When enhancedMetricsCollectionEnabled == false, no time zone IDs should be included.
+        assertNull(metricsTimeZoneDetectorState.getDeviceTimeZoneId());
+        final String[] omittedZoneIds = null;
+
+        MetricsTimeZoneSuggestion expectedManualSuggestion =
+                MetricsTimeZoneSuggestion.createCertain(
+                        omittedZoneIds,
+                        new int[] { 1 });
+        assertEquals(expectedManualSuggestion,
+                metricsTimeZoneDetectorState.getLatestManualSuggestion());
+
+        MetricsTimeZoneSuggestion expectedTelephonySuggestion =
+                MetricsTimeZoneSuggestion.createCertain(
+                        omittedZoneIds,
+                        new int[] { 2 });
+        assertEquals(expectedTelephonySuggestion,
+                metricsTimeZoneDetectorState.getLatestTelephonySuggestion());
+
+        MetricsTimeZoneSuggestion expectedGeoSuggestion =
+                MetricsTimeZoneSuggestion.createCertain(
+                        omittedZoneIds,
+                        new int[] { 3, 4 });
+        assertEquals(expectedGeoSuggestion,
+                metricsTimeZoneDetectorState.getLatestGeolocationSuggestion());
+    }
+
+    private static void assertCommonConfiguration(ConfigurationInternal configurationInternal,
+            MetricsTimeZoneDetectorState metricsTimeZoneDetectorState) {
+        assertEquals(configurationInternal.isTelephonyDetectionSupported(),
+                metricsTimeZoneDetectorState.isTelephonyDetectionSupported());
+        assertEquals(configurationInternal.isGeoDetectionSupported(),
+                metricsTimeZoneDetectorState.isGeoDetectionSupported());
+        assertEquals(configurationInternal.getAutoDetectionEnabledSetting(),
+                metricsTimeZoneDetectorState.getAutoDetectionEnabledSetting());
+        assertEquals(configurationInternal.getLocationEnabledSetting(),
+                metricsTimeZoneDetectorState.getUserLocationEnabledSetting());
+        assertEquals(configurationInternal.getGeoDetectionEnabledSetting(),
+                metricsTimeZoneDetectorState.getGeoDetectionEnabledSetting());
+        assertEquals(configurationInternal.isEnhancedMetricsCollectionEnabled(),
+                metricsTimeZoneDetectorState.isEnhancedMetricsCollectionEnabled());
+        assertEquals(0, metricsTimeZoneDetectorState.getDeviceTimeZoneIdOrdinal());
+        assertEquals(DETECTION_MODE_GEO, metricsTimeZoneDetectorState.getDetectionMode());
+    }
+
+    private static ConfigurationInternal createConfigurationInternal(
+            boolean enhancedMetricsCollectionEnabled) {
+        return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+                .setUserConfigAllowed(true)
+                .setTelephonyDetectionFeatureSupported(true)
+                .setGeoDetectionFeatureSupported(true)
+                .setEnhancedMetricsCollectionEnabled(enhancedMetricsCollectionEnabled)
+                .setAutoDetectionEnabledSetting(true)
+                .setLocationEnabledSetting(true)
+                .setGeoDetectionEnabledSetting(true)
+                .build();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
index 193b2e3..e0e5ba0 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
@@ -379,6 +379,7 @@
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(true)
                 .setTelephonyFallbackSupported(false)
+                .setEnhancedMetricsCollectionEnabled(false)
                 .setUserConfigAllowed(true)
                 .setAutoDetectionEnabledSetting(autoDetectionEnabled)
                 .setLocationEnabledSetting(geoDetectionEnabled)
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index ef1b4f5..27f7814 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -87,10 +87,11 @@
 
     private static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_DISABLED =
             new ConfigurationInternal.Builder(USER_ID)
-                    .setUserConfigAllowed(false)
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
                     .setTelephonyFallbackSupported(false)
+                    .setEnhancedMetricsCollectionEnabled(false)
+                    .setUserConfigAllowed(false)
                     .setAutoDetectionEnabledSetting(false)
                     .setLocationEnabledSetting(true)
                     .setGeoDetectionEnabledSetting(false)
@@ -98,10 +99,11 @@
 
     private static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_ENABLED =
             new ConfigurationInternal.Builder(USER_ID)
-                    .setUserConfigAllowed(false)
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
                     .setTelephonyFallbackSupported(false)
+                    .setEnhancedMetricsCollectionEnabled(false)
+                    .setUserConfigAllowed(false)
                     .setAutoDetectionEnabledSetting(true)
                     .setLocationEnabledSetting(true)
                     .setGeoDetectionEnabledSetting(true)
@@ -109,10 +111,11 @@
 
     private static final ConfigurationInternal CONFIG_AUTO_DETECT_NOT_SUPPORTED =
             new ConfigurationInternal.Builder(USER_ID)
-                    .setUserConfigAllowed(true)
                     .setTelephonyDetectionFeatureSupported(false)
                     .setGeoDetectionFeatureSupported(false)
                     .setTelephonyFallbackSupported(false)
+                    .setEnhancedMetricsCollectionEnabled(false)
+                    .setUserConfigAllowed(true)
                     .setAutoDetectionEnabledSetting(false)
                     .setLocationEnabledSetting(true)
                     .setGeoDetectionEnabledSetting(false)
@@ -120,10 +123,11 @@
 
     private static final ConfigurationInternal CONFIG_AUTO_DISABLED_GEO_DISABLED =
             new ConfigurationInternal.Builder(USER_ID)
-                    .setUserConfigAllowed(true)
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
                     .setTelephonyFallbackSupported(false)
+                    .setEnhancedMetricsCollectionEnabled(false)
+                    .setUserConfigAllowed(true)
                     .setAutoDetectionEnabledSetting(false)
                     .setLocationEnabledSetting(true)
                     .setGeoDetectionEnabledSetting(false)
@@ -134,6 +138,7 @@
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
                     .setTelephonyFallbackSupported(false)
+                    .setEnhancedMetricsCollectionEnabled(false)
                     .setUserConfigAllowed(true)
                     .setAutoDetectionEnabledSetting(true)
                     .setLocationEnabledSetting(true)
@@ -145,6 +150,7 @@
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
                     .setTelephonyFallbackSupported(false)
+                    .setEnhancedMetricsCollectionEnabled(false)
                     .setUserConfigAllowed(true)
                     .setAutoDetectionEnabledSetting(true)
                     .setLocationEnabledSetting(true)
@@ -955,8 +961,20 @@
     }
 
     @Test
-    public void testGenerateMetricsState() {
-        ConfigurationInternal expectedInternalConfig = CONFIG_AUTO_DISABLED_GEO_DISABLED;
+    public void testGenerateMetricsState_enhancedMetricsCollection() {
+        testGenerateMetricsState(true);
+    }
+
+    @Test
+    public void testGenerateMetricsState_notEnhancedMetricsCollection() {
+        testGenerateMetricsState(false);
+    }
+
+    private void testGenerateMetricsState(boolean enhancedMetricsCollection) {
+        ConfigurationInternal expectedInternalConfig =
+                new ConfigurationInternal.Builder(CONFIG_AUTO_DISABLED_GEO_DISABLED)
+                        .setEnhancedMetricsCollectionEnabled(enhancedMetricsCollection)
+                        .build();
         String expectedDeviceTimeZoneId = "InitialZoneId";
 
         Script script = new Script()
@@ -1028,7 +1046,7 @@
                         tzIdOrdinalGenerator, expectedInternalConfig, expectedDeviceTimeZoneId,
                         expectedManualSuggestion, expectedTelephonySuggestion,
                         expectedGeolocationTimeZoneSuggestion);
-        // Rely on MetricsTimeZoneDetectorState.equals() for time zone ID ordinal comparisons.
+        // Rely on MetricsTimeZoneDetectorState.equals() for time zone ID / ID ordinal comparisons.
         assertEquals(expectedState, actualState);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java
index a2df3130..9d01310 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java
@@ -47,6 +47,7 @@
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(true)
                 .setTelephonyFallbackSupported(false)
+                .setEnhancedMetricsCollectionEnabled(false)
                 .setAutoDetectionEnabledSetting(true)
                 .setLocationEnabledSetting(true)
                 .setGeoDetectionEnabledSetting(geoDetectionEnabledSetting)
diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
index 3c799e0..8c78f74 100644
--- a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
+++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
@@ -101,6 +101,25 @@
         }
     }
 
+    /**
+     * Convenience method for running the provided action in the provided
+     * executor enclosed in
+     * {@link Binder#clearCallingIdentity}/{@link Binder#restoreCallingIdentity}
+     *
+     * Any exception thrown by the given action will need to be handled by caller.
+     *
+     */
+    public static void runWithCleanCallingIdentity(
+            @NonNull Runnable action, @NonNull Executor executor) {
+        if (action != null) {
+            if (executor != null) {
+                executor.execute(() -> runWithCleanCallingIdentity(action));
+            } else {
+                runWithCleanCallingIdentity(action);
+            }
+        }
+    }
+
 
     /**
      * Convenience method for running the provided action enclosed in
diff --git a/telephony/java/android/telephony/ims/ImsCallSession.java b/telephony/java/android/telephony/ims/ImsCallSession.java
index dfe5e6c9..6569de6 100755
--- a/telephony/java/android/telephony/ims/ImsCallSession.java
+++ b/telephony/java/android/telephony/ims/ImsCallSession.java
@@ -27,10 +27,12 @@
 
 import com.android.ims.internal.IImsCallSession;
 import com.android.ims.internal.IImsVideoCallProvider;
+import com.android.internal.telephony.util.TelephonyUtils;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.Executor;
 
 /**
  * Provides the call initiation/termination, and media exchange between two IMS endpoints.
@@ -522,6 +524,7 @@
     private final IImsCallSession miSession;
     private boolean mClosed = false;
     private Listener mListener;
+    private Executor mListenerExecutor = Runnable::run;
 
     /** @hide */
     public ImsCallSession(IImsCallSession iSession) {
@@ -538,9 +541,9 @@
     }
 
     /** @hide */
-    public ImsCallSession(IImsCallSession iSession, Listener listener) {
+    public ImsCallSession(IImsCallSession iSession, Listener listener, Executor executor) {
         this(iSession);
-        setListener(listener);
+        setListener(listener, executor);
     }
 
     /**
@@ -738,10 +741,14 @@
      * override the previous listener.
      *
      * @param listener to listen to the session events of this object
+     * @param executor an Executor that will execute callbacks
      * @hide
      */
-    public void setListener(Listener listener) {
+    public void setListener(Listener listener, Executor executor) {
         mListener = listener;
+        if (executor != null) {
+            mListenerExecutor = executor;
+        }
     }
 
     /**
@@ -1206,42 +1213,48 @@
         @Override
         public void callSessionInitiating(ImsCallProfile profile) {
             if (mListener != null) {
-                mListener.callSessionInitiating(ImsCallSession.this, profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionInitiating(
+                        ImsCallSession.this, profile), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionProgressing(ImsStreamMediaProfile profile) {
             if (mListener != null) {
-                mListener.callSessionProgressing(ImsCallSession.this, profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionProgressing(
+                        ImsCallSession.this, profile), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionInitiated(ImsCallProfile profile) {
             if (mListener != null) {
-                mListener.callSessionStarted(ImsCallSession.this, profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionStarted(
+                        ImsCallSession.this, profile), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionInitiatingFailed(ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionStartFailed(ImsCallSession.this, reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionStartFailed(
+                        ImsCallSession.this, reasonInfo), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionInitiatedFailed(ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionStartFailed(ImsCallSession.this, reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionStartFailed(
+                        ImsCallSession.this, reasonInfo), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionTerminated(ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionTerminated(ImsCallSession.this, reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionTerminated(
+                        ImsCallSession.this, reasonInfo), mListenerExecutor);
             }
         }
 
@@ -1251,42 +1264,49 @@
         @Override
         public void callSessionHeld(ImsCallProfile profile) {
             if (mListener != null) {
-                mListener.callSessionHeld(ImsCallSession.this, profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionHeld(
+                        ImsCallSession.this, profile), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionHoldFailed(ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionHoldFailed(ImsCallSession.this, reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionHoldFailed(
+                        ImsCallSession.this, reasonInfo), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionHoldReceived(ImsCallProfile profile) {
             if (mListener != null) {
-                mListener.callSessionHoldReceived(ImsCallSession.this, profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionHoldReceived(
+                        ImsCallSession.this, profile), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionResumed(ImsCallProfile profile) {
             if (mListener != null) {
-                mListener.callSessionResumed(ImsCallSession.this, profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionResumed(
+                        ImsCallSession.this, profile), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionResumeFailed(ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionResumeFailed(ImsCallSession.this, reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionResumeFailed(
+                        ImsCallSession.this, reasonInfo), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionResumeReceived(ImsCallProfile profile) {
             if (mListener != null) {
-                mListener.callSessionResumeReceived(ImsCallSession.this, profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionResumeReceived(ImsCallSession.this, profile),
+                        mListenerExecutor);
             }
         }
 
@@ -1311,13 +1331,15 @@
         @Override
         public void callSessionMergeComplete(IImsCallSession newSession) {
             if (mListener != null) {
-                if (newSession != null) {
-                    // New session created after conference
-                    mListener.callSessionMergeComplete(new ImsCallSession(newSession));
-               } else {
-                   // Session already exists. Hence no need to pass
-                   mListener.callSessionMergeComplete(null);
-               }
+                TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                    if (newSession != null) {
+                        // New session created after conference
+                        mListener.callSessionMergeComplete(new ImsCallSession(newSession));
+                    } else {
+                        // Session already exists. Hence no need to pass
+                        mListener.callSessionMergeComplete(null);
+                    }
+                }, mListenerExecutor);
             }
         }
 
@@ -1329,7 +1351,9 @@
         @Override
         public void callSessionMergeFailed(ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionMergeFailed(ImsCallSession.this, reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionMergeFailed(ImsCallSession.this, reasonInfo),
+                        mListenerExecutor);
             }
         }
 
@@ -1339,21 +1363,27 @@
         @Override
         public void callSessionUpdated(ImsCallProfile profile) {
             if (mListener != null) {
-                mListener.callSessionUpdated(ImsCallSession.this, profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionUpdated(ImsCallSession.this, profile),
+                        mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionUpdateFailed(ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionUpdateFailed(ImsCallSession.this, reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionUpdateFailed(ImsCallSession.this, reasonInfo),
+                        mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionUpdateReceived(ImsCallProfile profile) {
             if (mListener != null) {
-                mListener.callSessionUpdateReceived(ImsCallSession.this, profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionUpdateReceived(ImsCallSession.this, profile),
+                        mListenerExecutor);
             }
         }
 
@@ -1364,15 +1394,18 @@
         public void callSessionConferenceExtended(IImsCallSession newSession,
                 ImsCallProfile profile) {
             if (mListener != null) {
-                mListener.callSessionConferenceExtended(ImsCallSession.this,
-                        new ImsCallSession(newSession), profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionConferenceExtended(ImsCallSession.this,
+                        new ImsCallSession(newSession), profile), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionConferenceExtendFailed(ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionConferenceExtendFailed(ImsCallSession.this, reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionConferenceExtendFailed(
+                        ImsCallSession.this, reasonInfo), mListenerExecutor);
             }
         }
 
@@ -1380,8 +1413,9 @@
         public void callSessionConferenceExtendReceived(IImsCallSession newSession,
                 ImsCallProfile profile) {
             if (mListener != null) {
-                mListener.callSessionConferenceExtendReceived(ImsCallSession.this,
-                        new ImsCallSession(newSession), profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionConferenceExtendReceived(ImsCallSession.this,
+                        new ImsCallSession(newSession), profile), mListenerExecutor);
             }
         }
 
@@ -1392,30 +1426,36 @@
         @Override
         public void callSessionInviteParticipantsRequestDelivered() {
             if (mListener != null) {
-                mListener.callSessionInviteParticipantsRequestDelivered(ImsCallSession.this);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionInviteParticipantsRequestDelivered(
+                        ImsCallSession.this), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionInviteParticipantsRequestFailed(ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionInviteParticipantsRequestFailed(ImsCallSession.this,
-                        reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionInviteParticipantsRequestFailed(ImsCallSession.this,
+                        reasonInfo), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionRemoveParticipantsRequestDelivered() {
             if (mListener != null) {
-                mListener.callSessionRemoveParticipantsRequestDelivered(ImsCallSession.this);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionRemoveParticipantsRequestDelivered(
+                        ImsCallSession.this), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionRemoveParticipantsRequestFailed(ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionRemoveParticipantsRequestFailed(ImsCallSession.this,
-                        reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionRemoveParticipantsRequestFailed(ImsCallSession.this,
+                        reasonInfo), mListenerExecutor);
             }
         }
 
@@ -1425,7 +1465,9 @@
         @Override
         public void callSessionConferenceStateUpdated(ImsConferenceState state) {
             if (mListener != null) {
-                mListener.callSessionConferenceStateUpdated(ImsCallSession.this, state);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionConferenceStateUpdated(ImsCallSession.this, state),
+                        mListenerExecutor);
             }
         }
 
@@ -1435,7 +1477,9 @@
         @Override
         public void callSessionUssdMessageReceived(int mode, String ussdMessage) {
             if (mListener != null) {
-                mListener.callSessionUssdMessageReceived(ImsCallSession.this, mode, ussdMessage);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionUssdMessageReceived(ImsCallSession.this, mode,
+                        ussdMessage), mListenerExecutor);
             }
         }
 
@@ -1453,8 +1497,9 @@
         @Override
         public void callSessionMayHandover(int srcNetworkType, int targetNetworkType) {
             if (mListener != null) {
-                mListener.callSessionMayHandover(ImsCallSession.this, srcNetworkType,
-                        targetNetworkType);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionMayHandover(ImsCallSession.this, srcNetworkType,
+                        targetNetworkType), mListenerExecutor);
             }
         }
 
@@ -1465,8 +1510,9 @@
         public void callSessionHandover(int srcNetworkType, int targetNetworkType,
                 ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionHandover(ImsCallSession.this, srcNetworkType,
-                        targetNetworkType, reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionHandover(ImsCallSession.this, srcNetworkType,
+                        targetNetworkType, reasonInfo), mListenerExecutor);
             }
         }
 
@@ -1477,8 +1523,9 @@
         public void callSessionHandoverFailed(int srcNetworkType, int targetNetworkType,
                 ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionHandoverFailed(ImsCallSession.this, srcNetworkType,
-                        targetNetworkType, reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionHandoverFailed(ImsCallSession.this, srcNetworkType,
+                        targetNetworkType, reasonInfo), mListenerExecutor);
             }
         }
 
@@ -1488,7 +1535,9 @@
         @Override
         public void callSessionTtyModeReceived(int mode) {
             if (mListener != null) {
-                mListener.callSessionTtyModeReceived(ImsCallSession.this, mode);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionTtyModeReceived(ImsCallSession.this, mode),
+                        mListenerExecutor);
             }
         }
 
@@ -1500,14 +1549,18 @@
          */
         public void callSessionMultipartyStateChanged(boolean isMultiParty) {
             if (mListener != null) {
-                mListener.callSessionMultipartyStateChanged(ImsCallSession.this, isMultiParty);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionMultipartyStateChanged(ImsCallSession.this,
+                        isMultiParty), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionSuppServiceReceived(ImsSuppServiceNotification suppServiceInfo ) {
             if (mListener != null) {
-                mListener.callSessionSuppServiceReceived(ImsCallSession.this, suppServiceInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionSuppServiceReceived(ImsCallSession.this,
+                        suppServiceInfo), mListenerExecutor);
             }
         }
 
@@ -1517,7 +1570,9 @@
         @Override
         public void callSessionRttModifyRequestReceived(ImsCallProfile callProfile) {
             if (mListener != null) {
-                mListener.callSessionRttModifyRequestReceived(ImsCallSession.this, callProfile);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionRttModifyRequestReceived(ImsCallSession.this,
+                        callProfile), mListenerExecutor);
             }
         }
 
@@ -1527,7 +1582,9 @@
         @Override
         public void callSessionRttModifyResponseReceived(int status) {
             if (mListener != null) {
-                mListener.callSessionRttModifyResponseReceived(status);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionRttModifyResponseReceived(status),
+                        mListenerExecutor);
             }
         }
 
@@ -1537,7 +1594,8 @@
         @Override
         public void callSessionRttMessageReceived(String rttMessage) {
             if (mListener != null) {
-                mListener.callSessionRttMessageReceived(rttMessage);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionRttMessageReceived(rttMessage), mListenerExecutor);
             }
         }
 
@@ -1547,21 +1605,26 @@
         @Override
         public void callSessionRttAudioIndicatorChanged(ImsStreamMediaProfile profile) {
             if (mListener != null) {
-                mListener.callSessionRttAudioIndicatorChanged(profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionRttAudioIndicatorChanged(profile),
+                        mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionTransferred() {
             if (mListener != null) {
-                mListener.callSessionTransferred(ImsCallSession.this);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionTransferred(ImsCallSession.this), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionTransferFailed(@Nullable ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionTransferFailed(ImsCallSession.this, reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionTransferFailed(ImsCallSession.this, reasonInfo),
+                        mListenerExecutor);
             }
         }
 
@@ -1572,7 +1635,8 @@
         @Override
         public void callSessionDtmfReceived(char dtmf) {
             if (mListener != null) {
-                mListener.callSessionDtmfReceived(dtmf);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionDtmfReceived(
+                        dtmf), mListenerExecutor);
             }
         }
 
@@ -1582,7 +1646,8 @@
         @Override
         public void callQualityChanged(CallQuality callQuality) {
             if (mListener != null) {
-                mListener.callQualityChanged(callQuality);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callQualityChanged(
+                        callQuality), mListenerExecutor);
             }
         }
 
@@ -1594,8 +1659,9 @@
         public void callSessionRtpHeaderExtensionsReceived(
                 @NonNull List<RtpHeaderExtension> extensions) {
             if (mListener != null) {
-                mListener.callSessionRtpHeaderExtensionsReceived(
-                        new ArraySet<RtpHeaderExtension>(extensions));
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionRtpHeaderExtensionsReceived(
+                        new ArraySet<RtpHeaderExtension>(extensions)), mListenerExecutor);
             }
         }
     }
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 da5468e..b21d7b5 100644
--- a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
@@ -44,6 +44,7 @@
 import com.android.cts.install.lib.Install;
 import com.android.cts.install.lib.InstallUtils;
 import com.android.cts.install.lib.TestApp;
+import com.android.cts.install.lib.Uninstall;
 
 import org.junit.After;
 import org.junit.Before;
@@ -108,6 +109,7 @@
     @Test
     public void cleanUp() throws Exception {
         Files.deleteIfExists(mTestStateFile.toPath());
+        Uninstall.packages(TestApp.A, TestApp.B);
     }
 
     @Test