Merge "Ensure VcnGatewayConnection#isQuitting never gets unset after being set" into sc-dev
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index 95712cd..262dc83 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -132,7 +132,7 @@
         if (!row.isPinned()) {
             mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
         }
-        mStatusBarKeyguardViewManager.showBouncer(true /* scrimmed */);
+        mStatusBarKeyguardViewManager.showGenericBouncer(true /* scrimmed */);
         mPendingRemoteInputView = clicked;
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
index 9a5e948..1fc1473 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
@@ -26,6 +26,7 @@
 import android.content.Intent;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.view.View;
 
 import androidx.test.filters.SmallTest;
 
@@ -37,6 +38,7 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.FakeExecutor;
@@ -94,4 +96,11 @@
         verify(mRemoteInputCallback, times(1)).onWorkChallengeChanged();
     }
 
+    @Test
+    public void testShowGenericBouncer_onLockedRemoteInput() {
+        mRemoteInputCallback.onLockedRemoteInput(
+                mock(ExpandableNotificationRow.class), mock(View.class));
+
+        verify(mStatusBarKeyguardViewManager).showGenericBouncer(true);
+    }
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 867c093..b068f86 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -24,6 +24,7 @@
 import static android.net.vcn.VcnManager.VCN_STATUS_CODE_INACTIVE;
 import static android.net.vcn.VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED;
 import static android.net.vcn.VcnManager.VCN_STATUS_CODE_SAFE_MODE;
+import static android.telephony.SubscriptionManager.isValidSubscriptionId;
 
 import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback;
@@ -435,6 +436,15 @@
         }
     }
 
+    private boolean isActiveSubGroup(
+            @NonNull ParcelUuid subGrp, @NonNull TelephonySubscriptionSnapshot snapshot) {
+        if (subGrp == null || snapshot == null) {
+            return false;
+        }
+
+        return Objects.equals(subGrp, snapshot.getActiveDataSubscriptionGroup());
+    }
+
     private class VcnSubscriptionTrackerCallback implements TelephonySubscriptionTrackerCallback {
         /**
          * Handles subscription group changes, as notified by {@link TelephonySubscriptionTracker}
@@ -452,28 +462,49 @@
 
                 // Start any VCN instances as necessary
                 for (Entry<ParcelUuid, VcnConfig> entry : mConfigs.entrySet()) {
+                    final ParcelUuid subGrp = entry.getKey();
+
+                    // TODO(b/193687515): Support multiple VCNs active at the same time
                     if (snapshot.packageHasPermissionsForSubscriptionGroup(
-                            entry.getKey(), entry.getValue().getProvisioningPackageName())) {
-                        if (!mVcns.containsKey(entry.getKey())) {
-                            startVcnLocked(entry.getKey(), entry.getValue());
+                                    subGrp, entry.getValue().getProvisioningPackageName())
+                            && isActiveSubGroup(subGrp, snapshot)) {
+                        if (!mVcns.containsKey(subGrp)) {
+                            startVcnLocked(subGrp, entry.getValue());
                         }
 
                         // Cancel any scheduled teardowns for active subscriptions
-                        mHandler.removeCallbacksAndMessages(mVcns.get(entry.getKey()));
+                        mHandler.removeCallbacksAndMessages(mVcns.get(subGrp));
                     }
                 }
 
                 // Schedule teardown of any VCN instances that have lost carrier privileges (after a
                 // delay)
                 for (Entry<ParcelUuid, Vcn> entry : mVcns.entrySet()) {
-                    final VcnConfig config = mConfigs.get(entry.getKey());
+                    final ParcelUuid subGrp = entry.getKey();
+                    final VcnConfig config = mConfigs.get(subGrp);
 
+                    final boolean isActiveSubGrp = isActiveSubGroup(subGrp, snapshot);
+                    final boolean isValidActiveDataSubIdNotInVcnSubGrp =
+                            isValidSubscriptionId(snapshot.getActiveDataSubscriptionId())
+                                    && !isActiveSubGroup(subGrp, snapshot);
+
+                    // TODO(b/193687515): Support multiple VCNs active at the same time
                     if (config == null
                             || !snapshot.packageHasPermissionsForSubscriptionGroup(
-                                    entry.getKey(), config.getProvisioningPackageName())) {
-                        final ParcelUuid uuidToTeardown = entry.getKey();
+                                    subGrp, config.getProvisioningPackageName())
+                            || !isActiveSubGrp) {
+                        final ParcelUuid uuidToTeardown = subGrp;
                         final Vcn instanceToTeardown = entry.getValue();
 
+                        // TODO(b/193687515): Support multiple VCNs active at the same time
+                        // If directly switching to a subscription not in the current group,
+                        // teardown immediately to prevent other subscription's network from being
+                        // outscored by the VCN. Otherwise, teardown after a delay to ensure that
+                        // SIM profile switches do not trigger the VCN to cycle.
+                        final long teardownDelayMs =
+                                isValidActiveDataSubIdNotInVcnSubGrp
+                                        ? 0
+                                        : CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS;
                         mHandler.postDelayed(() -> {
                             synchronized (mLock) {
                                 // Guard against case where this is run after a old instance was
@@ -489,7 +520,7 @@
                                             uuidToTeardown, VCN_STATUS_CODE_INACTIVE);
                                 }
                             }
-                        }, instanceToTeardown, CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
+                        }, instanceToTeardown, teardownDelayMs);
                     } else {
                         // If this VCN's status has not changed, update it with the new snapshot
                         entry.getValue().updateSubscriptionSnapshot(mLastSnapshot);
@@ -555,8 +586,13 @@
     private void startVcnLocked(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) {
         logDbg("Starting VCN config for subGrp: " + subscriptionGroup);
 
-        // TODO(b/176939047): Support multiple VCNs active at the same time, or limit to one active
-        //                    VCN.
+        // TODO(b/193687515): Support multiple VCNs active at the same time
+        if (!mVcns.isEmpty()) {
+            // Only one VCN supported at a time; teardown all others before starting new one
+            for (ParcelUuid uuidToTeardown : mVcns.keySet()) {
+                stopVcnLocked(uuidToTeardown);
+            }
+        }
 
         final VcnCallbackImpl vcnCallback = new VcnCallbackImpl(subscriptionGroup);
 
@@ -584,7 +620,10 @@
             final Vcn vcn = mVcns.get(subscriptionGroup);
             vcn.updateConfig(config);
         } else {
-            startVcnLocked(subscriptionGroup, config);
+            // TODO(b/193687515): Support multiple VCNs active at the same time
+            if (isActiveSubGroup(subscriptionGroup, mLastSnapshot)) {
+                startVcnLocked(subscriptionGroup, config);
+            }
         }
     }
 
@@ -1009,6 +1048,11 @@
         }
     }
 
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    void setLastSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) {
+        mLastSnapshot = Objects.requireNonNull(snapshot);
+    }
+
     private void logVdbg(String msg) {
         if (VDBG) {
             Slog.v(TAG, msg);
diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
index 9d1c838..9079ba8 100644
--- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
+++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
@@ -50,7 +50,7 @@
     static final boolean DEBUG_BROADCAST_LIGHT = DEBUG_BROADCAST || false;
     static final boolean DEBUG_BROADCAST_DEFERRAL = DEBUG_BROADCAST || false;
     static final boolean DEBUG_COMPACTION = DEBUG_ALL || false;
-    static final boolean DEBUG_FREEZER = DEBUG_ALL || true;
+    static final boolean DEBUG_FREEZER = DEBUG_ALL || false;
     static final boolean DEBUG_LRU = DEBUG_ALL || false;
     static final boolean DEBUG_MU = DEBUG_ALL || false;
     static final boolean DEBUG_NETWORK = DEBUG_ALL || false;
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 8db7eea..9dbb707 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -992,9 +992,7 @@
         }
 
         if (!opt.isFrozen()) {
-            if (DEBUG_FREEZER) {
-                Slog.d(TAG_AM, "sync unfroze " + pid + " " + app.processName);
-            }
+            Slog.d(TAG_AM, "sync unfroze " + pid + " " + app.processName);
 
             mFreezeHandler.sendMessage(
                     mFreezeHandler.obtainMessage(REPORT_UNFREEZE_MSG,
@@ -1386,9 +1384,7 @@
                 return;
             }
 
-            if (DEBUG_FREEZER) {
-                Slog.d(TAG_AM, "froze " + pid + " " + name);
-            }
+            Slog.d(TAG_AM, "froze " + pid + " " + name);
 
             EventLog.writeEvent(EventLogTags.AM_FREEZE, pid, name);
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 012e47e..183fabd 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -215,9 +215,9 @@
         }
 
         @Override // Binder call
-        public void enroll(final IBinder token, final byte[] hardwareAuthToken, final int userId,
-                final IFingerprintServiceReceiver receiver, final String opPackageName,
-                @FingerprintManager.EnrollReason int enrollReason) {
+        public void enroll(final IBinder token, @NonNull final byte[] hardwareAuthToken,
+                final int userId, final IFingerprintServiceReceiver receiver,
+                final String opPackageName, @FingerprintManager.EnrollReason int enrollReason) {
             Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
 
             final Pair<Integer, ServiceProvider> provider = getSingleProvider();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index 3a214f4..706ac10 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -85,9 +85,12 @@
     void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token,
             @NonNull String opPackageName, long challenge);
 
-    void scheduleEnroll(int sensorId, @NonNull IBinder token, byte[] hardwareAuthToken, int userId,
-            @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
-            @FingerprintManager.EnrollReason int enrollReason,
+    /**
+     * Schedules fingerprint enrollment.
+     */
+    void scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken,
+            int userId, @NonNull IFingerprintServiceReceiver receiver,
+            @NonNull String opPackageName, @FingerprintManager.EnrollReason int enrollReason,
             @NonNull FingerprintStateCallback fingerprintStateCallback);
 
     void cancelEnrollment(int sensorId, @NonNull IBinder token);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index cfc4674..102b074 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -329,9 +329,10 @@
     }
 
     @Override
-    public void scheduleEnroll(int sensorId, @NonNull IBinder token, byte[] hardwareAuthToken,
-            int userId, @NonNull IFingerprintServiceReceiver receiver,
-            @NonNull String opPackageName, @FingerprintManager.EnrollReason int enrollReason,
+    public void scheduleEnroll(int sensorId, @NonNull IBinder token,
+            @NonNull byte[] hardwareAuthToken, int userId,
+            @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
+            @FingerprintManager.EnrollReason int enrollReason,
             @NonNull FingerprintStateCallback fingerprintStateCallback) {
         mHandler.post(() -> {
             final int maxTemplatesPerUser = mSensors.get(sensorId).getSensorProperties()
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 0bec09c..c0b8648 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -2174,6 +2174,7 @@
     @Override
     public VerifyCredentialResponse verifyGatekeeperPasswordHandle(long gatekeeperPasswordHandle,
             long challenge, int userId) {
+
         checkPasswordReadPermission();
 
         final VerifyCredentialResponse response;
@@ -2185,6 +2186,7 @@
 
         synchronized (mSpManager) {
             if (gatekeeperPassword == null) {
+                Slog.d(TAG, "No gatekeeper password for handle");
                 response = VerifyCredentialResponse.ERROR;
             } else {
                 response = mSpManager.verifyChallengeInternal(getGateKeeperService(),
diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
index f13f406..5442e5b 100644
--- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
@@ -256,8 +256,8 @@
 
     private boolean componentMapsToRecognitionService(@NonNull ComponentName serviceComponent) {
         List<ResolveInfo> resolveInfos =
-                getContext().getPackageManager().queryIntentServices(
-                        new Intent(RecognitionService.SERVICE_INTERFACE), 0);
+                getContext().getPackageManager().queryIntentServicesAsUser(
+                        new Intent(RecognitionService.SERVICE_INTERFACE), 0, getUserId());
         if (resolveInfos == null) {
             return false;
         }
diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
index fca706b..a31c56a 100644
--- a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
+++ b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
@@ -36,6 +36,7 @@
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
+import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyManager;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -85,6 +86,8 @@
     @NonNull private final SubscriptionManager mSubscriptionManager;
     @NonNull private final CarrierConfigManager mCarrierConfigManager;
 
+    @NonNull private final ActiveDataSubscriptionIdListener mActiveDataSubIdListener;
+
     // TODO (Android T+): Add ability to handle multiple subIds per slot.
     @NonNull private final Map<Integer, Integer> mReadySubIdsBySlotId = new HashMap<>();
     @NonNull private final OnSubscriptionsChangedListener mSubscriptionChangedListener;
@@ -112,6 +115,7 @@
         mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
         mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class);
         mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
+        mActiveDataSubIdListener = new ActiveDataSubscriptionIdListener();
 
         mSubscriptionChangedListener =
                 new OnSubscriptionsChangedListener() {
@@ -124,16 +128,20 @@
 
     /** Registers the receivers, and starts tracking subscriptions. */
     public void register() {
+        final HandlerExecutor executor = new HandlerExecutor(mHandler);
+
         mContext.registerReceiver(
                 this, new IntentFilter(ACTION_CARRIER_CONFIG_CHANGED), null, mHandler);
         mSubscriptionManager.addOnSubscriptionsChangedListener(
-                new HandlerExecutor(mHandler), mSubscriptionChangedListener);
+                executor, mSubscriptionChangedListener);
+        mTelephonyManager.registerTelephonyCallback(executor, mActiveDataSubIdListener);
     }
 
     /** Unregisters the receivers, and stops tracking subscriptions. */
     public void unregister() {
         mContext.unregisterReceiver(this);
         mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionChangedListener);
+        mTelephonyManager.unregisterTelephonyCallback(mActiveDataSubIdListener);
     }
 
     /**
@@ -185,7 +193,8 @@
         }
 
         final TelephonySubscriptionSnapshot newSnapshot =
-                new TelephonySubscriptionSnapshot(newSubIdToInfoMap, privilegedPackages);
+                new TelephonySubscriptionSnapshot(
+                        mDeps.getActiveDataSubscriptionId(), newSubIdToInfoMap, privilegedPackages);
 
         // If snapshot was meaningfully updated, fire the callback
         if (!newSnapshot.equals(mCurrentSnapshot)) {
@@ -242,16 +251,20 @@
 
     /** TelephonySubscriptionSnapshot is a class containing info about active subscriptions */
     public static class TelephonySubscriptionSnapshot {
+        private final int mActiveDataSubId;
         private final Map<Integer, SubscriptionInfo> mSubIdToInfoMap;
         private final Map<ParcelUuid, Set<String>> mPrivilegedPackages;
 
         public static final TelephonySubscriptionSnapshot EMPTY_SNAPSHOT =
-                new TelephonySubscriptionSnapshot(Collections.emptyMap(), Collections.emptyMap());
+                new TelephonySubscriptionSnapshot(
+                        INVALID_SUBSCRIPTION_ID, Collections.emptyMap(), Collections.emptyMap());
 
         @VisibleForTesting(visibility = Visibility.PRIVATE)
         TelephonySubscriptionSnapshot(
+                int activeDataSubId,
                 @NonNull Map<Integer, SubscriptionInfo> subIdToInfoMap,
                 @NonNull Map<ParcelUuid, Set<String>> privilegedPackages) {
+            mActiveDataSubId = activeDataSubId;
             Objects.requireNonNull(subIdToInfoMap, "subIdToInfoMap was null");
             Objects.requireNonNull(privilegedPackages, "privilegedPackages was null");
 
@@ -265,6 +278,22 @@
             mPrivilegedPackages = Collections.unmodifiableMap(unmodifiableInnerSets);
         }
 
+        /** Returns the active subscription ID. May be INVALID_SUBSCRIPTION_ID */
+        public int getActiveDataSubscriptionId() {
+            return mActiveDataSubId;
+        }
+
+        /** Returns the active subscription group */
+        @Nullable
+        public ParcelUuid getActiveDataSubscriptionGroup() {
+            final SubscriptionInfo info = mSubIdToInfoMap.get(getActiveDataSubscriptionId());
+            if (info == null) {
+                return null;
+            }
+
+            return info.getGroupUuid();
+        }
+
         /** Returns the active subscription groups */
         @NonNull
         public Set<ParcelUuid> getActiveSubscriptionGroups() {
@@ -313,7 +342,7 @@
 
         @Override
         public int hashCode() {
-            return Objects.hash(mSubIdToInfoMap, mPrivilegedPackages);
+            return Objects.hash(mActiveDataSubId, mSubIdToInfoMap, mPrivilegedPackages);
         }
 
         @Override
@@ -324,7 +353,8 @@
 
             final TelephonySubscriptionSnapshot other = (TelephonySubscriptionSnapshot) obj;
 
-            return mSubIdToInfoMap.equals(other.mSubIdToInfoMap)
+            return mActiveDataSubId == other.mActiveDataSubId
+                    && mSubIdToInfoMap.equals(other.mSubIdToInfoMap)
                     && mPrivilegedPackages.equals(other.mPrivilegedPackages);
         }
 
@@ -333,6 +363,7 @@
             pw.println("TelephonySubscriptionSnapshot:");
             pw.increaseIndent();
 
+            pw.println("mActiveDataSubId: " + mActiveDataSubId);
             pw.println("mSubIdToInfoMap: " + mSubIdToInfoMap);
             pw.println("mPrivilegedPackages: " + mPrivilegedPackages);
 
@@ -342,7 +373,8 @@
         @Override
         public String toString() {
             return "TelephonySubscriptionSnapshot{ "
-                    + "mSubIdToInfoMap=" + mSubIdToInfoMap
+                    + "mActiveDataSubId=" + mActiveDataSubId
+                    + ", mSubIdToInfoMap=" + mSubIdToInfoMap
                     + ", mPrivilegedPackages=" + mPrivilegedPackages
                     + " }";
         }
@@ -362,6 +394,14 @@
         void onNewSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot);
     }
 
+    private class ActiveDataSubscriptionIdListener extends TelephonyCallback
+            implements TelephonyCallback.ActiveDataSubscriptionIdListener {
+        @Override
+        public void onActiveDataSubscriptionIdChanged(int subId) {
+            handleSubscriptionsChanged();
+        }
+    }
+
     /** External static dependencies for test injection */
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     public static class Dependencies {
@@ -369,5 +409,10 @@
         public boolean isConfigForIdentifiedCarrier(PersistableBundle bundle) {
             return CarrierConfigManager.isConfigForIdentifiedCarrier(bundle);
         }
+
+        /** Gets the active Subscription ID */
+        public int getActiveDataSubscriptionId() {
+            return SubscriptionManager.getActiveDataSubscriptionId();
+        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 4468252..4c1992e 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4725,7 +4725,8 @@
      */
     private void postApplyAnimation(boolean visible) {
         final boolean delayed = isAnimating(PARENTS | CHILDREN,
-                ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_WINDOW_ANIMATION);
+                ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_WINDOW_ANIMATION
+                        | ANIMATION_TYPE_RECENTS);
         if (!delayed) {
             // We aren't delayed anything, but exiting windows rely on the animation finished
             // callback being called in case the ActivityRecord was pretending to be delayed,
@@ -4745,7 +4746,8 @@
         // updated.
         // If we're becoming invisible, update the client visibility if we are not running an
         // animation. Otherwise, we'll update client visibility in onAnimationFinished.
-        if (visible || !isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION)) {
+        if (visible || !isAnimating(PARENTS,
+                ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)) {
             setClientVisible(visible);
         }
 
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index a10b5d6..b1bdc11 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -239,8 +239,10 @@
             // Fetch all the surface controls and pass them to the client to get the animation
             // started. Cancel any existing recents animation running synchronously (do not hold the
             // WM lock)
-            mWindowManager.cancelRecentsAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION,
-                    "startRecentsActivity");
+            if (mWindowManager.getRecentsAnimationController() != null) {
+                mWindowManager.getRecentsAnimationController().forceCancelAnimation(
+                        REORDER_MOVE_TO_ORIGINAL_POSITION, "startRecentsActivity");
+            }
             mWindowManager.initializeRecentsAnimation(mTargetActivityType, recentsAnimationRunner,
                     this, mDefaultTaskDisplayArea.getDisplayId(),
                     mTaskSupervisor.mRecentTasks.getRecentTaskIds(), targetActivity);
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 9e147b1..e346e3e 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -122,9 +122,7 @@
             new ArrayList<>();
     private final int mDisplayId;
     private boolean mWillFinishToHome = false;
-    private final Runnable mFailsafeRunnable = () -> cancelAnimation(
-            mWillFinishToHome ? REORDER_MOVE_TO_TOP : REORDER_MOVE_TO_ORIGINAL_POSITION,
-            "failSafeRunnable");
+    private final Runnable mFailsafeRunnable = this::onFailsafe;
 
     // The recents component app token that is shown behind the visibile tasks
     private ActivityRecord mTargetActivityRecord;
@@ -262,9 +260,6 @@
             final long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mService.getWindowManagerLock()) {
-                    if (mCanceled) {
-                        return;
-                    }
                     // Remove all new task targets.
                     for (int i = mPendingNewTaskTargets.size() - 1; i >= 0; i--) {
                         removeTaskInternal(mPendingNewTaskTargets.get(i));
@@ -807,6 +802,14 @@
                 }, mPendingWallpaperAnimations);
     }
 
+    void forceCancelAnimation(@ReorderMode int reorderMode, String reason) {
+        if (!mCanceled) {
+            cancelAnimation(reorderMode, reason);
+        } else {
+            continueDeferredCancelAnimation();
+        }
+    }
+
     void cancelAnimation(@ReorderMode int reorderMode, String reason) {
         cancelAnimation(reorderMode, false /*screenshot */, reason);
     }
@@ -821,9 +824,6 @@
      * finish the animation.
      */
     public void cancelAnimationForHomeStart() {
-        if (mCanceled) {
-            return;
-        }
         final int reorderMode = mTargetActivityType == ACTIVITY_TYPE_HOME && mWillFinishToHome
                 ? REORDER_MOVE_TO_TOP
                 : REORDER_KEEP_IN_PLACE;
@@ -836,9 +836,6 @@
      * how to finish the animation.
      */
     public void cancelAnimationForDisplayChange() {
-        if (mCanceled) {
-            return;
-        }
         cancelAnimation(mWillFinishToHome ? REORDER_MOVE_TO_TOP : REORDER_MOVE_TO_ORIGINAL_POSITION,
                 true /* screenshot */, "cancelAnimationForDisplayChange");
     }
@@ -868,6 +865,8 @@
                 if (taskSnapshot != null) {
                     // Defer until the runner calls back to cleanupScreenshot()
                     adapter.setSnapshotOverlay(taskSnapshot);
+                    // Schedule a new failsafe for if the runner doesn't clean up the screenshot
+                    scheduleFailsafe();
                 } else {
                     // Do a normal cancel since we couldn't screenshot
                     mCallbacks.onAnimationFinished(reorderMode, false /* sendUserLeaveHint */);
@@ -1014,6 +1013,12 @@
         mService.mH.postDelayed(mFailsafeRunnable, FAILSAFE_DELAY);
     }
 
+    void onFailsafe() {
+        forceCancelAnimation(
+                mWillFinishToHome ? REORDER_MOVE_TO_TOP : REORDER_MOVE_TO_ORIGINAL_POSITION,
+                "onFailsafe");
+    }
+
     private void linkToDeathOfRunner() throws RemoteException {
         if (!mLinkedToDeathOfRunner) {
             mRunner.asBinder().linkToDeath(this, 0);
@@ -1030,13 +1035,7 @@
 
     @Override
     public void binderDied() {
-        if (!mCanceled) {
-            cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "binderDied");
-        } else {
-            // If we are already canceled but with a screenshot, and are waiting for the
-            // cleanupScreenshot() callback, then force-finish the animation now
-            continueDeferredCancelAnimation();
-        }
+        forceCancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "binderDied");
 
         synchronized (mService.getWindowManagerLock()) {
             // Clear associated input consumers on runner death
@@ -1358,5 +1357,7 @@
                 + mCancelOnNextTransitionStart);
         pw.print(innerPrefix); pw.println("mCancelDeferredWithScreenshot="
                 + mCancelDeferredWithScreenshot);
+        pw.print(innerPrefix); pw.println("mPendingCancelWithScreenshotReorderMode="
+                + mPendingCancelWithScreenshotReorderMode);
     }
 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 6be47e1..cbcbf52 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -126,6 +126,9 @@
     final ServiceConnection mConnection = new ServiceConnection() {
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
+            if (DEBUG) {
+                Slog.d(TAG, "onServiceConnected to " + name + " for user(" + mUser + ")");
+            }
             synchronized (mServiceStub) {
                 mService = IVoiceInteractionService.Stub.asInterface(service);
                 try {
@@ -137,7 +140,13 @@
 
         @Override
         public void onServiceDisconnected(ComponentName name) {
-            mService = null;
+            if (DEBUG) {
+                Slog.d(TAG, "onServiceDisconnected to " + name);
+            }
+            synchronized (mServiceStub) {
+                mService = null;
+                resetHotwordDetectionConnectionLocked();
+            }
         }
     };
 
@@ -575,6 +584,20 @@
         mHotwordDetectionConnection.forceRestart();
     }
 
+    void resetHotwordDetectionConnectionLocked() {
+        if (DEBUG) {
+            Slog.d(TAG, "resetHotwordDetectionConnectionLocked");
+        }
+        if (mHotwordDetectionConnection == null) {
+            if (DEBUG) {
+                Slog.w(TAG, "reset, but no hotword detection connection");
+            }
+            return;
+        }
+        mHotwordDetectionConnection.cancelLocked();
+        mHotwordDetectionConnection = null;
+    }
+
     public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!mValid) {
             pw.print("  NOT VALID: ");
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index b7a6d0f..7c7dc4d 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -23,6 +23,7 @@
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.vcn.VcnManager.VCN_STATUS_CODE_ACTIVE;
 import static android.net.vcn.VcnManager.VCN_STATUS_CODE_SAFE_MODE;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
 import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
 
@@ -50,6 +51,7 @@
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.annotation.NonNull;
@@ -99,6 +101,7 @@
 import org.mockito.ArgumentCaptor;
 
 import java.io.FileNotFoundException;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -227,6 +230,7 @@
 
         setupMockedCarrierPrivilege(true);
         mVcnMgmtSvc = new VcnManagementService(mMockContext, mMockDeps);
+        setupActiveSubscription(TEST_UUID_1);
 
         doReturn(mMockIBinder).when(mMockPolicyListener).asBinder();
         doReturn(mMockIBinder).when(mMockStatusCallback).asBinder();
@@ -300,23 +304,65 @@
     }
 
     private TelephonySubscriptionSnapshot triggerSubscriptionTrackerCbAndGetSnapshot(
-            Set<ParcelUuid> activeSubscriptionGroups) {
+            ParcelUuid activeDataSubGrp, Set<ParcelUuid> activeSubscriptionGroups) {
         return triggerSubscriptionTrackerCbAndGetSnapshot(
-                activeSubscriptionGroups, Collections.emptyMap());
+                activeDataSubGrp, activeSubscriptionGroups, Collections.emptyMap());
     }
 
     private TelephonySubscriptionSnapshot triggerSubscriptionTrackerCbAndGetSnapshot(
-            Set<ParcelUuid> activeSubscriptionGroups, Map<Integer, ParcelUuid> subIdToGroupMap) {
+            ParcelUuid activeDataSubGrp,
+            Set<ParcelUuid> activeSubscriptionGroups,
+            Map<Integer, ParcelUuid> subIdToGroupMap) {
         return triggerSubscriptionTrackerCbAndGetSnapshot(
-                activeSubscriptionGroups, subIdToGroupMap, true /* hasCarrierPrivileges */);
+                activeDataSubGrp,
+                activeSubscriptionGroups,
+                subIdToGroupMap,
+                true /* hasCarrierPrivileges */);
     }
 
     private TelephonySubscriptionSnapshot triggerSubscriptionTrackerCbAndGetSnapshot(
+            ParcelUuid activeDataSubGrp,
+            Set<ParcelUuid> activeSubscriptionGroups,
+            Map<Integer, ParcelUuid> subIdToGroupMap,
+            boolean hasCarrierPrivileges) {
+        return triggerSubscriptionTrackerCbAndGetSnapshot(
+                TEST_SUBSCRIPTION_ID,
+                activeDataSubGrp,
+                activeSubscriptionGroups,
+                subIdToGroupMap,
+                hasCarrierPrivileges);
+    }
+
+    private TelephonySubscriptionSnapshot triggerSubscriptionTrackerCbAndGetSnapshot(
+            int activeDataSubId,
+            ParcelUuid activeDataSubGrp,
+            Set<ParcelUuid> activeSubscriptionGroups,
+            Map<Integer, ParcelUuid> subIdToGroupMap,
+            boolean hasCarrierPrivileges) {
+        final TelephonySubscriptionSnapshot snapshot =
+                buildSubscriptionSnapshot(
+                        activeDataSubId,
+                        activeDataSubGrp,
+                        activeSubscriptionGroups,
+                        subIdToGroupMap,
+                        hasCarrierPrivileges);
+
+        final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
+        cb.onNewSnapshot(snapshot);
+
+        return snapshot;
+    }
+
+    private TelephonySubscriptionSnapshot buildSubscriptionSnapshot(
+            int activeDataSubId,
+            ParcelUuid activeDataSubGrp,
             Set<ParcelUuid> activeSubscriptionGroups,
             Map<Integer, ParcelUuid> subIdToGroupMap,
             boolean hasCarrierPrivileges) {
         final TelephonySubscriptionSnapshot snapshot = mock(TelephonySubscriptionSnapshot.class);
         doReturn(activeSubscriptionGroups).when(snapshot).getActiveSubscriptionGroups();
+        doReturn(activeDataSubGrp).when(snapshot).getActiveDataSubscriptionGroup();
+        doReturn(activeDataSubId).when(snapshot).getActiveDataSubscriptionId();
 
         final Set<String> privilegedPackages =
                 (activeSubscriptionGroups == null || activeSubscriptionGroups.isEmpty())
@@ -343,12 +389,19 @@
             return subIds;
         }).when(snapshot).getAllSubIdsInGroup(any());
 
-        final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
-        cb.onNewSnapshot(snapshot);
-
         return snapshot;
     }
 
+    private void setupActiveSubscription(ParcelUuid activeDataSubGrp) {
+        mVcnMgmtSvc.setLastSnapshot(
+                buildSubscriptionSnapshot(
+                        TEST_SUBSCRIPTION_ID,
+                        activeDataSubGrp,
+                        Collections.emptySet(),
+                        Collections.emptyMap(),
+                        true /* hasCarrierPrivileges */));
+    }
+
     private TelephonySubscriptionTrackerCallback getTelephonySubscriptionTrackerCallback() {
         final ArgumentCaptor<TelephonySubscriptionTrackerCallback> captor =
                 ArgumentCaptor.forClass(TelephonySubscriptionTrackerCallback.class);
@@ -372,25 +425,56 @@
 
     @Test
     public void testTelephonyNetworkTrackerCallbackStartsInstances() throws Exception {
+        // Add a record for a non-active SIM
+        mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
+
         TelephonySubscriptionSnapshot snapshot =
-                triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1));
+                triggerSubscriptionTrackerCbAndGetSnapshot(
+                        TEST_UUID_1, new ArraySet<>(Arrays.asList(TEST_UUID_1, TEST_UUID_2)));
         verify(mMockDeps)
                 .newVcnContext(
                         eq(mMockContext),
                         eq(mTestLooper.getLooper()),
                         any(VcnNetworkProvider.class),
                         anyBoolean());
+
+        // Verify that only the VCN for the active data SIM was started.
         verify(mMockDeps)
                 .newVcn(eq(mVcnContext), eq(TEST_UUID_1), eq(TEST_VCN_CONFIG), eq(snapshot), any());
+        verify(mMockDeps, never())
+                .newVcn(eq(mVcnContext), eq(TEST_UUID_2), eq(TEST_VCN_CONFIG), eq(snapshot), any());
+    }
+
+    @Test
+    public void testTelephonyNetworkTrackerCallbackSwitchingActiveDataStartsAndStopsInstances()
+            throws Exception {
+        // Add a record for a non-active SIM
+        mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
+        final Vcn vcn = startAndGetVcnInstance(TEST_UUID_1);
+
+        TelephonySubscriptionSnapshot snapshot =
+                triggerSubscriptionTrackerCbAndGetSnapshot(
+                        TEST_UUID_2, new ArraySet<>(Arrays.asList(TEST_UUID_1, TEST_UUID_2)));
+
+        // Verify that a new VCN for UUID_2 was started, and the old instance was torn down
+        // immediately
+        verify(mMockDeps)
+                .newVcn(eq(mVcnContext), eq(TEST_UUID_2), eq(TEST_VCN_CONFIG), eq(snapshot), any());
+        verify(vcn).teardownAsynchronously();
+        assertEquals(1, mVcnMgmtSvc.getAllVcns().size());
+        assertFalse(mVcnMgmtSvc.getAllVcns().containsKey(TEST_UUID_1));
+        assertTrue(mVcnMgmtSvc.getAllVcns().containsKey(TEST_UUID_2));
     }
 
     @Test
     public void testTelephonyNetworkTrackerCallbackStopsInstances() throws Exception {
+        setupActiveSubscription(TEST_UUID_2);
+
         final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
         final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2);
         mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
 
-        triggerSubscriptionTrackerCbAndGetSnapshot(Collections.emptySet());
+        triggerSubscriptionTrackerCbAndGetSnapshot(null, Collections.emptySet());
 
         // Verify teardown after delay
         mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
@@ -400,19 +484,76 @@
     }
 
     @Test
+    public void testTelephonyNetworkTrackerCallbackSwitchToNewSubscriptionImmediatelyTearsDown()
+            throws Exception {
+        setupActiveSubscription(TEST_UUID_2);
+
+        final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
+        final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2);
+
+        // Simulate switch to different default data subscription that does not have a VCN.
+        triggerSubscriptionTrackerCbAndGetSnapshot(
+                TEST_SUBSCRIPTION_ID,
+                null /* activeDataSubscriptionGroup */,
+                Collections.emptySet(),
+                Collections.emptyMap(),
+                false /* hasCarrierPrivileges */);
+        mTestLooper.dispatchAll();
+
+        verify(vcn).teardownAsynchronously();
+        assertEquals(0, mVcnMgmtSvc.getAllVcns().size());
+    }
+
+    /**
+     * Tests an intermediate state where carrier privileges are marked as lost before active data
+     * subId changes during a SIM ejection.
+     *
+     * <p>The expected outcome is that the VCN is torn down after a delay, as opposed to
+     * immediately.
+     */
+    @Test
+    public void testTelephonyNetworkTrackerCallbackLostCarrierPrivilegesBeforeActiveDataSubChanges()
+            throws Exception {
+        setupActiveSubscription(TEST_UUID_2);
+
+        final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
+        final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2);
+
+        // Simulate privileges lost
+        triggerSubscriptionTrackerCbAndGetSnapshot(
+                TEST_SUBSCRIPTION_ID,
+                TEST_UUID_2,
+                Collections.emptySet(),
+                Collections.emptyMap(),
+                false /* hasCarrierPrivileges */);
+
+        // Verify teardown after delay
+        mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
+        mTestLooper.dispatchAll();
+        verify(vcn).teardownAsynchronously();
+    }
+
+    @Test
     public void testTelephonyNetworkTrackerCallbackSimSwitchesDoNotKillVcnInstances()
             throws Exception {
+        setupActiveSubscription(TEST_UUID_2);
+
         final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
         final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2);
 
         // Simulate SIM unloaded
-        triggerSubscriptionTrackerCbAndGetSnapshot(Collections.emptySet());
+        triggerSubscriptionTrackerCbAndGetSnapshot(
+                INVALID_SUBSCRIPTION_ID,
+                null /* activeDataSubscriptionGroup */,
+                Collections.emptySet(),
+                Collections.emptyMap(),
+                false /* hasCarrierPrivileges */);
 
         // Simulate new SIM loaded right during teardown delay.
         mTestLooper.moveTimeForward(
                 VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2);
         mTestLooper.dispatchAll();
-        triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_2));
+        triggerSubscriptionTrackerCbAndGetSnapshot(TEST_UUID_2, Collections.singleton(TEST_UUID_2));
 
         // Verify that even after the full timeout duration, the VCN instance is not torn down
         mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
@@ -422,11 +563,13 @@
 
     @Test
     public void testTelephonyNetworkTrackerCallbackDoesNotKillNewVcnInstances() throws Exception {
+        setupActiveSubscription(TEST_UUID_2);
+
         final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
         final Vcn oldInstance = startAndGetVcnInstance(TEST_UUID_2);
 
         // Simulate SIM unloaded
-        triggerSubscriptionTrackerCbAndGetSnapshot(Collections.emptySet());
+        triggerSubscriptionTrackerCbAndGetSnapshot(null, Collections.emptySet());
 
         // Config cleared, SIM reloaded & config re-added right before teardown delay, staring new
         // vcnInstance.
@@ -434,6 +577,7 @@
                 VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2);
         mTestLooper.dispatchAll();
         mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2, TEST_PACKAGE_NAME);
+        triggerSubscriptionTrackerCbAndGetSnapshot(TEST_UUID_2, Collections.singleton(TEST_UUID_2));
         final Vcn newInstance = startAndGetVcnInstance(TEST_UUID_2);
 
         // Verify that new instance was different, and the old one was torn down
@@ -538,6 +682,31 @@
     }
 
     @Test
+    public void testSetVcnConfigNonActiveSimDoesNotStartVcn() throws Exception {
+        // Use a different UUID to simulate a new VCN config.
+        mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
+        assertEquals(TEST_VCN_CONFIG, mVcnMgmtSvc.getConfigs().get(TEST_UUID_2));
+        verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class));
+
+        verify(mMockDeps, never()).newVcn(any(), any(), any(), any(), any());
+    }
+
+    @Test
+    public void testSetVcnConfigActiveSimTearsDownExistingVcnsImmediately() throws Exception {
+        final Vcn vcn = startAndGetVcnInstance(TEST_UUID_1);
+
+        // Use a different UUID to simulate a new VCN config.
+        setupActiveSubscription(TEST_UUID_2);
+        mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
+
+        verify(mMockDeps, times(2)).newVcn(any(), any(), any(), any(), any());
+        verify(vcn).teardownAsynchronously();
+        assertEquals(1, mVcnMgmtSvc.getAllVcns().size());
+        assertFalse(mVcnMgmtSvc.getAllVcns().containsKey(TEST_UUID_1));
+        assertTrue(mVcnMgmtSvc.getAllVcns().containsKey(TEST_UUID_2));
+    }
+
+    @Test
     public void testSetVcnConfigTestModeRequiresPermission() throws Exception {
         doThrow(new SecurityException("Requires MANAGE_TEST_NETWORKS"))
                 .when(mMockContext)
@@ -561,7 +730,7 @@
 
     @Test
     public void testSetVcnConfigNotifiesStatusCallback() throws Exception {
-        triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_2));
+        triggerSubscriptionTrackerCbAndGetSnapshot(TEST_UUID_2, Collections.singleton(TEST_UUID_2));
 
         mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_2, mMockStatusCallback, TEST_PACKAGE_NAME);
         verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED);
@@ -635,7 +804,9 @@
     }
 
     @Test
-    public void testSetVcnConfigClearVcnConfigStartsUpdatesAndTeardsDownVcns() throws Exception {
+    public void testSetVcnConfigClearVcnConfigStartsUpdatesAndTearsDownVcns() throws Exception {
+        setupActiveSubscription(TEST_UUID_2);
+
         // Use a different UUID to simulate a new VCN config.
         mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
         final Map<ParcelUuid, Vcn> vcnInstances = mVcnMgmtSvc.getAllVcns();
@@ -646,12 +817,7 @@
 
         // Verify Vcn is started
         verify(mMockDeps)
-                .newVcn(
-                        eq(mVcnContext),
-                        eq(TEST_UUID_2),
-                        eq(TEST_VCN_CONFIG),
-                        eq(TelephonySubscriptionSnapshot.EMPTY_SNAPSHOT),
-                        any());
+                .newVcn(eq(mVcnContext), eq(TEST_UUID_2), eq(TEST_VCN_CONFIG), any(), any());
 
         // Verify Vcn is updated if it was previously started
         mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
@@ -693,7 +859,7 @@
 
         // Assert that if both UUID 1 and 2 are provisioned, the caller only gets ones that they are
         // privileged for.
-        triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1));
+        triggerSubscriptionTrackerCbAndGetSnapshot(TEST_UUID_1, Collections.singleton(TEST_UUID_1));
         final List<ParcelUuid> subGrps =
                 mVcnMgmtSvc.getConfiguredSubscriptionGroups(TEST_PACKAGE_NAME);
         assertEquals(Collections.singletonList(TEST_UUID_1), subGrps);
@@ -760,6 +926,7 @@
             int subId, ParcelUuid subGrp, boolean isVcnActive, boolean hasCarrierPrivileges) {
         mVcnMgmtSvc.systemReady();
         triggerSubscriptionTrackerCbAndGetSnapshot(
+                subGrp,
                 Collections.singleton(subGrp),
                 Collections.singletonMap(subId, subGrp),
                 hasCarrierPrivileges);
@@ -927,18 +1094,23 @@
 
     @Test
     public void testSubscriptionSnapshotUpdateNotifiesVcn() {
+        setupActiveSubscription(TEST_UUID_2);
+
         mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
         final Map<ParcelUuid, Vcn> vcnInstances = mVcnMgmtSvc.getAllVcns();
         final Vcn vcnInstance = vcnInstances.get(TEST_UUID_2);
 
         TelephonySubscriptionSnapshot snapshot =
-                triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_2));
+                triggerSubscriptionTrackerCbAndGetSnapshot(
+                        TEST_UUID_2, Collections.singleton(TEST_UUID_2));
 
         verify(vcnInstance).updateSubscriptionSnapshot(eq(snapshot));
     }
 
     @Test
     public void testAddNewVcnUpdatesPolicyListener() throws Exception {
+        setupActiveSubscription(TEST_UUID_2);
+
         mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
 
         mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
@@ -948,6 +1120,8 @@
 
     @Test
     public void testRemoveVcnUpdatesPolicyListener() throws Exception {
+        setupActiveSubscription(TEST_UUID_2);
+
         mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
         mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
 
@@ -958,10 +1132,13 @@
 
     @Test
     public void testVcnSubIdChangeUpdatesPolicyListener() throws Exception {
+        setupActiveSubscription(TEST_UUID_2);
+
         startAndGetVcnInstance(TEST_UUID_2);
         mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
 
         triggerSubscriptionTrackerCbAndGetSnapshot(
+                TEST_UUID_2,
                 Collections.singleton(TEST_UUID_2),
                 Collections.singletonMap(TEST_SUBSCRIPTION_ID, TEST_UUID_2));
 
@@ -988,7 +1165,8 @@
     private void verifyVcnSafeModeChangesNotifiesPolicyListeners(boolean enterSafeMode)
             throws Exception {
         TelephonySubscriptionSnapshot snapshot =
-                triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1));
+                triggerSubscriptionTrackerCbAndGetSnapshot(
+                        TEST_UUID_1, Collections.singleton(TEST_UUID_1));
 
         mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
 
@@ -1014,7 +1192,8 @@
             boolean hasPermissionsforSubGroup)
             throws Exception {
         TelephonySubscriptionSnapshot snapshot =
-                triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(subGroup));
+                triggerSubscriptionTrackerCbAndGetSnapshot(
+                        subGroup, Collections.singleton(subGroup));
 
         setupSubscriptionAndStartVcn(
                 TEST_SUBSCRIPTION_ID, subGroup, true /* isActive */, hasPermissionsforSubGroup);
@@ -1089,6 +1268,7 @@
         // timeout so the VCN goes inactive.
         final TelephonySubscriptionSnapshot snapshot =
                 triggerSubscriptionTrackerCbAndGetSnapshot(
+                        TEST_UUID_1,
                         Collections.singleton(TEST_UUID_1),
                         Collections.singletonMap(TEST_SUBSCRIPTION_ID, TEST_UUID_1),
                         false /* hasCarrierPrivileges */);
diff --git a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
index ca74638..1f0df62 100644
--- a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
+++ b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
@@ -21,6 +21,7 @@
 import static android.telephony.CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX;
 import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+import static android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener;
 
 import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback;
@@ -54,6 +55,7 @@
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
+import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyManager;
 import android.util.ArraySet;
 
@@ -178,6 +180,14 @@
         return captor.getValue();
     }
 
+    private ActiveDataSubscriptionIdListener getActiveDataSubscriptionIdListener() {
+        final ArgumentCaptor<TelephonyCallback> captor =
+                ArgumentCaptor.forClass(TelephonyCallback.class);
+        verify(mTelephonyManager).registerTelephonyCallback(any(), captor.capture());
+
+        return (ActiveDataSubscriptionIdListener) captor.getValue();
+    }
+
     private Intent buildTestBroadcastIntent(boolean hasValidSubscription) {
         Intent intent = new Intent(ACTION_CARRIER_CONFIG_CHANGED);
         intent.putExtra(EXTRA_SLOT_INDEX, TEST_SIM_SLOT_INDEX);
@@ -196,7 +206,14 @@
     private TelephonySubscriptionSnapshot buildExpectedSnapshot(
             Map<Integer, SubscriptionInfo> subIdToInfoMap,
             Map<ParcelUuid, Set<String>> privilegedPackages) {
-        return new TelephonySubscriptionSnapshot(subIdToInfoMap, privilegedPackages);
+        return new TelephonySubscriptionSnapshot(0, subIdToInfoMap, privilegedPackages);
+    }
+
+    private TelephonySubscriptionSnapshot buildExpectedSnapshot(
+            int activeSubId,
+            Map<Integer, SubscriptionInfo> subIdToInfoMap,
+            Map<ParcelUuid, Set<String>> privilegedPackages) {
+        return new TelephonySubscriptionSnapshot(activeSubId, subIdToInfoMap, privilegedPackages);
     }
 
     private void verifyNoActiveSubscriptions() {
@@ -250,6 +267,26 @@
     }
 
     @Test
+    public void testOnSubscriptionsChangedFired_onActiveSubIdsChanged() throws Exception {
+        setupReadySubIds();
+        setPrivilegedPackagesForMock(Collections.emptyList());
+
+        doReturn(TEST_SUBSCRIPTION_ID_2).when(mDeps).getActiveDataSubscriptionId();
+        final ActiveDataSubscriptionIdListener listener = getActiveDataSubscriptionIdListener();
+        listener.onActiveDataSubscriptionIdChanged(TEST_SUBSCRIPTION_ID_2);
+        mTestLooper.dispatchAll();
+
+        ArgumentCaptor<TelephonySubscriptionSnapshot> snapshotCaptor =
+                ArgumentCaptor.forClass(TelephonySubscriptionSnapshot.class);
+        verify(mCallback).onNewSnapshot(snapshotCaptor.capture());
+
+        TelephonySubscriptionSnapshot snapshot = snapshotCaptor.getValue();
+        assertNotNull(snapshot);
+        assertEquals(TEST_SUBSCRIPTION_ID_2, snapshot.getActiveDataSubscriptionId());
+        assertEquals(TEST_PARCEL_UUID, snapshot.getActiveDataSubscriptionGroup());
+    }
+
+    @Test
     public void testOnSubscriptionsChangedFired_WithReadySubidsNoPrivilegedPackages()
             throws Exception {
         setupReadySubIds();
@@ -371,7 +408,8 @@
     @Test
     public void testTelephonySubscriptionSnapshotGetGroupForSubId() throws Exception {
         final TelephonySubscriptionSnapshot snapshot =
-                new TelephonySubscriptionSnapshot(TEST_SUBID_TO_INFO_MAP, emptyMap());
+                new TelephonySubscriptionSnapshot(
+                        TEST_SUBSCRIPTION_ID_1, TEST_SUBID_TO_INFO_MAP, emptyMap());
 
         assertEquals(TEST_PARCEL_UUID, snapshot.getGroupForSubId(TEST_SUBSCRIPTION_ID_1));
         assertEquals(TEST_PARCEL_UUID, snapshot.getGroupForSubId(TEST_SUBSCRIPTION_ID_2));
@@ -380,7 +418,8 @@
     @Test
     public void testTelephonySubscriptionSnapshotGetAllSubIdsInGroup() throws Exception {
         final TelephonySubscriptionSnapshot snapshot =
-                new TelephonySubscriptionSnapshot(TEST_SUBID_TO_INFO_MAP, emptyMap());
+                new TelephonySubscriptionSnapshot(
+                        TEST_SUBSCRIPTION_ID_1, TEST_SUBID_TO_INFO_MAP, emptyMap());
 
         assertEquals(
                 new ArraySet<>(Arrays.asList(TEST_SUBSCRIPTION_ID_1, TEST_SUBSCRIPTION_ID_2)),
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index b97023a..a696b3a 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -127,7 +127,9 @@
 
     protected static final TelephonySubscriptionSnapshot TEST_SUBSCRIPTION_SNAPSHOT =
             new TelephonySubscriptionSnapshot(
-                    Collections.singletonMap(TEST_SUB_ID, TEST_SUB_INFO), Collections.EMPTY_MAP);
+                    TEST_SUB_ID,
+                    Collections.singletonMap(TEST_SUB_ID, TEST_SUB_INFO),
+                    Collections.EMPTY_MAP);
 
     @NonNull protected final Context mContext;
     @NonNull protected final TestLooper mTestLooper;