Merge "[Geofence] Write/read entry value byte count and version number to/from the header block" into main
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index d7f7939..bd90a9d 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -158,6 +158,7 @@
 import android.telephony.satellite.ISatelliteCapabilitiesCallback;
 import android.telephony.satellite.ISatelliteCommunicationAllowedStateCallback;
 import android.telephony.satellite.ISatelliteDatagramCallback;
+import android.telephony.satellite.ISatelliteDisallowedReasonsCallback;
 import android.telephony.satellite.ISatelliteModemStateCallback;
 import android.telephony.satellite.ISatelliteProvisionStateCallback;
 import android.telephony.satellite.ISatelliteSupportedStateCallback;
@@ -13225,8 +13226,14 @@
                             return;
                         }
                         if (isAllowed) {
-                            mSatelliteController.requestSatelliteEnabled(
-                                    enableSatellite, enableDemoMode, isEmergency, callback);
+                            if (mFeatureFlags.carrierRoamingNbIotNtn()
+                                    && !mSatelliteAccessController.getSatelliteDisallowedReasons()
+                                    .isEmpty()) {
+                                result.accept(SATELLITE_RESULT_ACCESS_BARRED);
+                            } else {
+                                mSatelliteController.requestSatelliteEnabled(
+                                        enableSatellite, enableDemoMode, isEmergency, callback);
+                            }
                         } else {
                             result.accept(SATELLITE_RESULT_ACCESS_BARRED);
                         }
@@ -13634,6 +13641,64 @@
     }
 
     /**
+     * Returns integer array of disallowed reasons of satellite.
+     *
+     * @return Integer array of disallowed reasons of satellite.
+     *
+     * @throws SecurityException if the caller doesn't have the required permission.
+     */
+    @NonNull public int[] getSatelliteDisallowedReasons() {
+        enforceSatelliteCommunicationPermission("getSatelliteDisallowedReasons");
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return mSatelliteAccessController.getSatelliteDisallowedReasons()
+                    .stream().mapToInt(Integer::intValue).toArray();
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
+     * Registers for disallowed reasons change event from satellite service.
+     *
+     * @param callback The callback to handle disallowed reasons changed event.
+     *
+     * @throws SecurityException if the caller doesn't have the required permission.
+     */
+    @Override
+    public void registerForSatelliteDisallowedReasonsChanged(
+            @NonNull ISatelliteDisallowedReasonsCallback callback) {
+        enforceSatelliteCommunicationPermission("registerForSatelliteDisallowedReasonsChanged");
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            mSatelliteAccessController.registerForSatelliteDisallowedReasonsChanged(callback);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
+     * Unregisters for disallowed reasons change event from satellite service.
+     * If callback was not registered before, the request will be ignored.
+     *
+     * @param callback The callback to handle disallowed reasons changed event.
+     *                 {@link #registerForSatelliteDisallowedReasonsChanged(
+     *                 ISatelliteDisallowedReasonsCallback)}.
+     * @throws SecurityException if the caller doesn't have the required permission.
+     */
+    @Override
+    public void unregisterForSatelliteDisallowedReasonsChanged(
+            @NonNull ISatelliteDisallowedReasonsCallback callback) {
+        enforceSatelliteCommunicationPermission("unregisterForSatelliteDisallowedReasonsChanged");
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            mSatelliteAccessController.unregisterForSatelliteDisallowedReasonsChanged(callback);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
      * Request to get whether satellite communication is allowed for the current location.
      *
      * @param subId The subId of the subscription to check whether satellite communication is
diff --git a/src/com/android/phone/satellite/accesscontrol/SatelliteAccessController.java b/src/com/android/phone/satellite/accesscontrol/SatelliteAccessController.java
index 78e4ace..49edf6a 100644
--- a/src/com/android/phone/satellite/accesscontrol/SatelliteAccessController.java
+++ b/src/com/android/phone/satellite/accesscontrol/SatelliteAccessController.java
@@ -19,6 +19,11 @@
 import static android.telephony.satellite.SatelliteManager.KEY_SATELLITE_COMMUNICATION_ALLOWED;
 import static android.telephony.satellite.SatelliteManager.KEY_SATELLITE_PROVISIONED;
 import static android.telephony.satellite.SatelliteManager.KEY_SATELLITE_SUPPORTED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DISALLOWED_REASON_NOT_SUPPORTED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DISALLOWED_REASON_NOT_PROVISIONED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DISALLOWED_REASON_NOT_IN_ALLOWED_REGION;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DISALLOWED_REASON_UNSUPPORTED_DEFAULT_MSG_APP;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DISALLOWED_REASON_LOCATION_DISABLED;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_LOCATION_DISABLED;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_LOCATION_NOT_AVAILABLE;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_NOT_SUPPORTED;
@@ -34,7 +39,11 @@
 import android.annotation.ArrayRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -56,14 +65,17 @@
 import android.os.ResultReceiver;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.telecom.TelecomManager;
 import android.telephony.AnomalyReporter;
 import android.telephony.DropBoxManagerLoggerBackend;
+import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PersistentLogger;
 import android.telephony.Rlog;
 import android.telephony.SubscriptionManager;
 import android.telephony.satellite.ISatelliteCommunicationAllowedStateCallback;
+import android.telephony.satellite.ISatelliteDisallowedReasonsCallback;
 import android.telephony.satellite.ISatelliteProvisionStateCallback;
 import android.telephony.satellite.ISatelliteSupportedStateCallback;
 import android.telephony.satellite.SatelliteManager;
@@ -76,6 +88,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.SmsApplication;
 import com.android.internal.telephony.TelephonyCountryDetector;
 import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.satellite.SatelliteConfig;
@@ -97,6 +110,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -147,6 +161,56 @@
 
     public static final int DEFAULT_REGIONAL_SATELLITE_CONFIG_ID = 0;
 
+    private static final String KEY_AVAILABLE_NOTIFICATION_SHOWN = "available_notification_shown";
+    private static final String KEY_UNAVAILABLE_NOTIFICATION_SHOWN =
+            "unavailable_notification_shown";
+    private static final String NOTIFICATION_TAG = "SatelliteAccessController";
+    private static final int NOTIFICATION_ID = 1;
+    private static final String NOTIFICATION_CHANNEL = "satelliteChannel";
+    private static final String NOTIFICATION_CHANNEL_ID = "satellite";
+    private static final int SATELLITE_DISALLOWED_REASON_NONE = -1;
+    private static final List<Integer> DISALLOWED_REASONS_TO_BE_RESET =
+            Arrays.asList(SATELLITE_DISALLOWED_REASON_NOT_IN_ALLOWED_REGION,
+            SATELLITE_DISALLOWED_REASON_LOCATION_DISABLED);
+
+    private static final HashMap<Integer, Pair<Integer, Integer>>
+            SATELLITE_SOS_UNAVAILABLE_REASONS = new HashMap<>(Map.of(
+            SATELLITE_DISALLOWED_REASON_NOT_SUPPORTED, new Pair<>(
+                    R.string.satellite_sos_not_supported_notification_title,
+                    R.string.satellite_sos_not_supported_notification_summary),
+            SATELLITE_DISALLOWED_REASON_NOT_PROVISIONED, new Pair<>(
+                    R.string.satellite_sos_not_provisioned_notification_title,
+                    R.string.satellite_sos_not_provisioned_notification_summary),
+            SATELLITE_DISALLOWED_REASON_NOT_IN_ALLOWED_REGION, new Pair<>(
+                    R.string.satellite_sos_not_in_allowed_region_notification_title,
+                    R.string.satellite_sos_not_in_allowed_region_notification_summary),
+            SATELLITE_DISALLOWED_REASON_UNSUPPORTED_DEFAULT_MSG_APP, new Pair<>(
+                    R.string.satellite_sos_unsupported_default_sms_app_notification_title,
+                    R.string.satellite_sos_unsupported_default_sms_app_notification_summary),
+            SATELLITE_DISALLOWED_REASON_LOCATION_DISABLED, new Pair<>(
+                    R.string.satellite_sos_location_disabled_notification_title,
+                    R.string.satellite_sos_location_disabled_notification_summary)
+    ));
+
+    private static final HashMap<Integer, Pair<Integer, Integer>>
+            SATELLITE_MESSAGING_UNAVAILABLE_REASONS = new HashMap<>(Map.of(
+            SATELLITE_DISALLOWED_REASON_NOT_SUPPORTED, new Pair<>(
+                    R.string.satellite_messaging_not_supported_notification_title,
+                    R.string.satellite_messaging_not_supported_notification_summary),
+            SATELLITE_DISALLOWED_REASON_NOT_PROVISIONED, new Pair<>(
+                    R.string.satellite_messaging_not_provisioned_notification_title,
+                    R.string.satellite_messaging_not_provisioned_notification_summary),
+            SATELLITE_DISALLOWED_REASON_NOT_IN_ALLOWED_REGION, new Pair<>(
+                    R.string.satellite_messaging_not_in_allowed_region_notification_title,
+                    R.string.satellite_messaging_not_in_allowed_region_notification_summary),
+            SATELLITE_DISALLOWED_REASON_UNSUPPORTED_DEFAULT_MSG_APP, new Pair<>(
+                    R.string.satellite_messaging_unsupported_default_sms_app_notification_title,
+                    R.string.satellite_messaging_unsupported_default_sms_app_notification_summary),
+            SATELLITE_DISALLOWED_REASON_LOCATION_DISABLED, new Pair<>(
+                    R.string.satellite_messaging_location_disabled_notification_title,
+                    R.string.satellite_messaging_location_disabled_notification_summary)
+    ));
+
     private static SatelliteAccessController sInstance;
 
     /** Feature flags to control behavior and errors. */
@@ -268,6 +332,10 @@
     @GuardedBy("mSatelliteCommunicationAllowStateLock")
     protected boolean mCurrentSatelliteAllowedState = false;
 
+    private final ConcurrentHashMap<IBinder, ISatelliteDisallowedReasonsCallback>
+            mSatelliteDisallowedReasonsChangedListeners = new ConcurrentHashMap<>();
+    private final Object mSatelliteDisallowedReasonsLock = new Object();
+
     protected static final long ALLOWED_STATE_CACHE_VALID_DURATION_NANOS =
             TimeUnit.HOURS.toNanos(4);
 
@@ -278,6 +346,13 @@
     private long mOnDeviceLookupStartTimeMillis;
     private long mTotalCheckingStartTimeMillis;
 
+    private final boolean mNotifySatelliteAvailabilityEnabled;
+    private Notification mSatelliteAvailableNotification;
+    // Key: SatelliteManager#SatelliteDisallowedReason; Value: Notification
+    private final Map<Integer, Notification> mSatelliteUnAvailableNotifications = new HashMap<>();
+    private NotificationManager mNotificationManager;
+    private final List<Integer> mSatelliteDisallowedReasons = new ArrayList<>();
+
     protected BroadcastReceiver mLocationModeChangedBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -358,6 +433,9 @@
         };
 
         mConfigUpdaterMetricsStats = ConfigUpdaterMetricsStats.getOrCreateInstance();
+        mNotifySatelliteAvailabilityEnabled =
+                context.getResources().getBoolean(
+                        R.bool.config_satellite_should_notify_availability);
 
         mInternalSatelliteSupportedStateCallback = new ISatelliteSupportedStateCallback.Stub() {
             @Override
@@ -371,6 +449,19 @@
                                     // do nothing
                                 }
                             }, false);
+                    if (mSatelliteDisallowedReasons.contains(
+                            Integer.valueOf(SATELLITE_DISALLOWED_REASON_NOT_SUPPORTED))) {
+                        mSatelliteDisallowedReasons.remove(
+                                Integer.valueOf(SATELLITE_DISALLOWED_REASON_NOT_SUPPORTED));
+                        handleEventDisallowedReasonsChanged();
+                    }
+                } else {
+                    if (!mSatelliteDisallowedReasons.contains(
+                            Integer.valueOf(SATELLITE_DISALLOWED_REASON_NOT_SUPPORTED))) {
+                        mSatelliteDisallowedReasons.add(
+                                Integer.valueOf(SATELLITE_DISALLOWED_REASON_NOT_SUPPORTED));
+                        handleEventDisallowedReasonsChanged();
+                    }
                 }
             }
         };
@@ -389,6 +480,19 @@
                                     // do nothing
                                 }
                             }, false);
+                    if (mSatelliteDisallowedReasons.contains(
+                            SATELLITE_DISALLOWED_REASON_NOT_PROVISIONED)) {
+                        mSatelliteDisallowedReasons.remove(
+                                Integer.valueOf(SATELLITE_DISALLOWED_REASON_NOT_PROVISIONED));
+                        handleEventDisallowedReasonsChanged();
+                    }
+                } else {
+                    if (!mSatelliteDisallowedReasons.contains(
+                            SATELLITE_DISALLOWED_REASON_NOT_PROVISIONED)) {
+                        mSatelliteDisallowedReasons.add(
+                                SATELLITE_DISALLOWED_REASON_NOT_PROVISIONED);
+                        handleEventDisallowedReasonsChanged();
+                    }
                 }
             }
 
@@ -404,7 +508,9 @@
 
         // Init the SatelliteOnDeviceAccessController so that the S2 level can be cached
         initSatelliteOnDeviceAccessController();
+        initializeSatelliteSystemNotification(context);
         registerLocationModeChangedBroadcastReceiver(context);
+        registerDefaultSmsAppChangedBroadcastReceiver(context);
     }
 
     private void updateCurrentSatelliteAllowedState(boolean isAllowed) {
@@ -951,6 +1057,18 @@
         }
     }
 
+    private void registerDefaultSmsAppChangedBroadcastReceiver(Context context) {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            plogd("registerDefaultSmsAppChangedBroadcastReceiver: Flag "
+                    + "oemEnabledSatellite is disabled");
+            return;
+        }
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        intentFilter.addDataScheme("package");
+        context.registerReceiver(mDefaultSmsAppChangedBroadcastReceiver, intentFilter);
+    }
+
     private void registerLocationModeChangedBroadcastReceiver(Context context) {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
             plogd("registerLocationModeChangedBroadcastReceiver: Flag "
@@ -1054,7 +1172,7 @@
     }
 
     private void sendSatelliteAllowResultToReceivers(int resultCode, Bundle resultData,
-            boolean allowed) {
+                                                     boolean allowed) {
         plogd("sendSatelliteAllowResultToReceivers : resultCode is " + resultCode);
         if (resultCode == SATELLITE_RESULT_SUCCESS) {
             updateCurrentSatelliteAllowedState(allowed);
@@ -1068,12 +1186,95 @@
         if (!shouldRetryValidatingPossibleChangeInAllowedRegion(resultCode)) {
             setIsSatelliteAllowedRegionPossiblyChanged(false);
         }
+        Integer disallowedReason = getDisallowedReason(resultCode, allowed);
+        boolean isChanged = false;
+        if (disallowedReason != SATELLITE_DISALLOWED_REASON_NONE) {
+            if (!mSatelliteDisallowedReasons.contains(disallowedReason)) {
+                isChanged = true;
+            }
+        } else {
+            if (mSatelliteDisallowedReasons.contains(
+                    SATELLITE_DISALLOWED_REASON_NOT_IN_ALLOWED_REGION)
+                    || mSatelliteDisallowedReasons.contains(
+                    SATELLITE_DISALLOWED_REASON_LOCATION_DISABLED)) {
+                isChanged = true;
+            }
+        }
+        mSatelliteDisallowedReasons.removeAll(DISALLOWED_REASONS_TO_BE_RESET);
+        if (disallowedReason != SATELLITE_DISALLOWED_REASON_NONE) {
+            mSatelliteDisallowedReasons.add(disallowedReason);
+        }
+        if (isChanged) {
+            handleEventDisallowedReasonsChanged();
+        }
         synchronized (mIsAllowedCheckBeforeEnablingSatelliteLock) {
             mIsAllowedCheckBeforeEnablingSatellite = false;
         }
         reportMetrics(resultCode, allowed);
     }
 
+    private int getDisallowedReason(int resultCode, boolean allowed) {
+        if (resultCode == SATELLITE_RESULT_SUCCESS) {
+            if (!allowed) {
+                return SATELLITE_DISALLOWED_REASON_NOT_IN_ALLOWED_REGION;
+            }
+        } else if (resultCode == SATELLITE_RESULT_LOCATION_DISABLED) {
+            return SATELLITE_DISALLOWED_REASON_LOCATION_DISABLED;
+        }
+        return SATELLITE_DISALLOWED_REASON_NONE;
+    }
+
+    private void handleEventDisallowedReasonsChanged() {
+        logd("mSatelliteDisallowedReasons:"
+                + String.join(", ", mSatelliteDisallowedReasons.toString()));
+        notifySatelliteDisallowedReasonsChanged();
+        if (mNotifySatelliteAvailabilityEnabled) {
+            showSatelliteSystemNotification();
+        }
+    }
+
+    private void showSatelliteSystemNotification() {
+        if (mSatelliteDisallowedReasons.isEmpty()) {
+            if (!hasAlreadyNotified(KEY_AVAILABLE_NOTIFICATION_SHOWN, 0)) {
+                mNotificationManager.notifyAsUser(
+                        NOTIFICATION_TAG,
+                        NOTIFICATION_ID,
+                        mSatelliteAvailableNotification,
+                        UserHandle.ALL
+                );
+                markAsNotified(KEY_AVAILABLE_NOTIFICATION_SHOWN, 0);
+            }
+        } else {
+            for (Integer reason : mSatelliteDisallowedReasons) {
+                if (!hasAlreadyNotified(KEY_UNAVAILABLE_NOTIFICATION_SHOWN, reason)) {
+                    mNotificationManager.notifyAsUser(
+                            NOTIFICATION_TAG,
+                            NOTIFICATION_ID,
+                            mSatelliteUnAvailableNotifications.get(reason),
+                            UserHandle.ALL
+                    );
+                    markAsNotified(KEY_UNAVAILABLE_NOTIFICATION_SHOWN, reason);
+                    break;
+                }
+            }
+        }
+    }
+
+    private boolean hasAlreadyNotified(String key, int reason) {
+        Set<String> reasons = mSharedPreferences.getStringSet(key, new HashSet<>());
+        return reasons.contains(String.valueOf(reason));
+    }
+
+    private void markAsNotified(String key, int reason) {
+        Set<String> reasons = mSharedPreferences.getStringSet(key, new HashSet<>());
+        if (!reasons.contains(String.valueOf(reason))) {
+            reasons.add(String.valueOf(reason));
+            SharedPreferences.Editor editor = mSharedPreferences.edit();
+            editor.putStringSet(key, reasons);
+            editor.apply();
+        }
+    }
+
     /**
      * Telephony-internal logic to verify if satellite access is restricted at the current
      * location.
@@ -1137,6 +1338,122 @@
         };
     }
 
+    private void initializeSatelliteSystemNotification(@NonNull Context context) {
+        final NotificationChannel notificationChannel = new NotificationChannel(
+                NOTIFICATION_CHANNEL_ID,
+                NOTIFICATION_CHANNEL,
+                NotificationManager.IMPORTANCE_DEFAULT
+        );
+        notificationChannel.setSound(null, null);
+        mNotificationManager = context.getSystemService(NotificationManager.class);
+        mNotificationManager.createNotificationChannel(notificationChannel);
+
+        createAvailableNotifications(context);
+        createUnavailableNotifications(context);
+    }
+
+    private Notification createNotification(@NonNull Context context,
+                                            String title,
+                                            String content) {
+        Notification.Builder notificationBuilder = new Notification.Builder(context)
+                .setContentTitle(title)
+                .setContentText(content)
+                .setSmallIcon(R.drawable.ic_android_satellite_24px)
+                .setChannelId(NOTIFICATION_CHANNEL_ID)
+                .setAutoCancel(true)
+                .setColor(context.getColor(
+                        com.android.internal.R.color.system_notification_accent_color))
+                .setVisibility(Notification.VISIBILITY_PUBLIC);
+
+        return notificationBuilder.build();
+    }
+
+    private void createAvailableNotifications(Context context) {
+        int subId = mSatelliteController.getSelectedSatelliteSubId();
+        int titleId;
+        int summaryId;
+
+        if (mSatelliteController.isSatelliteServiceSupportedByCarrier(
+                subId, NetworkRegistrationInfo.SERVICE_TYPE_SMS)) {
+            titleId = R.string.satellite_messaging_available_notification_title;
+            summaryId = R.string.satellite_messaging_available_notification_summary;
+        } else {
+            titleId = R.string.satellite_sos_available_notification_title;
+            summaryId = R.string.satellite_sos_available_notification_summary;
+        }
+
+        mSatelliteAvailableNotification = createNotification(
+                context,
+                context.getResources().getString(titleId),
+                context.getResources().getString(summaryId));
+    }
+
+    private void createUnavailableNotifications(Context context) {
+        int subId = mSatelliteController.getSelectedSatelliteSubId();
+
+        HashMap<Integer, Pair<Integer, Integer>> unavailableReasons;
+        if (mSatelliteController.isSatelliteServiceSupportedByCarrier(
+                subId, NetworkRegistrationInfo.SERVICE_TYPE_SMS)) {
+            unavailableReasons = SATELLITE_MESSAGING_UNAVAILABLE_REASONS;
+        } else {
+            unavailableReasons = SATELLITE_SOS_UNAVAILABLE_REASONS;
+        }
+
+        for (int reason : unavailableReasons.keySet()) {
+            Pair<Integer, Integer> notificationString =
+                    unavailableReasons.getOrDefault(reason, null);
+            if (notificationString != null) {
+                mSatelliteUnAvailableNotifications.put(reason,
+                        createNotification(
+                                context,
+                                context.getResources().getString(notificationString.first),
+                                context.getResources().getString(notificationString.second)));
+            }
+        }
+    }
+
+    private final BroadcastReceiver mDefaultSmsAppChangedBroadcastReceiver =
+            new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    if (intent.getAction()
+                            .equals(Intent.ACTION_PACKAGE_CHANGED)) {
+                        boolean isDefaultMsgAppSupported = false;
+                        ComponentName componentName =
+                                SmsApplication.getDefaultSmsApplicationAsUser(
+                                        context, true, context.getUser());
+                        logd("Current default SMS app:" + componentName);
+                        if (componentName != null) {
+                            String packageName = componentName.getPackageName();
+                            List<String> supportedMsgApps =
+                                    mSatelliteController.getSatelliteSupportedMsgApps(
+                                            mSatelliteController.getSelectedSatelliteSubId());
+                            if (supportedMsgApps.contains(packageName)) {
+                                isDefaultMsgAppSupported = true;
+                            }
+                        } else {
+                            logd("No default SMS app");
+                        }
+
+                        if (isDefaultMsgAppSupported) {
+                            if (mSatelliteDisallowedReasons.contains(Integer.valueOf(
+                                    SATELLITE_DISALLOWED_REASON_UNSUPPORTED_DEFAULT_MSG_APP))) {
+                                mSatelliteDisallowedReasons.remove(Integer.valueOf(
+                                        SATELLITE_DISALLOWED_REASON_UNSUPPORTED_DEFAULT_MSG_APP));
+                                handleEventDisallowedReasonsChanged();
+                            }
+                        } else {
+                            if (!mSatelliteDisallowedReasons.contains(Integer.valueOf(
+                                    SATELLITE_DISALLOWED_REASON_UNSUPPORTED_DEFAULT_MSG_APP))) {
+                                mSatelliteDisallowedReasons.add(Integer.valueOf(
+                                        SATELLITE_DISALLOWED_REASON_UNSUPPORTED_DEFAULT_MSG_APP));
+                                handleEventDisallowedReasonsChanged();
+                            }
+                        }
+                    }
+                }
+            };
+
     private void handleSatelliteAllowedRegionPossiblyChanged(int handleEvent) {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
             ploge("handleSatelliteAllowedRegionPossiblyChanged: "
@@ -1912,6 +2229,74 @@
     }
 
     /**
+     * Returns integer array of disallowed reasons of satellite.
+     *
+     * @return Integer array of disallowed reasons of satellite.
+     *
+     */
+    @NonNull public List<Integer> getSatelliteDisallowedReasons() {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            plogd("getSatelliteDisallowedReasons: carrierRoamingNbIotNtn is disabled");
+            return new ArrayList<>();
+        }
+
+        synchronized (mSatelliteDisallowedReasonsLock) {
+            return mSatelliteDisallowedReasons;
+        }
+    }
+
+    /**
+     * Registers for disallowed reasons change event from satellite service.
+     *
+     * @param callback The callback to handle disallowed reasons changed event.
+     *
+     */
+    public void registerForSatelliteDisallowedReasonsChanged(
+            @NonNull ISatelliteDisallowedReasonsCallback callback) {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            plogd("registerForSatelliteDisallowedReasonsChanged: carrierRoamingNbIotNtn is "
+                    + "disabled");
+            return;
+        }
+
+        mSatelliteDisallowedReasonsChangedListeners.put(callback.asBinder(), callback);
+
+        this.post(() -> {
+            try {
+                synchronized (mSatelliteDisallowedReasonsLock) {
+                    callback.onSatelliteDisallowedReasonsChanged(
+                            mSatelliteDisallowedReasons.stream()
+                                    .mapToInt(Integer::intValue)
+                                    .toArray());
+                    logd("registerForSatelliteDisallowedReasonsChanged: "
+                            + "mSatelliteDisallowedReasons " + mSatelliteDisallowedReasons.size());
+                }
+            } catch (RemoteException ex) {
+                ploge("registerForSatelliteDisallowedReasonsChanged: RemoteException ex=" + ex);
+            }
+        });
+    }
+
+    /**
+     * Unregisters for disallowed reasons change event from satellite service.
+     * If callback was not registered before, the request will be ignored.
+     *
+     * @param callback The callback that was passed to
+     *                 {@link #registerForSatelliteDisallowedReasonsChanged(
+     *                 ISatelliteDisallowedReasonsCallback)}.
+     */
+    public void unregisterForSatelliteDisallowedReasonsChanged(
+            @NonNull ISatelliteDisallowedReasonsCallback callback) {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            plogd("unregisterForSatelliteDisallowedReasonsChanged: "
+                    + "carrierRoamingNbIotNtn is disabled");
+            return;
+        }
+
+        mSatelliteDisallowedReasonsChangedListeners.remove(callback.asBinder());
+    }
+
+    /**
      * This API can be used by only CTS to set the cache whether satellite communication is allowed.
      *
      * @param state a state indicates whether satellite access allowed state should be cached and
@@ -1977,6 +2362,26 @@
         });
     }
 
+    private void notifySatelliteDisallowedReasonsChanged() {
+        plogd("notifySatelliteDisallowedReasonsChanged");
+
+        List<ISatelliteDisallowedReasonsCallback> deadCallersList = new ArrayList<>();
+        mSatelliteDisallowedReasonsChangedListeners.values().forEach(listener -> {
+            try {
+                listener.onSatelliteDisallowedReasonsChanged(
+                        mSatelliteDisallowedReasons.stream()
+                                .mapToInt(Integer::intValue)
+                                .toArray());
+            } catch (RemoteException e) {
+                plogd("notifySatelliteDisallowedReasonsChanged RemoteException: " + e);
+                deadCallersList.add(listener);
+            }
+        });
+        deadCallersList.forEach(listener -> {
+            mSatelliteDisallowedReasonsChangedListeners.remove(listener.asBinder());
+        });
+    }
+
     private void reportMetrics(int resultCode, boolean allowed) {
         if (resultCode == SATELLITE_RESULT_SUCCESS) {
             mControllerMetricsStats.reportAllowedSatelliteAccessCount(allowed);
diff --git a/src/com/android/phone/settings/RadioInfo.java b/src/com/android/phone/settings/RadioInfo.java
index 7a1d093..579a853 100644
--- a/src/com/android/phone/settings/RadioInfo.java
+++ b/src/com/android/phone/settings/RadioInfo.java
@@ -786,9 +786,13 @@
         mSatelliteEnableNonEmergencyModeButton = (Button) findViewById(
                 R.id.satellite_enable_non_emergency_mode);
         CarrierConfigManager cm = getSystemService(CarrierConfigManager.class);
-        if (!cm.getConfigForSubId(mSubId,
-                        CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL)
-                .getBoolean(CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL)) {
+        PersistableBundle bundle = cm.getConfigForSubId(mSubId,
+                CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL,
+                CarrierConfigManager.KEY_SATELLITE_ESOS_SUPPORTED_BOOL);
+        if (!bundle.getBoolean(
+                CarrierConfigManager.KEY_SATELLITE_ESOS_SUPPORTED_BOOL, false)
+                || !bundle.getBoolean(
+                CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL, false)) {
             mSatelliteEnableNonEmergencyModeButton.setVisibility(View.GONE);
         }
         if (!Build.isDebuggable()) {
diff --git a/tests/src/com/android/phone/satellite/accesscontrol/SatelliteAccessControllerTest.java b/tests/src/com/android/phone/satellite/accesscontrol/SatelliteAccessControllerTest.java
index 971f87a..c2f5979 100644
--- a/tests/src/com/android/phone/satellite/accesscontrol/SatelliteAccessControllerTest.java
+++ b/tests/src/com/android/phone/satellite/accesscontrol/SatelliteAccessControllerTest.java
@@ -63,16 +63,21 @@
 import static org.mockito.Mockito.when;
 
 import android.annotation.Nullable;
+import android.app.NotificationManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.location.Location;
 import android.location.LocationManager;
 import android.location.LocationRequest;
 import android.os.AsyncResult;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.DropBoxManager;
@@ -81,8 +86,10 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.ResultReceiver;
+import android.os.UserHandle;
 import android.telecom.TelecomManager;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.telephony.satellite.SatelliteManager;
 import android.testing.TestableLooper;
 import android.util.Log;
@@ -189,6 +196,17 @@
     private Set<ResultReceiver> mMockSatelliteAllowResultReceivers;
     @Mock
     private ResultReceiver mMockSatelliteSupportedResultReceiver;
+    @Mock
+    private TelephonyManager mMockTelephonyManager;
+    @Mock
+    private PackageManager mMockPackageManager;
+    @Mock
+    private List<ResolveInfo> mMockResolveInfoList;
+    @Mock
+    private NotificationManager mMockNotificationManager;
+    @Mock
+    private ApplicationInfo mMockApplicationInfo;
+
 
     private Looper mLooper;
     private TestableLooper mTestableLooper;
@@ -343,6 +361,22 @@
         when(mMockFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
         when(mMockFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
 
+        when(mMockContext.getSystemService(Context.TELEPHONY_SERVICE))
+                .thenReturn(mMockTelephonyManager);
+        when(mMockTelephonyManager.isSmsCapable()).thenReturn(true);
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+        mMockResolveInfoList = new ArrayList<>();
+        when(mMockPackageManager.queryBroadcastReceiversAsUser(any(Intent.class), anyInt(), any(
+                UserHandle.class)))
+                .thenReturn(mMockResolveInfoList);
+        when(mMockContext.getSystemServiceName(
+                NotificationManager.class)).thenReturn(Context.NOTIFICATION_SERVICE);
+        when(mMockContext.getSystemService(Context.NOTIFICATION_SERVICE))
+                .thenReturn(mMockNotificationManager);
+        when(mMockContext.getApplicationInfo()).thenReturn(mMockApplicationInfo);
+        mMockApplicationInfo.targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+        when(mMockPackageManager.getApplicationInfo(anyString(), anyInt()))
+                .thenReturn(mMockApplicationInfo);
         mSatelliteAccessControllerUT = new TestSatelliteAccessController(mMockContext,
                 mMockFeatureFlags, mLooper, mMockLocationManager, mMockTelecomManager,
                 mMockSatelliteOnDeviceAccessController, mMockSatS2File);
@@ -1148,11 +1182,9 @@
         mSatelliteAccessControllerUT.elapsedRealtimeNanos = TEST_LOCATION_FRESH_DURATION_NANOS + 1;
 
         // Captor and Verify if the mockReceiver and mocContext is registered well
-        verify(mMockContext).registerReceiver(mLocationBroadcastReceiverCaptor.capture(),
+        verify(mMockContext, times(2))
+                .registerReceiver(mLocationBroadcastReceiverCaptor.capture(),
                 mIntentFilterCaptor.capture());
-        assertSame(mSatelliteAccessControllerUT.getLocationBroadcastReceiver(),
-                mLocationBroadcastReceiverCaptor.getValue());
-        assertSame(MODE_CHANGED_ACTION, mIntentFilterCaptor.getValue().getAction(0));
 
         // When the intent action is not MODE_CHANGED_ACTION,
         // verify if the location manager never invoke isLocationEnabled()