Merge "Add implementation and flags for new transparency callback APIs" into main
diff --git a/flags/misc.aconfig b/flags/misc.aconfig
index 2d6992a..303c0ff 100644
--- a/flags/misc.aconfig
+++ b/flags/misc.aconfig
@@ -78,6 +78,15 @@
   bug: "309896524"
 }
 
+# OWNER=rambowang TARGET=24Q3
+flag {
+    name: "reset_mobile_network_settings"
+    is_exported: true
+    namespace: "telephony"
+    description: "Allows applications to launch Reset Mobile Network Settings page in Settings app."
+    bug:"271921464"
+}
+
 # OWNER=sangyun TARGET=24Q3
 flag {
     name: "roaming_notification_for_single_data_network"
diff --git a/flags/subscription.aconfig b/flags/subscription.aconfig
index 76485be..aea9bd0 100644
--- a/flags/subscription.aconfig
+++ b/flags/subscription.aconfig
@@ -81,3 +81,11 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+# OWNER=jmattis TARGET=25Q2
+flag {
+  name: "subscription_plan_allow_status_and_end_date"
+  namespace: "telephony"
+  description: "Provide APIs to retrieve the status and recurrence rule info on a subscription plan"
+  bug: "357272015"
+}
diff --git a/src/java/com/android/internal/telephony/RILUtils.java b/src/java/com/android/internal/telephony/RILUtils.java
index a81dbc8..cbf2330 100644
--- a/src/java/com/android/internal/telephony/RILUtils.java
+++ b/src/java/com/android/internal/telephony/RILUtils.java
@@ -4132,6 +4132,34 @@
     }
 
     /**
+     * This API is for fallback to support getAllowedCarriers too.
+     *
+     * Convert an array of CarrierInfo defined in
+     * radio/aidl/android/hardware/radio/sim/CarrierInfo.aidl to a list of CarrierIdentifiers.
+     *
+     * @param carrierInfos array of CarrierInfo defined in
+     *                     radio/aidl/android/hardware/radio/sim/CarrierInfo.aidl
+     * @return The converted list of CarrierIdentifiers
+     */
+    public static List<CarrierIdentifier> convertAidlCarrierInfoListToHalCarrierList(
+            android.hardware.radio.sim.CarrierInfo[] carrierInfos) {
+        List<CarrierIdentifier> ret = new ArrayList<>();
+        if (carrierInfos == null) {
+            return ret;
+        }
+        for (android.hardware.radio.sim.CarrierInfo carrierInfo : carrierInfos) {
+            String mcc = carrierInfo.mcc;
+            String mnc = carrierInfo.mnc;
+            String spn = carrierInfo.spn;
+            String imsi = carrierInfo.imsiPrefix;
+            String gid1 = carrierInfo.gid1;
+            String gid2 = carrierInfo.gid2;
+            ret.add(new CarrierIdentifier(mcc, mnc, spn, imsi, gid1, gid2));
+        }
+        return ret;
+    }
+
+    /**
      * Convert the sim policy defined in
      * radio/aidl/android/hardware/radio/sim/SimLockMultiSimPolicy.aidl to the equivalent sim
      * policy defined in android.telephony/CarrierRestrictionRules.MultiSimPolicy
diff --git a/src/java/com/android/internal/telephony/SimResponse.java b/src/java/com/android/internal/telephony/SimResponse.java
index 97692a0..12c9a3c 100644
--- a/src/java/com/android/internal/telephony/SimResponse.java
+++ b/src/java/com/android/internal/telephony/SimResponse.java
@@ -123,19 +123,34 @@
         if (!carrierRestrictions.allowedCarriersPrioritized) {
             carrierRestrictionDefault = CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_ALLOWED;
         }
-
-        CarrierRestrictionRules ret = CarrierRestrictionRules.newBuilder().setAllowedCarriers(
-                RILUtils.convertHalCarrierList(
-                        carrierRestrictions.allowedCarriers)).setExcludedCarriers(
-                RILUtils.convertHalCarrierList(
-                        carrierRestrictions.excludedCarriers)).setDefaultCarrierRestriction(
-                carrierRestrictionDefault).setMultiSimPolicy(policy).setCarrierRestrictionStatus(
-                carrierRestrictions.status).setAllowedCarrierInfo(
-                RILUtils.convertAidlCarrierInfoList(
-                        carrierRestrictions.allowedCarrierInfoList)).setExcludedCarrierInfo(
-                RILUtils.convertAidlCarrierInfoList(
-                        carrierRestrictions.excludedCarrierInfoList)).setCarrierLockInfoFeature(
-                carrierLockInfoSupported).build();
+        CarrierRestrictionRules ret = null;
+        if (carrierLockInfoSupported) {
+            // In order to support the old API { @link TelephonyManager#getAllowedCarriers() } we
+            // are parsing the allowedCarrierInfoList to CarrierIdentifier List also along with
+            // CarrierInfo List
+            ret = CarrierRestrictionRules.newBuilder().setAllowedCarriers(
+                    RILUtils.convertAidlCarrierInfoListToHalCarrierList(
+                            carrierRestrictions.allowedCarrierInfoList)).setExcludedCarriers(
+                    RILUtils.convertAidlCarrierInfoListToHalCarrierList(
+                            carrierRestrictions.excludedCarrierInfoList)).
+                    setDefaultCarrierRestriction(
+                    carrierRestrictionDefault).setMultiSimPolicy(
+                    policy).setCarrierRestrictionStatus(
+                    carrierRestrictions.status).setAllowedCarrierInfo(
+                    RILUtils.convertAidlCarrierInfoList(
+                            carrierRestrictions.allowedCarrierInfoList)).setExcludedCarrierInfo(
+                    RILUtils.convertAidlCarrierInfoList(
+                            carrierRestrictions.excludedCarrierInfoList)).setCarrierLockInfoFeature(
+                            true).build();
+        } else {
+            ret = CarrierRestrictionRules.newBuilder().setAllowedCarriers(
+                    RILUtils.convertHalCarrierList(
+                            carrierRestrictions.allowedCarriers)).setExcludedCarriers(
+                    RILUtils.convertHalCarrierList(
+                            carrierRestrictions.excludedCarriers)).setDefaultCarrierRestriction(
+                    carrierRestrictionDefault).setMultiSimPolicy(
+                    policy).build();
+        }
         if (responseInfo.error == RadioError.NONE) {
             RadioResponse.sendMessageResponse(rr.mResult, ret);
         }
diff --git a/src/java/com/android/internal/telephony/SmsDispatchersController.java b/src/java/com/android/internal/telephony/SmsDispatchersController.java
index 77fd1f6..58f3490 100644
--- a/src/java/com/android/internal/telephony/SmsDispatchersController.java
+++ b/src/java/com/android/internal/telephony/SmsDispatchersController.java
@@ -1234,7 +1234,7 @@
             boolean isOverIms, boolean isLastSmsPart, boolean success) {
         notifySmsSentToEmergencyStateTracker(tracker.mDestAddress,
             tracker.mMessageId, isOverIms, isLastSmsPart, success);
-        notifySmsSentToDatagramDispatcher(tracker.mUniqueMessageId, success);
+        notifySmsSentToDatagramDispatcher(tracker.mUniqueMessageId, isLastSmsPart, success);
     }
 
     /**
@@ -1254,9 +1254,11 @@
         }
     }
 
-    private void notifySmsSentToDatagramDispatcher(long messageId, boolean success) {
+    private void notifySmsSentToDatagramDispatcher(
+            long messageId, boolean isLastSmsPart, boolean success) {
         if (SatelliteController.getInstance().shouldSendSmsToDatagramDispatcher(mPhone)) {
-            DatagramDispatcher.getInstance().onSendSmsDone(mPhone.getSubId(), messageId, success);
+            DatagramDispatcher.getInstance().onSendSmsDone(
+                    mPhone.getSubId(), messageId, isLastSmsPart, success);
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java b/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java
index 3c2ad0cd..48cc7cb 100644
--- a/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java
+++ b/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java
@@ -601,6 +601,11 @@
                     releaseWakeLock();
                     ((GsmCdmaPhone) mPhone).notifyEcbmTimerReset(Boolean.TRUE);
 
+                    if (mFeatureFlags.emergencyCallbackModeNotification()) {
+                        mPhone.stopEmergencyCallbackMode(EMERGENCY_CALLBACK_MODE_CALL,
+                                STOP_REASON_OUTGOING_EMERGENCY_CALL_INITIATED);
+                    }
+
                     mOngoingCallProperties = 0;
                     mCallEmergencyModeFuture = new CompletableFuture<>();
                     setEmergencyMode(mPhone, EMERGENCY_TYPE_CALL, MODE_EMERGENCY_WWAN,
diff --git a/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java b/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java
index 36d8b2b..d6b1a70 100644
--- a/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java
+++ b/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java
@@ -406,9 +406,10 @@
                 SomeArgs args = (SomeArgs) msg.obj;
                 int subId = (int) args.arg1;
                 long messageId = (long) args.arg2;
-                boolean success = (boolean) args.arg3;
+                boolean isLastPartSms = (boolean) args.arg3;
+                boolean success = (boolean) args.arg4;
                 try {
-                    handleEventSendSmsDone(subId, messageId, success);
+                    handleEventSendSmsDone(subId, messageId, isLastPartSms, success);
                 } finally {
                     args.recycle();
                 }
@@ -625,6 +626,13 @@
                     pendingDatagram.iterator().next().getValue();
             if (mDatagramController.needsWaitingForSatelliteConnected(datagramArg.datagramType)) {
                 plogd("sendPendingDatagrams: wait for satellite connected");
+                mDatagramController.updateSendStatus(datagramArg.subId,
+                        datagramArg.datagramType,
+                        SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT,
+                        getPendingMessagesCount(),
+                        SatelliteManager.SATELLITE_RESULT_SUCCESS);
+                startDatagramWaitForConnectedStateTimer(
+                        datagramArg.datagramType);
                 return;
             }
 
@@ -1144,16 +1152,23 @@
         }
 
         if (pendingSms != null && pendingSms.iterator().hasNext()) {
+            PendingRequest pendingRequest = pendingSms.iterator().next().getValue();
+            int datagramType = pendingRequest.isMtSmsPolling
+                    ? DATAGRAM_TYPE_CHECK_PENDING_INCOMING_SMS : DATAGRAM_TYPE_SMS;
             if (mDatagramController.needsWaitingForSatelliteConnected(DATAGRAM_TYPE_SMS)) {
                 plogd("sendPendingSms: wait for satellite connected");
+                mDatagramController.updateSendStatus(subId,
+                        datagramType,
+                        SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT,
+                        getPendingMessagesCount(),
+                        SatelliteManager.SATELLITE_RESULT_SUCCESS);
+                startDatagramWaitForConnectedStateTimer(datagramType);
                 return;
             }
 
             mSendingInProgress = true;
-            PendingRequest pendingRequest = pendingSms.iterator().next().getValue();
             mDatagramController.updateSendStatus(subId,
-                    pendingRequest.isMtSmsPolling ?
-                            DATAGRAM_TYPE_CHECK_PENDING_INCOMING_SMS : DATAGRAM_TYPE_SMS,
+                    datagramType,
                     SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING,
                     getPendingMessagesCount(), SATELLITE_RESULT_SUCCESS);
             sendMessage(obtainMessage(CMD_SEND_SMS, pendingRequest));
@@ -1167,13 +1182,15 @@
      * Sending MO SMS is completed.
      * @param subId subscription ID
      * @param messageId message ID of MO SMS
+     * @param isLastSmsPart whether this is the last sms part of MO SMS
      * @param success boolean specifying whether MO SMS is successfully sent or not.
      */
-    public void onSendSmsDone(int subId, long messageId, boolean success) {
+    public void onSendSmsDone(int subId, long messageId, boolean isLastSmsPart, boolean success) {
         SomeArgs args = SomeArgs.obtain();
         args.arg1 = subId;
         args.arg2 = messageId;
-        args.arg3 = success;
+        args.arg3 = isLastSmsPart;
+        args.arg4 = success;
         sendMessage(obtainMessage(EVENT_SEND_SMS_DONE, args));
     }
 
@@ -1214,7 +1231,8 @@
         pendingSmsMap.clear();
     }
 
-    private void handleEventSendSmsDone(int subId, long messageId, boolean success) {
+    private void handleEventSendSmsDone(
+            int subId, long messageId, boolean isLastPartSms, boolean success) {
         synchronized (mLock) {
             mSendingInProgress = false;
             PendingRequest pendingSms = mPendingSmsMap.remove(messageId);
@@ -1222,12 +1240,16 @@
                     ? DATAGRAM_TYPE_CHECK_PENDING_INCOMING_SMS  : DATAGRAM_TYPE_SMS;
 
             plogd("handleEventSendSmsDone subId=" + subId + " messageId=" + messageId
-                    + " success=" + success);
+                    + " isLastPartSms=" + isLastPartSms + " success=" + success
+                    + " datagramType=" + datagramType);
+
             if (success) {
-                // Update send status for current datagram
-                mDatagramController.updateSendStatus(subId, datagramType,
-                        SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS,
-                        getPendingMessagesCount(), SATELLITE_RESULT_SUCCESS);
+                if (isLastPartSms) {
+                    // Update send status only after all parts of the SMS are sent
+                    mDatagramController.updateSendStatus(subId, datagramType,
+                            SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS,
+                            getPendingMessagesCount(), SATELLITE_RESULT_SUCCESS);
+                }
                 if (datagramType == DATAGRAM_TYPE_CHECK_PENDING_INCOMING_SMS) {
                     startMtSmsPollingThrottle();
                 }
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteController.java b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
index 245f00c..a23e505 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteController.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
@@ -22,6 +22,7 @@
 import static android.telephony.CarrierConfigManager.CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC;
 import static android.telephony.CarrierConfigManager.CARRIER_ROAMING_NTN_CONNECT_MANUAL;
 import static android.telephony.CarrierConfigManager.CARRIER_ROAMING_NTN_CONNECT_TYPE;
+import static android.telephony.CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL;
 import static android.telephony.CarrierConfigManager.KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT;
 import static android.telephony.CarrierConfigManager.KEY_CARRIER_ROAMING_NTN_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_INT;
 import static android.telephony.CarrierConfigManager.KEY_CARRIER_ROAMING_SATELLITE_DEFAULT_SERVICES_INT_ARRAY;
@@ -567,6 +568,11 @@
     @GuardedBy("mSatelliteTokenProvisionedLock")
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     protected TreeMap<Integer, List<SubscriptionInfo>> mSubsInfoListPerPriority = new TreeMap<>();
+    // List of subscriber information and status at the time of last evaluation
+    @GuardedBy("mSatelliteTokenProvisionedLock")
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    private List<SatelliteSubscriberProvisionStatus> mLastEvaluatedSubscriberProvisionStatus =
+            new ArrayList<>();
     // The ID of the satellite subscription that has highest priority and is provisioned.
     @GuardedBy("mSatelliteTokenProvisionedLock")
     private int mSelectedSatelliteSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -2748,11 +2754,6 @@
      */
     @SatelliteManager.SatelliteResult public int registerForSatelliteProvisionStateChanged(
             @NonNull ISatelliteProvisionStateCallback callback) {
-        int error = evaluateOemSatelliteRequestAllowed(false);
-        if (error != SATELLITE_RESULT_SUCCESS) {
-            return error;
-        }
-
         mSatelliteProvisionStateChangedListeners.put(callback.asBinder(), callback);
 
         boolean isProvisioned = Boolean.TRUE.equals(isDeviceProvisioned());
@@ -4445,6 +4446,7 @@
         }
         registerForSatelliteSupportedStateChanged();
         selectBindingSatelliteSubscription(false);
+        notifySatelliteSupportedStateChanged(supported);
     }
 
     private void updateSatelliteEnabledState(boolean enabled, String caller) {
@@ -4772,7 +4774,9 @@
             }
             mIsSatelliteSupported = supported;
         }
+    }
 
+    private void notifySatelliteSupportedStateChanged(boolean supported) {
         List<ISatelliteSupportedStateCallback> deadCallersList = new ArrayList<>();
         mSatelliteSupportedStateChangedListeners.values().forEach(listener -> {
             try {
@@ -6788,6 +6792,11 @@
                 if (!isActive && !isNtnOnly) {
                     continue;
                 }
+                if (!isNtnOnly && !isCarrierConfigLoaded(subId)) {
+                    // Skip to add priority list if the carrier config is not loaded properly
+                    // for the given carrier subscription.
+                    continue;
+                }
 
                 int keyPriority = (isESOSSupported && isActive && isDefaultSmsSubId) ? 0
                         : (isESOSSupported && isActive) ? 1
@@ -6828,9 +6837,15 @@
 
         // If priority has changed, send broadcast for provisioned ESOS subs IDs
         synchronized (mSatelliteTokenProvisionedLock) {
+            List<SatelliteSubscriberProvisionStatus> newEvaluatedSubscriberProvisionStatus =
+                    getPrioritizedSatelliteSubscriberProvisionStatusList(
+                            newSubsInfoListPerPriority);
             if (isPriorityChanged(mSubsInfoListPerPriority, newSubsInfoListPerPriority)
+                    || isSubscriberContentChanged(mLastEvaluatedSubscriberProvisionStatus,
+                            newEvaluatedSubscriberProvisionStatus)
                     || isChanged) {
                 mSubsInfoListPerPriority = newSubsInfoListPerPriority;
+                mLastEvaluatedSubscriberProvisionStatus = newEvaluatedSubscriberProvisionStatus;
                 sendBroadCastForProvisionedESOSSubs();
                 mHasSentBroadcast = true;
                 selectBindingSatelliteSubscription(false);
@@ -6838,6 +6853,14 @@
         }
     }
 
+    // to check if the contents of carrier config is loaded properly
+    private Boolean isCarrierConfigLoaded(int subId) {
+        PersistableBundle carrierConfig = mCarrierConfigManager
+                .getConfigForSubId(subId, KEY_CARRIER_CONFIG_APPLIED_BOOL);
+        return carrierConfig != null ? carrierConfig.getBoolean(
+                CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL) : false;
+    }
+
     // The subscriberId for ntnOnly SIMs is the Iccid, whereas for ESOS supported SIMs, the
     // subscriberId is the Imsi prefix 6 digit + phone number.
     private Pair<String, Integer> getSubscriberIdAndType(@Nullable SubscriptionInfo info) {
@@ -6900,6 +6923,24 @@
         return false;
     }
 
+    // Checks if there are any changes between subscriberInfos. return false if the same.
+    // Note that, Use lists with the same priority so we can compare contents properly.
+    private boolean isSubscriberContentChanged(List<SatelliteSubscriberProvisionStatus> currentList,
+            List<SatelliteSubscriberProvisionStatus> newList) {
+        if (currentList.size() != newList.size()) {
+            return true;
+        }
+        for (int i = 0; i < currentList.size(); i++) {
+            SatelliteSubscriberProvisionStatus curSub = currentList.get(i);
+            SatelliteSubscriberProvisionStatus newSub = newList.get(i);
+            if (!curSub.getSatelliteSubscriberInfo().equals(newSub.getSatelliteSubscriberInfo())) {
+                logd("isSubscriberContentChanged: cur=" + curSub + " , new=" + newSub);
+                return true;
+            }
+        }
+        return false;
+    }
+
     private void sendBroadCastForProvisionedESOSSubs() {
         String packageName = getConfigSatelliteGatewayServicePackage();
         String className = getConfigSatelliteCarrierRoamingEsosProvisionedClass();
@@ -6953,10 +6994,18 @@
 
     private List<SatelliteSubscriberProvisionStatus>
             getPrioritizedSatelliteSubscriberProvisionStatusList() {
+        synchronized (mSatelliteTokenProvisionedLock) {
+            return getPrioritizedSatelliteSubscriberProvisionStatusList(mSubsInfoListPerPriority);
+        }
+    }
+
+    private List<SatelliteSubscriberProvisionStatus>
+            getPrioritizedSatelliteSubscriberProvisionStatusList(
+                    Map<Integer, List<SubscriptionInfo>> subsInfoListPerPriority) {
         List<SatelliteSubscriberProvisionStatus> list = new ArrayList<>();
         synchronized (mSatelliteTokenProvisionedLock) {
-            for (int priority : mSubsInfoListPerPriority.keySet()) {
-                List<SubscriptionInfo> infoList = mSubsInfoListPerPriority.get(priority);
+            for (int priority : subsInfoListPerPriority.keySet()) {
+                List<SubscriptionInfo> infoList = subsInfoListPerPriority.get(priority);
                 if (infoList == null) {
                     logd("getPrioritySatelliteSubscriberProvisionStatusList: no exist this "
                             + "priority " + priority);
@@ -7815,7 +7864,8 @@
         return carrierRoamingNtnSignalStrength;
     }
 
-    private void updateLastNotifiedCarrierRoamingNtnSignalStrengthAndNotify(@Nullable Phone phone) {
+    protected void updateLastNotifiedCarrierRoamingNtnSignalStrengthAndNotify(
+            @Nullable Phone phone) {
         if (!mFeatureFlags.carrierRoamingNbIotNtn()) return;
         if (phone == null) {
             return;
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java b/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java
index 19c3f65..d1d03a0 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java
@@ -137,6 +137,7 @@
     private static final int EVENT_ENABLE_CELLULAR_MODEM_WHILE_SATELLITE_MODE_IS_ON_DONE = 12;
     private static final int EVENT_SERVICE_STATE_CHANGED = 13;
     protected static final int EVENT_P2P_SMS_INACTIVITY_TIMER_TIMED_OUT = 14;
+
     private static final long REBIND_INITIAL_DELAY = 2 * 1000; // 2 seconds
     private static final long REBIND_MAXIMUM_DELAY = 64 * 1000; // 1 minute
     private static final int REBIND_MULTIPLIER = 2;
@@ -182,16 +183,12 @@
     boolean mIsScreenOn = true;
     private boolean mIsDeviceAlignedWithSatellite = false;
 
-    @GuardedBy("mLock")
-    @NonNull private boolean mIsDisableCellularModemInProgress = false;
     @NonNull private final SatelliteController mSatelliteController;
     @NonNull private final DatagramController mDatagramController;
     @Nullable private PersistentLogger mPersistentLogger = null;
     @Nullable private DeviceStateMonitor mDeviceStateMonitor;
     @NonNull private SessionMetricsStats mSessionMetricsStats;
     @NonNull private FeatureFlags mFeatureFlags;
-    @SatelliteManager.SatelliteModemState private int mModemStateFromController =
-            SATELLITE_MODEM_STATE_UNKNOWN;
     @NonNull private AlarmManager mAlarmManager;
     private final AlarmManager.OnAlarmListener mAlarmListener = new AlarmManager.OnAlarmListener() {
         @Override
@@ -386,10 +383,6 @@
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state) {
-        if (mFeatureFlags.carrierRoamingNbIotNtn()) {
-            plogd("onSatelliteModemStateChanged from SatelliteController : " + state);
-            mModemStateFromController = state;
-        }
         sendMessage(EVENT_SATELLITE_MODEM_STATE_CHANGED, state);
     }
 
@@ -695,9 +688,6 @@
             mPreviousState = mCurrentState;
             mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_OFF;
             mIsSendingTriggeredDuringTransferringState.set(false);
-            synchronized (mLock) {
-                mIsDisableCellularModemInProgress = false;
-            }
             unbindService();
             stopNbIotInactivityTimer();
             DemoSimulator.getInstance().onSatelliteModeOff();
@@ -899,7 +889,11 @@
             Message onCompleted =
                     obtainMessage(EVENT_ENABLE_CELLULAR_MODEM_WHILE_SATELLITE_MODE_IS_ON_DONE);
             mSatelliteModemInterface.enableCellularModemWhileSatelliteModeIsOn(true, onCompleted);
-            notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+            if (isConcurrentTnScanningSupported()) {
+                plogd("IDLE state is hidden from clients");
+            } else {
+                notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+            }
         }
 
         @Override
@@ -914,7 +908,7 @@
                     break;
                 case EVENT_DISABLE_CELLULAR_MODEM_WHILE_SATELLITE_MODE_IS_ON_DONE:
                     handleEventDisableCellularModemWhileSatelliteModeIsOnDone(
-                            (AsyncResult) msg.obj);
+                        (AsyncResult) msg.obj);
                     break;
                 case EVENT_SATELLITE_ENABLEMENT_STARTED:
                     handleSatelliteEnablementStarted((boolean) msg.obj);
@@ -954,17 +948,13 @@
             if ((datagramTransferState.sendState == SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING)
                     || (datagramTransferState.receiveState
                     == SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING)) {
-                if (mSatelliteController.isSatelliteAttachRequired()) {
-                    ploge("Unexpected transferring state received for NB-IOT NTN");
-                } else {
-                    transitionTo(mTransferringState);
-                }
+                transitionTo(mTransferringState);
             } else if ((datagramTransferState.sendState
                     == SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT)
                     || (datagramTransferState.receiveState
                     == SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT)) {
                 if (mSatelliteController.isSatelliteAttachRequired()) {
-                    disableCellularModemWhileSatelliteModeIsOn();
+                    transitionTo(mNotConnectedState);
                 } else {
                     ploge("Unexpected transferring state received for non-NB-IOT NTN");
                 }
@@ -1012,57 +1002,32 @@
 
         private void handleEventDisableCellularModemWhileSatelliteModeIsOnDone(
                 @NonNull AsyncResult result) {
-            synchronized (mLock) {
-                if (mIsDisableCellularModemInProgress) {
-                    int error = SatelliteServiceUtils.getSatelliteError(
-                            result, "DisableCellularModemWhileSatelliteModeIsOnDone");
-                    if (error == SatelliteManager.SATELLITE_RESULT_SUCCESS) {
-                        if (mFeatureFlags.carrierRoamingNbIotNtn()
-                                && mModemStateFromController == SATELLITE_MODEM_STATE_CONNECTED) {
-                            ploge("mPreviousState : " + mPreviousState
-                                    + " mModemStateFromController : "
-                                    + mModemStateFromController + " I->C");
-                            transitionTo(mConnectedState);
-                        } else {
-                            transitionTo(mNotConnectedState);
-                        }
-                    }
-                    mIsDisableCellularModemInProgress = false;
-                } else {
-                    ploge("DisableCellularModemWhileSatelliteModeIsOn is not in progress");
-                }
-            }
+            int error = SatelliteServiceUtils.getSatelliteError(
+                        result, "DisableCellularModemWhileSatelliteModeIsOnDone");
+            plogd("Disable TN scanning done with result: " + error);
         }
 
         private void handleSatelliteModemStateChanged(@NonNull Message msg) {
             int state = msg.arg1;
             if (state == SatelliteManager.SATELLITE_MODEM_STATE_OFF) {
                 transitionTo(mPowerOffState);
-            }
-        }
-
-        private void disableCellularModemWhileSatelliteModeIsOn() {
-            synchronized (mLock) {
-                if (mIsDisableCellularModemInProgress) {
-                    plogd("Cellular scanning is already being disabled");
-                    return;
+            } else if (state == SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED
+                           || state == SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED) {
+                if (isConcurrentTnScanningSupported()) {
+                    plogd("Notifying the new state " + state + " to clients but still"
+                            + " stay at IDLE state internally");
+                    notifyStateChangedEvent(state);
+                } else {
+                    plogd("Ignoring the modem state " + state);
                 }
-
-                mIsDisableCellularModemInProgress = true;
-                Message onCompleted =
-                        obtainMessage(EVENT_DISABLE_CELLULAR_MODEM_WHILE_SATELLITE_MODE_IS_ON_DONE);
-                mSatelliteModemInterface.enableCellularModemWhileSatelliteModeIsOn(false,
-                        onCompleted);
             }
         }
 
         @Override
         public void exit() {
             if (DBG) plogd("Exiting IdleState");
-            if (!mSatelliteController.isSatelliteAttachRequired()) {
-                // Disable cellular modem scanning
-                mSatelliteModemInterface.enableCellularModemWhileSatelliteModeIsOn(false, null);
-            }
+            // Disable cellular modem scanning
+            mSatelliteModemInterface.enableCellularModemWhileSatelliteModeIsOn(false, null);
         }
     }
 
@@ -1336,6 +1301,8 @@
             startNbIotInactivityTimer();
             evaluateStartingEsosInactivityTimer();
             evaluateStartingP2pSmsInactivityTimer();
+            mSatelliteController.updateLastNotifiedCarrierRoamingNtnSignalStrengthAndNotify(
+                    mSatelliteController.getSatellitePhone());
         }
 
         @Override
@@ -1989,6 +1956,16 @@
         }
     }
 
+    private boolean isConcurrentTnScanningSupported() {
+        try {
+            return mContext.getResources().getBoolean(
+                R.bool.config_satellite_modem_support_concurrent_tn_scanning);
+        } catch (RuntimeException e) {
+            plogd("isConcurrentTnScanningSupported: ex=" + e);
+            return false;
+        }
+    }
+
     private void plogd(@NonNull String log) {
         logd(log);
         if (mPersistentLogger != null) {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java
index 32cca3c..df14080 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java
@@ -1107,7 +1107,7 @@
         verify(phone0, never()).exitEmergencyMode(any(Message.class));
         verify(phone0, times(2)).startEmergencyCallbackMode(
                 eq(EMERGENCY_CALLBACK_MODE_CALL), anyLong());
-        verify(phone0, never()).stopEmergencyCallbackMode(
+        verify(phone0, times(1)).stopEmergencyCallbackMode(
                 eq(EMERGENCY_CALLBACK_MODE_CALL), anyInt());
     }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramDispatcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramDispatcherTest.java
index d964d88..1941518 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramDispatcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramDispatcherTest.java
@@ -824,7 +824,8 @@
                         eq(SATELLITE_RESULT_SUCCESS));
         verify(mMockSmsDispatchersController).sendCarrierRoamingNbIotNtnText(eq(mPendingSms));
 
-        mDatagramDispatcherUT.onSendSmsDone(mPhone.getSubId(), mPendingSms.uniqueMessageId, true);
+        mDatagramDispatcherUT.onSendSmsDone(mPhone.getSubId(), mPendingSms.uniqueMessageId,
+                true, true);
         processAllMessages();
 
         mInOrder.verify(mMockDatagramController)
@@ -858,7 +859,8 @@
                         eq(SATELLITE_RESULT_SUCCESS));
         verify(mMockSmsDispatchersController).sendCarrierRoamingNbIotNtnText(eq(mPendingSms));
 
-        mDatagramDispatcherUT.onSendSmsDone(mPhone.getSubId(), mPendingSms.uniqueMessageId, false);
+        mDatagramDispatcherUT.onSendSmsDone(mPhone.getSubId(), mPendingSms.uniqueMessageId,
+                true, false);
         processAllMessages();
 
         mInOrder.verify(mMockDatagramController)
@@ -1056,7 +1058,8 @@
                         eq(SATELLITE_RESULT_SUCCESS));
         verify(mMockSmsDispatchersController).sendCarrierRoamingNbIotNtnText(eq(mPendingSms));
 
-        mDatagramDispatcherUT.onSendSmsDone(mPhone.getSubId(), mPendingSms.uniqueMessageId, true);
+        mDatagramDispatcherUT.onSendSmsDone(mPhone.getSubId(), mPendingSms.uniqueMessageId,
+                true, true);
         processAllMessages();
 
         mInOrder.verify(mMockDatagramController)
@@ -1107,7 +1110,8 @@
         processAllMessages();
         verifyZeroInteractions(mMockSatelliteModemInterface);
 
-        mDatagramDispatcherUT.onSendSmsDone(mPhone.getSubId(), mPendingSms.uniqueMessageId, true);
+        mDatagramDispatcherUT.onSendSmsDone(mPhone.getSubId(), mPendingSms.uniqueMessageId,
+                true, true);
         processAllMessages();
         mInOrder.verify(mMockDatagramController)
                 .updateSendStatus(eq(SUB_ID), eq(datagramTypeSms),
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
index 3657f1a..876410c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
@@ -22,6 +22,7 @@
 import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN;
 import static android.hardware.devicestate.feature.flags.Flags.FLAG_DEVICE_STATE_PROPERTY_MIGRATION;
 import static android.telephony.CarrierConfigManager.CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC;
+import static android.telephony.CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL;
 import static android.telephony.CarrierConfigManager.KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT;
 import static android.telephony.CarrierConfigManager.KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT;
 import static android.telephony.CarrierConfigManager.KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT;
@@ -1722,12 +1723,7 @@
                     }
                 };
         int errorCode = mSatelliteControllerUT.registerForSatelliteProvisionStateChanged(callback);
-        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE, errorCode);
-
-        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
-        errorCode = mSatelliteControllerUT.registerForSatelliteProvisionStateChanged(callback);
-        assertEquals(SATELLITE_RESULT_NOT_SUPPORTED, errorCode);
+        assertEquals(SATELLITE_RESULT_SUCCESS, errorCode);
 
         resetSatelliteControllerUT();
         setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
@@ -1759,6 +1755,7 @@
                 semaphore, 1, "testRegisterForSatelliteProvisionStateChanged"));
 
         mSatelliteControllerUT.unregisterForSatelliteProvisionStateChanged(callback);
+        semaphore.drainPermits();
         cancelRemote = mSatelliteControllerUT.provisionSatelliteService(
                 TEST_SATELLITE_TOKEN,
                 testProvisionData, mIIntegerConsumer);
@@ -4495,6 +4492,7 @@
     private void verifyRequestSatelliteSubscriberProvisionStatus() throws Exception {
         setSatelliteSubscriberTesting();
         List<SatelliteSubscriberInfo> list = getExpectedSatelliteSubscriberInfoList();
+        mCarrierConfigBundle.putBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
         mCarrierConfigBundle.putString(KEY_SATELLITE_NIDD_APN_NAME_STRING, mNiddApn);
         mCarrierConfigBundle.putBoolean(KEY_SATELLITE_ESOS_SUPPORTED_BOOL, true);
         for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
@@ -4810,6 +4808,7 @@
     @Test
     public void testCheckForSubscriberIdChange_changed() {
         when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        mCarrierConfigBundle.putBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
         List<SubscriptionInfo> allSubInfos = new ArrayList<>();
 
         String imsi = "012345";
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 d67880d..8c1ae50 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSessionControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSessionControllerTest.java
@@ -67,6 +67,7 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import com.android.internal.R;
 import com.android.internal.telephony.IIntegerConsumer;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.flags.FeatureFlags;
@@ -146,6 +147,9 @@
 
         Resources resources = mContext.getResources();
         when(resources.getInteger(anyInt())).thenReturn(TEST_SATELLITE_TIMEOUT_MILLIS);
+        when(resources.getBoolean(
+                 R.bool.config_satellite_modem_support_concurrent_tn_scanning))
+            .thenReturn(false);
 
         when(mFeatureFlags.satellitePersistentLogging()).thenReturn(true);
         when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(false);
@@ -1526,23 +1530,6 @@
                 SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
         assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
 
-        // Set up error response for the request to disable cellular scanning
-        mSatelliteModemInterface.setErrorCode(SatelliteManager.SATELLITE_RESULT_MODEM_ERROR);
-
-        // Start sending datagrams
-        mTestSatelliteSessionController.onDatagramTransferStateChanged(
-                SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT,
-                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
-                DATAGRAM_TYPE_UNKNOWN);
-        processAllMessages();
-
-        // SatelliteSessionController should stay at IDLE state because it failed to disable
-        // cellular scanning.
-        assertModemStateChangedCallbackNotCalled(mTestSatelliteModemStateCallback);
-        assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
-
-        mSatelliteModemInterface.setErrorCode(SatelliteManager.SATELLITE_RESULT_SUCCESS);
-
         // Power off the modem.
         mTestSatelliteSessionController.onSatelliteEnabledStateChanged(false);
         processAllMessages();
@@ -1857,6 +1844,283 @@
         assertEmergencyModeChangedCallbackNotCalled(mTestSatelliteModemStateCallback);
     }
 
+    @Test
+    public void testNotConnectedToIdleToNotConnectedStateTransition() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(true);
+        when(mContext.getResources().getBoolean(
+                 R.bool.config_satellite_modem_support_concurrent_tn_scanning)).thenReturn(true);
+
+        assertNotNull(mTestSatelliteSessionController);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+        setupDatagramTransferringState(true);
+
+        powerOnSatelliteModem();
+
+        // SatelliteSessionController should move to NOT_CONNECTED state after the satellite modem
+        // is powered on.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+        // The inactivity timer should be started.
+        assertTrue(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        clearInvocations(mMockDatagramController);
+
+        // Wait for timeout
+        moveTimeForward(TEST_SATELLITE_TIMEOUT_MILLIS);
+        processAllMessages();
+
+        // SatelliteSessionController should move to IDLE state, but the state transition will
+        // be hidden because device does not support satellite modem IDLE state.
+        assertModemStateChangedCallbackNotCalled(mTestSatelliteModemStateCallback);
+        assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
+        // The inactivity timer should be stopped.
+        assertFalse(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        // The transition is hidden and thus DatagramController is not notified.
+        verify(mMockDatagramController, never()).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+        clearInvocations(mMockDatagramController);
+
+        // Start sending datagrams
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                DATAGRAM_TYPE_UNKNOWN);
+        processAllMessages();
+
+        // SatelliteSessionController should move to NOT_CONNECTED state
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        clearInvocations(mMockDatagramController);
+    }
+
+    @Test
+    public void testNotConnectedToIdleToTransferringStateTransition() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(true);
+        when(mContext.getResources().getBoolean(
+                 R.bool.config_satellite_modem_support_concurrent_tn_scanning)).thenReturn(true);
+
+        assertNotNull(mTestSatelliteSessionController);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+        setupDatagramTransferringState(true);
+
+        powerOnSatelliteModem();
+
+        // SatelliteSessionController should move to NOT_CONNECTED state after the satellite modem
+        // is powered on.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+        // The inactivity timer should be started.
+        assertTrue(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        clearInvocations(mMockDatagramController);
+
+        // Wait for timeout
+        moveTimeForward(TEST_SATELLITE_TIMEOUT_MILLIS);
+        processAllMessages();
+
+        // SatelliteSessionController should move to IDLE state, but the state transition will
+        // be hidden because device does not support satellite modem IDLE state.
+        assertModemStateChangedCallbackNotCalled(mTestSatelliteModemStateCallback);
+        assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
+        // The inactivity timer should be stopped.
+        assertFalse(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        // The transition is hidden and thus DatagramController is not notified.
+        verify(mMockDatagramController, never()).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+        clearInvocations(mMockDatagramController);
+
+        // Modem report CONNECTED state
+        mTestSatelliteSessionController.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        processAllMessages();
+
+        // SatelliteSessionController should stay in IDLE state, but clients should be
+        // notified that modem has moved to CONNECTED state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        clearInvocations(mMockDatagramController);
+
+        // Start sending datagrams
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                DATAGRAM_TYPE_UNKNOWN);
+        processAllMessages();
+
+        // SatelliteSessionController should move to TRANSFERRING state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
+        assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
+        clearInvocations(mMockDatagramController);
+    }
+
+    @Test
+    public void testConnectedToIdleToTransferringStateTransition() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(true);
+        when(mContext.getResources().getBoolean(
+                 R.bool.config_satellite_modem_support_concurrent_tn_scanning)).thenReturn(true);
+
+        assertNotNull(mTestSatelliteSessionController);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+        setupDatagramTransferringState(false);
+
+        powerOnSatelliteModem();
+
+        // SatelliteSessionController should move to NOT_CONNECTED state after the satellite modem
+        // is powered on.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+        assertFalse(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        clearInvocations(mMockDatagramController);
+
+        // Modem report CONNECTED state
+        setupDatagramTransferringState(true);
+        mTestSatelliteSessionController.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        processAllMessages();
+
+        // SatelliteSessionController should move to CONNECTED state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        assertEquals(STATE_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+        // The inactivity timer should be started.
+        assertTrue(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        clearInvocations(mMockDatagramController);
+
+        // Wait for timeout
+        moveTimeForward(TEST_SATELLITE_TIMEOUT_MILLIS);
+        processAllMessages();
+
+        // SatelliteSessionController should move to IDLE state, but the state transition will
+        // be hidden because device does not support satellite modem IDLE state.
+        assertModemStateChangedCallbackNotCalled(mTestSatelliteModemStateCallback);
+        assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
+        // The inactivity timer should be stopped.
+        assertFalse(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        // The transition is hidden and thus DatagramController is not notified.
+        verify(mMockDatagramController, never()).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+        clearInvocations(mMockDatagramController);
+
+        // Start sending datagrams
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                DATAGRAM_TYPE_UNKNOWN);
+        processAllMessages();
+
+        // SatelliteSessionController should move to TRANSFERRING state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
+        assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
+        clearInvocations(mMockDatagramController);
+    }
+
+    @Test
+    public void testConnectedToIdleToNotConnectedStateTransition() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(true);
+        when(mContext.getResources().getBoolean(
+                 R.bool.config_satellite_modem_support_concurrent_tn_scanning)).thenReturn(true);
+
+        assertNotNull(mTestSatelliteSessionController);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+        setupDatagramTransferringState(false);
+
+        powerOnSatelliteModem();
+
+        // SatelliteSessionController should move to NOT_CONNECTED state after the satellite modem
+        // is powered on.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+        assertFalse(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        clearInvocations(mMockDatagramController);
+
+        // Modem report CONNECTED state
+        setupDatagramTransferringState(true);
+        mTestSatelliteSessionController.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        processAllMessages();
+
+        // SatelliteSessionController should move to CONNECTED state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        assertEquals(STATE_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+        // The inactivity timer should be started.
+        assertTrue(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        clearInvocations(mMockDatagramController);
+
+        // Wait for timeout
+        moveTimeForward(TEST_SATELLITE_TIMEOUT_MILLIS);
+        processAllMessages();
+
+        // SatelliteSessionController should move to IDLE state, but the state transition will
+        // be hidden because device does not support satellite modem IDLE state.
+        assertModemStateChangedCallbackNotCalled(mTestSatelliteModemStateCallback);
+        assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
+        // The inactivity timer should be stopped.
+        assertFalse(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        // The transition is hidden and thus DatagramController is not notified.
+        verify(mMockDatagramController, never()).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+        clearInvocations(mMockDatagramController);
+
+        // Modem report NOT_CONNECTED state
+        mTestSatelliteSessionController.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        processAllMessages();
+
+        // SatelliteSessionController should stay in IDLE state, but the clients
+        // should be notified that modem has moved to NOT_CONNECTED state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        clearInvocations(mMockDatagramController);
+
+        // Start sending datagrams
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                DATAGRAM_TYPE_UNKNOWN);
+        processAllMessages();
+
+        // SatelliteSessionController should move to NOT_CONNECTED state
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        clearInvocations(mMockDatagramController);
+    }
 
     private void verifyEsosP2pSmsInactivityTimer(boolean esosTimer, boolean p2pSmsTimer) {
         assertEquals(mTestSatelliteSessionController.isEsosInActivityTimerStarted(), esosTimer);
@@ -1878,9 +2142,9 @@
         processAllMessages();
     }
 
-    private void setupDatagramTransferringState(boolean isTransferring) {
-        when(mMockDatagramController.isSendingInIdleState()).thenReturn(isTransferring);
-        when(mMockDatagramController.isPollingInIdleState()).thenReturn(isTransferring);
+    private void setupDatagramTransferringState(boolean isIdle) {
+        when(mMockDatagramController.isSendingInIdleState()).thenReturn(isIdle);
+        when(mMockDatagramController.isPollingInIdleState()).thenReturn(isIdle);
     }
 
     private void powerOnSatelliteModem() {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionPlanTest.java b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionPlanTest.java
new file mode 100644
index 0000000..2c13d3b
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionPlanTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.subscription;
+
+import static android.telephony.SubscriptionPlan.SUBSCRIPTION_STATUS_ACTIVE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.telephony.SubscriptionPlan;
+import android.testing.AndroidTestingRunner;
+
+import com.android.internal.telephony.flags.Flags;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.time.Period;
+import java.time.ZonedDateTime;
+
+@RunWith(AndroidTestingRunner.class)
+public class SubscriptionPlanTest {
+    private static final ZonedDateTime ZONED_DATE_TIME_START =
+            ZonedDateTime.parse("2007-03-14T00:00:00.000Z");
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_SUBSCRIPTION_PLAN_ALLOW_STATUS_AND_END_DATE)
+    public void testBuilderExpirationDateSetsCorrectly() {
+        ZonedDateTime endDate = ZonedDateTime.parse("2024-11-07T00:00:00.000Z");
+
+        SubscriptionPlan planNonRecurring = SubscriptionPlan.Builder
+                .createNonrecurring(ZONED_DATE_TIME_START, endDate)
+                .setTitle("unit test")
+                .build();
+        SubscriptionPlan planRecurring = SubscriptionPlan.Builder
+                .createRecurring(ZONED_DATE_TIME_START, Period.ofMonths(1))
+                .setTitle("unit test")
+                .build();
+
+        assertThat(planNonRecurring.getPlanEndDate()).isEqualTo(endDate);
+        assertNull(planRecurring.getPlanEndDate());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_SUBSCRIPTION_PLAN_ALLOW_STATUS_AND_END_DATE)
+    public void testBuilderValidSubscriptionStatusSetsCorrectly() {
+        @SubscriptionPlan.SubscriptionStatus int status = SUBSCRIPTION_STATUS_ACTIVE;
+
+        SubscriptionPlan plan = SubscriptionPlan.Builder
+                .createRecurring(ZONED_DATE_TIME_START, Period.ofMonths(1))
+                .setSubscriptionStatus(status)
+                .setTitle("unit test")
+                .build();
+
+        assertThat(plan.getSubscriptionStatus()).isEqualTo(status);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_SUBSCRIPTION_PLAN_ALLOW_STATUS_AND_END_DATE)
+    public void testBuilderInvalidSubscriptionStatusThrowsError() {
+        int minInvalid = -1;
+        int maxInvalid = 5;
+
+        assertThrows(IllegalArgumentException.class, () -> {
+            SubscriptionPlan.Builder
+                    .createRecurring(ZONED_DATE_TIME_START, Period.ofMonths(1))
+                    .setSubscriptionStatus(minInvalid)
+                    .setTitle("unit test")
+                    .build();
+        });
+
+        assertThrows(IllegalArgumentException.class, () -> {
+            SubscriptionPlan.Builder
+                    .createRecurring(ZONED_DATE_TIME_START, Period.ofMonths(1))
+                    .setSubscriptionStatus(maxInvalid)
+                    .setTitle("unit test")
+                    .build();
+        });
+    }
+}