Merge "Combine isSatelliteEnabled and isSatelliteBeingEnabled" into main
diff --git a/flags/Android.bp b/flags/Android.bp
index 1885032..edcfc3f 100644
--- a/flags/Android.bp
+++ b/flags/Android.bp
@@ -33,6 +33,7 @@
         "subscription.aconfig",
         "uicc.aconfig",
         "satellite.aconfig",
-        "iwlan.aconfig"
+        "iwlan.aconfig",
+        "carrier.aconfig",
     ],
 }
diff --git a/flags/carrier.aconfig b/flags/carrier.aconfig
new file mode 100644
index 0000000..dca2ad3
--- /dev/null
+++ b/flags/carrier.aconfig
@@ -0,0 +1,11 @@
+package: "com.android.internal.telephony.flags"
+container: "system"
+
+# OWNER=nharold TARGET=24Q4
+flag {
+    name: "async_init_carrier_privileges_tracker"
+    is_exported: true
+    namespace: "telephony"
+    description: "Offload the heavyweight initialization of CarrierPrivilegesTracker to a worker thread"
+    bug:"357096337"
+}
diff --git a/flags/data.aconfig b/flags/data.aconfig
index 0fd094d..ccd5db4 100644
--- a/flags/data.aconfig
+++ b/flags/data.aconfig
@@ -132,3 +132,10 @@
   }
 }
 
+# OWNER=TBD TARGET=TBD
+flag {
+  name: "oem_paid_private"
+  namespace: "telephony"
+  description: "Support OEM_PAID and OEM_PRIVATE networks"
+  bug: "366194627"
+}
diff --git a/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java b/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java
index 522cf77..6326d6c 100644
--- a/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java
+++ b/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java
@@ -46,6 +46,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
 import android.os.PersistableBundle;
@@ -68,7 +69,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.telephony.flags.FeatureFlags;
-import com.android.internal.telephony.uicc.IccUtils;
 import com.android.internal.telephony.uicc.UiccPort;
 import com.android.internal.telephony.uicc.UiccProfile;
 import com.android.telephony.Rlog;
@@ -80,7 +80,6 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
@@ -179,6 +178,8 @@
     private static final int ACTION_SET_TEST_OVERRIDE_CARRIER_SERVICE_PACKAGE = 11;
 
     private final Context mContext;
+    @NonNull
+    private final FeatureFlags mFeatureFlags;
     private final Phone mPhone;
     private final PackageManager mPackageManager;
     private final UserManager mUserManager;
@@ -198,7 +199,7 @@
     @Nullable private List<UiccAccessRule> mTestOverrideRules = null;
     @Nullable private String mTestOverrideCarrierServicePackage = null;
     // Map of PackageName -> Certificate hashes for that Package
-    @NonNull private final Map<String, Set<String>> mInstalledPackageCerts = new ArrayMap<>();
+    @NonNull private final Map<String, Set<Integer>> mInstalledPackageCertHashes = new ArrayMap<>();
     // Map of PackageName -> UIDs for that Package
     @NonNull private final Map<String, Set<Integer>> mCachedUids = new ArrayMap<>();
 
@@ -226,8 +227,7 @@
             "mPrivilegedPackageInfoLock.writeLock()"})
     private boolean mSimIsReadyButNotLoaded = false;
 
-    @NonNull
-    private final FeatureFlags mFeatureFlags;
+    private volatile Handler mCurrentHandler;
 
     /** Small snapshot to hold package names and UIDs of privileged packages. */
     private static final class PrivilegedPackageInfo {
@@ -303,7 +303,9 @@
                                 return;
                             }
 
-                            sendMessage(obtainMessage(ACTION_SIM_STATE_UPDATED, slotId, simState));
+                            mCurrentHandler.sendMessage(
+                                    mCurrentHandler.obtainMessage(
+                                            ACTION_SIM_STATE_UPDATED, slotId, simState));
                             break;
                         }
                         case Intent.ACTION_PACKAGE_ADDED: // fall through
@@ -335,19 +337,21 @@
                                     ? ACTION_PACKAGE_REMOVED_OR_DISABLED_BY_USER
                                     : ACTION_PACKAGE_ADDED_REPLACED_OR_CHANGED;
 
-                            sendMessage(obtainMessage(what, pkgName));
+                            mCurrentHandler.sendMessage(
+                                    mCurrentHandler.obtainMessage(what, pkgName));
                             break;
                         }
                     }
                 }
             };
 
-    public CarrierPrivilegesTracker(@NonNull Looper looper, @NonNull Phone phone,
-            @NonNull Context context, @NonNull FeatureFlags flags) {
+    public CarrierPrivilegesTracker(
+            @NonNull Looper looper, @NonNull Phone phone,
+            @NonNull Context context, @NonNull FeatureFlags featureFlags) {
         super(looper);
-        mContext = context;
-        mFeatureFlags = flags;
         mPhone = phone;
+        mContext = context;
+        mFeatureFlags = featureFlags;
         mPackageManager = mContext.getPackageManager();
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         mCarrierConfigManager =
@@ -363,6 +367,56 @@
                 (TelephonyRegistryManager)
                         mContext.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE);
 
+        if (mFeatureFlags.asyncInitCarrierPrivilegesTracker()) {
+            final Object localLock = new Object();
+            HandlerThread initializerThread =
+                    new HandlerThread("CarrierPrivilegesTracker Initializer") {
+                        @Override
+                        protected void onLooperPrepared() {
+                            synchronized (localLock) {
+                                localLock.notifyAll();
+                            }
+                        }
+                    };
+            synchronized (localLock) {
+                initializerThread.start();
+                while (true) {
+                    try {
+                        localLock.wait();
+                        break;
+                    } catch (InterruptedException ie) {
+                    }
+                }
+            }
+            mCurrentHandler = new Handler(initializerThread.getLooper()) {
+                @Override
+                public void handleMessage(Message msg) {
+                    switch(msg.what) {
+                        case ACTION_INITIALIZE_TRACKER:
+                            handleInitializeTracker();
+                            if (!hasMessagesOrCallbacks()) {
+                                mCurrentHandler = CarrierPrivilegesTracker.this;
+                                initializerThread.quitSafely();
+                            }
+                            break;
+                        default:
+                            Message m = CarrierPrivilegesTracker.this.obtainMessage();
+                            m.copyFrom(msg);
+                            m.sendToTarget();
+                            if (!hasMessagesOrCallbacks()) {
+                                mCurrentHandler = CarrierPrivilegesTracker.this;
+                                initializerThread.quitSafely();
+                            }
+                            break;
+                    }
+                }
+            };
+        } else {
+            mCurrentHandler = this;
+        }
+
+        mCurrentHandler.sendMessage(obtainMessage(ACTION_INITIALIZE_TRACKER));
+
         IntentFilter certFilter = new IntentFilter();
         certFilter.addAction(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED);
         certFilter.addAction(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED);
@@ -391,7 +445,6 @@
             mContext.registerReceiver(mIntentReceiver, packageFilter);
         }
 
-        sendMessage(obtainMessage(ACTION_INITIALIZE_TRACKER));
     }
 
     @Override
@@ -499,7 +552,7 @@
                     && mClearUiccRulesUptimeMillis == CLEAR_UICC_RULE_NOT_SCHEDULED) {
                 mClearUiccRulesUptimeMillis =
                         SystemClock.uptimeMillis() + CLEAR_UICC_RULES_DELAY_MILLIS;
-                sendMessageAtTime(obtainMessage(ACTION_CLEAR_UICC_RULES),
+                mCurrentHandler.sendMessageAtTime(obtainMessage(ACTION_CLEAR_UICC_RULES),
                         mClearUiccRulesUptimeMillis);
                 mLocalLog.log("SIM is gone, simState=" + TelephonyManager.simStateToString(simState)
                         + ". Delay " + TimeUnit.MILLISECONDS.toSeconds(
@@ -556,25 +609,29 @@
         return uiccProfile.getCarrierPrivilegeAccessRules();
     }
 
-    private void handlePackageAddedReplacedOrChanged(@Nullable String pkgName) {
-        if (pkgName == null) return;
+    private PackageInfo getPackageInfoForPackage(@Nullable String pkgName) {
+        if (pkgName == null) return null;
 
         PackageInfo pkg;
         try {
-            pkg =
-                    mFeatureFlags.supportCarrierServicesForHsum()
-                            ? mPackageManager.getPackageInfoAsUser(
-                                    pkgName,
-                                    INSTALLED_PACKAGES_QUERY_FLAGS,
-                                    ActivityManager.getCurrentUser())
-                            : mPackageManager.getPackageInfo(
-                                    pkgName, INSTALLED_PACKAGES_QUERY_FLAGS);
+            return mFeatureFlags.supportCarrierServicesForHsum()
+                        ? mPackageManager.getPackageInfoAsUser(
+                                pkgName,
+                                INSTALLED_PACKAGES_QUERY_FLAGS,
+                                ActivityManager.getCurrentUser())
+                        : mPackageManager.getPackageInfo(
+                                pkgName, INSTALLED_PACKAGES_QUERY_FLAGS);
         } catch (NameNotFoundException e) {
             Rlog.e(TAG, "Error getting installed package: " + pkgName, e);
-            return;
+            return null;
         }
+    }
 
-        updateCertsForPackage(pkg);
+    private void handlePackageAddedReplacedOrChanged(@Nullable String pkgName) {
+        PackageInfo pkg = getPackageInfoForPackage(pkgName);
+        if (pkg == null) return;
+
+        updateCertHashHashesForPackage(pkg);
         // Invalidate cache because this may be a package already on the device but getting
         // installed for a user it wasn't installed in before, which means there will be an
         // additional UID.
@@ -582,30 +639,46 @@
         if (VDBG) {
             Rlog.d(TAG, "Package added/replaced/changed:"
                     + " pkg=" + Rlog.pii(TAG, pkgName)
-                    + " cert hashes=" + mInstalledPackageCerts.get(pkgName));
+                    + " cert hashes=" + mInstalledPackageCertHashes.get(pkgName));
         }
 
         maybeUpdatePrivilegedPackagesAndNotifyRegistrants();
     }
 
-    private void updateCertsForPackage(@NonNull PackageInfo pkg) {
-        Set<String> certs = new ArraySet<>(1);
+    private void updateCertHashHashesForPackage(@NonNull PackageInfo pkg) {
+        Set<Integer> certs = new ArraySet<>(2);
         List<Signature> signatures = UiccAccessRule.getSignatures(pkg);
         for (Signature signature : signatures) {
             byte[] sha1 = UiccAccessRule.getCertHash(signature, SHA_1);
-            certs.add(IccUtils.bytesToHexString(sha1).toUpperCase(Locale.ROOT));
+            certs.add(UiccAccessRule.getCertificateHashHashCode(sha1));
 
             byte[] sha256 = UiccAccessRule.getCertHash(signature, SHA_256);
-            certs.add(IccUtils.bytesToHexString(sha256).toUpperCase(Locale.ROOT));
+            certs.add(UiccAccessRule.getCertificateHashHashCode(sha256));
         }
 
-        mInstalledPackageCerts.put(pkg.packageName, certs);
+        mInstalledPackageCertHashes.put(pkg.packageName, certs);
+    }
+
+    private Set<byte[]> getCertsForPackage(@NonNull String pkgName) {
+        PackageInfo pkg = getPackageInfoForPackage(pkgName);
+        if (pkg == null) return Collections.emptySet();
+
+        List<Signature> signatures = UiccAccessRule.getSignatures(pkg);
+
+        ArraySet<byte[]> certs = new ArraySet<>(2);
+        for (Signature signature : signatures) {
+            certs.add(UiccAccessRule.getCertHash(signature, SHA_1));
+            certs.add(UiccAccessRule.getCertHash(signature, SHA_256));
+        }
+
+        return certs;
     }
 
     private void handlePackageRemovedOrDisabledByUser(@Nullable String pkgName) {
         if (pkgName == null) return;
 
-        if (mInstalledPackageCerts.remove(pkgName) == null || mCachedUids.remove(pkgName) == null) {
+        if (mInstalledPackageCertHashes.remove(pkgName) == null
+                || mCachedUids.remove(pkgName) == null) {
             Rlog.e(TAG, "Unknown package was uninstalled or disabled by user: " + pkgName);
             return;
         }
@@ -637,7 +710,7 @@
             msg +=
                     " installed pkgs="
                             + getObfuscatedPackages(
-                                    mInstalledPackageCerts.entrySet(),
+                                    mInstalledPackageCertHashes.entrySet(),
                                     e -> "pkg(" + Rlog.pii(TAG, e.getKey()) + ")=" + e.getValue());
         }
         mLocalLog.log(msg);
@@ -651,7 +724,7 @@
                                 ? ActivityManager.getCurrentUser()
                                 : UserHandle.SYSTEM.getIdentifier());
         for (PackageInfo pkg : installedPackages) {
-            updateCertsForPackage(pkg);
+            updateCertHashHashesForPackage(pkg);
             // This may be unnecessary before initialization, but invalidate the cache all the time
             // just in case to ensure consistency.
             getUidsForPackage(pkg.packageName, /* invalidateCache= */ true);
@@ -740,8 +813,12 @@
         Set<String> carrierServiceEligiblePackages = new ArraySet<>();
         Set<String> privilegedPackageNames = new ArraySet<>();
         Set<Integer> privilegedUids = new ArraySet<>();
-        for (Map.Entry<String, Set<String>> e : mInstalledPackageCerts.entrySet()) {
-            final int priv = getPackagePrivilegedStatus(e.getKey(), e.getValue());
+        for (Map.Entry<String, Set<Integer>> e : mInstalledPackageCertHashes.entrySet()) {
+            if (!isPackageMaybePrivileged(e.getKey(), e.getValue())) continue;
+
+            Set<byte[]> fullCerts = getCertsForPackage(e.getKey());
+
+            final int priv = getPackagePrivilegedStatus(e.getKey(), fullCerts);
             switch (priv) {
                 case PACKAGE_PRIVILEGED_FROM_SIM:
                 case PACKAGE_PRIVILEGED_FROM_CARRIER_SERVICE_TEST_OVERRIDE: // fallthrough
@@ -760,32 +837,58 @@
                 getCarrierService(carrierServiceEligiblePackages));
     }
 
+    private boolean isPackageMaybePrivileged(
+            @NonNull String pkgName, @NonNull Set<Integer> hashHashes) {
+        for (Integer hashHash : hashHashes) {
+            // Non-null (whether empty or not) test override rule will ignore the UICC and CC rules
+            if (mTestOverrideRules != null) {
+                for (UiccAccessRule rule : mTestOverrideRules) {
+                    if (rule.hasMatchingCertificateHashHashAndPackageName(hashHash, pkgName)) {
+                        return true;
+                    }
+                }
+            } else {
+                for (UiccAccessRule rule : mUiccRules) {
+                    if (rule.hasMatchingCertificateHashHashAndPackageName(hashHash, pkgName)) {
+                        return true;
+                    }
+                }
+                for (UiccAccessRule rule : mCarrierConfigRules) {
+                    if (rule.hasMatchingCertificateHashHashAndPackageName(hashHash, pkgName)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
     /**
      * Returns the privilege status of the provided package.
      *
      * <p>Returned privilege status depends on whether a package matches the certificates from
      * carrier config, from test overrides or from certificates stored on the SIM.
      */
-    private int getPackagePrivilegedStatus(@NonNull String pkgName, @NonNull Set<String> certs) {
+    private int getPackagePrivilegedStatus(@NonNull String pkgName, @NonNull Set<byte[]> certs) {
         // Double-nested for loops, but each collection should contain at most 2 elements in nearly
         // every case.
         // TODO(b/184382310) find a way to speed this up
-        for (String cert : certs) {
+        for (byte[] cert : certs) {
             // Non-null (whether empty or not) test override rule will ignore the UICC and CC rules
             if (mTestOverrideRules != null) {
                 for (UiccAccessRule rule : mTestOverrideRules) {
-                    if (rule.matches(cert, pkgName)) {
+                    if (rule.hasMatchingCertificateHashAndPackageName(cert, pkgName)) {
                         return PACKAGE_PRIVILEGED_FROM_SIM;
                     }
                 }
             } else {
                 for (UiccAccessRule rule : mUiccRules) {
-                    if (rule.matches(cert, pkgName)) {
+                    if (rule.hasMatchingCertificateHashAndPackageName(cert, pkgName)) {
                         return PACKAGE_PRIVILEGED_FROM_SIM;
                     }
                 }
                 for (UiccAccessRule rule : mCarrierConfigRules) {
-                    if (rule.matches(cert, pkgName)) {
+                    if (rule.hasMatchingCertificateHashAndPackageName(cert, pkgName)) {
                         return pkgName.equals(mTestOverrideCarrierServicePackage)
                                 ? PACKAGE_PRIVILEGED_FROM_CARRIER_SERVICE_TEST_OVERRIDE
                                 : PACKAGE_PRIVILEGED_FROM_CARRIER_CONFIG;
@@ -856,7 +959,7 @@
             pw.println(
                     "CarrierPrivilegesTracker - Obfuscated Pkgs + Certs: "
                             + getObfuscatedPackages(
-                                    mInstalledPackageCerts.entrySet(),
+                                    mInstalledPackageCertHashes.entrySet(),
                                     e -> "pkg(" + Rlog.pii(TAG, e.getKey()) + ")=" + e.getValue()));
         }
         pw.println("mClearUiccRulesUptimeMillis: " + mClearUiccRulesUptimeMillis);
@@ -872,7 +975,8 @@
      * @see TelephonyManager#setCarrierTestOverride
      */
     public void setTestOverrideCarrierPrivilegeRules(@Nullable String carrierPrivilegeRules) {
-        sendMessage(obtainMessage(ACTION_SET_TEST_OVERRIDE_RULE, carrierPrivilegeRules));
+        mCurrentHandler.sendMessage(
+                obtainMessage(ACTION_SET_TEST_OVERRIDE_RULE, carrierPrivilegeRules));
     }
 
     /**
@@ -888,7 +992,7 @@
      * @see TelephonyManager#setCarrierServicePackageOverride
      */
     public void setTestOverrideCarrierServicePackage(@Nullable String carrierServicePackage) {
-        sendMessage(obtainMessage(
+        mCurrentHandler.sendMessage(obtainMessage(
                 ACTION_SET_TEST_OVERRIDE_CARRIER_SERVICE_PACKAGE, carrierServicePackage));
     }
 
diff --git a/src/java/com/android/internal/telephony/CarrierServiceBindHelper.java b/src/java/com/android/internal/telephony/CarrierServiceBindHelper.java
index 55baecc..4f9d84d 100644
--- a/src/java/com/android/internal/telephony/CarrierServiceBindHelper.java
+++ b/src/java/com/android/internal/telephony/CarrierServiceBindHelper.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony;
 
 import android.annotation.NonNull;
+import android.app.ActivityManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -161,7 +162,11 @@
     };
 
     public CarrierServiceBindHelper(Context context) {
-        mContext = context.createContextAsUser(Process.myUserHandle(), 0);
+        mContext =
+                context.createContextAsUser(
+                        Flags.supportCarrierServicesForHsum()
+                        ? UserHandle.of(ActivityManager.getCurrentUser())
+                        : Process.myUserHandle(), 0);
 
         updateBindingsAndSimStates();
 
diff --git a/src/java/com/android/internal/telephony/GsmCdmaPhone.java b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
index 5d59327..2e1a89f 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaPhone.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
@@ -391,8 +391,8 @@
 
         mCarrierResolver = mTelephonyComponentFactory.inject(CarrierResolver.class.getName())
                 .makeCarrierResolver(this, featureFlags);
-        mCarrierPrivilegesTracker = new CarrierPrivilegesTracker(Looper.myLooper(), this, context,
-                featureFlags);
+        mCarrierPrivilegesTracker = new CarrierPrivilegesTracker(
+                Looper.myLooper(), this, context, featureFlags);
 
         getCarrierActionAgent().registerForCarrierAction(
                 CarrierActionAgent.CARRIER_ACTION_SET_METERED_APNS_ENABLED, this,
diff --git a/src/java/com/android/internal/telephony/data/DataUtils.java b/src/java/com/android/internal/telephony/data/DataUtils.java
index 20da97f..c88e0b3 100644
--- a/src/java/com/android/internal/telephony/data/DataUtils.java
+++ b/src/java/com/android/internal/telephony/data/DataUtils.java
@@ -287,6 +287,8 @@
             case NetworkCapabilities.NET_CAPABILITY_VSIM -> ApnSetting.TYPE_VSIM;
             case NetworkCapabilities.NET_CAPABILITY_BIP -> ApnSetting.TYPE_BIP;
             case NetworkCapabilities.NET_CAPABILITY_RCS -> ApnSetting.TYPE_RCS;
+            case NetworkCapabilities.NET_CAPABILITY_OEM_PAID -> ApnSetting.TYPE_OEM_PAID;
+            case NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE -> ApnSetting.TYPE_OEM_PRIVATE;
             default -> ApnSetting.TYPE_NONE;
         };
     }
@@ -315,6 +317,8 @@
             case ApnSetting.TYPE_VSIM -> NetworkCapabilities.NET_CAPABILITY_VSIM;
             case ApnSetting.TYPE_ENTERPRISE -> NetworkCapabilities.NET_CAPABILITY_ENTERPRISE;
             case ApnSetting.TYPE_RCS -> NetworkCapabilities.NET_CAPABILITY_RCS;
+            case ApnSetting.TYPE_OEM_PAID -> NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
+            case ApnSetting.TYPE_OEM_PRIVATE -> NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
             default -> -1;
         };
     }
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteController.java b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
index daae3f2..be96c0e 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteController.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
@@ -2943,6 +2943,21 @@
     }
 
     /**
+     * This API can be used by only CTS to control ingoring cellular service state event.
+     *
+     * @param enabled Whether to enable boolean config.
+     * @return {@code true} if the value is set successfully, {@code false} otherwise.
+     */
+    public boolean setSatelliteIgnoreCellularServiceState(boolean enabled) {
+        plogd("setSatelliteIgnoreCellularServiceState - " + enabled);
+        if (mSatelliteSessionController == null) {
+            ploge("setSatelliteIgnoreCellularServiceState is not initialized yet");
+            return false;
+        }
+        return mSatelliteSessionController.setSatelliteIgnoreCellularServiceState(enabled);
+    }
+
+    /**
      * This API can be used by only CTS to override timeout durations used by DatagramController
      * module.
      *
@@ -4838,6 +4853,17 @@
         return getConfigForSubId(subId).getBoolean(KEY_SATELLITE_ESOS_SUPPORTED_BOOL);
     }
 
+    /**
+     * Return whether the device allows to turn off satellite session for emergency call.
+     *
+     * @param subId Associated subscription ID
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public boolean turnOffSatelliteSessionForEmergencyCall(int subId) {
+        return getConfigForSubId(subId).getBoolean(
+                KEY_SATELLITE_ROAMING_TURN_OFF_SESSION_FOR_EMERGENCY_CALL_BOOL);
+    }
+
     private int getCarrierRoamingNtnConnectType(int subId) {
         return getConfigForSubId(subId).getInt(KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT);
     }
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java b/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java
index cca973f..23dcefc 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java
@@ -285,7 +285,7 @@
 
     private void handleSatelliteProvisionStateChangedEvent(boolean provisioned) {
         if (!provisioned) {
-            cleanUpResources();
+            cleanUpResources(false);
         }
     }
 
@@ -336,8 +336,7 @@
                     + ", isCellularAvailable=" + isCellularAvailable
                     + ", isSatelliteAllowed=" + isSatelliteAllowed()
                     + ", shouldTrackCall=" + shouldTrackCall(mEmergencyConnection.getState()));
-            reportESosRecommenderDecision(isDialerNotified);
-            cleanUpResources();
+            cleanUpResources(isDialerNotified);
         }
     }
 
@@ -388,13 +387,12 @@
              * we're not tracking. There must be some unexpected things happened in
              * TelephonyConnectionService. Thus, we need to clean up the resources.
              */
-            cleanUpResources();
+            cleanUpResources(false);
             return;
         }
 
         if (!shouldTrackCall(state)) {
-            reportESosRecommenderDecision(false);
-            cleanUpResources();
+            cleanUpResources(false);
         } else {
             // Location service will enter emergency mode only when connection state changes to
             // STATE_DIALING
@@ -405,7 +403,8 @@
         }
     }
 
-    private void reportESosRecommenderDecision(boolean isDialerNotified) {
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected void reportESosRecommenderDecision(boolean isDialerNotified) {
         SatelliteStats.getInstance().onSatelliteSosMessageRecommender(
                 new SatelliteStats.SatelliteSosMessageRecommenderParams.Builder()
                         .setDisplaySosMessageSent(isDialerNotified)
@@ -419,8 +418,9 @@
                         .setCarrierId(getAvailableNtnCarrierID()).build());
     }
 
-    private void cleanUpResources() {
+    private void cleanUpResources(boolean isDialerNotified) {
         plogd("cleanUpResources");
+        reportESosRecommenderDecision(isDialerNotified);
         synchronized (mLock) {
             stopTimer();
             if (mEmergencyConnection != null) {
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java b/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java
index f4208f7..540d106 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java
@@ -52,6 +52,7 @@
 import android.os.SystemProperties;
 import android.telephony.DropBoxManagerLoggerBackend;
 import android.telephony.PersistentLogger;
+import android.telephony.ServiceState;
 import android.telephony.satellite.ISatelliteModemStateCallback;
 import android.telephony.satellite.SatelliteManager;
 import android.telephony.satellite.stub.ISatelliteGateway;
@@ -66,6 +67,7 @@
 import com.android.internal.telephony.ExponentialBackoff;
 import com.android.internal.telephony.IIntegerConsumer;
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.satellite.metrics.SessionMetricsStats;
 import com.android.internal.util.State;
@@ -124,6 +126,8 @@
     private static final int EVENT_SCREEN_STATE_CHANGED = 9;
     protected static final int EVENT_SCREEN_OFF_INACTIVITY_TIMER_TIMED_OUT = 10;
     protected static final int EVENT_CARRIER_ROAMING_NB_IOT_INACTIVITY_TIMER_TIMED_OUT = 11;
+    private static final int EVENT_ENABLE_CELLULAR_MODEM_WHILE_SATELLITE_MODE_IS_ON_DONE = 12;
+    private static final int EVENT_SERVICE_STATE_CHANGED = 13;
 
     private static final long REBIND_INITIAL_DELAY = 2 * 1000; // 2 seconds
     private static final long REBIND_MAXIMUM_DELAY = 64 * 1000; // 1 minute
@@ -160,6 +164,7 @@
     private long mSatelliteStayAtListeningFromSendingMillis;
     private long mSatelliteStayAtListeningFromReceivingMillis;
     private long mSatelliteNbIotInactivityTimeoutMillis;
+    private boolean mIgnoreCellularServiceState = false;
     private final ConcurrentHashMap<IBinder, ISatelliteModemStateCallback> mListeners;
     @SatelliteManager.SatelliteModemState private int mCurrentState;
     @SatelliteManager.SatelliteModemState private int mPreviousState;
@@ -205,8 +210,10 @@
             boolean isSatelliteSupported) {
         if (sInstance == null || isSatelliteSupported != sInstance.mIsSatelliteSupported) {
             ConcurrentHashMap<IBinder, ISatelliteModemStateCallback> existingListeners = null;
+            boolean existIgnoreCellularServiceState = false;
             if (sInstance != null) {
                 existingListeners = sInstance.mListeners;
+                existIgnoreCellularServiceState = sInstance.mIgnoreCellularServiceState;
                 sInstance.cleanUpResource();
             }
 
@@ -220,6 +227,10 @@
                 Log.d(TAG, "make() existingListeners: " + existingListeners.size());
                 sInstance.mListeners.putAll(existingListeners);
             }
+            if (existIgnoreCellularServiceState) {
+                Log.d(TAG, "make() existIgnoreCellularServiceState is true");
+                sInstance.mIgnoreCellularServiceState = true;
+            }
         }
         return sInstance;
     }
@@ -277,13 +288,27 @@
             bindService();
         });
 
-        Phone phone = mSatelliteController.getSatellitePhone();
-        if (phone == null) {
-            phone = SatelliteServiceUtils.getPhone();
+        Phone satellitePhone = mSatelliteController.getSatellitePhone();
+        if (satellitePhone == null) {
+            satellitePhone = SatelliteServiceUtils.getPhone();
         }
-        mDeviceStateMonitor = phone.getDeviceStateMonitor();
+        mDeviceStateMonitor = satellitePhone.getDeviceStateMonitor();
         mSessionMetricsStats = SessionMetricsStats.getInstance();
 
+        if (mFeatureFlags.carrierRoamingNbIotNtn()) {
+            // Register to received Cellular service state
+            for (Phone phone : PhoneFactory.getPhones()) {
+                if (phone == null) continue;
+
+                phone.registerForServiceStateChanged(
+                        getHandler(), EVENT_SERVICE_STATE_CHANGED, null);
+                if (DBG) {
+                    plogd("SatelliteSessionController: registerForServiceStateChanged phoneId "
+                            + phone.getPhoneId());
+                }
+            }
+        }
+
         addState(mUnavailableState);
         addState(mPowerOffState);
         addState(mEnablingState);
@@ -450,6 +475,23 @@
     }
 
     /**
+     * This API can be used by only CTS to control ingoring cellular service state event.
+     *
+     * @param enabled Whether to enable boolean config.
+     * @return {@code true} if the value is set successfully, {@code false} otherwise.
+     */
+    public boolean setSatelliteIgnoreCellularServiceState(boolean enabled) {
+        plogd("setSatelliteIgnoreCellularServiceState : "
+                + "old = " + mIgnoreCellularServiceState + " new : " + enabled);
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            return false;
+        }
+
+        mIgnoreCellularServiceState = enabled;
+        return true;
+    }
+
+    /**
      * This API can be used by only CTS to update satellite gateway service package name.
      *
      * @param servicePackageName The package name of the satellite gateway service.
@@ -547,9 +589,33 @@
         plogd("cleanUpResource");
         mIsDeviceAlignedWithSatellite = false;
         unregisterForScreenStateChanged();
+
+        if (mFeatureFlags.carrierRoamingNbIotNtn()) {
+            // Register to received Cellular service state
+            for (Phone phone : PhoneFactory.getPhones()) {
+                if (phone == null) continue;
+
+                phone.unregisterForServiceStateChanged(getHandler());
+                if (DBG) {
+                    plogd("cleanUpResource: unregisterForServiceStateChanged phoneId "
+                            + phone.getPhoneId());
+                }
+            }
+        }
+
         quitNow();
     }
 
+    /**
+     * Uses this function to notify that cellular service state has changed
+     *
+     * @param serviceState The state of the cellular service.
+     */
+    @VisibleForTesting
+    public void onCellularServiceStateChanged(ServiceState serviceState) {
+        sendMessage(EVENT_SERVICE_STATE_CHANGED, new AsyncResult(null, serviceState, null));
+    }
+
     private boolean isDemoMode() {
         return mIsDemoMode;
     }
@@ -790,8 +856,11 @@
             mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_IDLE;
             mIsSendingTriggeredDuringTransferringState.set(false);
             stopNbIotInactivityTimer();
+
             //Enable Cellular Modem scanning
-            mSatelliteModemInterface.enableCellularModemWhileSatelliteModeIsOn(true, null);
+            Message onCompleted =
+                    obtainMessage(EVENT_ENABLE_CELLULAR_MODEM_WHILE_SATELLITE_MODE_IS_ON_DONE);
+            mSatelliteModemInterface.enableCellularModemWhileSatelliteModeIsOn(true, onCompleted);
             notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
         }
 
@@ -821,6 +890,27 @@
                 case EVENT_SATELLITE_MODEM_STATE_CHANGED:
                     handleSatelliteModemStateChanged(msg);
                     break;
+                case EVENT_ENABLE_CELLULAR_MODEM_WHILE_SATELLITE_MODE_IS_ON_DONE:
+                    if (!mIgnoreCellularServiceState) {
+                        handleEventEnableCellularModemWhileSatelliteModeIsOnDone();
+                    } else {
+                        plogd("IdleState: processing: ignore "
+                                + "EVENT_ENABLE_CELLULAR_MODEM_WHILE_SATELLITE_MODE_IS_ON_DONE");
+                    }
+                    break;
+                case EVENT_SERVICE_STATE_CHANGED:
+                    if (!mIgnoreCellularServiceState) {
+                        AsyncResult ar = (msg.obj != null) ? (AsyncResult) msg.obj : null;
+                        if (ar == null || ar.result == null) {
+                            plogd("IdleState: processing: can't access ServiceState");
+                        } else {
+                            ServiceState newServiceState = (ServiceState) ar.result;
+                            handleEventServiceStateChanged(newServiceState);
+                        }
+                    } else {
+                        plogd("IdleState: processing: ignore EVENT_SERVICE_STATE_CHANGED");
+                    }
+                    break;
             }
             // Ignore all unexpected events.
             return HANDLED;
@@ -848,6 +938,58 @@
             }
         }
 
+        private void handleEventEnableCellularModemWhileSatelliteModeIsOnDone() {
+            if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+                Rlog.d(TAG, "handleEventEnableCellularModemWhileSatelliteModeIsOnDone: "
+                        + "carrierRoamingNbIotNtn is disabled");
+                return;
+            }
+
+            ServiceState serviceState = mSatelliteController.getSatellitePhone().getServiceState();
+            if (serviceState == null) {
+                plogd("handleEventEnableCellularModemWhileSatelliteModeIsOnDone: "
+                        + "can't access ServiceState");
+                return;
+            }
+            handleEventServiceStateChanged(serviceState);
+        }
+
+        private void handleEventServiceStateChanged(ServiceState serviceState) {
+            boolean isInServiceOrEmergency =
+                    serviceState.getVoiceRegState() == ServiceState.STATE_IN_SERVICE
+                    || serviceState.getDataRegState() == ServiceState.STATE_IN_SERVICE
+                    || serviceState.isEmergencyOnly();
+            if (!isInServiceOrEmergency) {
+                plogd("handleEventServiceStateChanged: is not IN_SERVICE or EMERGENCY_ONLY");
+                return;
+            }
+
+            // In emergency
+            boolean isEmergency = mSatelliteController.getRequestIsEmergency();
+            if (isEmergency) {
+                boolean isEmergencyCommunicationEstablished = (mDatagramController == null)
+                        ? false : mDatagramController.isEmergencyCommunicationEstablished();
+                boolean isTurnOffAllowed =
+                        mSatelliteController.turnOffSatelliteSessionForEmergencyCall(getSubId());
+                if (isEmergencyCommunicationEstablished || !isTurnOffAllowed) {
+                    logd("handleEventServiceStateChanged: "
+                            + "can't disable emergency satellite session");
+                    return;
+                }
+            }
+
+            mSatelliteController.requestSatelliteEnabled(
+                    false /*enableSatellite*/,
+                    false /*enableDemoMode*/,
+                    isEmergency /*isEmergency*/,
+                    new IIntegerConsumer.Stub() {
+                        @Override
+                        public void accept(int result) {
+                            plogd("requestSatelliteEnabled result=" + result);
+                        }
+                    });
+        }
+
         private void handleEventDisableCellularModemWhileSatelliteModeIsOnDone(
                 @NonNull AsyncResult result) {
             synchronized (mLock) {
@@ -1232,6 +1374,12 @@
             case EVENT_CARRIER_ROAMING_NB_IOT_INACTIVITY_TIMER_TIMED_OUT:
                 whatString = "EVENT_CARRIER_ROAMING_NB_IOT_INACTIVITY_TIMER_TIMED_OUT";
                 break;
+            case EVENT_ENABLE_CELLULAR_MODEM_WHILE_SATELLITE_MODE_IS_ON_DONE:
+                whatString = "EVENT_ENABLE_CELLULAR_MODEM_WHILE_SATELLITE_MODE_IS_ON_DONE";
+                break;
+            case EVENT_SERVICE_STATE_CHANGED:
+                whatString = "EVENT_SERVICE_STATE_CHANGED";
+                break;
             default:
                 whatString = "UNKNOWN EVENT " + what;
         }
diff --git a/src/java/com/android/internal/telephony/uicc/euicc/EuiccPort.java b/src/java/com/android/internal/telephony/uicc/euicc/EuiccPort.java
index 7bdec47..3bd66f8 100644
--- a/src/java/com/android/internal/telephony/uicc/euicc/EuiccPort.java
+++ b/src/java/com/android/internal/telephony/uicc/euicc/EuiccPort.java
@@ -133,8 +133,7 @@
             UiccCard card, MultipleEnabledProfilesMode supportedMepMode) {
         super(c, ci, ics, phoneId, lock, card);
         // TODO: Set supportExtendedApdu based on ATR.
-        mApduSender = new ApduSender(c, phoneId, ci, ISD_R_AID,
-                              false /* supportExtendedApdu */);
+        mApduSender = new ApduSender(ci, ISD_R_AID, false /* supportExtendedApdu */);
         if (TextUtils.isEmpty(ics.eid)) {
             loge("no eid given in constructor for phone " + phoneId);
         } else {
diff --git a/src/java/com/android/internal/telephony/uicc/euicc/apdu/ApduSender.java b/src/java/com/android/internal/telephony/uicc/euicc/apdu/ApduSender.java
index b17a0fd..0a56b2b 100644
--- a/src/java/com/android/internal/telephony/uicc/euicc/apdu/ApduSender.java
+++ b/src/java/com/android/internal/telephony/uicc/euicc/apdu/ApduSender.java
@@ -17,17 +17,11 @@
 package com.android.internal.telephony.uicc.euicc.apdu;
 
 import android.annotation.Nullable;
-import android.content.Context;
-import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
-import android.preference.PreferenceManager;
 import android.telephony.IccOpenLogicalChannelResponse;
-import android.util.Base64;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.telephony.CommandsInterface;
-import com.android.internal.telephony.euicc.EuiccSession;
 import com.android.internal.telephony.uicc.IccIoResult;
 import com.android.internal.telephony.uicc.euicc.async.AsyncResultCallback;
 import com.android.internal.telephony.uicc.euicc.async.AsyncResultHelper;
@@ -42,14 +36,7 @@
  * before sending and closed after all APDU commands are sent. The complete response of the last
  * APDU command will be returned. If any APDU command returns an error status (other than
  * {@link #STATUS_NO_ERROR}) or causing an exception, an {@link ApduException} will be returned
- * immediately without sending the rest of commands.
- *
- * <p>If {@link EuiccSession} indicates ongoing session(s), the behavior changes: 1) before
- * sending, check if a channel is opened already. If yes, reuse the channel and send APDU commands
- * directly. If no, open a channel before sending. 2) The channel is closed when EuiccSession
- * class ends all sessions, independent of APDU sending.
- *
- * <p>This class is thread-safe.
+ * immediately without sending the rest of commands. This class is thread-safe.
  *
  * @hide
  */
@@ -63,12 +50,8 @@
     // Status code of APDU response
     private static final int STATUS_NO_ERROR = 0x9000;
     private static final int SW1_NO_ERROR = 0x91;
-    private static final int STATUS_CHANNEL_CLOSED = 0x6881; // b/359336875
 
     private static final int WAIT_TIME_MS = 2000;
-    private static final String CHANNEL_ID_PRE = "esim-channel";
-    static final String ISD_R_AID = "A0000005591010FFFFFFFF8900000100";
-    private static final String CHANNEL_RESPONSE_ID_PRE = "esim-res-id";
 
     private static void logv(String msg) {
         Rlog.v(LOG_TAG, msg);
@@ -78,50 +61,26 @@
         Rlog.d(LOG_TAG, msg);
     }
 
-    private static void loge(String msg) {
-        Rlog.e(LOG_TAG, msg);
-    }
-
     private final String mAid;
     private final boolean mSupportExtendedApdu;
     private final OpenLogicalChannelInvocation mOpenChannel;
     private final CloseLogicalChannelInvocation mCloseChannel;
     private final TransmitApduLogicalChannelInvocation mTransmitApdu;
-    private final Context mContext;
-    private final String mChannelKey;
-    private final String mChannelResponseKey;
-    // closeAnyOpenChannel() needs a handler for its async callbacks.
-    private final Handler mHandler;
 
-    // Lock for accessing mChannelInUse. We only allow to open a single logical
-    // channel at any time for an AID and to invoke one command at any time.
-    // Only the thread (and its async callbacks) that sets mChannelInUse
-    // can open/close/send, and update mChannelOpened.
-    private final Object mChannelInUseLock = new Object();
-    @GuardedBy("mChannelInUseLock")
-    private boolean mChannelInUse;
+    // Lock for accessing mChannelOpened. We only allow to open a single logical channel at any
+    // time for an AID.
+    private final Object mChannelLock = new Object();
     private boolean mChannelOpened;
 
     /**
      * @param aid The AID that will be used to open a logical channel to.
      */
-    public ApduSender(Context context, int phoneId, CommandsInterface ci, String aid,
-            boolean supportExtendedApdu) {
-        if (!aid.equals(ISD_R_AID) && !"user".equals(Build.TYPE)) {
-            throw new IllegalArgumentException("Only ISD-R AID is supported.");
-        }
+    public ApduSender(CommandsInterface ci, String aid, boolean supportExtendedApdu) {
         mAid = aid;
-        mContext = context;
         mSupportExtendedApdu = supportExtendedApdu;
         mOpenChannel = new OpenLogicalChannelInvocation(ci);
         mCloseChannel = new CloseLogicalChannelInvocation(ci);
         mTransmitApdu = new TransmitApduLogicalChannelInvocation(ci);
-        mChannelKey = CHANNEL_ID_PRE + "_" + phoneId;
-        mChannelResponseKey = CHANNEL_RESPONSE_ID_PRE + "_" + phoneId;
-        mHandler = new Handler();
-
-        mChannelInUse = false;
-        closeAnyOpenChannel();
     }
 
     /**
@@ -140,125 +99,79 @@
             RequestProvider requestProvider,
             ApduSenderResultCallback resultCallback,
             Handler handler) {
-        if (!acquireChannelLock()) {
-            AsyncResultHelper.throwException(
-                    new ApduException("The logical channel is still in use."),
-                    resultCallback,
-                    handler);
-            return;
-        }
-
-        boolean euiccSession = EuiccSession.get().hasSession();
-        // Case 1, channel was already opened AND EuiccSession is ongoing.
-        // sendCommand directly. Do not immediately close channel after sendCommand.
-        // Case 2, channel was already opened AND EuiccSession is not ongoing. This means
-        // EuiccSession#endSession is already called but closeAnyOpenChannel() is not
-        // yet executed because of waiting to acquire lock hold by this thread.
-        // sendCommand directly. Close channel immediately anyways after sendCommand.
-        // Case 3, channel is not open AND EuiccSession is ongoing. Open channel
-        // before sendCommand. Do not immediately close channel after sendCommand.
-        // Case 4, channel is not open AND EuiccSession is not ongoing. Open channel
-        // before sendCommand. Close channel immediately after sendCommand.
-        if (mChannelOpened) {  // Case 1 or 2
-            if (euiccSession) {
-                EuiccSession.get().noteChannelOpen(this);
-            }
-            RequestBuilder builder = getRequestBuilderWithOpenedChannel(requestProvider,
-                    !euiccSession /* closeChannelImmediately */, resultCallback, handler);
-            if (builder == null) {
-                return;
-            }
-            sendCommand(builder.getCommands(), 0 /* index */,
-                    !euiccSession /* closeChannelImmediately */, resultCallback, handler);
-        } else {  // Case 3 or 4
-            if (euiccSession) {
-                EuiccSession.get().noteChannelOpen(this);
-            }
-            openChannel(requestProvider,
-                    !euiccSession /* closeChannelImmediately */, resultCallback, handler);
-        }
-    }
-
-    private RequestBuilder getRequestBuilderWithOpenedChannel(
-            RequestProvider requestProvider,
-            boolean closeChannelImmediately,
-            ApduSenderResultCallback resultCallback,
-            Handler handler) {
-        Throwable requestException = null;
-        int channel =
-                PreferenceManager.getDefaultSharedPreferences(mContext)
-                        .getInt(mChannelKey, IccOpenLogicalChannelResponse.INVALID_CHANNEL);
-        String storedResponse =
-                PreferenceManager.getDefaultSharedPreferences(mContext)
-                        .getString(mChannelResponseKey, "");
-        byte[] selectResponse = Base64.decode(storedResponse, Base64.DEFAULT);
-        RequestBuilder builder = new RequestBuilder(channel, mSupportExtendedApdu);
-        try {
-            requestProvider.buildRequest(selectResponse, builder);
-        } catch (Throwable e) {
-            requestException = e;
-        }
-        if (builder.getCommands().isEmpty() || requestException != null) {
-            logd("Release as commands are empty or exception occurred");
-            returnRespnseOrException(channel, closeChannelImmediately,
-                    null /* response */, requestException, resultCallback, handler);
-            return null;
-        }
-        return builder;
-    }
-
-    private void openChannel(
-            RequestProvider requestProvider,
-            boolean closeChannelImmediately,
-            ApduSenderResultCallback resultCallback,
-            Handler handler) {
-        mOpenChannel.invoke(mAid, new AsyncResultCallback<IccOpenLogicalChannelResponse>() {
-                    @Override
-                    public void onResult(IccOpenLogicalChannelResponse openChannelResponse) {
-                        int channel = openChannelResponse.getChannel();
-                        int status = openChannelResponse.getStatus();
-                        byte[] selectResponse = openChannelResponse.getSelectResponse();
-                        if (status == IccOpenLogicalChannelResponse.STATUS_NO_SUCH_ELEMENT) {
-                            channel = PreferenceManager.getDefaultSharedPreferences(mContext)
-                                            .getInt(mChannelKey,
-                                                    IccOpenLogicalChannelResponse.INVALID_CHANNEL);
-                            if (channel != IccOpenLogicalChannelResponse.INVALID_CHANNEL) {
-                                logv("Try to use already opened channel: " + channel);
-                                status = IccOpenLogicalChannelResponse.STATUS_NO_ERROR;
-                                String storedResponse = PreferenceManager
-                                        .getDefaultSharedPreferences(mContext)
-                                              .getString(mChannelResponseKey, "");
-                                selectResponse = Base64.decode(storedResponse, Base64.DEFAULT);
-                            }
-                        }
-
-                        if (channel == IccOpenLogicalChannelResponse.INVALID_CHANNEL
-                                || status != IccOpenLogicalChannelResponse.STATUS_NO_ERROR) {
-                            mChannelOpened = false;
-                            resultCallback.onException(
-                                    new ApduException("Failed to open logical channel for AID: "
-                                            + mAid + ", with status: " + status));
-                            return;
-                        }
-                        PreferenceManager.getDefaultSharedPreferences(mContext)
-                                .edit()
-                                .putInt(mChannelKey, channel)
-                                .putString(mChannelResponseKey,
-                                    Base64.encodeToString(selectResponse, Base64.DEFAULT)).apply();
-                        mChannelOpened = true;
-
-                        RequestBuilder builder =
-                                getRequestBuilderWithOpenedChannel(requestProvider,
-                                        closeChannelImmediately, resultCallback, handler);
-                        if (builder == null) {
-                            return;
-                        }
-
-                        sendCommand(builder.getCommands(), 0 /* index */,
-                                closeChannelImmediately, resultCallback, handler);
+        synchronized (mChannelLock) {
+            if (mChannelOpened) {
+                if (!Looper.getMainLooper().equals(Looper.myLooper())) {
+                    logd("Logical channel has already been opened. Wait.");
+                    try {
+                        mChannelLock.wait(WAIT_TIME_MS);
+                    } catch (InterruptedException e) {
+                        // nothing to do
                     }
-                },
-                handler);
+                    if (mChannelOpened) {
+                        AsyncResultHelper.throwException(
+                                new ApduException("The logical channel is still in use."),
+                                resultCallback, handler);
+                        return;
+                    }
+                } else {
+                    AsyncResultHelper.throwException(
+                            new ApduException("The logical channel is in use."),
+                            resultCallback, handler);
+                    return;
+                }
+            }
+            mChannelOpened = true;
+        }
+
+        mOpenChannel.invoke(mAid, new AsyncResultCallback<IccOpenLogicalChannelResponse>() {
+            @Override
+            public void onResult(IccOpenLogicalChannelResponse openChannelResponse) {
+                int channel = openChannelResponse.getChannel();
+                int status = openChannelResponse.getStatus();
+                if (channel == IccOpenLogicalChannelResponse.INVALID_CHANNEL
+                        || status != IccOpenLogicalChannelResponse.STATUS_NO_ERROR) {
+                    synchronized (mChannelLock) {
+                        mChannelOpened = false;
+                        mChannelLock.notify();
+                    }
+                    resultCallback.onException(
+                            new ApduException("Failed to open logical channel opened for AID: "
+                                    + mAid + ", with status: " + status));
+                    return;
+                }
+
+                RequestBuilder builder = new RequestBuilder(channel, mSupportExtendedApdu);
+                Throwable requestException = null;
+                try {
+                    requestProvider.buildRequest(openChannelResponse.getSelectResponse(), builder);
+                } catch (Throwable e) {
+                    requestException = e;
+                }
+                if (builder.getCommands().isEmpty() || requestException != null) {
+                    // Just close the channel if we don't have commands to send or an error
+                    // was encountered.
+                    closeAndReturn(channel, null /* response */, requestException, resultCallback,
+                            handler);
+                    return;
+                }
+                sendCommand(builder.getCommands(), 0 /* index */, resultCallback, handler);
+            }
+        }, handler);
+    }
+
+    /**
+     * Closes any open channel.
+     *
+     * <p>Used by EuiccSession#endSession.
+     */
+    public void closeAnyOpenChannel() {
+        // TODO: implement this. Different from existing closeExistingChannelIfExists()
+        // which is only used in constructor and don't worry about multi-thread racing.
+        // 1. Acquire channel lock
+        // 2. Check sharedpref for existing open channel
+        // 3. Close any open channel
+        // 4. Release channel lock
     }
 
     /**
@@ -271,7 +184,6 @@
     private void sendCommand(
             List<ApduCommand> commands,
             int index,
-            boolean closeChannelImmediately,
             ApduSenderResultCallback resultCallback,
             Handler handler) {
         ApduCommand command = commands.get(index);
@@ -286,21 +198,9 @@
                             public void onResult(IccIoResult fullResponse) {
                                 logv("Full APDU response: " + fullResponse);
                                 int status = (fullResponse.sw1 << 8) | fullResponse.sw2;
-                                if (status != STATUS_NO_ERROR
-                                        && fullResponse.sw1 != SW1_NO_ERROR) {
-                                    if (status == STATUS_CHANNEL_CLOSED) {
-                                        // Channel is closed by EUICC e.g. REFRESH.
-                                        tearDownPreferences();
-                                        mChannelOpened = false;
-                                        // TODO: add retry
-                                    }
-                                    returnRespnseOrException(
-                                            command.channel,
-                                            closeChannelImmediately,
-                                            null /* response */,
-                                            new ApduException(status),
-                                            resultCallback,
-                                            handler);
+                                if (status != STATUS_NO_ERROR && fullResponse.sw1 != SW1_NO_ERROR) {
+                                    closeAndReturn(command.channel, null /* response */,
+                                            new ApduException(status), resultCallback, handler);
                                     return;
                                 }
 
@@ -310,17 +210,11 @@
                                                 fullResponse);
                                 if (continueSendCommand) {
                                     // Sends the next command
-                                    sendCommand(commands, index + 1,
-                                            closeChannelImmediately, resultCallback, handler);
+                                    sendCommand(commands, index + 1, resultCallback, handler);
                                 } else {
                                     // Returns the result of the last command
-                                    returnRespnseOrException(
-                                            command.channel,
-                                            closeChannelImmediately,
-                                            fullResponse.payload,
-                                            null /* exception */,
-                                            resultCallback,
-                                            handler);
+                                    closeAndReturn(command.channel, fullResponse.payload,
+                                            null /* exception */, resultCallback, handler);
                                 }
                             }
                         }, handler);
@@ -343,7 +237,7 @@
             AsyncResultCallback<IccIoResult> resultCallback,
             Handler handler) {
         ByteArrayOutputStream resultBuilder =
-            responseBuilder == null ? new ByteArrayOutputStream() : responseBuilder;
+                responseBuilder == null ? new ByteArrayOutputStream() : responseBuilder;
         if (lastResponse.payload != null) {
             try {
                 resultBuilder.write(lastResponse.payload);
@@ -369,41 +263,6 @@
                 }, handler);
     }
 
-    private void tearDownPreferences() {
-        PreferenceManager.getDefaultSharedPreferences(mContext)
-                .edit()
-                .remove(mChannelKey)
-                .remove(mChannelResponseKey)
-                .apply();
-    }
-
-    /**
-     * Fires the {@code resultCallback} to return a response or exception. Also
-     * closes the open logical channel if {@code closeChannelImmediately} is {@code true}.
-     */
-    private void returnRespnseOrException(
-            int channel,
-            boolean closeChannelImmediately,
-            @Nullable byte[] response,
-            @Nullable Throwable exception,
-            ApduSenderResultCallback resultCallback,
-            Handler handler) {
-        if (closeChannelImmediately) {
-            closeAndReturn(
-                    channel,
-                    response,
-                    exception,
-                    resultCallback,
-                    handler);
-        } else {
-            releaseChannelLockAndReturn(
-                    response,
-                    exception,
-                    resultCallback,
-                    handler);
-        }
-    }
-
     /**
      * Closes the opened logical channel.
      *
@@ -421,9 +280,10 @@
         mCloseChannel.invoke(channel, new AsyncResultCallback<Boolean>() {
             @Override
             public void onResult(Boolean aBoolean) {
-                tearDownPreferences();
-                mChannelOpened = false;
-                releaseChannelLock();
+                synchronized (mChannelLock) {
+                    mChannelOpened = false;
+                    mChannelLock.notify();
+                }
 
                 if (exception == null) {
                     resultCallback.onResult(response);
@@ -433,99 +293,4 @@
             }
         }, handler);
     }
-
-    /**
-     * Cleanup the existing opened channel which remained opened earlier due
-     * to:
-     *
-     * <p> 1) onging EuiccSession. This will be called by {@link EuiccSession#endSession()}
-     * from non-main-thread. Or,
-     *
-     * <p> 2) telephony crash. This will be called by constructor from main-thread.
-     */
-    public void closeAnyOpenChannel() {
-        if (!acquireChannelLock()) {
-            // This cannot happen for case 2) when called by constructor
-            loge("[closeAnyOpenChannel] failed to acquire channel lock");
-            return;
-        }
-        int channelId = PreferenceManager.getDefaultSharedPreferences(mContext)
-                .getInt(mChannelKey, IccOpenLogicalChannelResponse.INVALID_CHANNEL);
-        if (channelId == IccOpenLogicalChannelResponse.INVALID_CHANNEL) {
-            releaseChannelLock();
-            return;
-        }
-        logv("[closeAnyOpenChannel] closing the open channel : " +  channelId);
-        mCloseChannel.invoke(channelId, new AsyncResultCallback<Boolean>() {
-            @Override
-            public void onResult(Boolean isSuccess) {
-                if (isSuccess) {
-                    logv("[closeAnyOpenChannel] Channel closed successfully: " + channelId);
-                    tearDownPreferences();
-                }
-                // Even if CloseChannel failed, pretend that the channel is closed.
-                // So next send() will try open the channel again. If the channel is
-                // indeed still open, we use the channelId saved in sharedPref.
-                mChannelOpened = false;
-                releaseChannelLock();
-            }
-        }, mHandler);
-    }
-
-    // releases channel and callback
-    private void releaseChannelLockAndReturn(
-            @Nullable byte[] response,
-            @Nullable Throwable exception,
-            ApduSenderResultCallback resultCallback,
-            Handler handler) {
-        handler.post(
-                () -> {
-                    releaseChannelLock();
-                    if (exception == null) {
-                        resultCallback.onResult(response);
-                    } else {
-                        resultCallback.onException(exception);
-                    }
-                });
-    }
-
-    private void releaseChannelLock() {
-        synchronized (mChannelInUseLock) {
-            logd("Channel lock released.");
-            mChannelInUse = false;
-            mChannelInUseLock.notify();
-        }
-    }
-
-    /**
-     * Acquires channel lock and returns {@code true} if successful.
-     *
-     * <p>It fails and returns {@code false} when:
-     * <ul>
-     *   <li>Called from main thread, and mChannelInUse=true, fails immediately.
-     *   <li>Called from non main thread, and mChannelInUse=true after 2 seconds waiting, fails.
-     * </ul>
-     */
-    private boolean acquireChannelLock() {
-        synchronized (mChannelInUseLock) {
-            if (mChannelInUse) {
-                if (!Looper.getMainLooper().equals(Looper.myLooper())) {
-                    logd("Logical channel is in use. Wait.");
-                    try {
-                        mChannelInUseLock.wait(WAIT_TIME_MS);
-                    } catch (InterruptedException e) {
-                        // nothing to do
-                    }
-                    if (mChannelInUse) {
-                        return false;
-                    }
-                } else {
-                    return false;
-                }
-            }
-            mChannelInUse = true;
-            logd("Channel lock acquired.");
-            return true;
-        }
-    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierPrivilegesTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierPrivilegesTrackerTest.java
index a860dff..1c58ef2 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierPrivilegesTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierPrivilegesTrackerTest.java
@@ -220,8 +220,9 @@
         // Capture CarrierConfigChangeListener to emulate the carrier config change notification
         ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> listenerArgumentCaptor =
                 ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
-        CarrierPrivilegesTracker cpt = new CarrierPrivilegesTracker(mTestableLooper.getLooper(),
-                mPhone, mContext, mFeatureFlags);
+        CarrierPrivilegesTracker cpt =
+                new CarrierPrivilegesTracker(
+                        mTestableLooper.getLooper(), mPhone, mContext, mFeatureFlags);
         verify(mCarrierConfigManager).registerCarrierConfigChangeListener(any(),
                 listenerArgumentCaptor.capture());
         mCarrierConfigChangeListener = listenerArgumentCaptor.getAllValues().get(0);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java
index a645439..fae5e8b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java
@@ -981,6 +981,8 @@
 
         mApplicationInfo.targetSdkVersion = Build.VERSION_CODES.TIRAMISU;
         doReturn(mApplicationInfo).when(mPackageManager).getApplicationInfo(anyString(), anyInt());
+        doReturn(mApplicationInfo).when(mPackageManager).getApplicationInfoAsUser(
+                anyString(), anyInt(), any(UserHandle.class));
         mContextFixture.addCallingOrSelfPermission("");
         mContextFixture.addCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE);
         mContextFixture.addCallingOrSelfPermission(
@@ -1077,6 +1079,8 @@
 
         mApplicationInfo.targetSdkVersion = Build.VERSION_CODES.TIRAMISU;
         doReturn(mApplicationInfo).when(mPackageManager).getApplicationInfo(anyString(), anyInt());
+        doReturn(mApplicationInfo).when(mPackageManager).getApplicationInfoAsUser(
+                anyString(), anyInt(), any(UserHandle.class));
         mContextFixture.addCallingOrSelfPermission("");
         mContextFixture.addCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE);
         mContextFixture.addCallingOrSelfPermission(
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommenderTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommenderTest.java
index 230a9b1..541cee2 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommenderTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommenderTest.java
@@ -29,6 +29,9 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -67,6 +70,7 @@
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.flags.Flags;
+import com.android.internal.telephony.metrics.SatelliteStats;
 
 import org.junit.After;
 import org.junit.Before;
@@ -118,6 +122,8 @@
     private Uri mTestConnectionAddress = Uri.parse("tel:1234");
     private TestSOSMessageRecommender mTestSOSMessageRecommender;
     private ServiceState mServiceState2;
+    @Mock
+    private SatelliteStats mMockSatelliteStats;
 
     @Before
     public void setUp() throws Exception {
@@ -153,6 +159,10 @@
         when(mServiceState2.getState()).thenReturn(STATE_OUT_OF_SERVICE);
         when(mPhone.isImsRegistered()).thenReturn(false);
         when(mPhone2.isImsRegistered()).thenReturn(false);
+        replaceInstance(SatelliteStats.class, "sInstance", null,
+                mMockSatelliteStats);
+        doNothing().when(mMockSatelliteStats).onSatelliteSosMessageRecommender(
+                any(SatelliteStats.SatelliteSosMessageRecommenderParams.class));
     }
 
     @After
@@ -165,6 +175,8 @@
         testTimeoutBeforeEmergencyCallEnd(EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911,
                 DEFAULT_SATELLITE_MESSAGING_PACKAGE, DEFAULT_SATELLITE_MESSAGING_CLASS,
                 DEFAULT_T911_HANDOVER_INTENT_ACTION);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertTrue(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
@@ -177,6 +189,8 @@
         testTimeoutBeforeEmergencyCallEnd(EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS,
                 "android.com.vendor.message", "android.com.vendor.message.SmsApp",
                 DEFAULT_HANDOVER_INTENT_ACTION);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertTrue(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
@@ -188,6 +202,8 @@
         mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(false);
         testTimeoutBeforeEmergencyCallEnd(EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS, "", "",
                 DEFAULT_HANDOVER_INTENT_ACTION);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertTrue(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
@@ -197,6 +213,8 @@
         mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(false);
         testTimeoutBeforeEmergencyCallEnd(EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS, "", "",
                 DEFAULT_HANDOVER_INTENT_ACTION);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertTrue(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
@@ -208,6 +226,7 @@
         processAllMessages();
         assertFalse(mTestSOSMessageRecommender.isTimerStarted());
         assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
+        verify(mMockSatelliteStats, never()).onSatelliteSosMessageRecommender(any());
     }
 
     private void testTimeoutBeforeEmergencyCallEnd(int expectedHandoverType,
@@ -239,6 +258,8 @@
         }
         assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1);
         assertUnregisterForStateChangedEventsTriggered(mPhone2, 1, 1);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertTrue(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
@@ -266,6 +287,8 @@
         assertFalse(mTestConnection.isEventSent(TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE));
         assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1);
         assertUnregisterForStateChangedEventsTriggered(mPhone2, 1, 1);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertFalse(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
@@ -302,11 +325,15 @@
     @Test
     public void testStopTrackingCallBeforeTimeout_ConnectionActive() {
         testStopTrackingCallBeforeTimeout(Connection.STATE_ACTIVE);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertFalse(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
     public void testStopTrackingCallBeforeTimeout_ConnectionDisconnected() {
         testStopTrackingCallBeforeTimeout(Connection.STATE_DISCONNECTED);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertFalse(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
@@ -337,6 +364,8 @@
         assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1);
         assertUnregisterForStateChangedEventsTriggered(mPhone2, 1, 1);
         assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertTrue(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
@@ -357,6 +386,9 @@
         assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
         assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1);
         assertUnregisterForStateChangedEventsTriggered(mPhone2, 1, 1);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertFalse(mTestSOSMessageRecommender.isDialerNotified());
+        reset(mMockSatelliteStats);
 
         mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, false);
         processAllMessages();
@@ -387,6 +419,8 @@
         assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
         assertUnregisterForStateChangedEventsTriggered(mPhone, 2, 2);
         assertUnregisterForStateChangedEventsTriggered(mPhone2, 2, 2);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertTrue(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
@@ -426,30 +460,40 @@
         assertUnregisterForStateChangedEventsTriggered(mPhone2, 1, 1);
         assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
         assertFalse(mTestSOSMessageRecommender.isTimerStarted());
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertTrue(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
     public void testCellularServiceStateChangedBeforeTimeout_InServiceToOutOfService() {
         testCellularServiceStateChangedBeforeTimeout(
                 ServiceState.STATE_IN_SERVICE, STATE_OUT_OF_SERVICE);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertTrue(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
     public void testCellularServiceStateChangedBeforeTimeout_InServiceToPowerOff() {
         testCellularServiceStateChangedBeforeTimeout(
                 ServiceState.STATE_IN_SERVICE, ServiceState.STATE_POWER_OFF);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertTrue(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
     public void testCellularServiceStateChangedBeforeTimeout_EmergencyOnlyToOutOfService() {
         testCellularServiceStateChangedBeforeTimeout(
                 ServiceState.STATE_EMERGENCY_ONLY, STATE_OUT_OF_SERVICE);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertTrue(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
     public void testCellularServiceStateChangedBeforeTimeout_EmergencyOnlyToPowerOff() {
         testCellularServiceStateChangedBeforeTimeout(
                 ServiceState.STATE_EMERGENCY_ONLY, ServiceState.STATE_POWER_OFF);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertTrue(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
@@ -471,11 +515,14 @@
         assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
         assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1);
         assertUnregisterForStateChangedEventsTriggered(mPhone2, 1, 1);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertFalse(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
     public void testSatelliteNotAllowedInCurrentLocation() {
         mTestSOSMessageRecommender.isSatelliteAllowedCallback = null;
+        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(false);
         mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, false);
         processAllMessages();
         assertNull(mTestSOSMessageRecommender.isSatelliteAllowedCallback);
@@ -498,6 +545,8 @@
         assertRegisterForStateChangedEventsTriggered(mPhone2, 1, 1);
         assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1);
         assertUnregisterForStateChangedEventsTriggered(mPhone2, 1, 1);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertFalse(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
@@ -513,6 +562,7 @@
 
         assertFalse(testSOSMessageRecommender.isTimerStarted());
         assertEquals(0, testSOSMessageRecommender.getCountOfTimerStarted());
+        verify(mMockSatelliteStats, never()).onSatelliteSosMessageRecommender(any());
     }
 
     @Test
@@ -548,6 +598,7 @@
         processAllMessages();
         assertEquals(TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS,
                 mTestSOSMessageRecommender.getTimeOutMillis());
+        verify(mMockSatelliteStats, never()).onSatelliteSosMessageRecommender(any());
 
         // Both OEM and carrier support satellite, but device is not connected to carrier satellite
         // within hysteresis time. Thus, OEM timer will be used.
@@ -560,6 +611,7 @@
         processAllMessages();
         assertEquals(TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS,
                 mTestSOSMessageRecommender.getTimeOutMillis());
+        verify(mMockSatelliteStats, never()).onSatelliteSosMessageRecommender(any());
 
         // Both OEM and carrier support satellite, and device is connected to carrier satellite
         // within hysteresis time. Thus, carrier timer will be used.
@@ -570,6 +622,7 @@
         mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, false);
         processAllMessages();
         assertEquals(carrierTimeoutMillis, mTestSOSMessageRecommender.getTimeOutMillis());
+        verify(mMockSatelliteStats, never()).onSatelliteSosMessageRecommender(any());
     }
 
     @Test
@@ -581,6 +634,7 @@
         mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, false);
         assertEquals(EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911,
                 mTestSOSMessageRecommender.getEmergencyCallToSatelliteHandoverType());
+        verify(mMockSatelliteStats, never()).onSatelliteSosMessageRecommender(any());
 
         mSetFlagsRule.disableFlags(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN);
     }
@@ -594,6 +648,7 @@
         mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, false);
         assertEquals(EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911,
                 mTestSOSMessageRecommender.getEmergencyCallToSatelliteHandoverType());
+        verify(mMockSatelliteStats, never()).onSatelliteSosMessageRecommender(any());
 
         mSetFlagsRule.disableFlags(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN);
     }
@@ -613,6 +668,7 @@
         mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, false);
         assertEquals(EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS,
                 mTestSOSMessageRecommender.getEmergencyCallToSatelliteHandoverType());
+        verify(mMockSatelliteStats, never()).onSatelliteSosMessageRecommender(any());
 
         mSetFlagsRule.disableFlags(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN);
     }
@@ -896,6 +952,7 @@
                 isSatelliteAllowedCallback = null;
         private ComponentName mSmsAppComponent = new ComponentName(
                 DEFAULT_SATELLITE_MESSAGING_PACKAGE, DEFAULT_SATELLITE_MESSAGING_CLASS);
+        private boolean mIsDialerNotified;
 
         /**
          * Create an instance of SatelliteSOSMessageRecommender.
@@ -924,6 +981,12 @@
             isSatelliteAllowedCallback = callback;
         }
 
+        @Override
+        protected void reportESosRecommenderDecision(boolean isDialerNotified) {
+            super.reportESosRecommenderDecision(isDialerNotified);
+            mIsDialerNotified = isDialerNotified;
+        }
+
         public boolean isTimerStarted() {
             return hasMessages(EVENT_TIME_OUT);
         }
@@ -939,6 +1002,10 @@
         public long getTimeOutMillis() {
             return mTimeoutMillis;
         }
+
+        public boolean isDialerNotified() {
+            return mIsDialerNotified;
+        }
     }
 
     private static class TestConnection extends Connection {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSessionControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSessionControllerTest.java
index b4af458..a617182 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSessionControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSessionControllerTest.java
@@ -49,6 +49,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.PersistableBundle;
+import android.telephony.ServiceState;
 import android.telephony.satellite.ISatelliteModemStateCallback;
 import android.telephony.satellite.SatelliteManager;
 import android.testing.AndroidTestingRunner;
@@ -104,6 +105,7 @@
     @Mock private DatagramReceiver mMockDatagramReceiver;
     @Mock private DatagramDispatcher mMockDatagramDispatcher;
     @Mock private DatagramController mMockDatagramController;
+    @Mock private ServiceState mMockServiceState;
 
     @Captor ArgumentCaptor<Handler> mHandlerCaptor;
     @Captor ArgumentCaptor<Integer> mMsgCaptor;
@@ -216,6 +218,7 @@
         // Notify Screen off
         sendScreenStateChanged(mHandlerCaptor.getValue(), mMsgCaptor.getValue(), false);
         processAllMessages();
+        clearInvocations(mMockSatelliteController);
 
         // Verify that the screen off inactivity timer is started.
         assertTrue(mTestSatelliteSessionController.isScreenOffInActivityTimerStarted());
@@ -283,6 +286,8 @@
         doNothing().when(mDeviceStateMonitor).registerForScreenStateChanged(
                 eq(mTestSatelliteSessionController.getHandler()), anyInt(), any());
         when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(true);
+        when(mMockSatelliteController.turnOffSatelliteSessionForEmergencyCall(
+                anyInt())).thenReturn(false);
 
         when(mMockSatelliteController.getRequestIsEmergency()).thenReturn(false);
         when(mMockSatelliteController.isSatelliteRoamingP2pSmSSupported(
@@ -295,6 +300,7 @@
 
         // Since satellite is supported, SatelliteSessionController should move to POWER_OFF state.
         assertNotNull(mTestSatelliteSessionController);
+        mTestSatelliteSessionController.setSatelliteEnabledForNtnOnlySubscription(false);
         assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
         setupDatagramTransferringState(true);
 
@@ -328,6 +334,8 @@
         doNothing().when(mDeviceStateMonitor).registerForScreenStateChanged(
                 eq(mTestSatelliteSessionController.getHandler()), anyInt(), any());
         when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(true);
+        when(mMockSatelliteController.turnOffSatelliteSessionForEmergencyCall(
+                anyInt())).thenReturn(false);
 
         when(mMockSatelliteController.getRequestIsEmergency()).thenReturn(true);
         when(mMockSatelliteController.isSatelliteEsosSupported(anyInt())).thenReturn(true);
@@ -338,6 +346,7 @@
 
         // Since satellite is supported, SatelliteSessionController should move to POWER_OFF state.
         assertNotNull(mTestSatelliteSessionController);
+        mTestSatelliteSessionController.setSatelliteEnabledForNtnOnlySubscription(false);
         assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
         setupDatagramTransferringState(true);
 
@@ -366,6 +375,76 @@
     }
 
     @Test
+    public void testDisableSatelliteWhenCellularModemEnabledInIdleMode() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        doNothing().when(mDeviceStateMonitor).registerForScreenStateChanged(
+                eq(mTestSatelliteSessionController.getHandler()), anyInt(), any());
+        when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(false);
+        when(mPhone.getServiceState()).thenReturn(mMockServiceState);
+
+        // Since satellite is supported, SatelliteSessionController should move to POWER_OFF state.
+        assertNotNull(mTestSatelliteSessionController);
+        mTestSatelliteSessionController.setSatelliteEnabledForNtnOnlySubscription(false);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+
+        // Conditions for operation
+        boolean isEmergency = true;
+        // Cellular network is not IN_SERVICE and emergency only.
+        // Satellite request is emergency and emergency communication was established.
+        // Disabling satellite was not allowed
+        when(mMockServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+        when(mMockServiceState.getDataRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+        when(mMockServiceState.isEmergencyOnly()).thenReturn(false);
+        when(mMockSatelliteController.getRequestIsEmergency()).thenReturn(isEmergency);
+        when(mMockDatagramController.isEmergencyCommunicationEstablished()).thenReturn(true);
+        when(mMockSatelliteController.turnOffSatelliteSessionForEmergencyCall(
+                anyInt())).thenReturn(false);
+
+        moveToIdleState();
+
+        // Cellular network is not in STATE_IN_SERVICE or emergency only.
+        // Should not disable satellite
+        verify(mMockSatelliteController, never()).requestSatelliteEnabled(
+                eq(false), eq(false), eq(isEmergency), any(IIntegerConsumer.Stub.class));
+
+        // Notify cellular service is in STATE_IN_SERVICE.
+        ServiceState serviceState = new ServiceState();
+        serviceState.setVoiceRegState(ServiceState.STATE_IN_SERVICE);
+        serviceState.setDataRegState(ServiceState.STATE_OUT_OF_SERVICE);
+        serviceState.setEmergencyOnly(false);
+        mTestSatelliteSessionController.onCellularServiceStateChanged(serviceState);
+        processAllMessages();
+
+        // Satellite is in emergency mode and emergency communication was established.
+        // Should not disable satellite
+        verify(mMockSatelliteController, never()).requestSatelliteEnabled(
+                eq(false), eq(false), eq(isEmergency), any(IIntegerConsumer.Stub.class));
+
+        // Satellite is in emergency mode but emergency communication was not established.
+        // Disabling satellite was not allowed
+        when(mMockDatagramController.isEmergencyCommunicationEstablished()).thenReturn(false);
+        when(mMockSatelliteController.turnOffSatelliteSessionForEmergencyCall(
+                anyInt())).thenReturn(false);
+        mTestSatelliteSessionController.onCellularServiceStateChanged(serviceState);
+        processAllMessages();
+
+        // Should not disable satellite
+        verify(mMockSatelliteController, never()).requestSatelliteEnabled(
+                eq(false), eq(false), eq(isEmergency), any(IIntegerConsumer.Stub.class));
+
+        // Satellite is in emergency mode but emergency communication was not established.
+        // Disabling satellite was allowed
+        when(mMockSatelliteController.turnOffSatelliteSessionForEmergencyCall(
+                anyInt())).thenReturn(true);
+        mTestSatelliteSessionController.onCellularServiceStateChanged(serviceState);
+        processAllMessages();
+
+        // Should disable satellite
+        verify(mMockSatelliteController).requestSatelliteEnabled(
+                eq(false), eq(false), eq(isEmergency), any(IIntegerConsumer.Stub.class));
+    }
+
+    @Test
     public void testStateTransition() {
         /**
          * Since satellite is supported, SatelliteSessionController should move to POWER_OFF state.
@@ -1419,6 +1498,8 @@
     }
 
     private static class TestSatelliteSessionController extends SatelliteSessionController {
+        boolean mSatelliteEnabledForNtnOnlySubscription = true;
+
         TestSatelliteSessionController(Context context, Looper looper, FeatureFlags featureFlags,
                 boolean isSatelliteSupported,
                 SatelliteModemInterface satelliteModemInterface) {
@@ -1450,7 +1531,11 @@
         }
 
         protected boolean isSatelliteEnabledForNtnOnlySubscription() {
-            return true;
+            return mSatelliteEnabledForNtnOnlySubscription;
+        }
+
+        void setSatelliteEnabledForNtnOnlySubscription(boolean enabled) {
+            mSatelliteEnabledForNtnOnlySubscription = false;
         }
     }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/ApduSenderTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/ApduSenderTest.java
index 172211c..a65814e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/ApduSenderTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/ApduSenderTest.java
@@ -16,52 +16,36 @@
 
 package com.android.internal.telephony.uicc.euicc.apdu;
 
-import static com.android.internal.telephony.CommandException.Error.RADIO_NOT_AVAILABLE;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.os.Handler;
 import android.os.Looper;
-import android.platform.test.flag.junit.SetFlagsRule;
-import android.preference.PreferenceManager;
-import android.telephony.IccOpenLogicalChannelResponse;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
-import androidx.test.InstrumentationRegistry;
-
 import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.CommandsInterface;
-import com.android.internal.telephony.euicc.EuiccSession;
-import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.uicc.IccIoResult;
 import com.android.internal.telephony.uicc.IccUtils;
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.InOrder;
-import org.mockito.Mockito;
 
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class ApduSenderTest {
-    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     private static class ResponseCaptor extends ApduSenderResultCallback {
         public byte[] response;
@@ -91,13 +75,6 @@
         }
     }
 
-    private static final int PHONE_ID = 0;
-    private static final String SESSION_ID = "TEST";
-    // keep in sync with ApduSender.mChannelKey
-    private static final String SHARED_PREFS_KEY_CHANNEL_ID = "esim-channel_0";
-    // keep in sync with ApduSender.mChannelResponseKey
-    private static final String SHARED_PREFS_KEY_CHANNEL_RESPONSE = "esim-res-id_0";
-
     // Mocked classes
     private CommandsInterface mMockCi;
 
@@ -105,20 +82,19 @@
     private Handler mHandler;
     private ResponseCaptor mResponseCaptor;
     private byte[] mSelectResponse;
+    private static final String AID = "B2C3D4";
     private ApduSender mSender;
 
     @Before
     public void setUp() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_OPTIMIZATION_APDU_SENDER);
-
         mMockCi = mock(CommandsInterface.class);
-        mLooper = TestableLooper.get(this);
-        mHandler = new Handler(mLooper.getLooper());
+        mHandler = new Handler(Looper.myLooper());
+
         mResponseCaptor = new ResponseCaptor();
         mSelectResponse = null;
 
-        mSender = new ApduSender(InstrumentationRegistry.getContext(), PHONE_ID,
-                            mMockCi, ApduSender.ISD_R_AID, false /* supportExtendedApdu */);
+        mSender = new ApduSender(mMockCi, AID, false /* supportExtendedApdu */);
+        mLooper = TestableLooper.get(this);
     }
 
     @After
@@ -129,19 +105,6 @@
         mResponseCaptor = null;
         mSelectResponse = null;
         mSender = null;
-
-        EuiccSession.get().endSession(SESSION_ID);
-        clearSharedPreferences();
-    }
-
-    @Test
-    public void testWrongAid_throwsIllegalArgumentException() {
-        String wrongAid = "-1";
-
-        assertThrows(IllegalArgumentException.class, () -> {
-            new ApduSender(InstrumentationRegistry.getContext(), 0 /* phoneId= */,
-                            mMockCi, wrongAid, false /* supportExtendedApdu */);
-        });
     }
 
     @Test
@@ -156,7 +119,7 @@
         assertEquals("A1A1A19000", IccUtils.bytesToHexString(mSelectResponse));
         assertNull(mResponseCaptor.response);
         assertNull(mResponseCaptor.exception);
-        verify(mMockCi).iccOpenLogicalChannel(eq(ApduSender.ISD_R_AID), anyInt(), any());
+        verify(mMockCi).iccOpenLogicalChannel(eq(AID), anyInt(), any());
         verify(mMockCi).iccCloseLogicalChannel(eq(channel), eq(true /*isEs10*/), any());
     }
 
@@ -172,7 +135,7 @@
         assertNull("Request provider should not be called when failed to open channel.",
                 mSelectResponse);
         assertTrue(mResponseCaptor.exception instanceof ApduException);
-        verify(mMockCi).iccOpenLogicalChannel(eq(ApduSender.ISD_R_AID), anyInt(), any());
+        verify(mMockCi).iccOpenLogicalChannel(eq(AID), anyInt(), any());
     }
 
     @Test
@@ -186,11 +149,8 @@
         mLooper.processAllMessages();
 
         assertEquals("A1A1A1", IccUtils.bytesToHexString(mResponseCaptor.response));
-        InOrder inOrder = inOrder(mMockCi);
-        inOrder.verify(mMockCi).iccOpenLogicalChannel(eq(ApduSender.ISD_R_AID), anyInt(), any());
-        inOrder.verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10),
-                eq(1), eq(2), eq(3), eq(0), eq("a"), anyBoolean(), any());
-        inOrder.verify(mMockCi).iccCloseLogicalChannel(eq(channel), eq(true /*isEs10*/), any());
+        verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2),
+                eq(3), eq(0), eq("a"), anyBoolean(), any());
     }
 
     @Test
@@ -209,17 +169,14 @@
         mLooper.processAllMessages();
 
         assertEquals("A4", IccUtils.bytesToHexString(mResponseCaptor.response));
-        InOrder inOrder = inOrder(mMockCi);
-        inOrder.verify(mMockCi).iccOpenLogicalChannel(eq(ApduSender.ISD_R_AID), anyInt(), any());
-        inOrder.verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10),
-                eq(1), eq(2), eq(3), eq(0), eq("a"), anyBoolean(), any());
-        inOrder.verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10),
-                eq(1), eq(2), eq(3), eq(1), eq("ab"), anyBoolean(), any());
-        inOrder.verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10),
-                eq(1), eq(2),  eq(3), eq(0), eq(""), anyBoolean(), any());
-        inOrder.verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81),
-                eq(0xE2), eq(0x91), eq(0), eq(2), eq("abcd"), anyBoolean(), any());
-        inOrder.verify(mMockCi).iccCloseLogicalChannel(eq(channel), eq(true /*isEs10*/), any());
+        verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2),
+                eq(3), eq(0), eq("a"), anyBoolean(), any());
+        verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2),
+                eq(3), eq(1), eq("ab"), anyBoolean(), any());
+        verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2),
+                eq(3), eq(0), eq(""), anyBoolean(), any());
+        verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), eq(0xE2), eq(0x91),
+                eq(0), eq(2), eq("abcd"), anyBoolean(), any());
     }
 
     @Test
@@ -382,157 +339,6 @@
 
         assertNull("Should not open channel when another one is already opened.", mSelectResponse);
         assertTrue(mResponseCaptor.exception instanceof ApduException);
-        verify(mMockCi, times(1)).iccOpenLogicalChannel(eq(ApduSender.ISD_R_AID), anyInt(), any());
-    }
-
-    @Test
-    public void testConstructor_closeOpenChannelInSharedPreference() throws InterruptedException {
-        // Open a channel and not close it, by making CI.iccTransmitApduLogicalChannel throw.
-        int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000");
-        doThrow(new RuntimeException()).when(mMockCi).iccTransmitApduLogicalChannel(
-                eq(channel), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(),
-                anyBoolean(), any());
-        mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu(
-                10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler);
-        mLooper.processAllMessages();
-        // Stub close channel
-        reset(mMockCi);
-        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null);
-
-        // Call constructor
-        mSender = new ApduSender(InstrumentationRegistry.getContext(), PHONE_ID,
-                            mMockCi, ApduSender.ISD_R_AID, false /* supportExtendedApdu */);
-        mLooper.processAllMessages();
-
-        // The constructor should have closed channel
-        verify(mMockCi).iccCloseLogicalChannel(eq(channel), eq(true /*isEs10*/), any());
-        assertEquals(-1, getChannelIdFromSharedPreferences());
-    }
-
-    @Test
-    public void testSend_OpenChannelFailedNoSuchElement_useChannelInSharedPreference() {
-        // Open a channel but not close, by making CI.iccTransmitApduLogicalChannel throw.
-        int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000");
-        doThrow(new RuntimeException()).when(mMockCi).iccTransmitApduLogicalChannel(
-                eq(channel), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(),
-                anyBoolean(), any());
-        mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu(
-                10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler);
-        mLooper.processAllMessages();
-        reset(mMockCi);
-        // Constructor fails to close channel
-        LogicalChannelMocker.mockCloseLogicalChannel(
-                mMockCi, channel, new CommandException(RADIO_NOT_AVAILABLE));
-        mSender = new ApduSender(InstrumentationRegistry.getContext(), PHONE_ID,
-                            mMockCi, ApduSender.ISD_R_AID, false /* supportExtendedApdu */);
-        mLooper.processAllMessages();
-        reset(mMockCi);
-        // Stub open channel failure NO_SUCH_ELEMENT
-        LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi,
-                new CommandException(CommandException.Error.NO_SUCH_ELEMENT));
-        LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, "A1A1A19000");
-        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null);
-
-        mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu(
-                10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler);
-        mLooper.processAllMessages();
-
-        // open channel would fail, and send/close would succeed because of
-        // previous open response saved in sharedPref
-        InOrder inOrder = inOrder(mMockCi);
-        inOrder.verify(mMockCi).iccOpenLogicalChannel(eq(ApduSender.ISD_R_AID), anyInt(), any());
-        inOrder.verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel),
-                 eq(channel | 10), eq(1), eq(2), eq(3), eq(0), eq("a"), anyBoolean(), any());
-        inOrder.verify(mMockCi).iccCloseLogicalChannel(eq(channel), eq(true /*isEs10*/), any());
-        inOrder.verifyNoMoreInteractions();
-    }
-
-    @Test
-    public void testSend_euiccSession_shouldNotCloseChannel()
-            throws InterruptedException {
-        int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000");
-        LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, "A1A1A19000");
-        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null);
-        EuiccSession.get().startSession(SESSION_ID);
-
-        mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu(
-                10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler);
-        mLooper.processAllMessages();
-
-        assertEquals("A1A1A1", IccUtils.bytesToHexString(mResponseCaptor.response));
-        InOrder inOrder = inOrder(mMockCi);
-        inOrder.verify(mMockCi).iccOpenLogicalChannel(eq(ApduSender.ISD_R_AID), anyInt(), any());
-        inOrder.verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10),
-                eq(1), eq(2), eq(3), eq(0), eq("a"), anyBoolean(), any());
-        // No iccCloseLogicalChannel
-        inOrder.verifyNoMoreInteractions();
-    }
-
-    @Test
-    public void testSendTwice_euiccSession_shouldOpenChannelOnceNotCloseChannel()
-            throws InterruptedException {
-        int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000");
-        LogicalChannelMocker.mockSendToLogicalChannel(
-                    mMockCi, channel, "A1A1A19000", "A1A1A19000");
-        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null);
-        EuiccSession.get().startSession(SESSION_ID);
-
-        mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu(
-                10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler);
-        mLooper.processAllMessages();
-        mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu(
-                10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler);
-        mLooper.processAllMessages();
-
-        assertEquals("A1A1A1", IccUtils.bytesToHexString(mResponseCaptor.response));
-        InOrder inOrder = inOrder(mMockCi);
-        // iccOpenLogicalChannel once
-        inOrder.verify(mMockCi).iccOpenLogicalChannel(eq(ApduSender.ISD_R_AID), anyInt(), any());
-        // iccTransmitApduLogicalChannel twice
-        inOrder.verify(mMockCi, times(2)).iccTransmitApduLogicalChannel(eq(channel),
-                 eq(channel | 10), eq(1), eq(2), eq(3), eq(0), eq("a"), anyBoolean(), any());
-        // No iccCloseLogicalChannel
-        inOrder.verifyNoMoreInteractions();
-    }
-
-    @Test
-    public void testSendTwice_thenEndSession() throws InterruptedException {
-        int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000");
-        LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel,
-                "A1A1A19000", "A1A1A19000");
-        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null);
-        EuiccSession.get().startSession(SESSION_ID);
-
-        mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu(
-                10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler);
-        mLooper.processAllMessages();
-        mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu(
-                10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler);
-        mLooper.processAllMessages();
-        EuiccSession.get().endSession(SESSION_ID);
-        mLooper.processAllMessages();
-
-        assertEquals("A1A1A1", IccUtils.bytesToHexString(mResponseCaptor.response));
-        InOrder inOrder = inOrder(mMockCi);
-        // iccOpenLogicalChannel once
-        inOrder.verify(mMockCi).iccOpenLogicalChannel(eq(ApduSender.ISD_R_AID), anyInt(), any());
-        // iccTransmitApduLogicalChannel twice
-        inOrder.verify(mMockCi, times(2)).iccTransmitApduLogicalChannel(eq(channel),
-                 eq(channel | 10), eq(1), eq(2), eq(3), eq(0), eq("a"), anyBoolean(), any());
-        // iccCloseLogicalChannel once
-        inOrder.verify(mMockCi).iccCloseLogicalChannel(eq(channel), eq(true /*isEs10*/), any());
-    }
-
-    private int getChannelIdFromSharedPreferences() {
-        return PreferenceManager.getDefaultSharedPreferences(InstrumentationRegistry.getContext())
-                .getInt(SHARED_PREFS_KEY_CHANNEL_ID, -1);
-    }
-
-    private void clearSharedPreferences() {
-        PreferenceManager.getDefaultSharedPreferences(InstrumentationRegistry.getContext())
-                .edit()
-                .remove(SHARED_PREFS_KEY_CHANNEL_ID)
-                .remove(SHARED_PREFS_KEY_CHANNEL_RESPONSE)
-                .apply();
+        verify(mMockCi, times(1)).iccOpenLogicalChannel(eq(AID), anyInt(), any());
     }
 }