Merge "Add setNtnSmsSupported API in SatelliteManager." into main
diff --git a/src/java/com/android/internal/telephony/GsmCdmaPhone.java b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
index 2e1a89f..a91000e 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaPhone.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
@@ -42,7 +42,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
 import android.database.SQLException;
 import android.hardware.radio.modem.ImeiInfo;
 import android.net.Uri;
@@ -470,12 +469,6 @@
         }
     };
 
-    private boolean hasCalling() {
-        if (!TelephonyCapabilities.minimalTelephonyCdmCheck(mFeatureFlags)) return true;
-        return mContext.getPackageManager().hasSystemFeature(
-            PackageManager.FEATURE_TELEPHONY_CALLING);
-    }
-
     private void initOnce(CommandsInterface ci) {
         if (ci instanceof SimulatedRadioControl) {
             mSimulatedRadioControl = (SimulatedRadioControl) ci;
diff --git a/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java b/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
index 3141406..9eebc60 100644
--- a/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
+++ b/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
@@ -48,6 +48,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
+import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
 import com.android.internal.telephony.uicc.IccConstants;
@@ -1511,8 +1512,7 @@
     @VisibleForTesting
     public void notifyIfOutgoingEmergencySms(String destAddr) {
         Phone[] allPhones = mPhoneFactoryProxy.getPhones();
-        EmergencyNumber emergencyNumber = mPhone.getEmergencyNumberTracker().getEmergencyNumber(
-                destAddr);
+        EmergencyNumber emergencyNumber = getEmergencyNumber(mPhone, destAddr);
         if (emergencyNumber != null) {
             mPhone.notifyOutgoingEmergencySms(emergencyNumber);
         } else if (allPhones.length > 1) {
@@ -1522,8 +1522,7 @@
                 if (phone.getPhoneId() == mPhone.getPhoneId()) {
                     continue;
                 }
-                emergencyNumber = phone.getEmergencyNumberTracker()
-                        .getEmergencyNumber(destAddr);
+                emergencyNumber = getEmergencyNumber(phone, destAddr);
                 if (emergencyNumber != null) {
                     mPhone.notifyOutgoingEmergencySms(emergencyNumber);
                     break;
@@ -1532,6 +1531,13 @@
         }
     }
 
+    private EmergencyNumber getEmergencyNumber(Phone phone, String number) {
+        if (!phone.hasCalling()) return null;
+        EmergencyNumberTracker tracker = phone.getEmergencyNumberTracker();
+        if (tracker == null) return null;
+        return tracker.getEmergencyNumber(number);
+    }
+
     private void returnUnspecifiedFailure(PendingIntent pi) {
         if (pi != null) {
             try {
diff --git a/src/java/com/android/internal/telephony/InboundSmsHandler.java b/src/java/com/android/internal/telephony/InboundSmsHandler.java
index 011e67b..90766d6 100644
--- a/src/java/com/android/internal/telephony/InboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/InboundSmsHandler.java
@@ -772,8 +772,7 @@
         if (result != Intents.RESULT_SMS_HANDLED && result != Activity.RESULT_OK) {
             mMetrics.writeIncomingSmsError(mPhone.getPhoneId(), is3gpp2(), smsSource, result);
             mPhone.getSmsStats().onIncomingSmsError(is3gpp2(), smsSource, result,
-                    TelephonyManager.from(mContext)
-                            .isEmergencyNumber(smsb.getOriginatingAddress()));
+                    isEmergencyNumber(smsb.getOriginatingAddress()));
             if (mPhone != null) {
                 TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
                 if (telephonyAnalytics != null) {
@@ -1052,7 +1051,7 @@
             logeWithLocalLog(errorMsg, tracker.getMessageId());
             mPhone.getSmsStats().onIncomingSmsError(
                     is3gpp2(), tracker.getSource(), RESULT_SMS_NULL_PDU,
-                    TelephonyManager.from(mContext).isEmergencyNumber(tracker.getAddress()));
+                    isEmergencyNumber(tracker.getAddress()));
             if (mPhone != null) {
                 TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
                 if (telephonyAnalytics != null) {
@@ -1082,8 +1081,7 @@
                                 tracker.getMessageId());
                         mPhone.getSmsStats().onIncomingSmsWapPush(tracker.getSource(),
                                 messageCount, RESULT_SMS_NULL_MESSAGE, tracker.getMessageId(),
-                                TelephonyManager.from(mContext)
-                                        .isEmergencyNumber(tracker.getAddress()));
+                                isEmergencyNumber(tracker.getAddress()));
                         return false;
                     }
                 }
@@ -1118,8 +1116,7 @@
             mMetrics.writeIncomingWapPush(mPhone.getPhoneId(), tracker.getSource(),
                     format, timestamps, wapPushResult, tracker.getMessageId());
             mPhone.getSmsStats().onIncomingSmsWapPush(tracker.getSource(), messageCount,
-                    result, tracker.getMessageId(), TelephonyManager.from(mContext)
-                            .isEmergencyNumber(tracker.getAddress()));
+                    result, tracker.getMessageId(), isEmergencyNumber(tracker.getAddress()));
             // result is Activity.RESULT_OK if an ordered broadcast was sent
             if (result == Activity.RESULT_OK) {
                 return true;
@@ -1140,7 +1137,7 @@
                 format, timestamps, block, tracker.getMessageId());
         mPhone.getSmsStats().onIncomingSmsSuccess(is3gpp2(), tracker.getSource(),
                 messageCount, block, tracker.getMessageId(),
-                TelephonyManager.from(mContext).isEmergencyNumber(tracker.getAddress()));
+                isEmergencyNumber(tracker.getAddress()));
         CarrierRoamingSatelliteSessionStats sessionStats =
                 CarrierRoamingSatelliteSessionStats.getInstance(mPhone.getSubId());
         sessionStats.onIncomingSms(mPhone.getSubId());
@@ -1178,6 +1175,13 @@
         return true;
     }
 
+    private boolean isEmergencyNumber(String number) {
+        if (!mPhone.hasCalling()) return false;
+        TelephonyManager manager = TelephonyManager.from(mContext);
+        if (manager == null) return false;
+        return manager.isEmergencyNumber(number);
+    }
+
     /**
      * Processes the message part while the credential-encrypted storage is still locked.
      *
diff --git a/src/java/com/android/internal/telephony/Phone.java b/src/java/com/android/internal/telephony/Phone.java
index ea5d99d..ab9be76 100644
--- a/src/java/com/android/internal/telephony/Phone.java
+++ b/src/java/com/android/internal/telephony/Phone.java
@@ -661,7 +661,7 @@
         mSmsStorageMonitor = mTelephonyComponentFactory.inject(SmsStorageMonitor.class.getName())
                 .makeSmsStorageMonitor(this, mFeatureFlags);
         mSmsUsageMonitor = mTelephonyComponentFactory.inject(SmsUsageMonitor.class.getName())
-                .makeSmsUsageMonitor(context);
+                .makeSmsUsageMonitor(context, mFeatureFlags);
         mUiccController = UiccController.getInstance();
         mUiccController.registerForIccChanged(this, EVENT_ICC_CHANGED, null);
         mSimActivationTracker = mTelephonyComponentFactory
@@ -1967,6 +1967,13 @@
     }
 
     /**
+     * @return true if this device supports calling, false otherwise.
+     */
+    public boolean hasCalling() {
+        return TelephonyCapabilities.supportsTelephonyCalling(mFeatureFlags, mContext);
+    }
+
+    /**
      * Retrieves the EmergencyNumberTracker of the phone instance.
      */
     public EmergencyNumberTracker getEmergencyNumberTracker() {
diff --git a/src/java/com/android/internal/telephony/SMSDispatcher.java b/src/java/com/android/internal/telephony/SMSDispatcher.java
index 8a2d5bf..871cabc 100644
--- a/src/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/SMSDispatcher.java
@@ -2120,7 +2120,7 @@
             }
         }
 
-        if (mTelephonyManager.isEmergencyNumber(trackers[0].mDestAddress)) {
+        if (mPhone.hasCalling() && mTelephonyManager.isEmergencyNumber(trackers[0].mDestAddress)) {
             new AsyncEmergencyContactNotifier(mContext).execute();
         }
     }
diff --git a/src/java/com/android/internal/telephony/SmsController.java b/src/java/com/android/internal/telephony/SmsController.java
index e3c409d..051fbbd 100644
--- a/src/java/com/android/internal/telephony/SmsController.java
+++ b/src/java/com/android/internal/telephony/SmsController.java
@@ -276,6 +276,7 @@
         }
         UserHandle callingUser = Binder.getCallingUserHandle();
 
+
         Rlog.d(LOG_TAG, "sendTextForSubscriber caller=" + callingPackage);
 
         if (skipFdnCheck || skipShortCodeCheck) {
@@ -1194,8 +1195,9 @@
         }
 
         // Skip FDN check for emergency numbers
+        if (!TelephonyCapabilities.supportsTelephonyCalling(mFlags, mContext)) return false;
         TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
-        if (tm.isEmergencyNumber(destAddr)) {
+        if (tm != null && tm.isEmergencyNumber(destAddr)) {
             return false;
         }
 
diff --git a/src/java/com/android/internal/telephony/SmsDispatchersController.java b/src/java/com/android/internal/telephony/SmsDispatchersController.java
index 5bcb085..77fd1f6 100644
--- a/src/java/com/android/internal/telephony/SmsDispatchersController.java
+++ b/src/java/com/android/internal/telephony/SmsDispatchersController.java
@@ -829,8 +829,7 @@
 
         if (!tracker.mUsesImsServiceForIms) {
             if (isSmsDomainSelectionEnabled()) {
-                TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
-                boolean isEmergency = tm.isEmergencyNumber(tracker.mDestAddress);
+                boolean isEmergency = isEmergencyNumber(tracker.mDestAddress);
                 // This may be invoked by another thread, so this operation is posted and
                 // handled through the execution flow of SmsDispatchersController.
                 SomeArgs args = SomeArgs.obtain();
@@ -1219,8 +1218,7 @@
     private void handleSmsSentCompletedUsingDomainSelection(@NonNull String destAddr,
             long messageId, boolean success, boolean isOverIms, boolean isLastSmsPart) {
         if (mEmergencyStateTracker != null) {
-            TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
-            if (tm.isEmergencyNumber(destAddr)) {
+            if (isEmergencyNumber(destAddr)) {
                 mEmergencyStateTracker.endSms(String.valueOf(messageId), success,
                         isOverIms ? NetworkRegistrationInfo.DOMAIN_PS
                                   : NetworkRegistrationInfo.DOMAIN_CS,
@@ -1269,8 +1267,7 @@
      */
     private void handleSmsReceivedViaIms(@Nullable String origAddr) {
         if (mEmergencyStateTracker != null) {
-            TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
-            if (origAddr != null && tm.isEmergencyNumber(origAddr)) {
+            if (origAddr != null && isEmergencyNumber(origAddr)) {
                 mEmergencyStateTracker.onEmergencySmsReceived();
             }
         }
@@ -1288,7 +1285,9 @@
 
     private boolean isTestEmergencyNumber(String number) {
         try {
+            if (!mPhone.hasCalling()) return false;
             TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+            if (tm == null) return false;
             Map<Integer, List<EmergencyNumber>> eMap = tm.getEmergencyNumberList();
             return eMap.values().stream().flatMap(Collection::stream).anyMatch(eNumber ->
                     eNumber.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_TEST)
@@ -1878,8 +1877,7 @@
         logd("sendTextInternal: messageId=" + request.messageId
                  + ", uniqueMessageId=" + request.uniqueMessageId);
         if (isSmsDomainSelectionEnabled()) {
-            TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
-            boolean isEmergency = tm.isEmergencyNumber(request.destAddr);
+            boolean isEmergency = isEmergencyNumber(request.destAddr);
             sendSmsUsingDomainSelection(getDomainSelectionConnectionHolder(isEmergency),
                     request, "sendText");
             return;
@@ -2039,11 +2037,17 @@
         sendMultipartTextInternal(pendingRequest);
     }
 
+    private boolean isEmergencyNumber(String number) {
+        if (!mPhone.hasCalling()) return false;
+        TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+        if (tm == null) return false;
+        return tm.isEmergencyNumber(number);
+    }
+
     private void sendMultipartTextInternal(PendingRequest request) {
         logd("sendMultipartTextInternal: messageId=" + request.messageId);
         if (isSmsDomainSelectionEnabled()) {
-            TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
-            boolean isEmergency = tm.isEmergencyNumber(request.destAddr);
+            boolean isEmergency = isEmergencyNumber(request.destAddr);
             sendSmsUsingDomainSelection(getDomainSelectionConnectionHolder(isEmergency),
                     request, "sendMultipartText");
             return;
diff --git a/src/java/com/android/internal/telephony/SmsUsageMonitor.java b/src/java/com/android/internal/telephony/SmsUsageMonitor.java
index 8e4ac60..7fb5811 100644
--- a/src/java/com/android/internal/telephony/SmsUsageMonitor.java
+++ b/src/java/com/android/internal/telephony/SmsUsageMonitor.java
@@ -35,6 +35,7 @@
 import android.util.AtomicFile;
 import android.util.Xml;
 
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.util.XmlUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.telephony.Rlog;
@@ -113,6 +114,8 @@
     /** Context for retrieving regexes from XML resource. */
     private final Context mContext;
 
+    private final FeatureFlags mFeatureFlags;
+
     /** Country code for the cached short code pattern matcher. */
     private String mCurrentCountry;
 
@@ -255,8 +258,9 @@
      * @param context the context to use to load resources and get TelephonyManager service
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public SmsUsageMonitor(Context context) {
+    public SmsUsageMonitor(Context context, FeatureFlags flags) {
         mContext = context;
+        mFeatureFlags = flags;
         ContentResolver resolver = context.getContentResolver();
         mRoleManager = (RoleManager) mContext.getSystemService(Context.ROLE_SERVICE);
 
@@ -404,7 +408,8 @@
         synchronized (mSettingsObserverHandler) {
             TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
             // always allow emergency numbers
-            if (tm.isEmergencyNumber(destAddress)) {
+            if (TelephonyCapabilities.supportsTelephonyCalling(mFeatureFlags, mContext)
+                    && tm != null && tm.isEmergencyNumber(destAddress)) {
                 if (DBG) Rlog.d(TAG, "isEmergencyNumber");
                 return SmsManager.SMS_CATEGORY_NOT_SHORT_CODE;
             }
diff --git a/src/java/com/android/internal/telephony/TelephonyCapabilities.java b/src/java/com/android/internal/telephony/TelephonyCapabilities.java
index 71d3b14..b650b43 100644
--- a/src/java/com/android/internal/telephony/TelephonyCapabilities.java
+++ b/src/java/com/android/internal/telephony/TelephonyCapabilities.java
@@ -18,6 +18,8 @@
 
 import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.Build;
 import android.os.SystemProperties;
 
@@ -209,4 +211,14 @@
 
         return featureFlags.minimalTelephonyCdmCheck();
     }
+
+    /**
+     * @return true if this device supports telephony calling, false if it does not.
+     */
+    public static boolean supportsTelephonyCalling(@NonNull FeatureFlags featureFlags,
+            Context context) {
+        if (!TelephonyCapabilities.minimalTelephonyCdmCheck(featureFlags)) return true;
+        return context.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_CALLING);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
index 10e97b6..b4a3ee6 100644
--- a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
+++ b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
@@ -301,8 +301,8 @@
         return new SmsStorageMonitor(phone, flags);
     }
 
-    public SmsUsageMonitor makeSmsUsageMonitor(Context context) {
-        return new SmsUsageMonitor(context);
+    public SmsUsageMonitor makeSmsUsageMonitor(Context context, FeatureFlags flags) {
+        return new SmsUsageMonitor(context, flags);
     }
 
     public ServiceStateTracker makeServiceStateTracker(GsmCdmaPhone phone, CommandsInterface ci,
diff --git a/src/java/com/android/internal/telephony/data/PhoneSwitcher.java b/src/java/com/android/internal/telephony/data/PhoneSwitcher.java
index 62d33d9..079e705 100644
--- a/src/java/com/android/internal/telephony/data/PhoneSwitcher.java
+++ b/src/java/com/android/internal/telephony/data/PhoneSwitcher.java
@@ -1834,16 +1834,18 @@
         if (phone == null) {
             return false;
         }
-
+        Call bgCall = phone.getBackgroundCall();
+        Call fgCall = phone.getForegroundCall();
+        if (bgCall == null || fgCall == null) {
+            return false;
+        }
         // A phone in voice call might trigger data being switched to it.
         // Exclude dialing to give modem time to process an EMC first before dealing with DDS switch
         // Include alerting because modem RLF leads to delay in switch, so carrier required to
         // switch in alerting phase.
         // TODO: check ringing call for vDADA
-        return (!phone.getBackgroundCall().isIdle()
-                && phone.getBackgroundCall().getState() != Call.State.DIALING)
-                || (!phone.getForegroundCall().isIdle()
-                && phone.getForegroundCall().getState() != Call.State.DIALING);
+        return (!bgCall.isIdle() && bgCall.getState() != Call.State.DIALING)
+                || (!fgCall.isIdle() && fgCall.getState() != Call.State.DIALING);
     }
 
     private void updateHalCommandToUse() {
diff --git a/src/java/com/android/internal/telephony/ims/ImsResolver.java b/src/java/com/android/internal/telephony/ims/ImsResolver.java
index d8b0fd4..b95911f 100644
--- a/src/java/com/android/internal/telephony/ims/ImsResolver.java
+++ b/src/java/com/android/internal/telephony/ims/ImsResolver.java
@@ -164,14 +164,19 @@
     }
 
     private static class OverrideConfig {
+        public final String packageName;
         public final int slotId;
+        public final int userId;
         public final boolean isCarrierService;
-        public final Map<Integer, String> featureTypeToPackageMap;
+        public final int[] featureTypes;
 
-        OverrideConfig(int slotIndex, boolean isCarrier, Map<Integer, String> feature) {
+        OverrideConfig(String pkgName, int slotIndex, int userIndex, boolean isCarrier,
+                int[] features) {
+            packageName = pkgName;
             slotId = slotIndex;
+            userId = userIndex;
             isCarrierService = isCarrier;
-            featureTypeToPackageMap = feature;
+            featureTypes = features;
         }
     }
 
@@ -552,11 +557,15 @@
                 }
                 case HANDLER_OVERRIDE_IMS_SERVICE_CONFIG: {
                     OverrideConfig config = (OverrideConfig) msg.obj;
+                    setPackageNameUserOverride(config.packageName, config.userId);
+                    Map<Integer, String> featureConfig = new HashMap<>();
+                    for (int featureType : config.featureTypes) {
+                        featureConfig.put(featureType, config.packageName);
+                    }
                     if (config.isCarrierService) {
-                        overrideCarrierService(config.slotId,
-                                config.featureTypeToPackageMap);
+                        overrideCarrierService(config.slotId, featureConfig);
                     } else {
-                        overrideDeviceService(config.featureTypeToPackageMap);
+                        overrideDeviceService(featureConfig);
                     }
                     break;
                 }
@@ -618,6 +627,8 @@
     // Array index corresponds to slot, per slot there is a feature->package name mapping.
     // should only be accessed from handler
     private final SparseArray<SparseArray<String>> mOverrideServices;
+    //Used during testing, restricts the ImsService to be bound on a specific user.
+    private final Map<String, UserHandle> mImsServiceTestUserRestrictions = new HashMap<>();
     // Outer array index corresponds to Slot Id, Maps ImsFeature.FEATURE->bound ImsServiceController
     // Locked on mBoundServicesLock
     private final SparseArray<SparseArray<ImsServiceController>> mBoundImsServicesByFeature;
@@ -899,14 +910,15 @@
     }
 
     // Used for testing only.
-    public boolean overrideImsServiceConfiguration(int slotId, boolean isCarrierService,
-            Map<Integer, String> featureConfig) {
+    public boolean overrideImsServiceConfiguration(String packageName, int slotId, int userId,
+            boolean isCarrierService, int[] overrideFeatureTypes) {
         if (slotId < 0 || slotId >= mNumSlots) {
             Log.w(TAG, "overrideImsServiceConfiguration: invalid slotId!");
             return false;
         }
 
-        OverrideConfig overrideConfig = new OverrideConfig(slotId, isCarrierService, featureConfig);
+        OverrideConfig overrideConfig = new OverrideConfig(packageName, slotId, userId,
+                isCarrierService, overrideFeatureTypes);
         Message.obtain(mHandler, HANDLER_OVERRIDE_IMS_SERVICE_CONFIG, overrideConfig)
                 .sendToTarget();
         return true;
@@ -947,10 +959,14 @@
     }
 
     // not synchronized, access in handler ONLY.
-    private void removeOverridePackageName(int slotId) {
+    private Set<String> removeOverridePackageName(int slotId) {
+        Set<String> removedOverrides = new HashSet<>();
         for (int f = ImsFeature.FEATURE_EMERGENCY_MMTEL; f < ImsFeature.FEATURE_MAX; f++) {
-            getOverridePackageName(slotId).remove(f);
+            SparseArray<String> overrides = getOverridePackageName(slotId);
+            String packageName = overrides.removeReturnOld(f);
+            if (packageName != null) removedOverrides.add(packageName);
         }
+        return removedOverrides;
     }
 
     // not synchronized, access in handler ONLY.
@@ -960,6 +976,22 @@
     }
 
     // not synchronized, access in handler ONLY.
+    private void setPackageNameUserOverride(String packageName, int userId) {
+        if (packageName == null || packageName.isEmpty() || userId == UserHandle.USER_NULL) return;
+        Log.i(TAG, "setPackageNameUserOverride: set for " + packageName + ", user= " + userId);
+        mImsServiceTestUserRestrictions.put(packageName, UserHandle.of(userId));
+    }
+
+    // not synchronized, access in handler ONLY.
+    private void clearPackageNameUserOverride(String packageName) {
+        UserHandle handle = mImsServiceTestUserRestrictions.remove(packageName);
+        if (handle != null) {
+            Log.i(TAG, "clearPackageNameUserOverride: cleared for " + packageName
+                    + "on user " + handle);
+        }
+    }
+
+    // not synchronized, access in handler ONLY.
     private @Nullable String getOverridePackageName(int slotId,
             @ImsFeature.FeatureType int featureType) {
         return getOverridePackageName(slotId).get(featureType);
@@ -1266,6 +1298,7 @@
                     match.controllerFactory);
             ImsServiceInfo newMatch = imsServices.isEmpty() ? null : imsServices.getFirst();
             if (newMatch == null) {
+                clearPackageNameUserOverride(match.name.getPackageName());
                 // The package doesn't exist anymore on any user, so remove
                 mInstalledServicesCache.remove(match.name);
                 mEventLog.log("maybeRemovedImsService - removing ImsService: " + match);
@@ -1327,11 +1360,10 @@
     private List<Integer> getSlotsForActiveCarrierService(ImsServiceInfo info) {
         if (info == null) return Collections.emptyList();
         if (mFeatureFlags.imsResolverUserAware()) {
-            Set<UserHandle> activeUsers = getActiveUsers();
-            activeUsers.retainAll(info.users);
-            if (activeUsers.isEmpty()) {
+            UserHandle activeUser = getUserForBind(info);
+            if (activeUser == null) {
                 Log.d(TAG, "getSlotsForActiveCarrierService: ImsService " + info.name + "is not "
-                        + "configured to run for users " + activeUsers + ", skipping...");
+                        + "configured to run for any users, skipping...");
                 return Collections.emptyList();
             }
         }
@@ -1542,7 +1574,10 @@
     private void clearCarrierServiceOverrides(int slotId) {
         Log.i(TAG, "clearing carrier ImsService overrides");
         mEventLog.log("clearing carrier ImsService overrides");
-        removeOverridePackageName(slotId);
+        Set<String> removedPackages = removeOverridePackageName(slotId);
+        for (String pkg : removedPackages) {
+            clearPackageNameUserOverride(pkg);
+        }
         carrierConfigChanged(slotId, getSubId(slotId));
     }
 
@@ -1560,6 +1595,7 @@
                         + oldPackageName + " -> " + overridePackageName);
                 mEventLog.log("overrideDeviceService - device package changed (override): "
                         + oldPackageName + " -> " + overridePackageName);
+                clearPackageNameUserOverride(oldPackageName);
                 setDeviceConfiguration(overridePackageName, featureType);
                 ImsServiceInfo info = getVisibleImsServiceInfoFromCache(overridePackageName);
                 if (info == null || info.featureFromMetadata) {
@@ -1908,6 +1944,13 @@
         List<UserHandle> activeUsers = getActiveUsers().stream()
                 .filter(info.users::contains).toList();
         if (activeUsers.isEmpty()) return null;
+        // If there is a test restriction in place for this package, prioritize that restriction
+        UserHandle testRestriction = mImsServiceTestUserRestrictions.getOrDefault(
+                info.name.getPackageName(), null);
+        if (testRestriction != null && activeUsers.stream()
+                .anyMatch(u -> Objects.equals(u, testRestriction))) {
+            return testRestriction;
+        }
         // Prioritize the User that Telephony is in, since it is always running
         if (activeUsers.stream()
                 .anyMatch(u -> Objects.equals(u, mContext.getUser()))) {
@@ -1955,11 +1998,10 @@
             return match;
         }
         if (match == null) return null;
-        Set<UserHandle> activeUsers = getActiveUsers();
-        activeUsers.retainAll(match.users);
+        UserHandle targetUser = getUserForBind(match);
         Log.d(TAG, "getVisibleImsServiceInfoFromCache: " + packageName + ", match=" + match
-                + ", activeUsers=" + activeUsers);
-        if (!activeUsers.isEmpty()) return match; else return null;
+                + ", targetUser=" + targetUser);
+        if (targetUser != null) return match; else return null;
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteController.java b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
index bbf9b71..f181852 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteController.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
@@ -3947,14 +3947,25 @@
             return false;
         }
 
-        if (!isCarrierRoamingNtnEligible(phone)) {
-            plogd("isInCarrierRoamingNbIotNtn: phone associated with subId "
-                      + phone.getSubId()
-                      + " is not carrier roaming ntn eligible.");
+        if (phone == null) {
+            plogd("isInCarrierRoamingNbIotNtn: phone is null");
             return false;
         }
 
         int subId = phone.getSubId();
+        if (!isSatelliteSupportedViaCarrier(subId)) {
+            plogd("isInCarrierRoamingNbIotNtn[phoneId=" + phone.getPhoneId()
+                    + "]: satellite is not supported via carrier");
+            return false;
+        }
+
+        int carrierRoamingNtnConnectType = getCarrierRoamingNtnConnectType(subId);
+        if (carrierRoamingNtnConnectType != CARRIER_ROAMING_NTN_CONNECT_MANUAL) {
+            plogd("isInCarrierRoamingNbIotNtn[phoneId=" + phone.getPhoneId() + "]: not manual "
+                    + "connect. carrierRoamingNtnConnectType = " + carrierRoamingNtnConnectType);
+            return false;
+        }
+
         if (subId != getSelectedSatelliteSubId()) {
             plogd("isInCarrierRoamingNbIotNtn: subId=" + subId
                     + " does not match satellite subId=" + getSelectedSatelliteSubId());
@@ -4309,7 +4320,7 @@
         RequestSatelliteEnabledArgument argument =
                 (RequestSatelliteEnabledArgument) request.argument;
         handlePersistentLoggingOnSessionStart(argument);
-        selectBindingSatelliteSubscription(true);
+        selectBindingSatelliteSubscription(argument.enableSatellite);
         SatelliteModemEnableRequestAttributes enableRequestAttributes =
                     createModemEnableRequest(argument);
         if (enableRequestAttributes == null) {
@@ -7219,6 +7230,11 @@
             return false;
         }
 
+        if (!mIsRadioOn) {
+            plogd("isCarrierRoamingNtnEligible: radio is off");
+            return false;
+        }
+
         if (phone == null) {
             plogd("isCarrierRoamingNtnEligible: phone is null");
             return false;
diff --git a/src/java/com/android/internal/telephony/uicc/UiccController.java b/src/java/com/android/internal/telephony/uicc/UiccController.java
index dd71c44..9db25b6 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccController.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccController.java
@@ -80,6 +80,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 import java.util.UUID;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
@@ -162,8 +163,7 @@
     // this needs to be here, because on bootup we dont know which index maps to which UiccSlot
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private CommandsInterface[] mCis;
-    @VisibleForTesting
-    public UiccSlot[] mUiccSlots;
+    private UiccSlot[] mUiccSlots;
     private int[] mPhoneIdToSlotId;
     private boolean mIsSlotStatusSupported = true;
 
@@ -491,6 +491,27 @@
     }
 
     /**
+     * Set UiccSlot object for a specific physical slot index on the device.
+     *
+     * This is only supposed to be used internally and by unit tests.
+     *
+     * @param slotId Slot index
+     * @param slot Slot object
+     */
+    @VisibleForTesting
+    public void setUiccSlot(int slotId, @NonNull UiccSlot slot) {
+        synchronized (mLock) {
+            if (!isValidSlotIndex(slotId)) {
+                throw new ArrayIndexOutOfBoundsException("Invalid slot index: " + slotId);
+            }
+            if (mUiccSlots[slotId] != null) {
+                mUiccSlots[slotId].dispose();
+            }
+            mUiccSlots[slotId] = Objects.requireNonNull(slot);
+        }
+    }
+
+    /**
      * API to get UiccSlot object for a given phone id
      * @return UiccSlot object for the given phone id
      */
@@ -1076,7 +1097,7 @@
                 log("Creating mUiccSlots[" + slotId + "]; mUiccSlots.length = "
                         + mUiccSlots.length);
             }
-            mUiccSlots[slotId] = new UiccSlot(mContext, true);
+            setUiccSlot(slotId, new UiccSlot(mContext, true));
         }
 
         mUiccSlots[slotId].update(mCis[index], status, index, slotId);
@@ -1353,7 +1374,7 @@
                 if (VDBG) {
                     log("Creating mUiccSlot[" + i + "]; mUiccSlots.length = " + mUiccSlots.length);
                 }
-                mUiccSlots[i] = new UiccSlot(mContext, isActive);
+                setUiccSlot(i, new UiccSlot(mContext, isActive));
             }
 
             if (isActive) { // check isActive flag so that we don't have to iterate through all
@@ -1803,6 +1824,17 @@
         return mCardStrings;
     }
 
+    /**
+     * Release resources. Must be called each time this class is used.
+     */
+    @VisibleForTesting
+    public void dispose() {
+        for (var slot : mUiccSlots) {
+            slot.dispose();
+        }
+        mUiccSlots = null;
+    }
+
     public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
         IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
         pw.println("mIsCdmaSupported=" + isCdmaSupported(mContext));
diff --git a/src/java/com/android/internal/telephony/uicc/UiccPort.java b/src/java/com/android/internal/telephony/uicc/UiccPort.java
index 905db70..e1228e9 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccPort.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccPort.java
@@ -440,7 +440,13 @@
      * removal or modem reset. The obsoleted records may trigger a redundant release of logical
      * channel that may have been assigned to other client.
      */
+    @SuppressWarnings("GuardedBy")
     private void cleanupOpenLogicalChannelRecordsIfNeeded() {
+        // This check may raise GuardedBy warning, but we need it as long as this method is called
+        // from finalize(). We can remove it from there once UiccPort is fully protected against
+        // resource leak (e.g. with CloseGuard) and all (direct and indirect) users are fixed.
+        if (mOpenChannelRecords == null) return;
+
         synchronized (mOpenChannelRecords) {
             for (OpenLogicalChannelRecord record : mOpenChannelRecords) {
                 if (DBG) log("Clean up " + record);
diff --git a/src/java/com/android/internal/telephony/uicc/UiccSlot.java b/src/java/com/android/internal/telephony/uicc/UiccSlot.java
index db10271..d986c93 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccSlot.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccSlot.java
@@ -391,6 +391,13 @@
         }
     }
 
+    /**
+     * Release resources. Must be called each time this class is used.
+     */
+    public void dispose() {
+        nullifyUiccCard(false);
+    }
+
     public boolean isStateUnknown() {
         // CardState is not specific to any port index, use default port.
         CardState cardState = mCardState.get(TelephonyManager.DEFAULT_PORT_INDEX);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SmsUsageMonitorShortCodeTest.java b/tests/telephonytests/src/com/android/internal/telephony/SmsUsageMonitorShortCodeTest.java
index 3b637c9..1e1e43f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SmsUsageMonitorShortCodeTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SmsUsageMonitorShortCodeTest.java
@@ -26,6 +26,8 @@
 
 import android.os.Looper;
 
+import com.android.internal.telephony.flags.FeatureFlagsImpl;
+
 import org.junit.Ignore;
 
 /**
@@ -465,7 +467,8 @@
         if (Looper.myLooper() == null) {
             Looper.prepare();
         }
-        SmsUsageMonitor monitor = new SmsUsageMonitor(TestApplication.getAppContext());
+        SmsUsageMonitor monitor = new SmsUsageMonitor(TestApplication.getAppContext(),
+                new FeatureFlagsImpl());
         for (ShortCodeTest test : sShortCodeTests) {
             assertEquals("country: " + test.countryIso + " number: " + test.address,
                     test.category, monitor.checkDestination(test.address, test.countryIso));
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
index d80c9a2..88eea32 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
@@ -747,6 +747,8 @@
         doReturn(mDataRetryManager).when(mDataNetworkController).getDataRetryManager();
         doReturn(mCarrierPrivilegesTracker).when(mPhone).getCarrierPrivilegesTracker();
         doReturn(0).when(mPhone).getPhoneId();
+        doReturn(true).when(mPhone).hasCalling();
+        doReturn(true).when(mPhone2).hasCalling();
 
         //mUiccController
         doReturn(mUiccCardApplication3gpp).when(mUiccController).getUiccCardApplication(anyInt(),
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardTest.java
index 33b195c..bfdca0f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardTest.java
@@ -69,6 +69,7 @@
 
     @After
     public void tearDown() throws Exception {
+        mUiccCard.dispose();
         mUiccCard = null;
         mIccIoResult = null;
         super.tearDown();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccControllerTest.java
index 58a8153..3343570 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccControllerTest.java
@@ -15,8 +15,6 @@
  */
 package com.android.internal.telephony.uicc;
 
-import static junit.framework.Assert.fail;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -131,6 +129,7 @@
 
     @After
     public void tearDown() throws Exception {
+        if (mUiccControllerUT != null) mUiccControllerUT.dispose();
         mUiccControllerUT = null;
         super.tearDown();
     }
@@ -145,6 +144,7 @@
                 com.android.internal.R.array.non_removable_euicc_slots,
                 nonRemovableEuiccSlots);
         replaceInstance(UiccController.class, "mInstance", null, null);
+        mUiccControllerUT.dispose();
         mUiccControllerUT = UiccController.make(mContext, mFeatureFlags);
         processAllMessages();
     }
@@ -250,7 +250,7 @@
         mUiccControllerUT.mContext = InstrumentationRegistry.getContext();
 
         // Mock out UiccSlots
-        mUiccControllerUT.mUiccSlots[0] = mMockSlot;
+        mUiccControllerUT.setUiccSlot(0, mMockSlot);
         doReturn(mMockCard).when(mMockSlot).getUiccCard();
         doReturn(mMockPort).when(mMockCard).getUiccPort(0);
         doReturn("A1B2C3D4").when(mMockPort).getIccId();
@@ -296,7 +296,7 @@
         mUiccControllerUT.mContext = InstrumentationRegistry.getContext();
 
         // Mock out UiccSlots
-        mUiccControllerUT.mUiccSlots[0] = mMockSlot;
+        mUiccControllerUT.setUiccSlot(0, mMockSlot);
         doReturn(true).when(mMockSlot).isEuicc();
 
         // simulate slot status loaded so that the UiccController sets the card ID
@@ -323,7 +323,7 @@
         mUiccControllerUT.mContext = InstrumentationRegistry.getContext();
 
         // Mock out UiccSlots
-        mUiccControllerUT.mUiccSlots[0] = mMockSlot;
+        mUiccControllerUT.setUiccSlot(0, mMockSlot);
         doReturn(true).when(mMockSlot).isEuicc();
 
         // simulate slot status loaded so that the UiccController sets the card ID
@@ -351,7 +351,7 @@
         mUiccControllerUT.mContext = InstrumentationRegistry.getContext();
 
         // Mock out UiccSlots
-        mUiccControllerUT.mUiccSlots[0] = mMockSlot;
+        mUiccControllerUT.setUiccSlot(0, mMockSlot);
         doReturn(false).when(mMockSlot).isEuicc();
         doReturn(mMockCard).when(mMockSlot).getUiccCard();
         doReturn("ASDF1234").when(mMockCard).getCardId();
@@ -402,7 +402,7 @@
         mUiccControllerUT.mContext = InstrumentationRegistry.getContext();
 
         // Mock out UiccSlots
-        mUiccControllerUT.mUiccSlots[0] = mMockSlot;
+        mUiccControllerUT.setUiccSlot(0, mMockSlot);
         doReturn(false).when(mMockSlot).isEuicc();
         doReturn(mMockCard).when(mMockSlot).getUiccCard();
         doReturn("ASDF1234").when(mMockCard).getCardId();
@@ -453,7 +453,7 @@
         mUiccControllerUT.mContext = InstrumentationRegistry.getContext();
 
         // Mock out UiccSlots
-        mUiccControllerUT.mUiccSlots[0] = mMockSlot;
+        mUiccControllerUT.setUiccSlot(0, mMockSlot);
         doReturn(true).when(mMockSlot).isEuicc();
         doReturn(null).when(mMockSlot).getUiccCard();
         doReturn(false).when(mMockSlot).isRemovable();
@@ -499,21 +499,17 @@
      * The default eUICC should not be the removable slot if there is a built-in eUICC.
      */
     @Test
-    public void testDefaultEuiccIsNotRemovable() {
-        try {
-            reconfigureSlots(2, new int[]{ 1 } /* non-removable slot */);
-        } catch (Exception e) {
-            fail("Unable to reconfigure slots.");
-        }
+    public void testDefaultEuiccIsNotRemovable() throws Exception {
+        reconfigureSlots(2, new int[]{ 1 } /* non-removable slot */);
 
         // Give UiccController a real context so it can use shared preferences
         mUiccControllerUT.mContext = InstrumentationRegistry.getContext();
 
         // Mock out UiccSlots so that [0] is a removable eUICC and [1] is built-in
-        mUiccControllerUT.mUiccSlots[0] = mMockRemovableEuiccSlot;
+        mUiccControllerUT.setUiccSlot(0, mMockRemovableEuiccSlot);
         doReturn(true).when(mMockRemovableEuiccSlot).isEuicc();
         doReturn(true).when(mMockRemovableEuiccSlot).isRemovable();
-        mUiccControllerUT.mUiccSlots[1] = mMockSlot;
+        mUiccControllerUT.setUiccSlot(1, mMockSlot);
         doReturn(true).when(mMockSlot).isEuicc();
         doReturn(false).when(mMockSlot).isRemovable();
 
@@ -550,21 +546,17 @@
      * not depend on the order of the slots.
      */
     @Test
-    public void testDefaultEuiccIsNotRemovable_swapSlotOrder() {
-        try {
-            reconfigureSlots(2, new int[]{ 0 } /* non-removable slot */);
-        } catch (Exception e) {
-            fail("Unable to reconfigure slots.");
-        }
+    public void testDefaultEuiccIsNotRemovable_swapSlotOrder() throws Exception {
+        reconfigureSlots(2, new int[]{ 0 } /* non-removable slot */);
 
         // Give UiccController a real context so it can use shared preferences
         mUiccControllerUT.mContext = InstrumentationRegistry.getContext();
 
         // Mock out UiccSlots so that [0] is a built-in eUICC and [1] is removable
-        mUiccControllerUT.mUiccSlots[0] = mMockSlot;
+        mUiccControllerUT.setUiccSlot(0, mMockSlot);
         doReturn(true).when(mMockSlot).isEuicc();
         doReturn(false).when(mMockSlot).isRemovable();
-        mUiccControllerUT.mUiccSlots[1] = mMockRemovableEuiccSlot;
+        mUiccControllerUT.setUiccSlot(1, mMockRemovableEuiccSlot);
         doReturn(true).when(mMockRemovableEuiccSlot).isEuicc();
         doReturn(true).when(mMockRemovableEuiccSlot).isRemovable();
 
@@ -603,21 +595,17 @@
      * the removable eUICC.
      */
     @Test
-    public void testDefaultEuiccIsNotRemovable_EuiccIsInactive() {
-        try {
-            reconfigureSlots(2, new int[]{ 1 } /* non-removable slot */);
-        } catch (Exception e) {
-            fail();
-        }
+    public void testDefaultEuiccIsNotRemovable_EuiccIsInactive() throws Exception {
+        reconfigureSlots(2, new int[]{ 1 } /* non-removable slot */);
 
         // Give UiccController a real context so it can use shared preferences
         mUiccControllerUT.mContext = InstrumentationRegistry.getContext();
 
         // Mock out UiccSlots. Slot 0 is inactive here.
-        mUiccControllerUT.mUiccSlots[0] = mMockSlot;
+        mUiccControllerUT.setUiccSlot(0, mMockSlot);
         doReturn(true).when(mMockSlot).isEuicc();
         doReturn(false).when(mMockSlot).isRemovable();
-        mUiccControllerUT.mUiccSlots[1] = mMockRemovableEuiccSlot;
+        mUiccControllerUT.setUiccSlot(1, mMockRemovableEuiccSlot);
         doReturn(true).when(mMockRemovableEuiccSlot).isEuicc();
         doReturn(true).when(mMockRemovableEuiccSlot).isRemovable();
 
@@ -669,7 +657,7 @@
         mUiccControllerUT.mContext = InstrumentationRegistry.getContext();
 
         // Mock out UiccSlots
-        mUiccControllerUT.mUiccSlots[0] = mMockSlot;
+        mUiccControllerUT.setUiccSlot(0, mMockSlot);
         doReturn(true).when(mMockSlot).isEuicc();
         doReturn(null).when(mMockSlot).getUiccCard();
         //doReturn("123451234567890").when(mMockSlot).getIccId();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccPortTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccPortTest.java
index a2b42af..47b7c53 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccPortTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccPortTest.java
@@ -82,6 +82,7 @@
 
     @After
     public void tearDown() throws Exception {
+        mUiccPort.dispose();
         mUiccPort = null;
         mIccIoResult = null;
         super.tearDown();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccSlotTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccSlotTest.java
index 671f273..8449ecc 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccSlotTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccSlotTest.java
@@ -103,6 +103,7 @@
         mTestHandlerThread = null;
         mTestHandler.removeCallbacksAndMessages(null);
         mTestHandler = null;
+        mUiccSlot.dispose();
         mUiccSlot = null;
         super.tearDown();
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccStateChangedLauncherTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccStateChangedLauncherTest.java
index f88bc1e..c9b159c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccStateChangedLauncherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccStateChangedLauncherTest.java
@@ -51,6 +51,8 @@
     private static final int CARD_COUNT = 1;
     private static final String PROVISIONING_PACKAGE_NAME = "test.provisioning.package";
 
+    private UiccCard mUiccCardToDispose;
+
     // Mocked classes
     private Resources mResources;
 
@@ -77,6 +79,9 @@
     @After
     public void tearDown() throws Exception {
         super.tearDown();
+
+        if (mUiccCardToDispose != null) mUiccCardToDispose.dispose();
+        mUiccCardToDispose = null;
     }
 
     @Test @SmallTest
@@ -99,7 +104,7 @@
         msg.what = integerArgumentCaptor.getValue();
 
         // The first broadcast should be sent after initialization.
-        UiccCard card = new UiccCard(mContext, mSimulatedCommands,
+        UiccCard card = mUiccCardToDispose = new UiccCard(mContext, mSimulatedCommands,
                 makeCardStatus(CardState.CARDSTATE_PRESENT), 0 /* phoneId */, new Object(),
                 IccSlotStatus.MultipleEnabledProfilesMode.NONE);
         when(UiccController.getInstance().getUiccCardForPhone(0)).thenReturn(card);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccCardTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccCardTest.java
index bcb5c4c..560c9aa 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccCardTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccCardTest.java
@@ -109,7 +109,10 @@
             mHandler.removeCallbacksAndMessages(null);
             mHandler = null;
         }
-        mEuiccCard = null;
+        if (mEuiccCard != null) {
+            mEuiccCard.dispose();
+            mEuiccCard = null;
+        }
         super.tearDown();
     }
 
@@ -132,6 +135,7 @@
     @Test
     public void testPassEidInConstructor() {
         mMockIccCardStatus.eid = "1A2B3C4D";
+        mEuiccCard.dispose();
         mEuiccCard = new EuiccCard(mContextFixture.getTestDouble(), mMockCi,
                 mMockIccCardStatus, 0 /* phoneId */, new Object(),
                 IccSlotStatus.MultipleEnabledProfilesMode.NONE);
@@ -154,6 +158,7 @@
     public void testLoadEidAndNotifyRegistrants() {
         int channel = mockLogicalChannelResponses("BF3E065A041A2B3C4D9000");
         mHandler.post(() -> {
+            mEuiccCard.dispose();
             mEuiccCard = new EuiccCard(mContextFixture.getTestDouble(), mMockCi,
                     mMockIccCardStatus, 0 /* phoneId */, new Object(),
                     IccSlotStatus.MultipleEnabledProfilesMode.NONE);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccPortTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccPortTest.java
index 2fef021..f0f1af3 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccPortTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccPortTest.java
@@ -141,6 +141,7 @@
     public void tearDown() throws Exception {
         mHandler.removeCallbacksAndMessages(null);
         mHandler = null;
+        mEuiccPort.dispose();
         mEuiccPort = null;
         super.tearDown();
     }