Merge changes from topic "presubmit-am-ff7699f855c14d1ca2c8a3438a9402f0" into sc-v2-dev-plus-aosp

* changes:
  [conflict] audioservice: improve SCO audio control logic 2p: a0b2ee74b2
  audioservice: improve SCO audio control logic
diff --git a/core/api/current.txt b/core/api/current.txt
index 4036c98..a09a359 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -42283,7 +42283,8 @@
     method public boolean isEnabled();
     method public boolean isSimPortAvailable(int);
     method public void startResolutionActivity(android.app.Activity, int, android.content.Intent, android.app.PendingIntent) throws android.content.IntentSender.SendIntentException;
-    method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void switchToSubscription(int, android.app.PendingIntent);
+    method @Deprecated @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void switchToSubscription(int, android.app.PendingIntent);
+    method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void switchToSubscription(int, int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.euicc.EuiccManager.ResultListener);
     method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void updateSubscriptionNickname(int, @Nullable String, @NonNull android.app.PendingIntent);
     field public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS = "android.telephony.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS";
     field public static final String ACTION_NOTIFY_CARRIER_SETUP_INCOMPLETE = "android.telephony.euicc.action.NOTIFY_CARRIER_SETUP_INCOMPLETE";
@@ -42329,6 +42330,10 @@
     field public static final int OPERATION_SYSTEM = 1; // 0x1
   }
 
+  public static interface EuiccManager.ResultListener {
+    method public void onComplete(int, @Nullable android.content.Intent);
+  }
+
 }
 
 package android.telephony.gsm {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index cbfd86e..d5be20f 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -9641,7 +9641,8 @@
     method @android.telephony.euicc.EuiccManager.OtaStatus public abstract int onGetOtaStatus(int);
     method public abstract int onRetainSubscriptionsForFactoryReset(int);
     method public abstract void onStartOtaIfNecessary(int, android.service.euicc.EuiccService.OtaStatusChangedCallback);
-    method public abstract int onSwitchToSubscription(int, @Nullable String, boolean);
+    method @Deprecated public abstract int onSwitchToSubscription(int, @Nullable String, boolean);
+    method public int onSwitchToSubscriptionWithPort(int, int, @Nullable String, boolean);
     method public abstract int onUpdateSubscriptionNickname(int, String, String);
     field public static final String ACTION_BIND_CARRIER_PROVISIONING_SERVICE = "android.service.euicc.action.BIND_CARRIER_PROVISIONING_SERVICE";
     field public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED = "android.service.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED";
@@ -9663,6 +9664,7 @@
     field public static final String EXTRA_RESOLUTION_CONFIRMATION_CODE = "android.service.euicc.extra.RESOLUTION_CONFIRMATION_CODE";
     field public static final String EXTRA_RESOLUTION_CONFIRMATION_CODE_RETRIED = "android.service.euicc.extra.RESOLUTION_CONFIRMATION_CODE_RETRIED";
     field public static final String EXTRA_RESOLUTION_CONSENT = "android.service.euicc.extra.RESOLUTION_CONSENT";
+    field public static final String EXTRA_RESOLUTION_PORT_INDEX = "android.service.euicc.extra.RESOLUTION_PORT_INDEX";
     field public static final String EXTRA_RESOLVABLE_ERRORS = "android.service.euicc.extra.RESOLVABLE_ERRORS";
     field public static final int RESOLVABLE_ERROR_CONFIRMATION_CODE = 1; // 0x1
     field public static final int RESOLVABLE_ERROR_POLICY_RULES = 2; // 0x2
@@ -11661,10 +11663,14 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getVoiceActivationState();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handlePinMmi(String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handlePinMmiForSubscriber(int, String);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean iccCloseLogicalChannelBySlot(int, int);
-    method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannelBySlot(int, @Nullable String, int);
-    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduBasicChannelBySlot(int, int, int, int, int, int, @Nullable String);
-    method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduLogicalChannelBySlot(int, int, int, int, int, int, int, @Nullable String);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void iccCloseLogicalChannelByPort(int, int, int);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean iccCloseLogicalChannelBySlot(int, int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannelByPort(int, int, @Nullable String, int);
+    method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannelBySlot(int, @Nullable String, int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduBasicChannelByPort(int, int, int, int, int, int, int, @Nullable String);
+    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduBasicChannelBySlot(int, int, int, int, int, int, @Nullable String);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduLogicalChannelByPort(int, int, int, int, int, int, int, int, @Nullable String);
+    method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduLogicalChannelBySlot(int, int, int, int, int, int, int, @Nullable String);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isAnyRadioPoweredOn();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApnMetered(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApplicationOnUicc(int);
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 7be4c3e..4c30f56 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -586,8 +586,9 @@
     /**
      * Delete uid from the ActivityManagerService PendingStartActivityUids list.
      * @param uid uid
+     * @param nowElapsed starting time of updateOomAdj
      */
-    public abstract void deletePendingTopUid(int uid);
+    public abstract void deletePendingTopUid(int uid, long nowElapsed);
 
     /**
      * Is the uid in ActivityManagerService PendingStartActivityUids list?
diff --git a/core/java/android/view/translation/UiTranslationManager.java b/core/java/android/view/translation/UiTranslationManager.java
index 3012e93..b57134b 100644
--- a/core/java/android/view/translation/UiTranslationManager.java
+++ b/core/java/android/view/translation/UiTranslationManager.java
@@ -101,26 +101,26 @@
     public static final String LOG_TAG = "UiTranslation";
 
     /**
-     * The state caller request to disable utranslation,, it is no longer need to ui translation.
+     * The state the caller requests to enable UI translation.
      *
      * @hide
      */
     public static final int STATE_UI_TRANSLATION_STARTED = 0;
     /**
-     * The state caller request to pause ui translation, it will switch back to the original text.
+     * The state caller requests to pause UI translation. It will switch back to the original text.
      *
      * @hide
      */
     public static final int STATE_UI_TRANSLATION_PAUSED = 1;
     /**
-     * The state caller request to resume the paused ui translation, it will show the translated
+     * The state caller requests to resume the paused UI translation. It will show the translated
      * text again if the text had been translated.
      *
      * @hide
      */
     public static final int STATE_UI_TRANSLATION_RESUMED = 2;
     /**
-     * The state the caller request to enable ui translation.
+     * The state the caller requests to disable UI translation when it no longer needs translation.
      *
      * @hide
      */
diff --git a/core/java/android/widget/TextViewTranslationCallback.java b/core/java/android/widget/TextViewTranslationCallback.java
index 942be21..1713d84 100644
--- a/core/java/android/widget/TextViewTranslationCallback.java
+++ b/core/java/android/widget/TextViewTranslationCallback.java
@@ -89,7 +89,7 @@
                     originalTranslationMethod);
         }
         final TransformationMethod transformation = mTranslationTransformation;
-        runWithAnimation(
+        runChangeTextWithAnimationIfNeeded(
                 (TextView) view,
                 () -> {
                     mIsShowingTranslation = true;
@@ -122,7 +122,7 @@
         if (mTranslationTransformation != null) {
             final TransformationMethod transformation =
                     mTranslationTransformation.getOriginalTransformationMethod();
-            runWithAnimation(
+            runChangeTextWithAnimationIfNeeded(
                     (TextView) view,
                     () -> {
                         mIsShowingTranslation = false;
@@ -232,10 +232,16 @@
      * Applies a simple text alpha animation when toggling between original and translated text. The
      * text is fully faded out, then swapped to the new text, then the fading is reversed.
      *
-     * @param runnable the operation to run on the view after the text is faded out, to change to
-     * displaying the original or translated text.
+     * @param changeTextRunnable the operation to run on the view after the text is faded out, to
+     * change to displaying the original or translated text.
      */
-    private void runWithAnimation(TextView view, Runnable runnable) {
+    private void runChangeTextWithAnimationIfNeeded(TextView view, Runnable changeTextRunnable) {
+        boolean areAnimatorsEnabled = ValueAnimator.areAnimatorsEnabled();
+        if (!areAnimatorsEnabled) {
+            // The animation is disabled, just change display text
+            changeTextRunnable.run();
+            return;
+        }
         if (mAnimator != null) {
             mAnimator.end();
             // Note: mAnimator is now null; do not use again here.
@@ -269,7 +275,7 @@
 
             @Override
             public void onAnimationRepeat(Animator animation) {
-                runnable.run();
+                changeTextRunnable.run();
             }
         });
         mAnimator.start();
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index c9b652c..ce212e2 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -4672,7 +4672,6 @@
 
     public void noteLongPartialWakelockStart(String name, String historyName, int uid,
             long elapsedRealtimeMs, long uptimeMs) {
-        uid = mapUid(uid);
         noteLongPartialWakeLockStartInternal(name, historyName, uid, elapsedRealtimeMs, uptimeMs);
     }
 
@@ -4704,15 +4703,21 @@
 
     private void noteLongPartialWakeLockStartInternal(String name, String historyName, int uid,
             long elapsedRealtimeMs, long uptimeMs) {
+        final int mappedUid = mapUid(uid);
         if (historyName == null) {
             historyName = name;
         }
-        if (!mActiveEvents.updateState(HistoryItem.EVENT_LONG_WAKE_LOCK_START, historyName, uid,
-                0)) {
+        if (!mActiveEvents.updateState(HistoryItem.EVENT_LONG_WAKE_LOCK_START, historyName,
+                mappedUid, 0)) {
             return;
         }
         addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_LONG_WAKE_LOCK_START,
-                historyName, uid);
+                historyName, mappedUid);
+        if (mappedUid != uid) {
+            // Prevent the isolated uid mapping from being removed while the wakelock is
+            // being held.
+            incrementIsolatedUidRefCount(uid);
+        }
     }
 
     public void noteLongPartialWakelockFinish(String name, String historyName, int uid) {
@@ -4722,7 +4727,6 @@
 
     public void noteLongPartialWakelockFinish(String name, String historyName, int uid,
             long elapsedRealtimeMs, long uptimeMs) {
-        uid = mapUid(uid);
         noteLongPartialWakeLockFinishInternal(name, historyName, uid, elapsedRealtimeMs, uptimeMs);
     }
 
@@ -4754,15 +4758,20 @@
 
     private void noteLongPartialWakeLockFinishInternal(String name, String historyName, int uid,
             long elapsedRealtimeMs, long uptimeMs) {
+        final int mappedUid = mapUid(uid);
         if (historyName == null) {
             historyName = name;
         }
-        if (!mActiveEvents.updateState(HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH, historyName, uid,
-                0)) {
+        if (!mActiveEvents.updateState(HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH, historyName,
+                mappedUid, 0)) {
             return;
         }
         addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH,
-                historyName, uid);
+                historyName, mappedUid);
+        if (mappedUid != uid) {
+            // Decrement the ref count for the isolated uid and delete the mapping if uneeded.
+            maybeRemoveIsolatedUidLocked(uid, elapsedRealtimeMs, uptimeMs);
+        }
     }
 
     void aggregateLastWakeupUptimeLocked(long elapsedRealtimeMs, long uptimeMs) {
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index 3e2885a..a6bc456 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -23,6 +23,8 @@
 import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_CPU;
 import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_DISPLAY;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import android.app.ActivityManager;
 import android.os.BatteryStats;
 import android.os.BatteryStats.HistoryItem;
@@ -206,6 +208,116 @@
         assertEquals(120_000, bgTime);
     }
 
+    /**
+     * Test BatteryStatsImpl.Uid.noteLongPartialWakelockStart for an isolated uid.
+     */
+    @SmallTest
+    public void testNoteLongPartialWakelockStart_isolatedUid() throws Exception {
+        final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+        MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+
+
+        bi.setRecordAllHistoryLocked(true);
+        bi.forceRecordAllHistory();
+
+        int pid = 10;
+        String name = "name";
+        String historyName = "historyName";
+
+        WorkSource.WorkChain isolatedWorkChain = new WorkSource.WorkChain();
+        isolatedWorkChain.addNode(ISOLATED_UID, name);
+
+        // Map ISOLATED_UID to UID.
+        bi.addIsolatedUidLocked(ISOLATED_UID, UID);
+
+        bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+        bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
+        bi.noteLongPartialWakelockStart(name, historyName, ISOLATED_UID);
+
+        clocks.realtime = clocks.uptime = 100;
+        bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+
+        clocks.realtime = clocks.uptime = 220;
+        bi.noteLongPartialWakelockFinish(name, historyName, ISOLATED_UID);
+
+        final BatteryStatsHistoryIterator iterator =
+                bi.createBatteryStatsHistoryIterator();
+
+        BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
+
+        while (iterator.next(item)) {
+            if (item.eventCode == HistoryItem.EVENT_LONG_WAKE_LOCK_START) break;
+        }
+        assertThat(item.eventCode).isEqualTo(HistoryItem.EVENT_LONG_WAKE_LOCK_START);
+        assertThat(item.eventTag).isNotNull();
+        assertThat(item.eventTag.string).isEqualTo(historyName);
+        assertThat(item.eventTag.uid).isEqualTo(UID);
+
+        while (iterator.next(item)) {
+            if (item.eventCode == HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH) break;
+        }
+        assertThat(item.eventCode).isEqualTo(HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH);
+        assertThat(item.eventTag).isNotNull();
+        assertThat(item.eventTag.string).isEqualTo(historyName);
+        assertThat(item.eventTag.uid).isEqualTo(UID);
+    }
+
+    /**
+     * Test BatteryStatsImpl.Uid.noteLongPartialWakelockStart for an isolated uid.
+     */
+    @SmallTest
+    public void testNoteLongPartialWakelockStart_isolatedUidRace() throws Exception {
+        final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+        MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+
+
+        bi.setRecordAllHistoryLocked(true);
+        bi.forceRecordAllHistory();
+
+        int pid = 10;
+        String name = "name";
+        String historyName = "historyName";
+
+        WorkSource.WorkChain isolatedWorkChain = new WorkSource.WorkChain();
+        isolatedWorkChain.addNode(ISOLATED_UID, name);
+
+        // Map ISOLATED_UID to UID.
+        bi.addIsolatedUidLocked(ISOLATED_UID, UID);
+
+        bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+        bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
+        bi.noteLongPartialWakelockStart(name, historyName, ISOLATED_UID);
+
+        clocks.realtime = clocks.uptime = 100;
+        bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+
+        clocks.realtime = clocks.uptime = 150;
+        bi.maybeRemoveIsolatedUidLocked(ISOLATED_UID, clocks.realtime, clocks.uptime);
+
+        clocks.realtime = clocks.uptime = 220;
+        bi.noteLongPartialWakelockFinish(name, historyName, ISOLATED_UID);
+
+        final BatteryStatsHistoryIterator iterator =
+                bi.createBatteryStatsHistoryIterator();
+
+        BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
+
+        while (iterator.next(item)) {
+            if (item.eventCode == HistoryItem.EVENT_LONG_WAKE_LOCK_START) break;
+        }
+        assertThat(item.eventCode).isEqualTo(HistoryItem.EVENT_LONG_WAKE_LOCK_START);
+        assertThat(item.eventTag).isNotNull();
+        assertThat(item.eventTag.string).isEqualTo(historyName);
+        assertThat(item.eventTag.uid).isEqualTo(UID);
+
+        while (iterator.next(item)) {
+            if (item.eventCode == HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH) break;
+        }
+        assertThat(item.eventCode).isEqualTo(HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH);
+        assertThat(item.eventTag).isNotNull();
+        assertThat(item.eventTag.string).isEqualTo(historyName);
+        assertThat(item.eventTag.uid).isEqualTo(UID);
+    }
 
     /**
      * Test BatteryStatsImpl.noteUidProcessStateLocked.
diff --git a/data/etc/car/com.android.car.shell.xml b/data/etc/car/com.android.car.shell.xml
index 3e57753..f29f6ee 100644
--- a/data/etc/car/com.android.car.shell.xml
+++ b/data/etc/car/com.android.car.shell.xml
@@ -37,6 +37,6 @@
         <permission name="android.car.permission.USE_CAR_WATCHDOG" />
         <permission name="android.car.permission.COLLECT_CAR_WATCHDOG_METRICS" />
         <permission name="android.car.permission.CONTROL_CAR_WATCHDOG_CONFIG" />
-        <permission name="android.car.permission.USE_CAR_TELEMETRY_SERVICE"/>
+        <permission name="com.android.car.permission.USE_CAR_TELEMETRY_SERVICE"/>
     </privapp-permissions>
 </permissions>
diff --git a/data/etc/car/com.google.android.car.kitchensink.xml b/data/etc/car/com.google.android.car.kitchensink.xml
index 2c59c73..313fa30 100644
--- a/data/etc/car/com.google.android.car.kitchensink.xml
+++ b/data/etc/car/com.google.android.car.kitchensink.xml
@@ -95,6 +95,6 @@
         <permission name="android.car.permission.CONTROL_CAR_EVS_ACTIVITY" />
         <permission name="android.car.permission.USE_CAR_EVS_CAMERA" />
         <permission name="android.car.permission.MONITOR_CAR_EVS_STATUS" />
-        <permission name="android.car.permission.USE_CAR_TELEMETRY_SERVICE" />
+        <permission name="com.android.car.permission.USE_CAR_TELEMETRY_SERVICE" />
     </privapp-permissions>
 </permissions>
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java
index 624d130..8733c15 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java
@@ -80,7 +80,7 @@
     /**
      * The possible states for a folding hinge.
      */
-    @IntDef({COMMON_STATE_FLAT, COMMON_STATE_HALF_OPENED})
+    @IntDef({COMMON_STATE_UNKNOWN, COMMON_STATE_FLAT, COMMON_STATE_HALF_OPENED})
     @Retention(RetentionPolicy.SOURCE)
     public @interface State {
     }
@@ -236,7 +236,8 @@
     }
 
     private static void assertValidState(@Nullable Integer state) {
-        if (state != null && state != COMMON_STATE_FLAT && state != COMMON_STATE_HALF_OPENED) {
+        if (state != null && state != COMMON_STATE_FLAT
+                && state != COMMON_STATE_HALF_OPENED && state != COMMON_STATE_UNKNOWN) {
             throw new IllegalArgumentException("Invalid state: " + state
                     + "must be either COMMON_STATE_FLAT or COMMON_STATE_HALF_OPENED");
         }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDisplayFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDisplayFeatureProducer.java
index e9d213e..0e696eb 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDisplayFeatureProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDisplayFeatureProducer.java
@@ -16,6 +16,8 @@
 
 package androidx.window.common;
 
+import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_FLAT;
+import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_HALF_OPENED;
 import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_UNKNOWN;
 import static androidx.window.common.CommonFoldingFeature.parseListFromString;
 
@@ -42,7 +44,10 @@
 public final class SettingsDisplayFeatureProducer
         extends BaseDataProducer<List<CommonFoldingFeature>> {
     private static final String DISPLAY_FEATURES = "display_features";
+    private static final String DEVICE_POSTURE = "device_posture";
 
+    private final Uri mDevicePostureUri =
+            Settings.Global.getUriFor(DEVICE_POSTURE);
     private final Uri mDisplayFeaturesUri =
             Settings.Global.getUriFor(DISPLAY_FEATURES);
 
@@ -55,6 +60,15 @@
         mObserver = new SettingsObserver();
     }
 
+    private int getPosture() {
+        int posture = Settings.Global.getInt(mResolver, DEVICE_POSTURE, COMMON_STATE_UNKNOWN);
+        if (posture == COMMON_STATE_HALF_OPENED || posture == COMMON_STATE_FLAT) {
+            return posture;
+        } else {
+            return COMMON_STATE_UNKNOWN;
+        }
+    }
+
     @Override
     @NonNull
     public Optional<List<CommonFoldingFeature>> getData() {
@@ -66,7 +80,7 @@
         if (TextUtils.isEmpty(displayFeaturesString)) {
             return Optional.of(Collections.emptyList());
         }
-        return Optional.of(parseListFromString(displayFeaturesString, COMMON_STATE_UNKNOWN));
+        return Optional.of(parseListFromString(displayFeaturesString, getPosture()));
     }
 
     /**
@@ -80,6 +94,7 @@
         mRegisteredObservers = true;
         mResolver.registerContentObserver(mDisplayFeaturesUri, false /* notifyForDescendants */,
                 mObserver /* ContentObserver */);
+        mResolver.registerContentObserver(mDevicePostureUri, false, mObserver);
     }
 
     /**
@@ -101,7 +116,7 @@
 
         @Override
         public void onChange(boolean selfChange, Uri uri) {
-            if (mDisplayFeaturesUri.equals(uri)) {
+            if (mDisplayFeaturesUri.equals(uri) || mDevicePostureUri.equals(uri)) {
                 notifyDataChanged();
             }
         }
diff --git a/libs/WindowManager/Jetpack/tests/OWNERS b/libs/WindowManager/Jetpack/tests/OWNERS
new file mode 100644
index 0000000..f2c3388
--- /dev/null
+++ b/libs/WindowManager/Jetpack/tests/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 909476
+# includes OWNERS from parent directories
+charlesccchen@google.com
+diegovela@google.com
diff --git a/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java b/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java
index 4f61dbf..e02ea89 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java
@@ -39,8 +39,10 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Executor;
+import java.util.function.IntConsumer;
 
 /**
  * A class that manages and configures Ethernet interfaces.
@@ -53,15 +55,31 @@
     private static final String TAG = "EthernetManager";
 
     private final IEthernetManager mService;
-    @GuardedBy("mListeners")
-    private final ArrayList<ListenerInfo> mListeners = new ArrayList<>();
+    @GuardedBy("mListenerLock")
+    private final ArrayList<ListenerInfo<InterfaceStateListener>> mIfaceListeners =
+            new ArrayList<>();
+    @GuardedBy("mListenerLock")
+    private final ArrayList<ListenerInfo<IntConsumer>> mEthernetStateListeners =
+            new ArrayList<>();
+    final Object mListenerLock = new Object();
     private final IEthernetServiceListener.Stub mServiceListener =
             new IEthernetServiceListener.Stub() {
                 @Override
+                public void onEthernetStateChanged(int state) {
+                    synchronized (mListenerLock) {
+                        for (ListenerInfo<IntConsumer> li : mEthernetStateListeners) {
+                            li.executor.execute(() -> {
+                                li.listener.accept(state);
+                            });
+                        }
+                    }
+                }
+
+                @Override
                 public void onInterfaceStateChanged(String iface, int state, int role,
                         IpConfiguration configuration) {
-                    synchronized (mListeners) {
-                        for (ListenerInfo li : mListeners) {
+                    synchronized (mListenerLock) {
+                        for (ListenerInfo<InterfaceStateListener> li : mIfaceListeners) {
                             li.executor.execute(() ->
                                     li.listener.onInterfaceStateChanged(iface, state, role,
                                             configuration));
@@ -70,13 +88,29 @@
                 }
             };
 
-    private static class ListenerInfo {
+    /**
+     * Indicates that Ethernet is disabled.
+     *
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    public static final int ETHERNET_STATE_DISABLED = 0;
+
+    /**
+     * Indicates that Ethernet is enabled.
+     *
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    public static final int ETHERNET_STATE_ENABLED  = 1;
+
+    private static class ListenerInfo<T> {
         @NonNull
         public final Executor executor;
         @NonNull
-        public final InterfaceStateListener listener;
+        public final T listener;
 
-        private ListenerInfo(@NonNull Executor executor, @NonNull InterfaceStateListener listener) {
+        private ListenerInfo(@NonNull Executor executor, @NonNull T listener) {
             this.executor = executor;
             this.listener = listener;
         }
@@ -289,18 +323,24 @@
         if (listener == null || executor == null) {
             throw new NullPointerException("listener and executor must not be null");
         }
-        synchronized (mListeners) {
-            mListeners.add(new ListenerInfo(executor, listener));
-            if (mListeners.size() == 1) {
-                try {
-                    mService.addListener(mServiceListener);
-                } catch (RemoteException e) {
-                    throw e.rethrowFromSystemServer();
-                }
-            }
+        synchronized (mListenerLock) {
+            maybeAddServiceListener();
+            mIfaceListeners.add(new ListenerInfo<InterfaceStateListener>(executor, listener));
         }
     }
 
+    @GuardedBy("mListenerLock")
+    private void maybeAddServiceListener() {
+        if (!mIfaceListeners.isEmpty() || !mEthernetStateListeners.isEmpty()) return;
+
+        try {
+            mService.addListener(mServiceListener);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+    }
+
     /**
      * Returns an array of available Ethernet interface names.
      * @hide
@@ -323,15 +363,20 @@
     @SystemApi(client = MODULE_LIBRARIES)
     public void removeInterfaceStateListener(@NonNull InterfaceStateListener listener) {
         Objects.requireNonNull(listener);
-        synchronized (mListeners) {
-            mListeners.removeIf(l -> l.listener == listener);
-            if (mListeners.isEmpty()) {
-                try {
-                    mService.removeListener(mServiceListener);
-                } catch (RemoteException e) {
-                    throw e.rethrowFromSystemServer();
-                }
-            }
+        synchronized (mListenerLock) {
+            mIfaceListeners.removeIf(l -> l.listener == listener);
+            maybeRemoveServiceListener();
+        }
+    }
+
+    @GuardedBy("mListenerLock")
+    private void maybeRemoveServiceListener() {
+        if (!mIfaceListeners.isEmpty() || !mEthernetStateListeners.isEmpty()) return;
+
+        try {
+            mService.removeListener(mServiceListener);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -535,14 +580,13 @@
     }
 
     /**
-     * Set an ethernet network's link state up.
+     * Enable a network interface.
      *
-     * When the link is successfully turned up, the callback will be called with the network
-     * interface was torn down, if any. If any error or unexpected condition happens while the
-     * system tries to turn the interface down, the callback will be called with an appropriate
-     * exception. The callback is guaranteed to be called exactly once for each call to this method.
+     * Enables a previously disabled network interface.
+     * This function accepts an {@link OutcomeReceiver} that is called once the operation has
+     * finished execution.
      *
-     * @param iface the name of the interface to act upon.
+     * @param iface the name of the interface to enable.
      * @param executor an {@link Executor} to execute the callback on. Optional if callback is null.
      * @param callback an optional {@link OutcomeReceiver} to listen for completion of the
      *                 operation. On success, {@link OutcomeReceiver#onResult} is called with the
@@ -550,7 +594,6 @@
      *                 information about the error.
      * @throws SecurityException if the process doesn't hold
      *                          {@link android.Manifest.permission.MANAGE_ETHERNET_NETWORKS}.
-     * @throws UnsupportedOperationException if called on a non-automotive device.
      * @hide
      */
     @SystemApi
@@ -559,7 +602,7 @@
             android.Manifest.permission.NETWORK_STACK,
             android.Manifest.permission.MANAGE_ETHERNET_NETWORKS})
     @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
-    public void connectNetwork(
+    public void enableInterface(
             @NonNull String iface,
             @Nullable @CallbackExecutor Executor executor,
             @Nullable OutcomeReceiver<String, EthernetNetworkManagementException> callback) {
@@ -574,14 +617,14 @@
     }
 
     /**
-     * Set an ethernet network's link state down.
+     * Disable a network interface.
      *
-     * When the link is successfully turned down, the callback will be called with the network
-     * interface was torn down, if any. If any error or unexpected condition happens while the
-     * system tries to turn the interface down, the callback will be called with an appropriate
-     * exception. The callback is guaranteed to be called exactly once for each call to this method.
+     * Disables the use of a network interface to fulfill network requests. If the interface
+     * currently serves a request, the network will be torn down.
+     * This function accepts an {@link OutcomeReceiver} that is called once the operation has
+     * finished execution.
      *
-     * @param iface the name of the interface to act upon.
+     * @param iface the name of the interface to disable.
      * @param executor an {@link Executor} to execute the callback on. Optional if callback is null.
      * @param callback an optional {@link OutcomeReceiver} to listen for completion of the
      *                 operation. On success, {@link OutcomeReceiver#onResult} is called with the
@@ -589,7 +632,6 @@
      *                 information about the error.
      * @throws SecurityException if the process doesn't hold
      *                          {@link android.Manifest.permission.MANAGE_ETHERNET_NETWORKS}.
-     * @throws UnsupportedOperationException if called on a non-automotive device.
      * @hide
      */
     @SystemApi
@@ -598,7 +640,7 @@
             android.Manifest.permission.NETWORK_STACK,
             android.Manifest.permission.MANAGE_ETHERNET_NETWORKS})
     @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
-    public void disconnectNetwork(
+    public void disableInterface(
             @NonNull String iface,
             @Nullable @CallbackExecutor Executor executor,
             @Nullable OutcomeReceiver<String, EthernetNetworkManagementException> callback) {
@@ -611,4 +653,77 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Change ethernet setting.
+     *
+     * @param enabled enable or disable ethernet settings.
+     *
+     * @hide
+     */
+    @RequiresPermission(anyOf = {
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_STACK,
+            android.Manifest.permission.NETWORK_SETTINGS})
+    @SystemApi(client = MODULE_LIBRARIES)
+    public void setEthernetEnabled(boolean enabled) {
+        try {
+            mService.setEthernetEnabled(enabled);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Listen to changes in the state of ethernet.
+     *
+     * @param executor to run callbacks on.
+     * @param listener to listen ethernet state changed.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+    @SystemApi(client = MODULE_LIBRARIES)
+    public void addEthernetStateListener(@NonNull Executor executor,
+            @NonNull IntConsumer listener) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(listener);
+        synchronized (mListenerLock) {
+            maybeAddServiceListener();
+            mEthernetStateListeners.add(new ListenerInfo<IntConsumer>(executor, listener));
+        }
+    }
+
+    /**
+     * Removes a listener.
+     *
+     * @param listener to listen ethernet state changed.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+    @SystemApi(client = MODULE_LIBRARIES)
+    public void removeEthernetStateListener(@NonNull IntConsumer listener) {
+        Objects.requireNonNull(listener);
+        synchronized (mListenerLock) {
+            mEthernetStateListeners.removeIf(l -> l.listener == listener);
+            maybeRemoveServiceListener();
+        }
+    }
+
+    /**
+     * Returns an array of existing Ethernet interface names regardless whether the interface
+     * is available or not currently.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+    @SystemApi(client = MODULE_LIBRARIES)
+    @NonNull
+    public List<String> getInterfaceList() {
+        try {
+            return mService.getInterfaceList();
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
 }
diff --git a/packages/ConnectivityT/framework-t/src/android/net/IEthernetManager.aidl b/packages/ConnectivityT/framework-t/src/android/net/IEthernetManager.aidl
index 95ae907a..42e4c1a 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/IEthernetManager.aidl
+++ b/packages/ConnectivityT/framework-t/src/android/net/IEthernetManager.aidl
@@ -23,6 +23,8 @@
 import android.net.INetworkInterfaceOutcomeReceiver;
 import android.net.ITetheredInterfaceCallback;
 
+import java.util.List;
+
 /**
  * Interface that answers queries about, and allows changing
  * ethernet configuration.
@@ -43,4 +45,6 @@
         in INetworkInterfaceOutcomeReceiver listener);
     void connectNetwork(String iface, in INetworkInterfaceOutcomeReceiver listener);
     void disconnectNetwork(String iface, in INetworkInterfaceOutcomeReceiver listener);
+    void setEthernetEnabled(boolean enabled);
+    List<String> getInterfaceList();
 }
diff --git a/packages/ConnectivityT/framework-t/src/android/net/IEthernetServiceListener.aidl b/packages/ConnectivityT/framework-t/src/android/net/IEthernetServiceListener.aidl
index 6d2ba03..751605b 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/IEthernetServiceListener.aidl
+++ b/packages/ConnectivityT/framework-t/src/android/net/IEthernetServiceListener.aidl
@@ -21,6 +21,7 @@
 /** @hide */
 oneway interface IEthernetServiceListener
 {
+    void onEthernetStateChanged(int state);
     void onInterfaceStateChanged(String iface, int state, int role,
             in IpConfiguration configuration);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index b85f1072..f6a1184 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -209,17 +209,17 @@
                 oldKey: String?,
                 data: MediaData,
                 immediately: Boolean,
-                receivedSmartspaceCardLatency: Int
+                receivedSmartspaceCardLatency: Int,
+                isSsReactivated: Boolean
             ) {
-                if (addOrUpdatePlayer(key, oldKey, data)) {
+                if (addOrUpdatePlayer(key, oldKey, data, isSsReactivated)) {
                     // Log card received if a new resumable media card is added
                     MediaPlayerData.getMediaPlayer(key)?.let {
                         /* ktlint-disable max-line-length */
                         logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
                                 it.mInstanceId,
                                 it.mUid,
-                                /* isRecommendationCard */ false,
-                                intArrayOf(
+                                surfaces = intArrayOf(
                                         SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
                                         SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN),
                                 rank = MediaPlayerData.getMediaPlayerIndex(key))
@@ -242,8 +242,7 @@
                             logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
                                     it.mInstanceId,
                                     it.mUid,
-                                    /* isRecommendationCard */ false,
-                                    intArrayOf(
+                                    surfaces = intArrayOf(
                                             SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
                                             SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN),
                                     rank = index,
@@ -277,12 +276,17 @@
             override fun onSmartspaceMediaDataLoaded(
                 key: String,
                 data: SmartspaceMediaData,
-                shouldPrioritize: Boolean,
-                isSsReactivated: Boolean
+                shouldPrioritize: Boolean
             ) {
                 if (DEBUG) Log.d(TAG, "Loading Smartspace media update")
+                // Log the case where the hidden media carousel with the existed inactive resume
+                // media is shown by the Smartspace signal.
                 if (data.isActive) {
-                    if (isSsReactivated && shouldPrioritize) {
+                    val hasActivatedExistedResumeMedia =
+                            !mediaManager.hasActiveMedia() &&
+                                    mediaManager.hasAnyMedia() &&
+                                    shouldPrioritize
+                    if (hasActivatedExistedResumeMedia) {
                         // Log resume card received if resumable media card is reactivated and
                         // recommendation card is valid and ranked first
                         MediaPlayerData.players().forEachIndexed { index, it ->
@@ -294,8 +298,7 @@
                                 logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
                                         it.mInstanceId,
                                         it.mUid,
-                                        /* isRecommendationCard */ false,
-                                        intArrayOf(
+                                        surfaces = intArrayOf(
                                                 SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
                                                 SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN),
                                         rank = index,
@@ -310,8 +313,7 @@
                         logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
                                 it.mInstanceId,
                                 it.mUid,
-                                /* isRecommendationCard */ true,
-                                intArrayOf(
+                                surfaces = intArrayOf(
                                         SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
                                         SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN),
                                 rank = MediaPlayerData.getMediaPlayerIndex(key),
@@ -408,7 +410,12 @@
     }
 
     // Returns true if new player is added
-    private fun addOrUpdatePlayer(key: String, oldKey: String?, data: MediaData): Boolean {
+    private fun addOrUpdatePlayer(
+        key: String,
+        oldKey: String?,
+        data: MediaData,
+        isSsReactivated: Boolean
+    ): Boolean {
         val dataCopy = data.copy(backgroundColor = bgColor)
         MediaPlayerData.moveIfExists(oldKey, key)
         val existingPlayer = MediaPlayerData.getMediaPlayer(key)
@@ -424,12 +431,13 @@
             newPlayer.playerViewHolder?.player?.setLayoutParams(lp)
             newPlayer.bindPlayer(dataCopy, key)
             newPlayer.setListening(currentlyExpanded)
-            MediaPlayerData.addMediaPlayer(key, dataCopy, newPlayer, systemClock)
+            MediaPlayerData.addMediaPlayer(key, dataCopy, newPlayer, systemClock, isSsReactivated)
             updatePlayerToState(newPlayer, noAnimation = true)
             reorderAllPlayers(curVisibleMediaKey)
         } else {
             existingPlayer.bindPlayer(dataCopy, key)
-            MediaPlayerData.addMediaPlayer(key, dataCopy, existingPlayer, systemClock)
+            MediaPlayerData.addMediaPlayer(key, dataCopy, existingPlayer, systemClock,
+                    isSsReactivated)
             if (visualStabilityManager.isReorderingAllowed || shouldScrollToActivePlayer) {
                 reorderAllPlayers(curVisibleMediaKey)
             } else {
@@ -523,8 +531,10 @@
                             it.targetId, it, MediaPlayerData.shouldPrioritizeSs)
                 }
             } else {
+                val isSsReactivated = MediaPlayerData.isSsReactivated(key)
                 removePlayer(key, dismissMediaData = false, dismissRecommendation = false)
-                addOrUpdatePlayer(key = key, oldKey = null, data = data)
+                addOrUpdatePlayer(
+                        key = key, oldKey = null, data = data, isSsReactivated = isSsReactivated)
             }
         }
     }
@@ -682,7 +692,8 @@
             this.desiredHostState = it
             currentlyExpanded = it.expansion > 0
 
-            val shouldCloseGuts = !currentlyExpanded && !mediaManager.hasActiveMedia() &&
+            val shouldCloseGuts = !currentlyExpanded &&
+                    !mediaManager.hasActiveMediaOrRecommendation() &&
                     desiredHostState.showsOnlyActiveMedia
 
             for (mediaPlayer in MediaPlayerData.players()) {
@@ -747,7 +758,6 @@
             val mediaControlPanel = MediaPlayerData.players().elementAt(visibleMediaIndex)
             val hasActiveMediaOrRecommendationCard =
                     MediaPlayerData.hasActiveMediaOrRecommendationCard()
-            val isRecommendationCard = mediaControlPanel.recommendationViewHolder != null
             if (!hasActiveMediaOrRecommendationCard && !qsExpanded) {
                 // Skip logging if on LS or QQS, and there is no active media card
                 return
@@ -755,7 +765,6 @@
             logSmartspaceCardReported(800, // SMARTSPACE_CARD_SEEN
                     mediaControlPanel.mInstanceId,
                     mediaControlPanel.mUid,
-                    isRecommendationCard,
                     intArrayOf(mediaControlPanel.surfaceForSmartspaceLogging))
             mediaControlPanel.mIsImpressed = true
         }
@@ -769,7 +778,6 @@
      * @param instanceId id to uniquely identify a card, e.g. each headphone generates a new
      * instanceId
      * @param uid uid for the application that media comes from
-     * @param isRecommendationCard whether the card is media recommendation
      * @param surfaces list of display surfaces the media card is on (e.g. lockscreen, shade) when
      * the event happened
      * @param interactedSubcardRank the rank for interacted media item for recommendation card, -1
@@ -779,21 +787,27 @@
      * @param rank the rank for media card in the media carousel, starting from 0
      * @param receivedLatencyMillis latency in milliseconds for card received events. E.g. latency
      * between headphone connection to sysUI displays media recommendation card
+     * @param isSwipeToDismiss whether is to log swipe-to-dismiss event
      *
      */
     fun logSmartspaceCardReported(
         eventId: Int,
         instanceId: Int,
         uid: Int,
-        isRecommendationCard: Boolean,
         surfaces: IntArray,
         interactedSubcardRank: Int = 0,
         interactedSubcardCardinality: Int = 0,
         rank: Int = mediaCarouselScrollHandler.visibleMediaIndex,
-        receivedLatencyMillis: Int = 0
+        receivedLatencyMillis: Int = 0,
+        isSwipeToDismiss: Boolean = false
     ) {
+        if (MediaPlayerData.players().size <= rank) {
+            return
+        }
+
+        val mediaControlKey = MediaPlayerData.playerKeys().elementAt(rank)
         // Only log media resume card when Smartspace data is available
-        if (!isRecommendationCard &&
+        if (!mediaControlKey.isSsMediaRec &&
                 !mediaManager.smartspaceMediaData.isActive &&
                 MediaPlayerData.smartspaceMediaData == null) {
             return
@@ -809,10 +823,13 @@
                     // card type for each new feature.
                     SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__UNKNOWN_CARD,
                     surface,
-                    rank,
+                    // Use -1 as rank value to indicate user swipe to dismiss the card
+                    if (isSwipeToDismiss) -1 else rank,
                     cardinality,
-                    if (isRecommendationCard)
+                    if (mediaControlKey.isSsMediaRec)
                         15 // MEDIA_RECOMMENDATION
+                    else if (mediaControlKey.isSsReactivated)
+                        43 // MEDIA_RESUME_SS_ACTIVATED
                     else
                         31, // MEDIA_RESUME
                     uid,
@@ -824,7 +841,9 @@
             if (DEBUG) {
                 Log.d(TAG, "Log Smartspace card event id: $eventId instance id: $instanceId" +
                         " surface: $surface rank: $rank cardinality: $cardinality " +
-                        "isRecommendationCard: $isRecommendationCard uid: $uid " +
+                        "isRecommendationCard: ${mediaControlKey.isSsMediaRec} " +
+                        "isSsReactivated: ${mediaControlKey.isSsReactivated}" +
+                        "uid: $uid " +
                         "interactedSubcardRank: $interactedSubcardRank " +
                         "interactedSubcardCardinality: $interactedSubcardCardinality " +
                         "received_latency_millis: $receivedLatencyMillis")
@@ -839,10 +858,9 @@
                 logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS
                         it.mInstanceId,
                         it.mUid,
-                        it.recommendationViewHolder != null,
                         intArrayOf(it.surfaceForSmartspaceLogging),
-                        // Use -1 as rank value to indicate user swipe to dismiss the card
-                        rank = -1)
+                        rank = index,
+                        isSwipeToDismiss = true)
                 // Reset card impressed state when swipe to dismissed
                 it.mIsImpressed = false
             }
@@ -871,29 +889,37 @@
         private set
 
     data class MediaSortKey(
-            // Whether the item represents a Smartspace media recommendation.
-        val isSsMediaRec: Boolean,
+        val isSsMediaRec: Boolean, // Whether the item represents a Smartspace media recommendation.
         val data: MediaData,
-        val updateTime: Long = 0
+        val updateTime: Long = 0,
+        val isSsReactivated: Boolean = false
     )
 
     private val comparator =
             compareByDescending<MediaSortKey> { it.data.isPlaying == true &&
                         it.data.playbackLocation == MediaData.PLAYBACK_LOCAL }
-                .thenByDescending { it.data.isPlaying == true &&
-                        it.data.playbackLocation == MediaData.PLAYBACK_CAST_LOCAL }
-                .thenByDescending { if (shouldPrioritizeSs) it.isSsMediaRec else !it.isSsMediaRec }
-                .thenByDescending { !it.data.resumption }
-                .thenByDescending { it.data.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE }
-                .thenByDescending { it.updateTime }
-                .thenByDescending { it.data.notificationKey }
+                    .thenByDescending { it.data.isPlaying == true &&
+                        it.data.playbackLocation == MediaData.PLAYBACK_CAST_LOCAL
+                    }
+                    .thenByDescending { if (shouldPrioritizeSs) it.isSsMediaRec else !it.isSsMediaRec }
+                    .thenByDescending { !it.data.resumption }
+                    .thenByDescending { it.data.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE }
+                    .thenByDescending { it.updateTime }
+                    .thenByDescending { it.data.notificationKey }
 
     private val mediaPlayers = TreeMap<MediaSortKey, MediaControlPanel>(comparator)
     private val mediaData: MutableMap<String, MediaSortKey> = mutableMapOf()
 
-    fun addMediaPlayer(key: String, data: MediaData, player: MediaControlPanel, clock: SystemClock) {
+    fun addMediaPlayer(
+        key: String,
+        data: MediaData,
+        player: MediaControlPanel,
+        clock: SystemClock,
+        isSsReactivated: Boolean
+    ) {
         removeMediaPlayer(key)
-        val sortKey = MediaSortKey(isSsMediaRec = false, data, clock.currentTimeMillis())
+        val sortKey = MediaSortKey(isSsMediaRec = false,
+                data, clock.currentTimeMillis(), isSsReactivated = isSsReactivated)
         mediaData.put(key, sortKey)
         mediaPlayers.put(sortKey, player)
     }
@@ -907,8 +933,8 @@
     ) {
         shouldPrioritizeSs = shouldPrioritize
         removeMediaPlayer(key)
-        val sortKey = MediaSortKey(/* isSsMediaRec= */ true,
-            EMPTY.copy(isPlaying = false), clock.currentTimeMillis())
+        val sortKey = MediaSortKey(isSsMediaRec = true,
+            EMPTY.copy(isPlaying = false), clock.currentTimeMillis(), isSsReactivated = true)
         mediaData.put(key, sortKey)
         mediaPlayers.put(sortKey, player)
         smartspaceMediaData = data
@@ -988,4 +1014,8 @@
         }
         return false
     }
+
+    fun isSsReactivated(key: String): Boolean = mediaData.get(key)?.let {
+        it.isSsReactivated
+    } ?: false
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index f66eb5b..65b0694 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -22,6 +22,7 @@
 import android.app.smartspace.SmartspaceAction;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.ColorStateList;
@@ -35,6 +36,7 @@
 import android.media.session.PlaybackState;
 import android.os.Process;
 import android.text.Layout;
+import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
@@ -85,6 +87,7 @@
     private static final int MEDIA_RECOMMENDATION_MAX_NUM = 6;
     private static final String KEY_SMARTSPACE_ARTIST_NAME = "artist_name";
     private static final String KEY_SMARTSPACE_OPEN_IN_FOREGROUND = "KEY_OPEN_IN_FOREGROUND";
+    private static final String KEY_SMARTSPACE_APP_NAME = "KEY_SMARTSPACE_APP_NAME";
 
     private static final Intent SETTINGS_INTENT = new Intent(ACTION_MEDIA_CONTROLS_SETTINGS);
 
@@ -155,8 +158,8 @@
 
         mSeekBarViewModel.setLogSmartspaceClick(() -> {
             logSmartspaceCardReported(
-                    760, // SMARTSPACE_CARD_CLICK
-                    /* isRecommendationCard */ false);
+                    760 // SMARTSPACE_CARD_CLICK
+            );
             return Unit.INSTANCE;
         });
     }
@@ -323,8 +326,9 @@
                 if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return;
                 if (mMediaViewController.isGutsVisible()) return;
 
-                logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
-                        /* isRecommendationCard */ false);
+                logSmartspaceCardReported(
+                        760 // SMARTSPACE_CARD_CLICK
+                );
                 mActivityStarter.postStartActivityDismissingKeyguard(clickIntent,
                         buildLaunchAnimatorController(mPlayerViewHolder.getPlayer()));
             });
@@ -444,8 +448,9 @@
                 button.setEnabled(true);
                 button.setOnClickListener(v -> {
                     if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
-                        logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
-                                /* isRecommendationCard */ false);
+                        logSmartspaceCardReported(
+                                760 // SMARTSPACE_CARD_CLICK
+                        );
                         action.run();
                     }
                 });
@@ -481,8 +486,9 @@
         mPlayerViewHolder.getDismiss().setOnClickListener(v -> {
             if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return;
 
-            logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS
-                    /* isRecommendationCard */ false);
+            logSmartspaceCardReported(
+                    761 // SMARTSPACE_CARD_DISMISS
+            );
 
             if (mKey != null) {
                 closeGuts();
@@ -578,18 +584,33 @@
         icon.setColorFilter(getGrayscaleFilter());
         ImageView headerLogoImageView = mRecommendationViewHolder.getCardIcon();
         headerLogoImageView.setImageDrawable(icon);
+
         // Set up media source app's label text.
-        CharSequence appLabel = packageManager.getApplicationLabel(applicationInfo);
-        if (appLabel.length() != 0) {
-            TextView headerTitleText = mRecommendationViewHolder.getCardText();
-            headerTitleText.setText(appLabel);
+        CharSequence appName = getAppName(data.getCardAction());
+        if (TextUtils.isEmpty(appName)) {
+            Intent launchIntent =
+                    packageManager.getLaunchIntentForPackage(data.getPackageName());
+            if (launchIntent != null) {
+                ActivityInfo launchActivity = launchIntent.resolveActivityInfo(packageManager, 0);
+                appName = launchActivity.loadLabel(packageManager);
+            } else {
+                Log.w(TAG, "Package " + data.getPackageName()
+                        +  " does not have a main launcher activity. Fallback to full app name");
+                appName = packageManager.getApplicationLabel(applicationInfo);
+            }
         }
+        // Set the app name as card's title.
+        if (!TextUtils.isEmpty(appName)) {
+            TextView headerTitleText = mRecommendationViewHolder.getCardText();
+            headerTitleText.setText(appName);
+        }
+
         // Set up media rec card's tap action if applicable.
         setSmartspaceRecItemOnClickListener(recommendationCard, data.getCardAction(),
                 /* interactedSubcardRank */ -1);
         // Set up media rec card's accessibility label.
         recommendationCard.setContentDescription(
-                mContext.getString(R.string.controls_media_smartspace_rec_description, appLabel));
+                mContext.getString(R.string.controls_media_smartspace_rec_description, appName));
 
         List<ImageView> mediaCoverItems = mRecommendationViewHolder.getMediaCoverItems();
         List<ViewGroup> mediaCoverContainers = mRecommendationViewHolder.getMediaCoverContainers();
@@ -634,12 +655,12 @@
                 mediaCoverImageView.setContentDescription(
                         mContext.getString(
                                 R.string.controls_media_smartspace_rec_item_no_artist_description,
-                                recommendation.getTitle(), appLabel));
+                                recommendation.getTitle(), appName));
             } else {
                 mediaCoverImageView.setContentDescription(
                         mContext.getString(
                                 R.string.controls_media_smartspace_rec_item_description,
-                                recommendation.getTitle(), artistName, appLabel));
+                                recommendation.getTitle(), artistName, appName));
             }
 
             if (uiComponentIndex < MEDIA_RECOMMENDATION_ITEMS_PER_ROW) {
@@ -665,8 +686,9 @@
         mRecommendationViewHolder.getDismiss().setOnClickListener(v -> {
             if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return;
 
-            logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS
-                    /* isRecommendationCard */ true);
+            logSmartspaceCardReported(
+                    761 // SMARTSPACE_CARD_DISMISS
+            );
             closeGuts();
             mMediaDataManagerLazy.get().dismissSmartspaceRecommendation(
                     data.getTargetId(), MediaViewController.GUTS_ANIMATION_DURATION + 100L);
@@ -823,7 +845,6 @@
             if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return;
 
             logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
-                    /* isRecommendationCard */ true,
                     interactedSubcardRank,
                     getSmartspaceSubCardCardinality());
 
@@ -844,6 +865,17 @@
         });
     }
 
+    /** Returns the upstream app name if available. */
+    @Nullable
+    private String getAppName(SmartspaceAction action) {
+        if (action == null || action.getIntent() == null
+                || action.getIntent().getExtras() == null) {
+            return null;
+        }
+
+        return action.getIntent().getExtras().getString(KEY_SMARTSPACE_APP_NAME);
+    }
+
     /** Returns if the Smartspace action will open the activity in foreground. */
     private boolean shouldSmartspaceRecItemOpenInForeground(SmartspaceAction action) {
         if (action == null || action.getIntent() == null
@@ -882,18 +914,17 @@
         return SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DEFAULT_SURFACE;
     }
 
-    private void logSmartspaceCardReported(int eventId, boolean isRecommendationCard) {
-        logSmartspaceCardReported(eventId, isRecommendationCard,
+    private void logSmartspaceCardReported(int eventId) {
+        logSmartspaceCardReported(eventId,
                 /* interactedSubcardRank */ 0,
                 /* interactedSubcardCardinality */ 0);
     }
 
-    private void logSmartspaceCardReported(int eventId, boolean isRecommendationCard,
+    private void logSmartspaceCardReported(int eventId,
             int interactedSubcardRank, int interactedSubcardCardinality) {
         mMediaCarouselController.logSmartspaceCardReported(eventId,
                 mInstanceId,
                 mUid,
-                isRecommendationCard,
                 new int[]{getSurfaceForSmartspaceLogging()},
                 interactedSubcardRank,
                 interactedSubcardCardinality);
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
index b68f2a7..311973a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
@@ -32,7 +32,8 @@
         oldKey: String?,
         data: MediaData,
         immediately: Boolean,
-        receivedSmartspaceCardLatency: Int
+        receivedSmartspaceCardLatency: Int,
+        isSsReactivated: Boolean
     ) {
         if (oldKey != null && oldKey != key && entries.contains(oldKey)) {
             entries[key] = data to entries.remove(oldKey)?.second
@@ -46,8 +47,7 @@
     override fun onSmartspaceMediaDataLoaded(
         key: String,
         data: SmartspaceMediaData,
-        shouldPrioritize: Boolean,
-        isSsReactivated: Boolean
+        shouldPrioritize: Boolean
     ) {
         listeners.toSet().forEach { it.onSmartspaceMediaDataLoaded(key, data) }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
index ae5c1f2..919bce2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
@@ -87,7 +87,8 @@
         oldKey: String?,
         data: MediaData,
         immediately: Boolean,
-        receivedSmartspaceCardLatency: Int
+        receivedSmartspaceCardLatency: Int,
+        isSsReactivated: Boolean
     ) {
         if (oldKey != null && oldKey != key) {
             allEntries.remove(oldKey)
@@ -112,8 +113,7 @@
     override fun onSmartspaceMediaDataLoaded(
         key: String,
         data: SmartspaceMediaData,
-        shouldPrioritize: Boolean,
-        isSsReactivated: Boolean
+        shouldPrioritize: Boolean
     ) {
         if (!data.isActive) {
             Log.d(TAG, "Inactive recommendation data. Skip triggering.")
@@ -138,13 +138,12 @@
             }
         }
 
-        val activeMedia = userEntries.filter { (key, value) -> value.active }
-        var isSsReactivatedMutable = activeMedia.isEmpty() && userEntries.isNotEmpty()
+        val shouldReactivate = !hasActiveMedia() && hasAnyMedia()
 
         if (timeSinceActive < smartspaceMaxAgeMillis) {
             // It could happen there are existing active media resume cards, then we don't need to
             // reactivate.
-            if (isSsReactivatedMutable) {
+            if (shouldReactivate) {
                 val lastActiveKey = sorted.lastKey() // most recently active
                 // Notify listeners to consider this media active
                 Log.d(TAG, "reactivating $lastActiveKey instead of smartspace")
@@ -154,7 +153,7 @@
                     it.onMediaDataLoaded(lastActiveKey, lastActiveKey, mediaData,
                             receivedSmartspaceCardLatency =
                             (systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis)
-                                    .toInt())
+                                    .toInt(), isSsReactivated = true)
                 }
             }
         } else {
@@ -166,8 +165,7 @@
             Log.d(TAG, "Invalid recommendation data. Skip showing the rec card")
             return
         }
-        listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable,
-                isSsReactivatedMutable) }
+        listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) }
     }
 
     override fun onMediaDataRemoved(key: String) {
@@ -258,14 +256,27 @@
     }
 
     /**
-     * Are there any media notifications active?
+     * Are there any media notifications active, including the recommendation?
      */
-    fun hasActiveMedia() = userEntries.any { it.value.active } || smartspaceMediaData.isActive
+    fun hasActiveMediaOrRecommendation() =
+            userEntries.any { it.value.active } ||
+                    (smartspaceMediaData.isActive && smartspaceMediaData.isValid)
 
     /**
      * Are there any media entries we should display?
      */
-    fun hasAnyMedia() = userEntries.isNotEmpty() || smartspaceMediaData.isActive
+    fun hasAnyMediaOrRecommendation() = userEntries.isNotEmpty() ||
+            (smartspaceMediaData.isActive && smartspaceMediaData.isValid)
+
+    /**
+     * Are there any media notifications active (excluding the recommendation)?
+     */
+    fun hasActiveMedia() = userEntries.any { it.value.active }
+
+    /**
+     * Are there any media entries we should display (excluding the recommendation)?
+     */
+    fun hasAnyMedia() = userEntries.isNotEmpty()
 
     /**
      * Add a listener for filtered [MediaData] changes
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 7c0f7fc..5d2d556 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -828,15 +828,27 @@
     fun onSwipeToDismiss() = mediaDataFilter.onSwipeToDismiss()
 
     /**
-     * Are there any media notifications active?
+     * Are there any media notifications active, including the recommendations?
+     */
+    fun hasActiveMediaOrRecommendation() = mediaDataFilter.hasActiveMediaOrRecommendation()
+
+    /**
+     * Are there any media entries we should display, including the recommendations?
+     * If resumption is enabled, this will include inactive players
+     * If resumption is disabled, we only want to show active players
+     */
+    fun hasAnyMediaOrRecommendation() = mediaDataFilter.hasAnyMediaOrRecommendation()
+
+    /**
+     * Are there any resume media notifications active, excluding the recommendations?
      */
     fun hasActiveMedia() = mediaDataFilter.hasActiveMedia()
 
     /**
-     * Are there any media entries we should display?
-     * If resumption is enabled, this will include inactive players
-     * If resumption is disabled, we only want to show active players
-     */
+    * Are there any resume media notifications active, excluding the recommendations?
+    * If resumption is enabled, this will include inactive players
+    * If resumption is disabled, we only want to show active players
+    */
     fun hasAnyMedia() = mediaDataFilter.hasAnyMedia()
 
     interface Listener {
@@ -855,13 +867,17 @@
          * @param receivedSmartspaceCardLatency is the latency between headphone connects and sysUI
          * displays Smartspace media targets. Will be 0 if the data is not activated by Smartspace
          * signal.
+         *
+         * @param isSsReactivated indicates resume media card is reactivated by Smartspace
+         * recommendation signal
          */
         fun onMediaDataLoaded(
             key: String,
             oldKey: String?,
             data: MediaData,
             immediately: Boolean = true,
-            receivedSmartspaceCardLatency: Int = 0
+            receivedSmartspaceCardLatency: Int = 0,
+            isSsReactivated: Boolean = false
         ) {}
 
         /**
@@ -870,15 +886,11 @@
          * @param shouldPrioritize indicates the sorting priority of the Smartspace card. If true,
          * it will be prioritized as the first card. Otherwise, it will show up as the last card as
          * default.
-         *
-         * @param isSsReactivated indicates resume media card is reactivated by Smartspace
-         * recommendation signal
          */
         fun onSmartspaceMediaDataLoaded(
             key: String,
             data: SmartspaceMediaData,
-            shouldPrioritize: Boolean = false,
-            isSsReactivated: Boolean = false
+            shouldPrioritize: Boolean = false
         ) {}
 
         /** Called whenever a previously existing Media notification was removed. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
index bed254f..085bae8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
@@ -68,7 +68,8 @@
         oldKey: String?,
         data: MediaData,
         immediately: Boolean,
-        receivedSmartspaceCardLatency: Int
+        receivedSmartspaceCardLatency: Int,
+        isSsReactivated: Boolean
     ) {
         if (oldKey != null && oldKey != key) {
             val oldEntry = entries.remove(oldKey)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
index 0a4b68b..3ed90fd0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
@@ -61,7 +61,8 @@
             oldKey: String?,
             data: MediaData,
             immediately: Boolean,
-            receivedSmartspaceCardLatency: Int
+            receivedSmartspaceCardLatency: Int,
+            isSsReactivated: Boolean
         ) {
             if (immediately) {
                 updateViewVisibility()
@@ -71,8 +72,7 @@
         override fun onSmartspaceMediaDataLoaded(
             key: String,
             data: SmartspaceMediaData,
-            shouldPrioritize: Boolean,
-            isSsReactivated: Boolean
+            shouldPrioritize: Boolean
         ) {
             updateViewVisibility()
         }
@@ -162,9 +162,9 @@
 
     private fun updateViewVisibility() {
         state.visible = if (showsOnlyActiveMedia) {
-            mediaDataManager.hasActiveMedia()
+            mediaDataManager.hasActiveMediaOrRecommendation()
         } else {
-            mediaDataManager.hasAnyMedia()
+            mediaDataManager.hasAnyMediaOrRecommendation()
         }
         val newVisibility = if (visible) View.VISIBLE else View.GONE
         if (newVisibility != hostView.visibility) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
index 35f95dd..61d0b41 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
@@ -184,7 +184,8 @@
         oldKey: String?,
         data: MediaData,
         immediately: Boolean,
-        receivedSmartspaceCardLatency: Int
+        receivedSmartspaceCardLatency: Int,
+        isSsReactivated: Boolean
     ) {
         if (useMediaResumption) {
             // If this had been started from a resume state, disconnect now that it's live
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt
index 1c448a2..3179296 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt
@@ -96,7 +96,8 @@
         oldKey: String?,
         data: MediaData,
         immediately: Boolean,
-        receivedSmartspaceCardLatency: Int
+        receivedSmartspaceCardLatency: Int,
+        isSsReactivated: Boolean
     ) {
         backgroundExecutor.execute {
             data.token?.let {
@@ -143,8 +144,7 @@
     override fun onSmartspaceMediaDataLoaded(
         key: String,
         data: SmartspaceMediaData,
-        shouldPrioritize: Boolean,
-        isSsReactivated: Boolean
+        shouldPrioritize: Boolean
     ) {
         backgroundExecutor.execute {
             dispatchSmartspaceMediaDataLoaded(key, data)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
index 9581a63..51755065 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
@@ -63,7 +63,8 @@
         oldKey: String?,
         data: MediaData,
         immediately: Boolean,
-        receivedSmartspaceCardLatency: Int
+        receivedSmartspaceCardLatency: Int,
+        isSsReactivated: Boolean
     ) {
         var reusedListener: PlaybackStateListener? = null
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 0e3b5b5..5b37800 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -242,13 +242,12 @@
             @Override
             public void onMediaDataLoaded(@NonNull String key,
                     @Nullable String oldKey, @NonNull MediaData data, boolean immediately,
-                    int receivedSmartspaceCardLatency) {
+                    int receivedSmartspaceCardLatency, boolean isSsReactivated) {
             }
 
             @Override
             public void onSmartspaceMediaDataLoaded(@NonNull String key,
-                    @NonNull SmartspaceMediaData data, boolean shouldPrioritize,
-                    boolean isSsReactivated) {
+                    @NonNull SmartspaceMediaData data, boolean shouldPrioritize) {
             }
 
             @Override
@@ -317,13 +316,12 @@
             @Override
             public void onMediaDataLoaded(@NonNull String key,
                     @Nullable String oldKey, @NonNull MediaData data, boolean immediately,
-                    int receivedSmartspaceCardLatency) {
+                    int receivedSmartspaceCardLatency, boolean isSsReactivated) {
             }
 
             @Override
             public void onSmartspaceMediaDataLoaded(@NonNull String key,
-                    @NonNull SmartspaceMediaData data, boolean shouldPrioritize,
-                    boolean isSsReactivated) {
+                    @NonNull SmartspaceMediaData data, boolean shouldPrioritize) {
 
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 3ba66bf..7432fa9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -1297,9 +1297,11 @@
         int userSwitcherPreferredY = mStatusBarHeaderHeightKeyguard;
         boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled();
         final boolean hasVisibleNotifications = mNotificationStackScrollLayoutController
-                .getVisibleNotificationCount() != 0 || mMediaDataManager.hasActiveMedia();
+                .getVisibleNotificationCount() != 0
+                || mMediaDataManager.hasActiveMediaOrRecommendation();
         boolean splitShadeWithActiveMedia =
-                mShouldUseSplitNotificationShade && mMediaDataManager.hasActiveMedia();
+                mShouldUseSplitNotificationShade
+                        && mMediaDataManager.hasActiveMediaOrRecommendation();
         if ((hasVisibleNotifications && !mShouldUseSplitNotificationShade)
                 || (splitShadeWithActiveMedia && !mDozing)) {
             mKeyguardStatusViewController.displayClock(SMALL);
@@ -1365,7 +1367,8 @@
 
     private void updateKeyguardStatusViewAlignment(boolean animate) {
         boolean hasVisibleNotifications = mNotificationStackScrollLayoutController
-                .getVisibleNotificationCount() != 0 || mMediaDataManager.hasActiveMedia();
+                .getVisibleNotificationCount() != 0
+                || mMediaDataManager.hasActiveMediaOrRecommendation();
         boolean shouldBeCentered =
                 !mShouldUseSplitNotificationShade || !hasVisibleNotifications || mDozing;
         if (mStatusViewCentered != shouldBeCentered) {
@@ -2580,7 +2583,7 @@
         float endPosition = 0;
         if (pxAmount > 0.0f) {
             if (mNotificationStackScrollLayoutController.getVisibleNotificationCount() == 0
-                    && !mMediaDataManager.hasActiveMedia()) {
+                    && !mMediaDataManager.hasActiveMediaOrRecommendation()) {
                 // No notifications are visible, let's animate to the height of qs instead
                 if (mQs != null) {
                     // Let's interpolate to the header height instead of the top padding,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
index a6e567e..36e8370 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
@@ -156,7 +156,7 @@
         expected.forEach {
             clock.setCurrentTimeMillis(it.third)
             MediaPlayerData.addMediaPlayer(it.first, it.second.copy(notificationKey = it.first),
-                panel, clock)
+                panel, clock, isSsReactivated = false)
         }
 
         for ((index, key) in MediaPlayerData.playerKeys().withIndex()) {
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..06fa288 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
@@ -84,10 +84,10 @@
     public void eventNotEmittedWithoutDevice() {
         // WHEN data source emits an event without device data
         mManager.onMediaDataLoaded(KEY, null, mMediaData, true /* immediately */,
-                0 /* receivedSmartspaceCardLatency */);
+                0 /* receivedSmartspaceCardLatency */, false /* isSsReactivated */);
         // THEN an event isn't emitted
         verify(mListener, never()).onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(),
-                anyInt());
+                anyInt(), anyBoolean());
     }
 
     @Test
@@ -96,7 +96,7 @@
         mManager.onMediaDeviceChanged(KEY, null, mDeviceData);
         // THEN an event isn't emitted
         verify(mListener, never()).onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(),
-                anyInt());
+                anyInt(), anyBoolean());
     }
 
     @Test
@@ -105,11 +105,11 @@
         mManager.onMediaDeviceChanged(KEY, null, mDeviceData);
         // WHEN media event is received
         mManager.onMediaDataLoaded(KEY, null, mMediaData, true /* immediately */,
-                0 /* receivedSmartspaceCardLatency */);
+                0 /* receivedSmartspaceCardLatency */, false /* isSsReactivated */);
         // THEN the listener receives a combined event
         ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
         verify(mListener).onMediaDataLoaded(eq(KEY), any(), captor.capture(), anyBoolean(),
-                anyInt());
+                anyInt(), anyBoolean());
         assertThat(captor.getValue().getDevice()).isNotNull();
     }
 
@@ -117,13 +117,13 @@
     public void emitEventAfterMediaFirst() {
         // GIVEN that media event has already been received
         mManager.onMediaDataLoaded(KEY, null, mMediaData, true /* immediately */,
-                0 /* receivedSmartspaceCardLatency */);
+                0 /* receivedSmartspaceCardLatency */, false /* isSsReactivated */);
         // WHEN device event is received
         mManager.onMediaDeviceChanged(KEY, null, mDeviceData);
         // THEN the listener receives a combined event
         ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
         verify(mListener).onMediaDataLoaded(eq(KEY), any(), captor.capture(), anyBoolean(),
-                anyInt());
+                anyInt(), anyBoolean());
         assertThat(captor.getValue().getDevice()).isNotNull();
     }
 
@@ -131,16 +131,16 @@
     public void migrateKeyMediaFirst() {
         // GIVEN that media and device info has already been received
         mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData, true /* immediately */,
-                0 /* receivedSmartspaceCardLatency */);
+                0 /* receivedSmartspaceCardLatency */, false /* isSsReactivated */);
         mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
         reset(mListener);
         // WHEN a key migration event is received
         mManager.onMediaDataLoaded(KEY, OLD_KEY, mMediaData, true /* immediately */,
-                0 /* receivedSmartspaceCardLatency */);
+                0 /* receivedSmartspaceCardLatency */, false /* isSsReactivated */);
         // THEN the listener receives a combined event
         ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
         verify(mListener).onMediaDataLoaded(eq(KEY), eq(OLD_KEY), captor.capture(), anyBoolean(),
-                anyInt());
+                anyInt(), anyBoolean());
         assertThat(captor.getValue().getDevice()).isNotNull();
     }
 
@@ -148,7 +148,7 @@
     public void migrateKeyDeviceFirst() {
         // GIVEN that media and device info has already been received
         mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData, true /* immediately */,
-                0 /* receivedSmartspaceCardLatency */);
+                0 /* receivedSmartspaceCardLatency */, false /* isSsReactivated */);
         mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
         reset(mListener);
         // WHEN a key migration event is received
@@ -156,7 +156,7 @@
         // THEN the listener receives a combined event
         ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
         verify(mListener).onMediaDataLoaded(eq(KEY), eq(OLD_KEY), captor.capture(), anyBoolean(),
-                anyInt());
+                anyInt(), anyBoolean());
         assertThat(captor.getValue().getDevice()).isNotNull();
     }
 
@@ -164,17 +164,17 @@
     public void migrateKeyMediaAfter() {
         // GIVEN that media and device info has already been received
         mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData, true /* immediately */,
-                0 /* receivedSmartspaceCardLatency */);
+                0 /* receivedSmartspaceCardLatency */, false /* isSsReactivated */);
         mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
         mManager.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData);
         reset(mListener);
         // WHEN a second key migration event is received for media
         mManager.onMediaDataLoaded(KEY, OLD_KEY, mMediaData, true /* immediately */,
-                0 /* receivedSmartspaceCardLatency */);
+                0 /* receivedSmartspaceCardLatency */, false /* isSsReactivated */);
         // THEN the key has already been migrated
         ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
         verify(mListener).onMediaDataLoaded(eq(KEY), eq(KEY), captor.capture(), anyBoolean(),
-                anyInt());
+                anyInt(), anyBoolean());
         assertThat(captor.getValue().getDevice()).isNotNull();
     }
 
@@ -182,17 +182,17 @@
     public void migrateKeyDeviceAfter() {
         // GIVEN that media and device info has already been received
         mManager.onMediaDataLoaded(OLD_KEY, null, mMediaData, true /* immediately */,
-                0 /* receivedSmartspaceCardLatency */);
+                0 /* receivedSmartspaceCardLatency */, false /* isSsReactivated */);
         mManager.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
         mManager.onMediaDataLoaded(KEY, OLD_KEY, mMediaData, true /* immediately */,
-                0 /* receivedSmartspaceCardLatency */);
+                0 /* receivedSmartspaceCardLatency */, false /* isSsReactivated */);
         reset(mListener);
         // WHEN a second key migration event is received for the device
         mManager.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData);
         // THEN the key has already be migrated
         ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
         verify(mListener).onMediaDataLoaded(eq(KEY), eq(KEY), captor.capture(), anyBoolean(),
-                anyInt());
+                anyInt(), anyBoolean());
         assertThat(captor.getValue().getDevice()).isNotNull();
     }
 
@@ -207,7 +207,7 @@
     @Test
     public void mediaDataRemovedAfterMediaEvent() {
         mManager.onMediaDataLoaded(KEY, null, mMediaData, true /* immediately */,
-                0 /* receivedSmartspaceCardLatency */);
+                0 /* receivedSmartspaceCardLatency */, false /* isSsReactivated */);
         mManager.onMediaDataRemoved(KEY);
         verify(mListener).onMediaDataRemoved(eq(KEY));
     }
@@ -223,14 +223,14 @@
     public void mediaDataKeyUpdated() {
         // GIVEN that device and media events have already been received
         mManager.onMediaDataLoaded(KEY, null, mMediaData, true /* immediately */,
-                0 /* receivedSmartspaceCardLatency */);
+                0 /* receivedSmartspaceCardLatency */, false /* isSsReactivated */);
         mManager.onMediaDeviceChanged(KEY, null, mDeviceData);
         // WHEN the key is changed
         mManager.onMediaDataLoaded("NEW_KEY", KEY, mMediaData, true /* immediately */,
-                0 /* receivedSmartspaceCardLatency */);
+                0 /* receivedSmartspaceCardLatency */, false /* isSsReactivated */);
         // THEN the listener gets a load event with the correct keys
         ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
         verify(mListener).onMediaDataLoaded(
-                eq("NEW_KEY"), any(), captor.capture(), anyBoolean(), anyInt());
+                eq("NEW_KEY"), any(), captor.capture(), anyBoolean(), anyInt(), anyBoolean());
     }
 }
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..2ecf80e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
@@ -124,7 +124,7 @@
 
         // THEN we should tell the listener
         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataMain), eq(true),
-                eq(0))
+                eq(0), eq(false))
     }
 
     @Test
@@ -133,7 +133,8 @@
         mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
 
         // THEN we should NOT tell the listener
-        verify(listener, never()).onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt())
+        verify(listener, never()).onMediaDataLoaded(any(), any(), any(), anyBoolean(),
+                anyInt(), anyBoolean())
     }
 
     @Test
@@ -180,51 +181,56 @@
 
         // THEN we should add back the guest user media
         verify(listener).onMediaDataLoaded(eq(KEY_ALT), eq(null), eq(dataGuest), eq(true),
-                eq(0))
+                eq(0), eq(false))
 
         // but not the main user's
         verify(listener, never()).onMediaDataLoaded(eq(KEY), any(), eq(dataMain), anyBoolean(),
-                anyInt())
+                anyInt(), anyBoolean())
     }
 
     @Test
-    fun testHasAnyMedia() {
-        assertThat(mediaDataFilter.hasAnyMedia()).isFalse()
+    fun testHasAnyMediaOrRecommendation() {
+        assertThat(mediaDataFilter.hasAnyMediaOrRecommendation()).isFalse()
 
         mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain)
+        assertThat(mediaDataFilter.hasAnyMediaOrRecommendation()).isTrue()
         assertThat(mediaDataFilter.hasAnyMedia()).isTrue()
     }
 
     @Test
-    fun testHasActiveMedia() {
-        assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
+    fun testHasActiveMediaOrRecommendation() {
+        assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
         val data = dataMain.copy(active = true)
 
         mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data)
+        assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue()
         assertThat(mediaDataFilter.hasActiveMedia()).isTrue()
     }
 
     @Test
-    fun testHasAnyMedia_onlyCurrentUser() {
-        assertThat(mediaDataFilter.hasAnyMedia()).isFalse()
+    fun testHasAnyMediaOrRecommendation_onlyCurrentUser() {
+        assertThat(mediaDataFilter.hasAnyMediaOrRecommendation()).isFalse()
 
         mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataGuest)
+        assertThat(mediaDataFilter.hasAnyMediaOrRecommendation()).isFalse()
         assertThat(mediaDataFilter.hasAnyMedia()).isFalse()
     }
 
     @Test
-    fun testHasActiveMedia_onlyCurrentUser() {
-        assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
+    fun testHasActiveMediaOrRecommendation_onlyCurrentUser() {
+        assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
         val data = dataGuest.copy(active = true)
 
         mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data)
-        assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
+        assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
+        assertThat(mediaDataFilter.hasAnyMedia()).isFalse()
     }
 
     @Test
     fun testOnNotificationRemoved_doesntHaveMedia() {
         mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain)
         mediaDataFilter.onMediaDataRemoved(KEY)
+        assertThat(mediaDataFilter.hasAnyMediaOrRecommendation()).isFalse()
         assertThat(mediaDataFilter.hasAnyMedia()).isFalse()
     }
 
@@ -241,9 +247,9 @@
         mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
         verify(listener)
-                .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true),
-                        eq(false))
-        assertThat(mediaDataFilter.hasActiveMedia()).isTrue()
+                .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true))
+        assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue()
+        assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
     }
 
     @Test
@@ -252,9 +258,10 @@
 
         mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
-        verify(listener, never()).onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt())
-        verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean(),
-                anyBoolean())
+        verify(listener, never()).onMediaDataLoaded(any(), any(), any(), anyBoolean(),
+                anyInt(), anyBoolean())
+        verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
+        assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
         assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
     }
 
@@ -266,9 +273,9 @@
         mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
         verify(listener)
-                .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true),
-                        eq(true))
-        assertThat(mediaDataFilter.hasActiveMedia()).isTrue()
+                .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true))
+        assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue()
+        assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
     }
 
     @Test
@@ -280,8 +287,8 @@
         clock.advanceTime(SMARTSPACE_MAX_AGE + 100)
         mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
-        verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean(),
-                anyBoolean())
+        verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
+        assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
         assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
     }
 
@@ -293,16 +300,16 @@
         val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
         mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true),
-                eq(0))
+                eq(0), eq(false))
 
         // AND we get a smartspace signal
         mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
         // THEN we should tell listeners to treat the media as not active instead
         verify(listener, never()).onMediaDataLoaded(eq(KEY), eq(KEY), any(), anyBoolean(),
-                anyInt())
-        verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean(),
-                anyBoolean())
+                anyInt(), anyBoolean())
+        verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
+        assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
         assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
     }
 
@@ -314,7 +321,7 @@
         val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
         mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true),
-                eq(0))
+                eq(0), eq(false))
 
         // AND we get a smartspace signal
         mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
@@ -322,11 +329,10 @@
         // THEN we should tell listeners to treat the media as active instead
         val dataCurrentAndActive = dataCurrent.copy(active = true)
         verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), eq(dataCurrentAndActive), eq(true),
-                eq(100))
-        assertThat(mediaDataFilter.hasActiveMedia()).isTrue()
+                eq(100), eq(true))
+        assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
         // Smartspace update shouldn't be propagated for the empty rec list.
-        verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean(),
-                anyBoolean())
+        verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
     }
 
     @Test
@@ -335,7 +341,7 @@
         val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
         mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true),
-                eq(0))
+                eq(0), eq(false))
 
         // AND we get a smartspace signal
         mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
@@ -343,12 +349,11 @@
         // THEN we should tell listeners to treat the media as active instead
         val dataCurrentAndActive = dataCurrent.copy(active = true)
         verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), eq(dataCurrentAndActive), eq(true),
-                eq(100))
-        assertThat(mediaDataFilter.hasActiveMedia()).isTrue()
+                eq(100), eq(true))
+        assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue()
         // Smartspace update should also be propagated but not prioritized.
         verify(listener)
-                .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false),
-                        eq(true))
+                .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
     }
 
     @Test
@@ -357,6 +362,7 @@
         mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
 
         verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
+        assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
         assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
     }
 
@@ -365,17 +371,18 @@
         val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
         mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true),
-                eq(0))
+                eq(0), eq(false))
 
         mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
 
         val dataCurrentAndActive = dataCurrent.copy(active = true)
         verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), eq(dataCurrentAndActive), eq(true),
-                eq(100))
+                eq(100), eq(true))
 
         mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
 
         verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
+        assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
         assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
     }
 }
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..81e4182 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -214,7 +214,7 @@
         mediaDataManager.onNotificationAdded(KEY, mediaNotification)
         mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = mock(MediaData::class.java))
         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), anyObject(), eq(true),
-                eq(0))
+                eq(0), eq(false))
     }
 
     @Test
@@ -226,7 +226,7 @@
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
-                eq(0))
+                eq(0), eq(false))
         assertThat(mediaDataCaptor.value!!.active).isTrue()
     }
 
@@ -249,7 +249,7 @@
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
-                eq(0))
+                eq(0), eq(false))
         assertThat(mediaDataCaptor.value!!.playbackLocation).isEqualTo(
                 MediaData.PLAYBACK_CAST_REMOTE)
     }
@@ -270,7 +270,7 @@
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
-                eq(0))
+                eq(0), eq(false))
         val data = mediaDataCaptor.value
         assertThat(data.resumption).isFalse()
         mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
@@ -279,7 +279,7 @@
         // THEN the media data indicates that it is for resumption
         verify(listener)
             .onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor), eq(true),
-                    eq(0))
+                    eq(0), eq(false))
         assertThat(mediaDataCaptor.value.resumption).isTrue()
         assertThat(mediaDataCaptor.value.isPlaying).isFalse()
     }
@@ -294,7 +294,7 @@
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(2)
         verify(listener)
             .onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
-                    eq(0))
+                    eq(0), eq(false))
         val data = mediaDataCaptor.value
         assertThat(data.resumption).isFalse()
         val resumableData = data.copy(resumeAction = Runnable {})
@@ -306,7 +306,7 @@
         // THEN the data is for resumption and the key is migrated to the package name
         verify(listener)
             .onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor), eq(true),
-                    eq(0))
+                    eq(0), eq(false))
         assertThat(mediaDataCaptor.value.resumption).isTrue()
         verify(listener, never()).onMediaDataRemoved(eq(KEY))
         // WHEN the second is removed
@@ -315,7 +315,7 @@
         verify(listener)
             .onMediaDataLoaded(
                 eq(PACKAGE_NAME), eq(PACKAGE_NAME), capture(mediaDataCaptor), eq(true),
-                    eq(0))
+                    eq(0), eq(false))
         assertThat(mediaDataCaptor.value.resumption).isTrue()
         verify(listener).onMediaDataRemoved(eq(KEY_2))
     }
@@ -330,7 +330,7 @@
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
-                eq(0))
+                eq(0), eq(false))
         val data = mediaDataCaptor.value
         val dataRemoteWithResume = data.copy(resumeAction = Runnable {},
                 playbackLocation = MediaData.PLAYBACK_CAST_LOCAL)
@@ -358,7 +358,7 @@
         // THEN the media data indicates that it is for resumption
         verify(listener)
             .onMediaDataLoaded(eq(PACKAGE_NAME), eq(null), capture(mediaDataCaptor), eq(true),
-                    eq(0))
+                    eq(0), eq(false))
         val data = mediaDataCaptor.value
         assertThat(data.resumption).isTrue()
         assertThat(data.song).isEqualTo(SESSION_TITLE)
@@ -406,7 +406,7 @@
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
         verify(listener)
             .onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
-                    eq(0))
+                    eq(0), eq(false))
     }
 
     @Test
@@ -417,7 +417,7 @@
             eq(SmartspaceMediaData(KEY_MEDIA_SMARTSPACE, true /* isActive */, true /*isValid */,
                 PACKAGE_NAME, mediaSmartspaceBaseAction, listOf(mediaRecommendationItem),
                 DISMISS_INTENT, 0, 1234L)),
-            eq(false), eq(false))
+            eq(false))
     }
 
     @Test
@@ -430,7 +430,7 @@
                 .copy(targetId = KEY_MEDIA_SMARTSPACE, isActive = true,
                     isValid = false, dismissIntent = DISMISS_INTENT,
                 headphoneConnectionTimeMillis = 1234L)),
-            eq(false), eq(false))
+            eq(false))
     }
 
     @Test
@@ -450,14 +450,14 @@
             eq(EMPTY_SMARTSPACE_MEDIA_DATA
                 .copy(targetId = KEY_MEDIA_SMARTSPACE, isActive = true,
                     isValid = false, dismissIntent = null, headphoneConnectionTimeMillis = 1234L)),
-            eq(false), eq(false))
+            eq(false))
     }
 
     @Test
     fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_notCallsListener() {
         smartspaceMediaDataProvider.onTargetsAvailable(listOf())
         verify(listener, never())
-                .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean(), anyBoolean())
+                .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
     }
 
     @Test
@@ -481,7 +481,7 @@
 
         // THEN smartspace signal is ignored
         verify(listener, never())
-                .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean(), anyBoolean())
+                .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
     }
 
     @Test
@@ -489,7 +489,7 @@
         // GIVEN a media recommendation card is present
         smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
         verify(listener).onSmartspaceMediaDataLoaded(eq(KEY_MEDIA_SMARTSPACE), anyObject(),
-                anyBoolean(), anyBoolean())
+                anyBoolean())
 
         // WHEN the media recommendation setting is turned off
         Settings.Secure.putInt(context.contentResolver,
@@ -509,7 +509,7 @@
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
-                eq(0))
+                eq(0), eq(false))
         assertThat(mediaDataCaptor.value!!.lastActive).isAtLeast(currentTime)
     }
 
@@ -527,7 +527,7 @@
 
         // THEN the last active time is not changed
         verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), capture(mediaDataCaptor), eq(true),
-                eq(0))
+                eq(0), eq(false))
         assertThat(mediaDataCaptor.value.lastActive).isLessThan(currentTime)
     }
 
@@ -539,7 +539,7 @@
         assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
         assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
-                eq(0))
+                eq(0), eq(false))
         val data = mediaDataCaptor.value
         assertThat(data.resumption).isFalse()
         mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
@@ -552,7 +552,7 @@
         // THEN the last active time is not changed
         verify(listener)
             .onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor), eq(true),
-                    eq(0))
+                    eq(0), eq(false))
         assertThat(mediaDataCaptor.value.resumption).isTrue()
         assertThat(mediaDataCaptor.value.lastActive).isLessThan(currentTime)
     }
@@ -579,7 +579,7 @@
 
         // THEN only the first MAX_COMPACT_ACTIONS are actually set
         verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
-                eq(0))
+                eq(0), eq(false))
         assertThat(mediaDataCaptor.value.actionsToShowInCompact.size).isEqualTo(
                 MediaDataManager.MAX_COMPACT_ACTIONS)
     }
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..e92f6b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
@@ -61,8 +61,10 @@
         val playerIsRemote = mock(MediaControlPanel::class.java)
         val dataIsRemote = createMediaData("app2", PLAYING, REMOTE, !RESUMPTION)
 
-        MediaPlayerData.addMediaPlayer("2", dataIsRemote, playerIsRemote, systemClock)
-        MediaPlayerData.addMediaPlayer("1", dataIsPlaying, playerIsPlaying, systemClock)
+        MediaPlayerData.addMediaPlayer("2", dataIsRemote, playerIsRemote, systemClock,
+                isSsReactivated = false)
+        MediaPlayerData.addMediaPlayer("1", dataIsPlaying, playerIsPlaying, systemClock,
+                isSsReactivated = false)
 
         val players = MediaPlayerData.players()
         assertThat(players).hasSize(2)
@@ -77,18 +79,22 @@
         val playerIsPlaying2 = mock(MediaControlPanel::class.java)
         var dataIsPlaying2 = createMediaData("app2", !PLAYING, LOCAL, !RESUMPTION)
 
-        MediaPlayerData.addMediaPlayer("1", dataIsPlaying1, playerIsPlaying1, systemClock)
+        MediaPlayerData.addMediaPlayer("1", dataIsPlaying1, playerIsPlaying1, systemClock,
+                isSsReactivated = false)
         systemClock.advanceTime(1)
-        MediaPlayerData.addMediaPlayer("2", dataIsPlaying2, playerIsPlaying2, systemClock)
+        MediaPlayerData.addMediaPlayer("2", dataIsPlaying2, playerIsPlaying2, systemClock,
+                isSsReactivated = false)
         systemClock.advanceTime(1)
 
         dataIsPlaying1 = createMediaData("app1", !PLAYING, LOCAL, !RESUMPTION)
         dataIsPlaying2 = createMediaData("app2", PLAYING, LOCAL, !RESUMPTION)
 
-        MediaPlayerData.addMediaPlayer("1", dataIsPlaying1, playerIsPlaying1, systemClock)
+        MediaPlayerData.addMediaPlayer("1", dataIsPlaying1, playerIsPlaying1, systemClock,
+                isSsReactivated = false)
         systemClock.advanceTime(1)
 
-        MediaPlayerData.addMediaPlayer("2", dataIsPlaying2, playerIsPlaying2, systemClock)
+        MediaPlayerData.addMediaPlayer("2", dataIsPlaying2, playerIsPlaying2, systemClock,
+                isSsReactivated = false)
         systemClock.advanceTime(1)
 
         val players = MediaPlayerData.players()
@@ -116,14 +122,20 @@
         val dataUndetermined = createMediaData("app6", UNDETERMINED, LOCAL, RESUMPTION)
 
         MediaPlayerData.addMediaPlayer(
-                "3", dataIsStoppedAndLocal, playerIsStoppedAndLocal, systemClock)
+                "3", dataIsStoppedAndLocal, playerIsStoppedAndLocal, systemClock,
+                isSsReactivated = false)
         MediaPlayerData.addMediaPlayer(
-                "5", dataIsStoppedAndRemote, playerIsStoppedAndRemote, systemClock)
-        MediaPlayerData.addMediaPlayer("4", dataCanResume, playerCanResume, systemClock)
-        MediaPlayerData.addMediaPlayer("1", dataIsPlaying, playerIsPlaying, systemClock)
+                "5", dataIsStoppedAndRemote, playerIsStoppedAndRemote, systemClock,
+                isSsReactivated = false)
+        MediaPlayerData.addMediaPlayer("4", dataCanResume, playerCanResume, systemClock,
+                isSsReactivated = false)
+        MediaPlayerData.addMediaPlayer("1", dataIsPlaying, playerIsPlaying, systemClock,
+                isSsReactivated = false)
         MediaPlayerData.addMediaPlayer(
-                "2", dataIsPlayingAndRemote, playerIsPlayingAndRemote, systemClock)
-        MediaPlayerData.addMediaPlayer("6", dataUndetermined, playerUndetermined, systemClock)
+                "2", dataIsPlayingAndRemote, playerIsPlayingAndRemote, systemClock,
+                isSsReactivated = false)
+        MediaPlayerData.addMediaPlayer("6", dataUndetermined, playerUndetermined, systemClock,
+                isSsReactivated = false)
 
         val players = MediaPlayerData.players()
         assertThat(players).hasSize(6)
@@ -141,11 +153,13 @@
 
         assertThat(MediaPlayerData.players()).hasSize(0)
 
-        MediaPlayerData.addMediaPlayer(keyA, data, playerIsPlaying, systemClock)
+        MediaPlayerData.addMediaPlayer(keyA, data, playerIsPlaying, systemClock,
+                isSsReactivated = false)
         systemClock.advanceTime(1)
 
         assertThat(MediaPlayerData.players()).hasSize(1)
-        MediaPlayerData.addMediaPlayer(keyB, data, playerIsPlaying, systemClock)
+        MediaPlayerData.addMediaPlayer(keyB, data, playerIsPlaying, systemClock,
+                isSsReactivated = false)
         systemClock.advanceTime(1)
 
         assertThat(MediaPlayerData.players()).hasSize(2)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt
index 5d53181..983f749 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt
@@ -187,7 +187,7 @@
         bgExecutor.runAllReady()
         fgExecutor.runAllReady()
         verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
-                eq(0))
+                eq(0), eq(false))
     }
 
     @Test
@@ -210,7 +210,7 @@
         fgExecutor.runAllReady()
         // THEN the event is not filtered
         verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
-                eq(0))
+                eq(0), eq(false))
     }
 
     @Test
@@ -240,7 +240,7 @@
         fgExecutor.runAllReady()
         // THEN the event is not filtered
         verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
-                eq(0))
+                eq(0), eq(false))
     }
 
     @Test
@@ -256,14 +256,14 @@
         fgExecutor.runAllReady()
         // THEN the event is not filtered
         verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
-                eq(0))
+                eq(0), eq(false))
         // WHEN a loaded event is received that matches the local session
         filter.onMediaDataLoaded(KEY, null, mediaData2)
         bgExecutor.runAllReady()
         fgExecutor.runAllReady()
         // THEN the event is filtered
         verify(mediaListener, never()).onMediaDataLoaded(
-            eq(KEY), eq(null), eq(mediaData2), anyBoolean(), anyInt())
+            eq(KEY), eq(null), eq(mediaData2), anyBoolean(), anyInt(), anyBoolean())
     }
 
     @Test
@@ -280,7 +280,7 @@
         // THEN the event is not filtered because there isn't a notification for the remote
         // session.
         verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
-                eq(0))
+                eq(0), eq(false))
     }
 
     @Test
@@ -298,14 +298,15 @@
         fgExecutor.runAllReady()
         // THEN the event is not filtered
         verify(mediaListener).onMediaDataLoaded(eq(key1), eq(null), eq(mediaData1), eq(true),
-                eq(0))
+                eq(0), eq(false))
         // WHEN a loaded event is received that matches the local session
         filter.onMediaDataLoaded(key2, null, mediaData2)
         bgExecutor.runAllReady()
         fgExecutor.runAllReady()
         // THEN the event is filtered
         verify(mediaListener, never())
-            .onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2), anyBoolean(), anyInt())
+            .onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2), anyBoolean(),
+                    anyInt(), anyBoolean())
         // AND there should be a removed event for key2
         verify(mediaListener).onMediaDataRemoved(eq(key2))
     }
@@ -325,14 +326,14 @@
         fgExecutor.runAllReady()
         // THEN the event is not filtered
         verify(mediaListener).onMediaDataLoaded(eq(key1), eq(null), eq(mediaData1), eq(true),
-                eq(0))
+                eq(0), eq(false))
         // WHEN a loaded event is received that matches the remote session
         filter.onMediaDataLoaded(key2, null, mediaData2)
         bgExecutor.runAllReady()
         fgExecutor.runAllReady()
         // THEN the event is not filtered
         verify(mediaListener).onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2), eq(true),
-                eq(0))
+                eq(0), eq(false))
     }
 
     @Test
@@ -349,14 +350,14 @@
         fgExecutor.runAllReady()
         // THEN the event is not filtered
         verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
-                eq(0))
+                eq(0), eq(false))
         // WHEN a loaded event is received that matches the local session
         filter.onMediaDataLoaded(KEY, null, mediaData2)
         bgExecutor.runAllReady()
         fgExecutor.runAllReady()
         // THEN the event is not filtered
         verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData2), eq(true),
-                eq(0))
+                eq(0), eq(false))
     }
 
     @Test
@@ -375,7 +376,7 @@
         fgExecutor.runAllReady()
         // THEN the event is not filtered
         verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
-                eq(0))
+                eq(0), eq(false))
     }
 
     @Test
@@ -398,7 +399,7 @@
         fgExecutor.runAllReady()
         // THEN the key migration event is fired
         verify(mediaListener).onMediaDataLoaded(eq(key2), eq(key1), eq(mediaData2), eq(true),
-                eq(0))
+                eq(0), eq(false))
     }
 
     @Test
@@ -428,13 +429,14 @@
         fgExecutor.runAllReady()
         // THEN the key migration event is filtered
         verify(mediaListener, never())
-            .onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2), anyBoolean(), anyInt())
+            .onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2), anyBoolean(),
+                    anyInt(), anyBoolean())
         // WHEN a loaded event is received that matches the remote session
         filter.onMediaDataLoaded(key2, null, mediaData1)
         bgExecutor.runAllReady()
         fgExecutor.runAllReady()
         // THEN the key migration event is fired
         verify(mediaListener).onMediaDataLoaded(eq(key2), eq(null), eq(mediaData1), eq(true),
-                eq(0))
+                eq(0), eq(false))
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index 5eaa60a..3fc6037 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
@@ -829,7 +829,7 @@
     public void testSwitchesToBigClockInSplitShadeOnAod() {
         mStatusBarStateController.setState(KEYGUARD);
         enableSplitShade(/* enabled= */ true);
-        when(mMediaDataManager.hasActiveMedia()).thenReturn(true);
+        when(mMediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true);
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
 
         mNotificationPanelViewController.setDozing(true, false, null);
@@ -841,7 +841,7 @@
     public void testDisplaysSmallClockOnLockscreenInSplitShadeWhenMediaIsPlaying() {
         mStatusBarStateController.setState(KEYGUARD);
         enableSplitShade(/* enabled= */ true);
-        when(mMediaDataManager.hasActiveMedia()).thenReturn(true);
+        when(mMediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true);
 
         // one notification + media player visible
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
diff --git a/services/core/java/com/android/server/FactoryResetter.java b/services/core/java/com/android/server/FactoryResetter.java
new file mode 100644
index 0000000..30314a3
--- /dev/null
+++ b/services/core/java/com/android/server/FactoryResetter.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * TODO(b/225012970): add javadoc from {@code com.android.server.devicepolicy.FactoryResetter}
+ */
+public final class FactoryResetter {
+
+    private static final AtomicBoolean sFactoryResetting = new AtomicBoolean(false);
+
+    /**
+     * Checks whether a factory reset is in progress.
+     */
+    public static boolean isFactoryResetting() {
+        return sFactoryResetting.get();
+    }
+
+    /**
+     * @deprecated called by {@code com.android.server.devicepolicy.FactoryResetter}, won't be
+     * needed once that class logic is moved into this.
+     */
+    @Deprecated
+    public static void setFactoryResetting(Context context) {
+        Preconditions.checkCallAuthorization(context.checkCallingOrSelfPermission(
+                android.Manifest.permission.MASTER_CLEAR) == PackageManager.PERMISSION_GRANTED);
+        sFactoryResetting.set(true);
+    }
+
+    private FactoryResetter() {
+        throw new UnsupportedOperationException("Provides only static methods");
+    }
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 8e72f0f..dc393b6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -16318,8 +16318,8 @@
         }
 
         @Override
-        public void deletePendingTopUid(int uid) {
-            mPendingStartActivityUids.delete(uid);
+        public void deletePendingTopUid(int uid, long nowElapsed) {
+            mPendingStartActivityUids.delete(uid, nowElapsed);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 96ea6db..9e04410 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1378,7 +1378,7 @@
                     mService.mServices.foregroundServiceProcStateChangedLocked(uidRec);
                 }
             }
-            mService.mInternal.deletePendingTopUid(uidRec.getUid());
+            mService.mInternal.deletePendingTopUid(uidRec.getUid(), nowElapsed);
         }
         if (mLocalPowerManager != null) {
             mLocalPowerManager.finishUidChanges();
diff --git a/services/core/java/com/android/server/am/PendingStartActivityUids.java b/services/core/java/com/android/server/am/PendingStartActivityUids.java
index 6bf9d4e..20f6bb2 100644
--- a/services/core/java/com/android/server/am/PendingStartActivityUids.java
+++ b/services/core/java/com/android/server/am/PendingStartActivityUids.java
@@ -50,9 +50,15 @@
         }
     }
 
-    synchronized void delete(int uid) {
+    synchronized void delete(int uid, long nowElapsed) {
         final Pair<Integer, Long> pendingPid = mPendingUids.get(uid);
         if (pendingPid != null) {
+            if (nowElapsed < pendingPid.second) {
+                Slog.i(TAG,
+                        "updateOomAdj start time is before than pendingPid added,"
+                        + " don't delete it");
+                return;
+            }
             final long delay = SystemClock.elapsedRealtime() - pendingPid.second;
             if (delay >= 1000 /*ms*/) {
                 Slog.i(TAG,
@@ -75,4 +81,4 @@
     synchronized boolean isPendingTopUid(int uid) {
         return mPendingUids.get(uid) != null;
     }
-}
\ No newline at end of file
+}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 5a43f4d..c465be1 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -106,6 +106,7 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.server.FactoryResetter;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemServiceManager;
@@ -1756,6 +1757,10 @@
             Slogf.w(TAG, "Cannot switch to User #" + targetUserId + ": not a full user");
             return false;
         }
+        if (FactoryResetter.isFactoryResetting()) {
+            Slogf.w(TAG, "Cannot switch to User #" + targetUserId + ": factory reset in progress");
+            return false;
+        }
         boolean userSwitchUiEnabled;
         synchronized (mLock) {
             if (!mInitialized) {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 470aa0e..67b4469 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -4127,7 +4127,7 @@
         if (mRestrictedNetworkingMode) {
             // Note: setUidFirewallRule also updates mUidFirewallRestrictedModeRules.
             // In this case, default firewall rules can also be added.
-            setUidFirewallRule(FIREWALL_CHAIN_RESTRICTED, uid,
+            setUidFirewallRuleUL(FIREWALL_CHAIN_RESTRICTED, uid,
                     getRestrictedModeFirewallRule(uidBlockedState));
         }
     }
@@ -4291,9 +4291,9 @@
             final boolean isWhitelisted = isWhitelistedFromPowerSaveUL(uid,
                     chain == FIREWALL_CHAIN_DOZABLE);
             if (isWhitelisted || isUidForegroundOnRestrictPowerUL(uid)) {
-                setUidFirewallRule(chain, uid, FIREWALL_RULE_ALLOW);
+                setUidFirewallRuleUL(chain, uid, FIREWALL_RULE_ALLOW);
             } else {
-                setUidFirewallRule(chain, uid, FIREWALL_RULE_DEFAULT);
+                setUidFirewallRuleUL(chain, uid, FIREWALL_RULE_DEFAULT);
             }
         }
     }
@@ -4339,10 +4339,10 @@
             int appId = UserHandle.getAppId(uid);
             if (!mPowerSaveTempWhitelistAppIds.get(appId) && isUidIdle(uid)
                     && !isUidForegroundOnRestrictPowerUL(uid)) {
-                setUidFirewallRule(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DENY);
+                setUidFirewallRuleUL(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DENY);
                 if (LOGD) Log.d(TAG, "updateRuleForAppIdleUL DENY " + uid);
             } else {
-                setUidFirewallRule(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DEFAULT);
+                setUidFirewallRuleUL(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DEFAULT);
                 if (LOGD) Log.d(TAG, "updateRuleForAppIdleUL " + uid + " to DEFAULT");
             }
         } finally {
@@ -5406,10 +5406,11 @@
     /**
      * Add or remove a uid to the firewall denylist for all network ifaces.
      */
-    private void setUidFirewallRule(int chain, int uid, int rule) {
+    @GuardedBy("mUidRulesFirstLock")
+    private void setUidFirewallRuleUL(int chain, int uid, int rule) {
         if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
             Trace.traceBegin(Trace.TRACE_TAG_NETWORK,
-                    "setUidFirewallRule: " + chain + "/" + uid + "/" + rule);
+                    "setUidFirewallRuleUL: " + chain + "/" + uid + "/" + rule);
         }
         try {
             if (chain == FIREWALL_CHAIN_DOZABLE) {
diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
index 30e2617..092853f 100644
--- a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
+++ b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
@@ -39,7 +39,7 @@
 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
 import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyManager;
-import android.telephony.TelephonyManager.CarrierPrivilegesListener;
+import android.telephony.TelephonyManager.CarrierPrivilegesCallback;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
@@ -98,8 +98,7 @@
     @NonNull private final OnSubscriptionsChangedListener mSubscriptionChangedListener;
 
     @NonNull
-    private final List<CarrierPrivilegesListener> mCarrierPrivilegesChangedListeners =
-            new ArrayList<>();
+    private final List<CarrierPrivilegesCallback> mCarrierPrivilegesCallbacks = new ArrayList<>();
 
     @NonNull private TelephonySubscriptionSnapshot mCurrentSnapshot;
 
@@ -151,20 +150,21 @@
                 executor, mSubscriptionChangedListener);
         mTelephonyManager.registerTelephonyCallback(executor, mActiveDataSubIdListener);
 
-        registerCarrierPrivilegesListeners();
+        registerCarrierPrivilegesCallbacks();
     }
 
-    private void registerCarrierPrivilegesListeners() {
+    // TODO(b/221306368): Refactor with the new onCarrierServiceChange in the new CPCallback
+    private void registerCarrierPrivilegesCallbacks() {
         final HandlerExecutor executor = new HandlerExecutor(mHandler);
         final int modemCount = mTelephonyManager.getActiveModemCount();
         try {
             for (int i = 0; i < modemCount; i++) {
-                CarrierPrivilegesListener carrierPrivilegesListener =
-                        new CarrierPrivilegesListener() {
+                CarrierPrivilegesCallback carrierPrivilegesCallback =
+                        new CarrierPrivilegesCallback() {
                             @Override
                             public void onCarrierPrivilegesChanged(
-                                    @NonNull List<String> privilegedPackageNames,
-                                    @NonNull int[] privilegedUids) {
+                                    @NonNull Set<String> privilegedPackageNames,
+                                    @NonNull Set<Integer> privilegedUids) {
                                 // Re-trigger the synchronous check (which is also very cheap due
                                 // to caching in CarrierPrivilegesTracker). This allows consistency
                                 // with the onSubscriptionsChangedListener and broadcasts.
@@ -172,9 +172,9 @@
                             }
                         };
 
-                mTelephonyManager.addCarrierPrivilegesListener(
-                        i, executor, carrierPrivilegesListener);
-                mCarrierPrivilegesChangedListeners.add(carrierPrivilegesListener);
+                mTelephonyManager.registerCarrierPrivilegesCallback(
+                        i, executor, carrierPrivilegesCallback);
+                mCarrierPrivilegesCallbacks.add(carrierPrivilegesCallback);
             }
         } catch (IllegalArgumentException e) {
             Slog.wtf(TAG, "Encounted exception registering carrier privileges listeners", e);
@@ -191,15 +191,15 @@
         mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionChangedListener);
         mTelephonyManager.unregisterTelephonyCallback(mActiveDataSubIdListener);
 
-        unregisterCarrierPrivilegesListeners();
+        unregisterCarrierPrivilegesCallbacks();
     }
 
-    private void unregisterCarrierPrivilegesListeners() {
-        for (CarrierPrivilegesListener carrierPrivilegesListener :
-                mCarrierPrivilegesChangedListeners) {
-            mTelephonyManager.removeCarrierPrivilegesListener(carrierPrivilegesListener);
+    private void unregisterCarrierPrivilegesCallbacks() {
+        for (CarrierPrivilegesCallback carrierPrivilegesCallback :
+                mCarrierPrivilegesCallbacks) {
+            mTelephonyManager.unregisterCarrierPrivilegesCallback(carrierPrivilegesCallback);
         }
-        mCarrierPrivilegesChangedListeners.clear();
+        mCarrierPrivilegesCallbacks.clear();
     }
 
     /**
@@ -283,7 +283,7 @@
     }
 
     private void handleActionMultiSimConfigChanged(Context context, Intent intent) {
-        unregisterCarrierPrivilegesListeners();
+        unregisterCarrierPrivilegesCallbacks();
 
         // Clear invalid slotIds from the mReadySubIdsBySlotId map.
         final int modemCount = mTelephonyManager.getActiveModemCount();
@@ -296,7 +296,7 @@
             }
         }
 
-        registerCarrierPrivilegesListeners();
+        registerCarrierPrivilegesCallbacks();
         handleSubscriptionsChanged();
     }
 
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index d69d32e..9e87a17 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -967,6 +967,26 @@
                 break;
         }
 
+        if (LayoutParams.isSystemAlertWindowType(attrs.type)) {
+            float maxOpacity = mService.mMaximumObscuringOpacityForTouch;
+            if (attrs.alpha > maxOpacity
+                    && (attrs.flags & FLAG_NOT_TOUCHABLE) != 0
+                    && (attrs.privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) == 0) {
+                // The app is posting a SAW with the intent of letting touches pass through, but
+                // they are going to be deemed untrusted and will be blocked. Try to honor the
+                // intent of letting touches pass through at the cost of 0.2 opacity for app
+                // compatibility reasons. More details on b/218777508.
+                Slog.w(TAG, String.format(
+                        "App %s has a system alert window (type = %d) with FLAG_NOT_TOUCHABLE and "
+                                + "LayoutParams.alpha = %.2f > %.2f, setting alpha to %.2f to "
+                                + "let touches pass through (if this is isn't desirable, remove "
+                                + "flag FLAG_NOT_TOUCHABLE).",
+                        attrs.packageName, attrs.type, attrs.alpha, maxOpacity, maxOpacity));
+                attrs.alpha = maxOpacity;
+                win.mWinAnimator.mAlpha = maxOpacity;
+            }
+        }
+
         // Check if alternate bars positions were updated.
         if (mStatusBarAlt == win) {
             mStatusBarAltPosition = getAltBarPosition(attrs);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 575ae691..8839fba 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -769,6 +769,9 @@
 
     private final DisplayHashController mDisplayHashController;
 
+    volatile float mMaximumObscuringOpacityForTouch =
+            InputManager.DEFAULT_MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH;
+
     @VisibleForTesting
     final WindowContextListenerController mWindowContextListenerController =
             new WindowContextListenerController();
@@ -801,6 +804,8 @@
                 DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR);
         private final Uri mDisplaySettingsPathUri = Settings.Global.getUriFor(
                 DEVELOPMENT_WM_DISPLAY_SETTINGS_PATH);
+        private final Uri mMaximumObscuringOpacityForTouchUri = Settings.Global.getUriFor(
+                Settings.Global.MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH);
 
         public SettingsObserver() {
             super(new Handler());
@@ -827,6 +832,8 @@
                     UserHandle.USER_ALL);
             resolver.registerContentObserver(mDisplaySettingsPathUri, false, this,
                     UserHandle.USER_ALL);
+            resolver.registerContentObserver(mMaximumObscuringOpacityForTouchUri, false, this,
+                    UserHandle.USER_ALL);
         }
 
         @Override
@@ -875,6 +882,11 @@
                 return;
             }
 
+            if (mMaximumObscuringOpacityForTouchUri.equals(uri)) {
+                updateMaximumObscuringOpacityForTouch();
+                return;
+            }
+
             @UpdateAnimationScaleMode
             final int mode;
             if (mWindowAnimationScaleUri.equals(uri)) {
@@ -894,6 +906,14 @@
         void loadSettings() {
             updateSystemUiSettings(false /* handleChange */);
             updatePointerLocation();
+            updateMaximumObscuringOpacityForTouch();
+        }
+
+        void updateMaximumObscuringOpacityForTouch() {
+            ContentResolver resolver = mContext.getContentResolver();
+            mMaximumObscuringOpacityForTouch = Settings.Global.getFloat(resolver,
+                    Settings.Global.MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH,
+                    InputManager.DEFAULT_MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH);
         }
 
         void updateSystemUiSettings(boolean handleChange) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/FactoryResetter.java b/services/devicepolicy/java/com/android/server/devicepolicy/FactoryResetter.java
index 964be38..c72e1ea 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/FactoryResetter.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/FactoryResetter.java
@@ -16,6 +16,8 @@
 
 package com.android.server.devicepolicy;
 
+import static com.android.server.FactoryResetter.setFactoryResetting;
+
 import android.annotation.Nullable;
 import android.app.admin.DevicePolicySafetyChecker;
 import android.content.Context;
@@ -36,7 +38,10 @@
 
 /**
  * Entry point for "factory reset" requests.
+ *
+ * @deprecated TODO(b/225012970): should be moved to {@code com.android.server.FactoryResetter}
  */
+@Deprecated
 public final class FactoryResetter {
 
     private static final String TAG = FactoryResetter.class.getSimpleName();
@@ -60,6 +65,8 @@
         Preconditions.checkCallAuthorization(mContext.checkCallingOrSelfPermission(
                 android.Manifest.permission.MASTER_CLEAR) == PackageManager.PERMISSION_GRANTED);
 
+        setFactoryResetting(mContext);
+
         if (mSafetyChecker == null) {
             factoryResetInternalUnchecked();
             return true;
diff --git a/services/tests/mockingservicestests/src/com/android/server/devicepolicy/FactoryResetterTest.java b/services/tests/mockingservicestests/src/com/android/server/devicepolicy/FactoryResetterTest.java
index 457c8db..4ffa0fb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/devicepolicy/FactoryResetterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/devicepolicy/FactoryResetterTest.java
@@ -14,11 +14,13 @@
  * limitations under the License.
  */
 
+// TODO(b/225012970): should be moved to com.android.server
 package com.android.server.devicepolicy;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.FactoryResetter.isFactoryResetting;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -165,6 +167,7 @@
                 .factoryReset();
 
         assertThat(success).isTrue();
+        assertThat(isFactoryResetting()).isTrue();
         verifyWipeAdoptableStorageCalled();
         verifyWipeFactoryResetProtectionNotCalled();
         verifyRebootWipeUserDataMinimumArgsCalled();
@@ -179,6 +182,7 @@
                 .build().factoryReset();
 
         assertThat(success).isTrue();
+        assertThat(isFactoryResetting()).isTrue();
         verifyWipeAdoptableStorageNotCalled();
         verifyWipeFactoryResetProtectionCalled();
         verifyRebootWipeUserDataMinimumArgsCalled();
@@ -198,6 +202,7 @@
                 .build().factoryReset();
 
         assertThat(success).isTrue();
+        assertThat(isFactoryResetting()).isTrue();
         verifyWipeAdoptableStorageCalled();
         verifyWipeFactoryResetProtectionCalled();
         verifyRebootWipeUserDataAllArgsCalled();
@@ -211,6 +216,7 @@
                 .setSafetyChecker(mSafetyChecker).build().factoryReset();
 
         assertThat(success).isFalse();
+        assertThat(isFactoryResetting()).isTrue();
         verifyWipeAdoptableStorageNotCalled();
         verifyWipeFactoryResetProtectionNotCalled();
         verifyRebootWipeUserDataNotCalled();
@@ -238,6 +244,7 @@
                 .build().factoryReset();
 
         assertThat(success).isFalse();
+        assertThat(isFactoryResetting()).isTrue();
         verifyWipeAdoptableStorageCalled();
         verifyWipeFactoryResetProtectionCalled();
         verifyRebootWipeUserDataAllArgsCalled();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
index 66da2a6..716612c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
@@ -284,7 +284,7 @@
                 .setCreateActivity(true).build().getTopMostActivity();
         activity2.getTask().setResumedActivity(activity2, "test");
 
-        mAtm.mAmInternal.deletePendingTopUid(activity1.getUid());
+        mAtm.mAmInternal.deletePendingTopUid(activity1.getUid(), Long.MAX_VALUE);
         clearInvocations(mAtm);
         activity1.moveFocusableActivityToTop("test");
         assertTrue(mAtm.mAmInternal.isPendingTopUid(activity1.getUid()));
diff --git a/telephony/java/android/service/euicc/EuiccService.java b/telephony/java/android/service/euicc/EuiccService.java
index fcbb008..7d857a2 100644
--- a/telephony/java/android/service/euicc/EuiccService.java
+++ b/telephony/java/android/service/euicc/EuiccService.java
@@ -255,6 +255,12 @@
     public static final String EXTRA_RESOLUTION_CARD_ID =
             "android.service.euicc.extra.RESOLUTION_CARD_ID";
 
+    /**
+     * Intent extra set for resolution requests containing an int indicating the current port index.
+     */
+    public static final String EXTRA_RESOLUTION_PORT_INDEX =
+            "android.service.euicc.extra.RESOLUTION_PORT_INDEX";
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = { "RESULT_" }, value = {
@@ -579,9 +585,32 @@
      * @return the result of the switch operation. May be one of the predefined {@code RESULT_}
      *     constants or any implementation-specific code starting with {@link #RESULT_FIRST_USER}.
      * @see android.telephony.euicc.EuiccManager#switchToSubscription
+     *
+     * @deprecated prefer {@link #onSwitchToSubscriptionWithPort(int, int, String, boolean)}
      */
-    public abstract @Result int onSwitchToSubscription(int slotId, @Nullable String iccid,
-            boolean forceDeactivateSim);
+    @Deprecated public abstract @Result int onSwitchToSubscription(int slotId,
+            @Nullable String iccid, boolean forceDeactivateSim);
+
+    /**
+     * Switch to the given subscription.
+     *
+     * @param slotId ID of the SIM slot to use for the operation.
+     * @param portIndex which port on the eUICC to use
+     * @param iccid the ICCID of the subscription to enable. May be null, in which case the current
+     *     profile should be deactivated and no profile should be activated to replace it - this is
+     *     equivalent to a physical SIM being ejected.
+     * @param forceDeactivateSim If true, and if an active SIM must be deactivated to access the
+     *     eUICC, perform this action automatically. Otherwise, {@link #RESULT_MUST_DEACTIVATE_SIM}
+     *     should be returned to allow the user to consent to this operation first.
+     * @return the result of the switch operation. May be one of the predefined {@code RESULT_}
+     *     constants or any implementation-specific code starting with {@link #RESULT_FIRST_USER}.
+     * @see android.telephony.euicc.EuiccManager#switchToSubscription
+     */
+    public @Result int onSwitchToSubscriptionWithPort(int slotId, int portIndex,
+            @Nullable String iccid, boolean forceDeactivateSim) {
+        // stub implementation, LPA needs to implement this
+        throw new UnsupportedOperationException("LPA must override onSwitchToSubscriptionWithPort");
+    }
 
     /**
      * Update the nickname of the given subscription.
@@ -821,16 +850,15 @@
                 }
             });
         }
-
         @Override
-        public void switchToSubscription(int slotId, String iccid, boolean forceDeactivateSim,
-                ISwitchToSubscriptionCallback callback) {
+        public void switchToSubscription(int slotId, int portIndex, String iccid,
+                boolean forceDeactivateSim, ISwitchToSubscriptionCallback callback) {
             mExecutor.execute(new Runnable() {
                 @Override
                 public void run() {
                     int result =
-                            EuiccService.this.onSwitchToSubscription(
-                                    slotId, iccid, forceDeactivateSim);
+                            EuiccService.this.onSwitchToSubscriptionWithPort(
+                                    slotId, portIndex, iccid, forceDeactivateSim);
                     try {
                         callback.onComplete(result);
                     } catch (RemoteException e) {
diff --git a/telephony/java/android/service/euicc/IEuiccService.aidl b/telephony/java/android/service/euicc/IEuiccService.aidl
index bb7b569..aa30c9e 100644
--- a/telephony/java/android/service/euicc/IEuiccService.aidl
+++ b/telephony/java/android/service/euicc/IEuiccService.aidl
@@ -48,7 +48,7 @@
             in IGetDefaultDownloadableSubscriptionListCallback callback);
     void getEuiccInfo(int slotId, in IGetEuiccInfoCallback callback);
     void deleteSubscription(int slotId, String iccid, in IDeleteSubscriptionCallback callback);
-    void switchToSubscription(int slotId, String iccid, boolean forceDeactivateSim,
+    void switchToSubscription(int slotId, int portIndex, String iccid, boolean forceDeactivateSim,
             in ISwitchToSubscriptionCallback callback);
     void updateSubscriptionNickname(int slotId, String iccid, String nickname,
             in IUpdateSubscriptionNicknameCallback callback);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 47dfb42..cdba635 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -6776,10 +6776,12 @@
      * @param p2 P2 parameter (described in ISO 7816-4).
      * @return an IccOpenLogicalChannelResponse object.
      * @hide
+     * @deprecated instead use {@link #iccOpenLogicalChannelByPort(int, int, String, int)}
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     @SystemApi
     @Nullable
+    @Deprecated
     public IccOpenLogicalChannelResponse iccOpenLogicalChannelBySlot(int slotIndex,
             @Nullable String aid, int p2) {
         try {
@@ -6800,6 +6802,58 @@
     }
 
     /**
+     * Opens a logical channel to the ICC card using the physical slot index and port index.
+     *
+     * Use this method when no subscriptions are available on the SIM and the operation must be
+     * performed using the physical slot index and port index.
+     *
+     * This operation wraps two APDU instructions:
+     * <ul>
+     *     <li>MANAGE CHANNEL to open a logical channel</li>
+     *     <li>SELECT the given {@code AID} using the given {@code p2}</li>
+     * </ul>
+     *
+     * Per Open Mobile API Specification v3.2 section 6.2.7.h, only p2 values of 0x00, 0x04, 0x08,
+     * and 0x0C are guaranteed to be supported.
+     *
+     * If the SELECT command's status word is not '9000', '62xx', or '63xx', the status word will be
+     * considered an error and the channel shall not be opened.
+     *
+     * Input parameters equivalent to TS 27.007 AT+CCHO command.
+     *
+     * @param slotIndex the physical slot index of the ICC card
+     * @param portIndex The port index is an enumeration of the ports available on the UICC.
+     *                  Use {@link UiccPortInfo#getPortIndex()} to get portIndex.
+     * @param aid Application id. See ETSI 102.221 and 101.220.
+     * @param p2 P2 parameter (described in ISO 7816-4).
+     * @return an IccOpenLogicalChannelResponse object.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    @SystemApi
+    @NonNull
+    public IccOpenLogicalChannelResponse iccOpenLogicalChannelByPort(int slotIndex,
+            int portIndex, @Nullable String aid, int p2) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                IccLogicalChannelRequest request = new IccLogicalChannelRequest();
+                request.slotIndex = slotIndex;
+                request.portIndex = portIndex;
+                request.aid = aid;
+                request.p2 = p2;
+                request.callingPackage = getOpPackageName();
+                request.binder = new Binder();
+                return telephony.iccOpenLogicalChannel(request);
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            throw ex.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
      * Opens a logical channel to the ICC card.
      *
      * This operation wraps two APDU instructions:
@@ -6889,9 +6943,11 @@
      *            iccOpenLogicalChannel.
      * @return true if the channel was closed successfully.
      * @hide
+     * @deprecated instead use {@link #iccCloseLogicalChannelByPort(int, int, int)}
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     @SystemApi
+    @Deprecated
     public boolean iccCloseLogicalChannelBySlot(int slotIndex, int channel) {
         try {
             ITelephony telephony = getITelephony();
@@ -6908,6 +6964,45 @@
     }
 
     /**
+     * Closes a previously opened logical channel to the ICC card using the physical slot index and
+     * port index.
+     *
+     * Use this method when no subscriptions are available on the SIM and the operation must be
+     * performed using the physical slot index and port index.
+     *
+     * Input parameters equivalent to TS 27.007 AT+CCHC command.
+     *
+     * @param slotIndex the physical slot index of the ICC card
+     * @param portIndex The port index is an enumeration of the ports available on the UICC.
+     *                  Use {@link UiccPortInfo#getPortIndex()} to get portIndex.
+     * @param channel is the channel id to be closed as returned by a successful
+     *            iccOpenLogicalChannel.
+     *
+     * @throws IllegalStateException if the Telephony process is not currently available or modem
+     *                               currently can't process this command.
+     * @throws IllegalArgumentException if invalid arguments are passed.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    @SystemApi
+    public void iccCloseLogicalChannelByPort(int slotIndex, int portIndex, int channel) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                IccLogicalChannelRequest request = new IccLogicalChannelRequest();
+                request.slotIndex = slotIndex;
+                request.portIndex = portIndex;
+                request.channel = channel;
+                telephony.iccCloseLogicalChannel(request);
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            throw ex.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
      * Closes a previously opened logical channel to the ICC card.
      *
      * Input parameters equivalent to TS 27.007 AT+CCHC command.
@@ -6978,10 +7073,13 @@
      * @return The APDU response from the ICC card with the status appended at the end, or null if
      * there is an issue connecting to the Telephony service.
      * @hide
+     * @deprecated instead use
+     * {@link #iccTransmitApduLogicalChannelByPort(int, int, int, int, int, int, int, int, String)}
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     @SystemApi
     @Nullable
+    @Deprecated
     public String iccTransmitApduLogicalChannelBySlot(int slotIndex, int channel, int cla,
             int instruction, int p1, int p2, int p3, @Nullable String data) {
         try {
@@ -6997,6 +7095,50 @@
     }
 
     /**
+     * Transmit an APDU to the ICC card over a logical channel using the physical slot index.
+     *
+     * Use this method when no subscriptions are available on the SIM and the operation must be
+     * performed using the physical slot index.
+     *
+     * Input parameters equivalent to TS 27.007 AT+CGLA command.
+     *
+     * @param slotIndex the physical slot index of the ICC card
+     * @param portIndex The port index is an enumeration of the ports available on the UICC.
+     *                  Use {@link UiccPortInfo#getPortIndex()} to get portIndex.
+     * @param channel is the channel id to be closed as returned by a successful
+     *            iccOpenLogicalChannel.
+     * @param cla Class of the APDU command.
+     * @param instruction Instruction of the APDU command.
+     * @param p1 P1 value of the APDU command.
+     * @param p2 P2 value of the APDU command.
+     * @param p3 P3 value of the APDU command. If p3 is negative a 4 byte APDU
+     *            is sent to the SIM.
+     * @param data Data to be sent with the APDU.
+     * @return The APDU response from the ICC card with the status appended at the end, or null if
+     * there is an issue connecting to the Telephony service.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    @SystemApi
+    @NonNull
+    public String iccTransmitApduLogicalChannelByPort(int slotIndex, int portIndex, int channel,
+            int cla, int instruction, int p1, int p2, int p3, @Nullable String data) {
+        String response;
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                response = telephony.iccTransmitApduLogicalChannelByPort(slotIndex, portIndex,
+                        channel, cla, instruction, p1, p2, p3, data);
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            throw ex.rethrowAsRuntimeException();
+        }
+        return response;
+    }
+
+    /**
      * Transmit an APDU to the ICC card over a logical channel.
      *
      * Input parameters equivalent to TS 27.007 AT+CGLA command.
@@ -7081,10 +7223,13 @@
      * @return The APDU response from the ICC card with the status appended at
      *            the end.
      * @hide
+     * @deprecated instead use
+     * {@link #iccTransmitApduBasicChannelByPort(int, int, int, int, int, int, int, String)}
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     @SystemApi
     @NonNull
+    @Deprecated
     public String iccTransmitApduBasicChannelBySlot(int slotIndex, int cla, int instruction, int p1,
             int p2, int p3, @Nullable String data) {
         try {
@@ -7100,6 +7245,47 @@
     }
 
     /**
+     * Transmit an APDU to the ICC card over the basic channel using the physical slot index.
+     *
+     * Use this method when no subscriptions are available on the SIM and the operation must be
+     * performed using the physical slot index.
+     *
+     * Input parameters equivalent to TS 27.007 AT+CSIM command.
+     *
+     * @param slotIndex the physical slot index of the ICC card to target
+     * @param portIndex The port index is an enumeration of the ports available on the UICC.
+     *                  Use {@link UiccPortInfo#getPortIndex()} to get portIndex.
+     * @param cla Class of the APDU command.
+     * @param instruction Instruction of the APDU command.
+     * @param p1 P1 value of the APDU command.
+     * @param p2 P2 value of the APDU command.
+     * @param p3 P3 value of the APDU command. If p3 is negative a 4 byte APDU
+     *            is sent to the SIM.
+     * @param data Data to be sent with the APDU.
+     * @return The APDU response from the ICC card with the status appended at
+     *            the end.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    @SystemApi
+    @NonNull
+    public String iccTransmitApduBasicChannelByPort(int slotIndex, int portIndex, int cla,
+            int instruction, int p1, int p2, int p3, @Nullable String data) {
+        String response;
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                response = telephony.iccTransmitApduBasicChannelByPort(slotIndex, portIndex,
+                        getOpPackageName(), cla, instruction, p1, p2, p3, data);
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            throw ex.rethrowAsRuntimeException();
+        }
+        return response;
+    }
+    /**
      * Transmit an APDU to the ICC card over the basic channel.
      *
      * Input parameters equivalent to TS 27.007 AT+CSIM command.
@@ -9417,15 +9603,7 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public @Nullable String getCarrierServicePackageName() {
-        // TODO(b/205736323) plumb this through to CarrierPrivilegesTracker, which will cache the
-        // value instead of re-querying every time.
-        List<String> carrierServicePackages =
-                getCarrierPackageNamesForIntent(
-                        new Intent(CarrierService.CARRIER_SERVICE_INTERFACE));
-        if (carrierServicePackages != null && !carrierServicePackages.isEmpty()) {
-            return carrierServicePackages.get(0);
-        }
-        return null;
+        return getCarrierServicePackageNameForLogicalSlot(getPhoneId());
     }
 
     /**
@@ -9442,13 +9620,15 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public @Nullable String getCarrierServicePackageNameForLogicalSlot(int logicalSlotIndex) {
-        // TODO(b/205736323) plumb this through to CarrierPrivilegesTracker, which will cache the
-        // value instead of re-querying every time.
-        List<String> carrierServicePackages =
-                getCarrierPackageNamesForIntentAndPhone(
-                        new Intent(CarrierService.CARRIER_SERVICE_INTERFACE), logicalSlotIndex);
-        if (carrierServicePackages != null && !carrierServicePackages.isEmpty()) {
-            return carrierServicePackages.get(0);
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.getCarrierServicePackageNameForLogicalSlot(logicalSlotIndex);
+            }
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "getCarrierServicePackageNameForLogicalSlot RemoteException", ex);
+        } catch (NullPointerException ex) {
+            Rlog.e(TAG, "getCarrierServicePackageNameForLogicalSlot NPE", ex);
         }
         return null;
     }
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index 45022a6..aa514b9 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -16,6 +16,7 @@
 package android.telephony.euicc;
 
 import android.Manifest;
+import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -28,6 +29,7 @@
 import android.content.Intent;
 import android.content.IntentSender;
 import android.content.pm.PackageManager;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.telephony.TelephonyFrameworkInitializer;
@@ -35,11 +37,13 @@
 import android.telephony.euicc.EuiccCardManager.ResetOption;
 
 import com.android.internal.telephony.euicc.IEuiccController;
+import com.android.internal.telephony.euicc.IResultCallback;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.Executor;
 import java.util.stream.Collectors;
 
 /**
@@ -215,6 +219,20 @@
             "android.telephony.euicc.action.START_EUICC_ACTIVATION";
 
     /**
+     * Result codes passed to the ResultListener by
+     * {@link #switchToSubscription(int, int, Executor, ResultListener)}
+     *
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"EMBEDDED_SUBSCRIPTION_RESULT_"}, value = {
+            EMBEDDED_SUBSCRIPTION_RESULT_OK,
+            EMBEDDED_SUBSCRIPTION_RESULT_ERROR,
+            EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR
+    })
+    public @interface ResultCode{}
+
+    /**
      * Result code for an operation indicating that the operation succeeded.
      */
     public static final int EMBEDDED_SUBSCRIPTION_RESULT_OK = 0;
@@ -1125,7 +1143,12 @@
      *     permission, or the calling app must be authorized to manage the active subscription on
      *     the target eUICC.
      * @param callbackIntent a PendingIntent to launch when the operation completes.
+     *
+     * @deprecated From T, callers should use
+     * {@link #switchToSubscription(int, int, Executor, ResultListener)} instead to specify a port
+     * index on the card to switch to.
      */
+    @Deprecated
     @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
     public void switchToSubscription(int subscriptionId, PendingIntent callbackIntent) {
         if (!isEnabled()) {
@@ -1141,6 +1164,71 @@
     }
 
     /**
+     * Switch to (enable) the given subscription.
+     *
+     * <p>Requires the {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission,
+     * or the calling app must be authorized to manage both the currently-active subscription and
+     * the subscription to be enabled according to the subscription metadata. Without the former,
+     * an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} will be returned in the callback
+     * intent to prompt the user to accept the download.
+     *
+     * <p>On a multi-active SIM device, requires the
+     * {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission, or a calling app
+     * only if the targeted eUICC does not currently have an active subscription or the calling app
+     * is authorized to manage the active subscription on the target eUICC, and the calling app is
+     * authorized to manage any active subscription on any SIM. Without it, an
+     * {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} will be returned in the callback
+     * intent to prompt the user to accept the download. The caller should also be authorized to
+     * manage the subscription to be enabled.
+     *
+     * @param subscriptionId the ID of the subscription to enable. May be
+     *     {@link android.telephony.SubscriptionManager#INVALID_SUBSCRIPTION_ID} to deactivate the
+     *     current profile without activating another profile to replace it. If it's a disable
+     *     operation, requires the {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS}
+     *     permission, or the calling app must be authorized to manage the active subscription on
+     *     the target eUICC.
+     * @param portIndex the index of the port to target for the enabled subscription
+     * @param executor an Executor on which to run the callback
+     * @param callback a {@link ResultListener} which will run when the operation completes
+     */
+    @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+    public void switchToSubscription(int subscriptionId, int portIndex,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull ResultListener callback) {
+        if (!isEnabled()) {
+            sendUnavailableErrorToCallback(executor, callback);
+            return;
+        }
+        try {
+            IResultCallback internalCallback = new IResultCallback.Stub() {
+                @Override
+                public void onComplete(int result, Intent resultIntent) {
+                    executor.execute(() -> Binder.withCleanCallingIdentity(
+                            () -> callback.onComplete(result, resultIntent)));
+                }
+            };
+            getIEuiccController().switchToSubscriptionWithPort(mCardId, portIndex,
+                    subscriptionId, mContext.getOpPackageName(), internalCallback);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Callback to receive the result of an EuiccManager API.
+     */
+    public interface ResultListener {
+        /**
+         * Called on completion of some operation.
+         * @param resultCode representing success or specific failure of the operation
+         *                   (See {@link ResultCode})
+         * @param resultIntent an intent used to start a resolution activity when an error
+         *                     occurs that can be resolved by the user
+         */
+        void onComplete(@ResultCode int resultCode, @Nullable Intent resultIntent);
+    }
+
+    /**
      * Update the nickname for the given subscription.
      *
      * <p>Requires that the calling app has carrier privileges according to the metadata of the
@@ -1411,6 +1499,13 @@
         }
     }
 
+    private static void sendUnavailableErrorToCallback(@NonNull Executor executor,
+            ResultListener callback) {
+        Integer result = EMBEDDED_SUBSCRIPTION_RESULT_ERROR;
+        executor.execute(() ->
+                Binder.withCleanCallingIdentity(() -> callback.onComplete(result, null)));
+    }
+
     private static IEuiccController getIEuiccController() {
         return IEuiccController.Stub.asInterface(
                 TelephonyFrameworkInitializer
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 0d8881b..883e2ad 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2545,4 +2545,15 @@
      * registration technology specified, false if it is not required.
      */
     boolean isRcsProvisioningRequiredForCapability(int subId, int capability, int tech);
+
+    /**
+     * Returns the package name that provides the {@link CarrierService} implementation for the
+     * specified {@code logicalSlotIndex}, or {@code null} if no package with carrier privileges
+     * declares one.
+     *
+     * @param logicalSlotIndex The slot index to fetch the {@link CarrierService} package for
+     * @return The system-selected package that provides the {@link CarrierService} implementation
+     * for the slot, or {@code null} if none is resolved
+     */
+    String getCarrierServicePackageNameForLogicalSlot(int logicalSlotIndex);
 }
diff --git a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
index 944ce348..7f5982f 100644
--- a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
+++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
@@ -21,6 +21,9 @@
 import android.os.Bundle;
 import android.telephony.euicc.DownloadableSubscription;
 import android.telephony.euicc.EuiccInfo;
+
+import com.android.internal.telephony.euicc.IResultCallback;
+
 import java.util.List;
 
 /** @hide */
@@ -42,6 +45,8 @@
         in PendingIntent callbackIntent);
     oneway void switchToSubscription(int cardId, int subscriptionId, String callingPackage,
         in PendingIntent callbackIntent);
+    oneway void switchToSubscriptionWithPort(int cardId, int portIndex, int subscriptionId,
+        String callingPackage, in IResultCallback callback);
     oneway void updateSubscriptionNickname(int cardId, int subscriptionId, String nickname,
         String callingPackage, in PendingIntent callbackIntent);
     oneway void eraseSubscriptions(int cardId, in PendingIntent callbackIntent);
diff --git a/telephony/java/com/android/internal/telephony/euicc/IResultCallback.aidl b/telephony/java/com/android/internal/telephony/euicc/IResultCallback.aidl
new file mode 100644
index 0000000..69f479c
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/euicc/IResultCallback.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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.internal.telephony.euicc;
+
+import android.content.Intent;
+
+/** @hide */
+oneway interface IResultCallback {
+    void onComplete(int resultCode, in Intent resultIntent);
+}
diff --git a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
index 7b1f7a5..b673957 100644
--- a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
+++ b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
@@ -60,7 +60,7 @@
 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
 import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyManager;
-import android.telephony.TelephonyManager.CarrierPrivilegesListener;
+import android.telephony.TelephonyManager.CarrierPrivilegesCallback;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 
@@ -187,11 +187,11 @@
         return captor.getValue();
     }
 
-    private List<CarrierPrivilegesListener> getCarrierPrivilegesListeners() {
-        final ArgumentCaptor<CarrierPrivilegesListener> captor =
-                ArgumentCaptor.forClass(CarrierPrivilegesListener.class);
+    private List<CarrierPrivilegesCallback> getCarrierPrivilegesCallbacks() {
+        final ArgumentCaptor<CarrierPrivilegesCallback> captor =
+                ArgumentCaptor.forClass(CarrierPrivilegesCallback.class);
         verify(mTelephonyManager, atLeastOnce())
-                .addCarrierPrivilegesListener(anyInt(), any(), captor.capture());
+                .registerCarrierPrivilegesCallback(anyInt(), any(), captor.capture());
 
         return captor.getAllValues();
     }
@@ -270,12 +270,12 @@
         assertNotNull(getOnSubscriptionsChangedListener());
 
         verify(mTelephonyManager, times(2))
-                .addCarrierPrivilegesListener(anyInt(), any(HandlerExecutor.class), any());
+                .registerCarrierPrivilegesCallback(anyInt(), any(HandlerExecutor.class), any());
         verify(mTelephonyManager)
-                .addCarrierPrivilegesListener(eq(0), any(HandlerExecutor.class), any());
+                .registerCarrierPrivilegesCallback(eq(0), any(HandlerExecutor.class), any());
         verify(mTelephonyManager)
-                .addCarrierPrivilegesListener(eq(1), any(HandlerExecutor.class), any());
-        assertEquals(2, getCarrierPrivilegesListeners().size());
+                .registerCarrierPrivilegesCallback(eq(1), any(HandlerExecutor.class), any());
+        assertEquals(2, getCarrierPrivilegesCallbacks().size());
     }
 
     @Test
@@ -287,10 +287,10 @@
         final OnSubscriptionsChangedListener listener = getOnSubscriptionsChangedListener();
         verify(mSubscriptionManager).removeOnSubscriptionsChangedListener(eq(listener));
 
-        for (CarrierPrivilegesListener carrierPrivilegesListener :
-                getCarrierPrivilegesListeners()) {
+        for (CarrierPrivilegesCallback carrierPrivilegesCallback :
+                getCarrierPrivilegesCallbacks()) {
             verify(mTelephonyManager)
-                    .removeCarrierPrivilegesListener(eq(carrierPrivilegesListener));
+                    .unregisterCarrierPrivilegesCallback(eq(carrierPrivilegesCallback));
         }
     }
 
@@ -303,15 +303,15 @@
         mTelephonySubscriptionTracker.setReadySubIdsBySlotId(readySubIdsBySlotId);
         doReturn(1).when(mTelephonyManager).getActiveModemCount();
 
-        List<CarrierPrivilegesListener> carrierPrivilegesListeners =
-                getCarrierPrivilegesListeners();
+        List<CarrierPrivilegesCallback> carrierPrivilegesCallbacks =
+                getCarrierPrivilegesCallbacks();
 
         mTelephonySubscriptionTracker.onReceive(mContext, buildTestMultiSimConfigBroadcastIntent());
         mTestLooper.dispatchAll();
 
-        for (CarrierPrivilegesListener carrierPrivilegesListener : carrierPrivilegesListeners) {
+        for (CarrierPrivilegesCallback carrierPrivilegesCallback : carrierPrivilegesCallbacks) {
             verify(mTelephonyManager)
-                    .removeCarrierPrivilegesListener(eq(carrierPrivilegesListener));
+                    .unregisterCarrierPrivilegesCallback(eq(carrierPrivilegesCallback));
         }
 
         // Expect cache cleared for inactive slots.
@@ -323,9 +323,9 @@
         // Expect a new CarrierPrivilegesListener to have been registered for slot 0, and none other
         // (2 previously registered during startup, for slots 0 & 1)
         verify(mTelephonyManager, times(3))
-                .addCarrierPrivilegesListener(anyInt(), any(HandlerExecutor.class), any());
+                .registerCarrierPrivilegesCallback(anyInt(), any(HandlerExecutor.class), any());
         verify(mTelephonyManager, times(2))
-                .addCarrierPrivilegesListener(eq(0), any(HandlerExecutor.class), any());
+                .registerCarrierPrivilegesCallback(eq(0), any(HandlerExecutor.class), any());
 
         // Verify that this triggers a re-evaluation
         verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES)));
@@ -391,8 +391,8 @@
     public void testOnCarrierPrivilegesChanged() throws Exception {
         setupReadySubIds();
 
-        final CarrierPrivilegesListener listener = getCarrierPrivilegesListeners().get(0);
-        listener.onCarrierPrivilegesChanged(Collections.emptyList(), new int[] {});
+        final CarrierPrivilegesCallback callback = getCarrierPrivilegesCallbacks().get(0);
+        callback.onCarrierPrivilegesChanged(Collections.emptySet(), Collections.emptySet());
         mTestLooper.dispatchAll();
 
         verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES)));