Merge "Clear calling identity for setNtnSmsSupported API." into main
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index cfd812e..0afb24f 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -163,6 +163,7 @@
 import android.telephony.satellite.ISatelliteProvisionStateCallback;
 import android.telephony.satellite.ISatelliteSupportedStateCallback;
 import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
+import android.telephony.satellite.ISelectedNbIotSatelliteSubscriptionCallback;
 import android.telephony.satellite.NtnSignalStrength;
 import android.telephony.satellite.NtnSignalStrengthCallback;
 import android.telephony.satellite.SatelliteCapabilities;
@@ -8595,9 +8596,10 @@
                 setNetworkSelectionModeAutomatic(subId);
                 Phone phone = getPhone(subId);
                 cleanUpAllowedNetworkTypes(phone, subId);
+
                 setDataRoamingEnabled(subId, phone == null ? false
                         : phone.getDataSettingsManager().isDefaultDataRoamingEnabled());
-                getPhone(subId).resetCarrierKeysForImsiEncryption();
+                getPhone(subId).resetCarrierKeysForImsiEncryption(true);
             }
             // There has been issues when Sms raw table somehow stores orphan
             // fragments. They lead to garbled message when new fragments come
@@ -13789,6 +13791,54 @@
     }
 
     /**
+     * Registers for selected satellite subscription changed event from the satellite service.
+     *
+     * @param callback The callback to handle the satellite subscription changed event.
+     *
+     * @return The {@link SatelliteManager.SatelliteResult} result of the operation.
+     *
+     * @throws SecurityException if the caller doesn't have required permission.
+     */
+    @Override
+    @SatelliteManager.SatelliteResult
+    public int registerForSelectedNbIotSatelliteSubscriptionChanged(
+            @NonNull ISelectedNbIotSatelliteSubscriptionCallback callback) {
+        enforceSatelliteCommunicationPermission(
+                "registerForSelectedNbIotSatelliteSubscriptionChanged");
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return mSatelliteController.registerForSelectedNbIotSatelliteSubscriptionChanged(
+                    callback);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
+     * Unregisters for selected satellite subscription changed event from the satellite service.
+     * If callback was not registered before, the request will be ignored.
+     *
+     * @param callback The callback that was passed to {@link
+     *     #registerForSelectedNbIotSatelliteSubscriptionChanged(
+     *     ISelectedNbIotSatelliteSubscriptionCallback)}.
+     *
+     * @throws SecurityException if the caller doesn't have required permission.
+     */
+    @Override
+    public void unregisterForSelectedNbIotSatelliteSubscriptionChanged(
+            @NonNull ISelectedNbIotSatelliteSubscriptionCallback callback) {
+        enforceSatelliteCommunicationPermission(
+                "unregisterForSelectedNbIotSatelliteSubscriptionChanged");
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            mSatelliteController.unregisterForSelectedNbIotSatelliteSubscriptionChanged(
+                    callback);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
      * Inform whether the device is aligned with the satellite in both real and demo mode.
      *
      * @param isAligned {@code true} Device is aligned with the satellite.
@@ -14873,4 +14923,25 @@
             Binder.restoreCallingIdentity(identity);
         }
     }
+
+    /**
+     * Returns carrier id maps to the passing {@link CarrierIdentifier}.
+     *
+     * @param carrierIdentifier {@link CarrierIdentifier}.
+     *
+     * @return carrier id from passing {@link CarrierIdentifier} or UNKNOWN_CARRIER_ID
+     * if the carrier cannot be identified
+     */
+    public int getCarrierIdFromIdentifier(@NonNull CarrierIdentifier carrierIdentifier) {
+        enforceReadPrivilegedPermission("getCarrierIdFromIdentifier");
+        enforceTelephonyFeatureWithException(getCurrentPackageName(),
+                PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getCarrierIdFromIdentifier");
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return CarrierResolver.getCarrierIdFromIdentifier(mApp, carrierIdentifier);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
 }
diff --git a/src/com/android/phone/TelephonyShellCommand.java b/src/com/android/phone/TelephonyShellCommand.java
index 5ea1304..93ac55e 100644
--- a/src/com/android/phone/TelephonyShellCommand.java
+++ b/src/com/android/phone/TelephonyShellCommand.java
@@ -232,6 +232,7 @@
             "set-allowed-network-types-for-users";
     private static final String GET_IMEI = "get-imei";
     private static final String GET_SIM_SLOTS_MAPPING = "get-sim-slots-mapping";
+    private static final String COMMAND_DELETE_IMSI_KEY = "delete_imsi_key";
     // Take advantage of existing methods that already contain permissions checks when possible.
     private final ITelephony mInterface;
 
@@ -431,6 +432,8 @@
                 return handleSetSatelliteSubscriberIdListChangedIntentComponent();
             case SET_SATELLITE_ACCESS_RESTRICTION_CHECKING_RESULT:
                 return handleOverrideCarrierRoamingNtnEligibilityChanged();
+            case COMMAND_DELETE_IMSI_KEY:
+                return handleDeleteTestImsiKey();
             default: {
                 return handleDefaultCommands(cmd);
             }
@@ -4132,4 +4135,22 @@
         Log.d(LOG_TAG, "handleSetSatelliteAccessRestrictionCheckingResult(" + state + ")");
         return 0;
     }
+
+    private int handleDeleteTestImsiKey() {
+        if (!(checkShellUid())) {
+                Log.v(LOG_TAG,
+                    "handleCarrierRestrictionStatusCommand, MockModem service check fails or "
+                            + " checkShellUid fails");
+            return -1;
+        }
+
+        Phone phone = PhoneFactory.getDefaultPhone();
+        if (phone == null) {
+            Log.e(LOG_TAG,
+                    "handleCarrierRestrictionStatusCommand" + "No default Phone available");
+            return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+        }
+        phone.resetCarrierKeysForImsiEncryption(true);
+        return 1;
+    }
 }
diff --git a/src/com/android/phone/satellite/accesscontrol/SatelliteAccessConfigurationParser.java b/src/com/android/phone/satellite/accesscontrol/SatelliteAccessConfigurationParser.java
new file mode 100644
index 0000000..0658279
--- /dev/null
+++ b/src/com/android/phone/satellite/accesscontrol/SatelliteAccessConfigurationParser.java
@@ -0,0 +1,392 @@
+/*
+ * 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.phone.satellite.accesscontrol;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.ParseException;
+import android.os.Build;
+import android.telephony.satellite.EarfcnRange;
+import android.telephony.satellite.SatelliteAccessConfiguration;
+import android.telephony.satellite.SatelliteInfo;
+import android.telephony.satellite.SatellitePosition;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+public class SatelliteAccessConfigurationParser {
+    private static final String TAG = "SatelliteAccessConfigurationParser";
+
+    public static final String SATELLITE_ACCESS_CONTROL_CONFIGS = "access_control_configs";
+    public static final String SATELLITE_CONFIG_ID = "config_id";
+    public static final String SATELLITE_INFOS = "satellite_infos";
+    public static final String SATELLITE_ID = "satellite_id";
+    public static final String SATELLITE_POSITION = "satellite_position";
+    public static final String SATELLITE_LONGITUDE = "longitude";
+    public static final String SATELLITE_ALTITUDE = "altitude";
+    public static final String SATELLITE_EARFCN_RANGES = "earfcn_ranges";
+    public static final String SATELLITE_START_EARFCN = "start_earfcn";
+    public static final String SATELLITE_END_EARFCN = "end_earfcn";
+    public static final String SATELLITE_BANDS = "bands";
+    public static final String SATELLITE_TAG_ID_LIST = "tag_ids";
+
+
+    /**
+     * Parses a JSON file containing satellite access configurations.
+     *
+     * @param fileName The name of the JSON file to parse.
+     * @return A map of satellite access configurations, keyed by config ID.
+     * @throws RuntimeException if the JSON file cannot be parsed or if a required field is missing.
+     */
+    @Nullable
+    public static Map<Integer, SatelliteAccessConfiguration> parse(@NonNull String fileName) {
+        logd("SatelliteAccessConfigurationParser: parse: " + fileName);
+        Map<Integer, SatelliteAccessConfiguration> satelliteAccessConfigurationMap;
+
+        try {
+            String jsonString = readJsonStringFromFile(fileName);
+            JSONObject satelliteAccessConfigJsonObject = new JSONObject(jsonString);
+            JSONArray configurationArrayJson = satelliteAccessConfigJsonObject.optJSONArray(
+                    SATELLITE_ACCESS_CONTROL_CONFIGS);
+
+            if (configurationArrayJson == null) {
+                loge("parse : failed to parse satellite access configurations json");
+                return null;
+            }
+
+            satelliteAccessConfigurationMap =
+                    parseSatelliteAccessConfigurations(configurationArrayJson);
+
+        } catch (JSONException | ParseException e) {
+            loge("Failed to parse satellite access configurations: " + e.getMessage());
+            throw new RuntimeException(e);
+        }
+
+        logd("satelliteAccessConfigurationMap= " + satelliteAccessConfigurationMap);
+        return satelliteAccessConfigurationMap;
+    }
+
+    private static void logd(String log) {
+        if (!Build.TYPE.equals("user")) {
+            Log.d(TAG, log);
+        }
+    }
+
+    private static void loge(String log) {
+        Log.e(TAG, log);
+    }
+
+    @NonNull
+    protected static List<Integer> parseSatelliteTagIdList(@NonNull JSONObject satelliteInfoJson) {
+        List<Integer> tagIdList = new ArrayList<>();
+        try {
+            JSONArray tagIdArray = satelliteInfoJson.optJSONArray(SATELLITE_TAG_ID_LIST);
+            tagIdList = parseIntegerList(tagIdArray);
+        } catch (JSONException e) {
+            loge("parseSatelliteInfo:  parsing is error");
+            return tagIdList;
+        }
+
+        logd("parseSatelliteBandList: " + tagIdList);
+        return tagIdList;
+    }
+
+    @Nullable
+    private static Map<Integer, SatelliteAccessConfiguration> parseSatelliteAccessConfigurations(
+            JSONArray satelliteAccessConfigurationJsonArray) throws JSONException {
+        Map<Integer, SatelliteAccessConfiguration> satelliteConfigMap = new HashMap<>();
+        if (satelliteAccessConfigurationJsonArray == null) {
+            loge("parseSatelliteAccessConfigurations: jsonArray is null, return null");
+            return null;
+        }
+
+        for (int i = 0; i < satelliteAccessConfigurationJsonArray.length(); i++) {
+            JSONObject satelliteAccessConfigurationJson =
+                    satelliteAccessConfigurationJsonArray.getJSONObject(i);
+
+            int configId = satelliteAccessConfigurationJson.optInt(SATELLITE_CONFIG_ID, -1);
+            if (!isRegionalConfigIdValid(configId)) {
+                loge("parseAccessControlConfigs: invalid config_id, return null");
+                return null;
+            }
+
+            JSONArray satelliteInfoJsonArray = satelliteAccessConfigurationJson
+                    .getJSONArray(SATELLITE_INFOS);
+            List<SatelliteInfo> satelliteInfoList = parseSatelliteInfoList(satelliteInfoJsonArray);
+            if (satelliteInfoList.isEmpty()) {
+                logd("parseAccessControlConfigs: satelliteInfoList is empty");
+            }
+
+            List<Integer> tagIdList = parseSatelliteTagIdList(satelliteAccessConfigurationJson);
+            if (satelliteInfoList.isEmpty() && tagIdList.isEmpty()) {
+                loge("parseAccessControlConfigs: satelliteInfoList is empty and tagId is null");
+                return null;
+            }
+
+            satelliteConfigMap.put(configId,
+                    new SatelliteAccessConfiguration(satelliteInfoList, tagIdList));
+        }
+
+        logd("parseSatelliteAccessConfigurations: " + satelliteConfigMap);
+        return satelliteConfigMap;
+    }
+
+    /**
+     * Checks if a regional configuration ID is valid.
+     * A valid regional configuration ID is a non-null integer that is greater than or equal to
+     * zero.
+     *
+     * @param configId The regional configuration ID to check.
+     * @return {@code true} if the ID is valid, {@code false} otherwise.
+     */
+    public static boolean isRegionalConfigIdValid(@Nullable Integer configId) {
+        return (configId != null && configId >= 0);
+    }
+
+    @Nullable
+    protected static UUID parseSatelliteId(@NonNull JSONObject satelliteInfoJson) {
+        String uuidString = satelliteInfoJson.optString(SATELLITE_ID, null);
+        UUID satelliteId;
+        if (uuidString != null) {
+            try {
+                satelliteId = UUID.fromString(uuidString);
+            } catch (IllegalArgumentException e) {
+                loge("getSatelliteId: invalid UUID format: " + uuidString + " | " + e.getMessage());
+                return null;
+            }
+        } else {
+            loge("getSatelliteId: satellite uuid is missing");
+            return null;
+        }
+
+        logd("getSatelliteId: satellite uuid is " + satelliteId);
+        return satelliteId;
+    }
+
+    @Nullable
+    protected static SatellitePosition parseSatellitePosition(
+            @NonNull JSONObject satelliteInfoJson) {
+        JSONObject jsonObject = satelliteInfoJson.optJSONObject(SATELLITE_POSITION);
+        if (jsonObject == null) {
+            loge("parseSatellitePosition: jsonObject is null");
+            return null;
+        }
+        SatellitePosition satellitePosition;
+        try {
+            double longitude = jsonObject.getDouble(SATELLITE_LONGITUDE);
+            double altitude = jsonObject.getDouble(SATELLITE_ALTITUDE);
+            if (isValidLongitude(longitude) && isValidAltitude(altitude)) {
+                satellitePosition = new SatellitePosition(longitude, altitude);
+            } else {
+                loge("parseSatellitePosition: invalid value: " + longitude + " | " + altitude);
+                return null;
+            }
+        } catch (JSONException e) {
+            loge("parseSatellitePosition: json parsing error " + e.getMessage());
+            return null;
+        }
+
+        logd("parseSatellitePosition: " + satellitePosition);
+        return satellitePosition;
+    }
+
+    @NonNull
+    protected static List<EarfcnRange> parseSatelliteEarfcnRangeList(
+            @NonNull JSONObject satelliteInfoJson) {
+        JSONArray earfcnRangesArray = satelliteInfoJson.optJSONArray(SATELLITE_EARFCN_RANGES);
+        List<EarfcnRange> earfcnRangeList = new ArrayList<>();
+        if (earfcnRangesArray == null) {
+            loge("parseSatelliteEarfcnRangeList: earfcn_ranges is missing");
+            return earfcnRangeList;
+        }
+
+        try {
+            for (int j = 0; j < earfcnRangesArray.length(); j++) {
+                JSONObject earfcnRangeJson = earfcnRangesArray.getJSONObject(j);
+                EarfcnRange earfcnRange = parseEarfcnRange(earfcnRangeJson);
+                if (earfcnRange == null) {
+                    loge("parseSatelliteEarfcnRangeList: earfcnRange is null, return empty list");
+                    earfcnRangeList.clear();
+                    return earfcnRangeList;
+                }
+                earfcnRangeList.add(earfcnRange);
+            }
+        } catch (JSONException e) {
+            loge("parseSatelliteEarfcnRangeList: earfcnRange json parsing error");
+            earfcnRangeList.clear();
+            return earfcnRangeList;
+        }
+        logd("parseSatelliteEarfcnRangeList: " + earfcnRangeList);
+        return earfcnRangeList;
+    }
+
+    @NonNull
+    protected static List<Integer> parseSatelliteBandList(@NonNull JSONObject satelliteInfoJson) {
+        List<Integer> bandList = new ArrayList<>();
+        try {
+            JSONArray bandArray = satelliteInfoJson.getJSONArray(SATELLITE_BANDS);
+            bandList = parseIntegerList(bandArray);
+        } catch (JSONException e) {
+            loge("parseSatelliteInfo: bands parsing is error");
+            return bandList;
+        }
+
+        logd("parseSatelliteBandList: " + bandList);
+        return bandList;
+    }
+
+    @NonNull
+    protected static List<SatelliteInfo> parseSatelliteInfoList(JSONArray satelliteInfojsonArray)
+            throws JSONException {
+        List<SatelliteInfo> satelliteInfoList = new ArrayList<>();
+        for (int i = 0; i < satelliteInfojsonArray.length(); i++) {
+            JSONObject SatelliteInfoJson = satelliteInfojsonArray.getJSONObject(i);
+            if (SatelliteInfoJson == null) {
+                satelliteInfoList.clear();
+                break;
+            }
+            UUID id = parseSatelliteId(SatelliteInfoJson);
+            SatellitePosition position = parseSatellitePosition(SatelliteInfoJson);
+            List<EarfcnRange> earfcnRangeList = parseSatelliteEarfcnRangeList(SatelliteInfoJson);
+            List<Integer> bandList = parseSatelliteBandList(SatelliteInfoJson);
+
+            if (id == null || (bandList.isEmpty() && earfcnRangeList.isEmpty())) {
+                loge("parseSatelliteInfo: id is " + id
+                        + " or both band list and earfcn range list are empty");
+                satelliteInfoList.clear();
+                return satelliteInfoList;
+            }
+
+            SatelliteInfo info = new SatelliteInfo(id, position, bandList, earfcnRangeList);
+            satelliteInfoList.add(info);
+        }
+        logd("parseSatelliteInfoList: " + satelliteInfoList);
+        return satelliteInfoList;
+    }
+
+    /**
+     * Load json file from the filePath
+     *
+     * @param jsonFilePath The file path of json file
+     * @return json string type json contents
+     */
+    @Nullable
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public static String readJsonStringFromFile(@NonNull String jsonFilePath) {
+        logd("jsonFilePath is " + jsonFilePath);
+        String json = null;
+        try (InputStream inputStream = new FileInputStream(jsonFilePath);
+                ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream()) {
+            byte[] buffer = new byte[1024];
+            int length;
+            while ((length = inputStream.read(buffer)) != -1) {
+                byteArrayStream.write(buffer, 0, length);
+            }
+            json = byteArrayStream.toString(StandardCharsets.UTF_8);
+        } catch (FileNotFoundException e) {
+            loge("Error file " + jsonFilePath + " is not founded: " + e.getMessage());
+        } catch (IOException | NullPointerException e) {
+            loge("Error reading file " + jsonFilePath + ": " + e);
+        } finally {
+            logd("jsonString is " + json);
+        }
+        return json;
+    }
+
+    private static boolean isValidEarfcn(int earfcn) {
+        if (earfcn >= 0 && earfcn <= 65535) {
+            return true;
+        }
+        loge("isValidEarfcn: earfcn value is out of valid range: " + earfcn);
+        return false;
+    }
+
+    private static boolean isValidEarfcnRange(int start, int end) {
+        if (start <= end) {
+            return true;
+        }
+        loge("isValidEarfcnRange: earfcn range start " + start + " is bigger than end " + end);
+        return false;
+    }
+
+    @Nullable
+    private static EarfcnRange parseEarfcnRange(@Nullable JSONObject jsonObject) {
+        logd("parseEarfcnRange");
+        if (jsonObject == null) {
+            loge("parseEarfcnRange: jsonObject is null");
+            return null;
+        }
+        try {
+            int start = jsonObject.getInt(SATELLITE_START_EARFCN);
+            int end = jsonObject.getInt(SATELLITE_END_EARFCN);
+
+            if (isValidEarfcn(start) && isValidEarfcn(end) && isValidEarfcnRange(start, end)) {
+                return new EarfcnRange(start, end);
+            }
+
+            loge("parseEarfcnRange: earfcn value is not valid, return null");
+            return null;
+        } catch (JSONException e) {
+            loge("parseEarfcnRange: json parsing error: " + e.getMessage());
+            return null;
+        }
+    }
+
+    @NonNull
+    private static List<Integer> parseIntegerList(@Nullable JSONArray jsonArray)
+            throws JSONException {
+        List<Integer> intList = new ArrayList<>();
+        if (jsonArray == null) {
+            loge("parseIntegerList: jsonArray is null, return IntArray with empty");
+            return intList;
+        }
+        for (int i = 0; i < jsonArray.length(); i++) {
+            try {
+                intList.add(jsonArray.getInt(i));
+            } catch (JSONException e) {
+                loge("parseIntegerList: jsonArray parsing error: " + e.getMessage());
+                intList.clear();
+            }
+        }
+        logd("parseIntegerList: " + intList);
+        return intList;
+    }
+
+    private static boolean isValidLongitude(double longitude) {
+        return (longitude >= -180.0 && longitude <= 180.0);
+    }
+
+    private static boolean isValidAltitude(double altitude) {
+        return (altitude >= 0);
+    }
+}
diff --git a/src/com/android/phone/satellite/accesscontrol/SatelliteAccessController.java b/src/com/android/phone/satellite/accesscontrol/SatelliteAccessController.java
index 0a1a1ad..f17377c 100644
--- a/src/com/android/phone/satellite/accesscontrol/SatelliteAccessController.java
+++ b/src/com/android/phone/satellite/accesscontrol/SatelliteAccessController.java
@@ -20,15 +20,17 @@
 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_DISALLOWED_REASON_NOT_IN_ALLOWED_REGION;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DISALLOWED_REASON_NOT_PROVISIONED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DISALLOWED_REASON_NOT_SUPPORTED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DISALLOWED_REASON_UNSUPPORTED_DEFAULT_MSG_APP;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_ACCESS_BARRED;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE;
 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;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_NO_RESOURCES;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
 
@@ -79,11 +81,13 @@
 import android.telephony.Rlog;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
+import android.telephony.satellite.EarfcnRange;
 import android.telephony.satellite.ISatelliteCommunicationAllowedStateCallback;
 import android.telephony.satellite.ISatelliteDisallowedReasonsCallback;
 import android.telephony.satellite.ISatelliteProvisionStateCallback;
 import android.telephony.satellite.ISatelliteSupportedStateCallback;
 import android.telephony.satellite.SatelliteAccessConfiguration;
+import android.telephony.satellite.SatelliteInfo;
 import android.telephony.satellite.SatelliteManager;
 import android.telephony.satellite.SatelliteSubscriberProvisionStatus;
 import android.telephony.satellite.SystemSelectionSpecifier;
@@ -126,11 +130,13 @@
 import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 
 /**
  * This module is responsible for making sure that satellite communication can be used by devices
@@ -178,14 +184,15 @@
     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 String AVAILABLE_NOTIFICATION_TAG = "available_notification_tag";
+    private static final String UNAVAILABLE_NOTIFICATION_TAG = "unavailable_notification_tag";
     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);
+                    SATELLITE_DISALLOWED_REASON_LOCATION_DISABLED);
 
     private static final HashMap<Integer, Pair<Integer, Integer>>
             SATELLITE_SOS_UNAVAILABLE_REASONS = new HashMap<>(Map.of(
@@ -230,6 +237,8 @@
     /** Feature flags to control behavior and errors. */
     @NonNull
     private final FeatureFlags mFeatureFlags;
+    @NonNull
+    private final Context mContext;
     @GuardedBy("mLock")
     @Nullable
     protected SatelliteOnDeviceAccessController mSatelliteOnDeviceAccessController;
@@ -277,6 +286,7 @@
     @Nullable
     private File mOverriddenSatelliteS2CellFile;
     private long mOverriddenLocationFreshDurationNanos;
+
     @GuardedBy("mLock")
     @NonNull
     private final Map<SatelliteOnDeviceAccessController.LocationToken, Integer>
@@ -300,14 +310,10 @@
     @GuardedBy("mLock")
     @Nullable
     protected Integer mNewRegionalConfigId = null;
-
-    /** Key: Config ID; Value: SatelliteAccessConfiguration */
     @NonNull
-    private HashMap<Integer, SatelliteAccessConfiguration> mSatelliteAccessConfigMap =
-            new HashMap<>();
-    @NonNull private final CarrierConfigManager mCarrierConfigManager;
-    @NonNull private final CarrierConfigManager.CarrierConfigChangeListener
-            mCarrierConfigChangeListener;
+    private final CarrierConfigManager mCarrierConfigManager;
+    @NonNull
+    private final CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener;
     /**
      * Key: Sub Id, Value: (key: Regional satellite config Id, value: SatelliteRegionalConfig
      * contains satellite config IDs and set of earfcns in the corresponding regions).
@@ -317,6 +323,12 @@
             mSatelliteRegionalConfigPerSubMap = new HashMap();
     @NonNull private final Object mRegionalSatelliteEarfcnsLock = new Object();
 
+    /** Key: Config ID; Value: SatelliteAccessConfiguration */
+    @GuardedBy("mLock")
+    @Nullable
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected Map<Integer, SatelliteAccessConfiguration> mSatelliteAccessConfigMap;
+
     /** These are used for CTS test */
     private Path mCtsSatS2FilePath = null;
     protected static final String GOOGLE_US_SAN_SAT_S2_FILE_NAME = "google_us_san_sat_s2.dat";
@@ -352,6 +364,7 @@
     protected static final int
             DEFAULT_MAX_RETRY_COUNT_FOR_VALIDATING_POSSIBLE_CHANGE_IN_ALLOWED_REGION = 3;
     protected static final int DEFAULT_THROTTLE_INTERVAL_FOR_LOCATION_QUERY_MINUTES = 10;
+    private static final int MAX_EARFCN_ARRAY_LENGTH = 32;
 
     private long mRetryIntervalToEvaluateUserInSatelliteAllowedRegion = 0;
     private int mMaxRetryCountForValidatingPossibleChangeInAllowedRegion = 0;
@@ -432,6 +445,7 @@
             @Nullable SatelliteOnDeviceAccessController satelliteOnDeviceAccessController,
             @Nullable File s2CellFile) {
         super(looper);
+        mContext = context;
         if (isSatellitePersistentLoggingEnabled(context, featureFlags)) {
             mPersistentLogger = new PersistentLogger(
                     DropBoxManagerLoggerBackend.getInstance(context));
@@ -716,28 +730,29 @@
 
                 SatelliteAccessConfiguration satelliteAccessConfig = null;
                 synchronized (mLock) {
-                    if (isSatelliteCommunicationAllowed && isRegionalConfigIdValid(
-                            mRegionalConfigId)) {
+                    if (isSatelliteCommunicationAllowed && SatelliteAccessConfigurationParser
+                            .isRegionalConfigIdValid(mRegionalConfigId)) {
                         plogd("requestSatelliteAccessConfigurationForCurrentLocation : "
                                 + "mRegionalConfigId is " + mRegionalConfigId);
-                        satelliteAccessConfig =
-                                mSatelliteAccessConfigMap.get(mRegionalConfigId);
+                        satelliteAccessConfig = Optional.ofNullable(mSatelliteAccessConfigMap)
+                                .map(map -> map.get(mRegionalConfigId))
+                                .orElse(null);
                     }
                 }
                 plogd("requestSatelliteAccessConfigurationForCurrentLocation : "
                         + "satelliteAccessConfig is " + satelliteAccessConfig);
-                Bundle bundle = new Bundle();
-                bundle.putParcelable(KEY_SATELLITE_ACCESS_CONFIGURATION, satelliteAccessConfig);
-                result.send(resultCode, bundle);
+                if (satelliteAccessConfig == null) {
+                    result.send(SATELLITE_RESULT_NO_RESOURCES, null);
+                } else {
+                    Bundle bundle = new Bundle();
+                    bundle.putParcelable(KEY_SATELLITE_ACCESS_CONFIGURATION, satelliteAccessConfig);
+                    result.send(resultCode, bundle);
+                }
             }
         };
         requestIsCommunicationAllowedForCurrentLocation(internalResultReceiver, false);
     }
 
-    private boolean isRegionalConfigIdValid(@Nullable Integer configId) {
-        return (configId != null && configId >= 0);
-    }
-
     /**
      * This API should be used by only CTS tests to override the overlay configs of satellite
      * access controller.
@@ -800,7 +815,7 @@
             if (mRegionalConfigId == null) {
                 plogd("updateSystemSelectionChannels: Invalid Regional config ID."
                         + " System Selection channels can not be passed down to modem");
-                result.send(SatelliteManager.SATELLITE_RESULT_ACCESS_BARRED, null);
+                result.send(SATELLITE_RESULT_ACCESS_BARRED, null);
                 return;
             }
         }
@@ -1101,6 +1116,7 @@
             ploge("The satellite S2 cell file " + satelliteS2CellFileName + " does not exist");
             mSatelliteS2CellFile = null;
         }
+
         mLocationFreshDurationNanos = getSatelliteLocationFreshDurationFromOverlayConfig(context);
         mAccessControllerMetricsStats.setConfigDataSource(
                 SatelliteConstants.CONFIG_DATA_SOURCE_DEVICE_CONFIG);
@@ -1111,6 +1127,28 @@
         mLocationQueryThrottleIntervalNanos = getLocationQueryThrottleIntervalNanos(context);
     }
 
+    protected void loadSatelliteAccessConfigurationFromDeviceConfig() {
+        String satelliteConfigurationFileName =
+                getSatelliteConfigurationFileNameFromOverlayConfig(mContext);
+        loadSatelliteAccessConfigurationFromFile(satelliteConfigurationFileName);
+    }
+
+    protected void loadSatelliteAccessConfigurationFromFile(String fileName) {
+        logd("loadSatelliteAccessConfigurationFromFile: " + fileName);
+        if (!TextUtils.isEmpty(fileName)) {
+            try {
+                synchronized (mLock) {
+                    mSatelliteAccessConfigMap =
+                            SatelliteAccessConfigurationParser.parse(fileName);
+                }
+            } catch (Exception e) {
+                loge("loadSatelliteAccessConfigurationFromFile: failed load json file: " + e);
+            }
+        } else {
+            loge("loadSatelliteAccessConfigurationFromFile: fileName is empty");
+        }
+    }
+
     private void loadConfigUpdaterConfigs() {
         if (mSharedPreferences == null) {
             ploge("loadConfigUpdaterConfigs : mSharedPreferences is null");
@@ -1346,7 +1384,7 @@
     }
 
     private void sendSatelliteAllowResultToReceivers(int resultCode, Bundle resultData,
-                                                     boolean allowed) {
+            boolean allowed) {
         plogd("sendSatelliteAllowResultToReceivers : resultCode is " + resultCode);
         if (resultCode == SATELLITE_RESULT_SUCCESS) {
             updateCurrentSatelliteAllowedState(allowed);
@@ -1416,44 +1454,41 @@
         }
 
         if (mSatelliteDisallowedReasons.isEmpty()) {
-            if (!hasAlreadyNotified(KEY_AVAILABLE_NOTIFICATION_SHOWN, 0)) {
+            mNotificationManager.cancel(UNAVAILABLE_NOTIFICATION_TAG, NOTIFICATION_ID);
+            if (!hasAlreadyNotified(KEY_AVAILABLE_NOTIFICATION_SHOWN)) {
                 mNotificationManager.notifyAsUser(
-                        NOTIFICATION_TAG,
+                        AVAILABLE_NOTIFICATION_TAG,
                         NOTIFICATION_ID,
                         mSatelliteAvailableNotification,
                         UserHandle.ALL
                 );
-                markAsNotified(KEY_AVAILABLE_NOTIFICATION_SHOWN, 0);
+                markAsNotified(KEY_AVAILABLE_NOTIFICATION_SHOWN, true);
+                markAsNotified(KEY_UNAVAILABLE_NOTIFICATION_SHOWN, false);
             }
         } else {
+            mNotificationManager.cancel(AVAILABLE_NOTIFICATION_TAG, NOTIFICATION_ID);
             for (Integer reason : mSatelliteDisallowedReasons) {
-                if (!hasAlreadyNotified(KEY_UNAVAILABLE_NOTIFICATION_SHOWN, reason)) {
+                if (!hasAlreadyNotified(KEY_UNAVAILABLE_NOTIFICATION_SHOWN)) {
                     mNotificationManager.notifyAsUser(
-                            NOTIFICATION_TAG,
+                            UNAVAILABLE_NOTIFICATION_TAG,
                             NOTIFICATION_ID,
                             mSatelliteUnAvailableNotifications.get(reason),
                             UserHandle.ALL
                     );
-                    markAsNotified(KEY_UNAVAILABLE_NOTIFICATION_SHOWN, reason);
+                    markAsNotified(KEY_UNAVAILABLE_NOTIFICATION_SHOWN, true);
+                    markAsNotified(KEY_AVAILABLE_NOTIFICATION_SHOWN, false);
                     break;
                 }
             }
         }
     }
 
-    private boolean hasAlreadyNotified(String key, int reason) {
-        Set<String> reasons = mSharedPreferences.getStringSet(key, new HashSet<>());
-        return reasons.contains(String.valueOf(reason));
+    private boolean hasAlreadyNotified(String key) {
+        return mSharedPreferences.getBoolean(key, false);
     }
 
-    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();
-        }
+    private void markAsNotified(String key, boolean notified) {
+        mSharedPreferences.edit().putBoolean(key, notified).apply();
     }
 
     /**
@@ -1537,9 +1572,8 @@
         createUnavailableNotifications(context);
     }
 
-    private Notification createNotification(@NonNull Context context,
-                                            String title,
-                                            String content) {
+    private Notification createNotification(@NonNull Context context, String title,
+            String content) {
         Notification.Builder notificationBuilder = new Notification.Builder(context)
                 .setContentTitle(title)
                 .setContentText(content)
@@ -1965,7 +1999,9 @@
             if (!Objects.equals(mRegionalConfigId, mNewRegionalConfigId)) {
                 mRegionalConfigId = mNewRegionalConfigId;
                 notifyRegionalSatelliteConfigurationChanged(
-                        mSatelliteAccessConfigMap.get(mRegionalConfigId));
+                        Optional.ofNullable(mSatelliteAccessConfigMap)
+                                .map(map -> map.get(mRegionalConfigId))
+                                .orElse(null));
             }
         }
     }
@@ -2116,7 +2152,8 @@
      *                               {@link SatelliteOnDeviceAccessController} instance and the
      *                               device is using a user build.
      */
-    private boolean initSatelliteOnDeviceAccessController() throws IllegalStateException {
+    private boolean initSatelliteOnDeviceAccessController()
+            throws IllegalStateException {
         synchronized (mLock) {
             if (getSatelliteS2CellFile() == null) return false;
 
@@ -2133,6 +2170,7 @@
                 restartKeepOnDeviceAccessControllerResourcesTimer();
                 mS2Level = mSatelliteOnDeviceAccessController.getS2Level();
                 plogd("mS2Level=" + mS2Level);
+                loadSatelliteAccessConfigurationFromDeviceConfig();
             } catch (Exception ex) {
                 ploge("Got exception in creating an instance of SatelliteOnDeviceAccessController,"
                         + " ex=" + ex + ", sat s2 file="
@@ -2140,6 +2178,7 @@
                 reportAnomaly(UUID_CREATE_ON_DEVICE_ACCESS_CONTROLLER_EXCEPTION,
                         "Exception in creating on-device satellite access controller");
                 mSatelliteOnDeviceAccessController = null;
+                mSatelliteAccessConfigMap = null;
                 if (!mIsOverlayConfigOverridden) {
                     mSatelliteS2CellFile = null;
                 }
@@ -2160,6 +2199,7 @@
                     ploge("cleanupOnDeviceAccessControllerResources: ex=" + ex);
                 }
                 mSatelliteOnDeviceAccessController = null;
+                mSatelliteAccessConfigMap = null;
                 stopKeepOnDeviceAccessControllerResourcesTimer();
             }
         }
@@ -2188,41 +2228,66 @@
                 mccmnc = subInfo.getMccString() + subInfo.getMncString();
             }
 
-            synchronized (mRegionalSatelliteEarfcnsLock) {
-                /* Key: Regional satellite config ID, Value: SatelliteRegionalConfig
-                 * contains satellite config IDs and set of earfcns in the corresponding regions.
-                 */
-                Map<Integer, SatelliteRegionalConfig> satelliteRegionalConfigMap =
-                        mSatelliteRegionalConfigPerSubMap.get(subId);
-                if (satelliteRegionalConfigMap == null || satelliteRegionalConfigMap.isEmpty()) {
-                    plogd("handleCmdUpdateSystemSelectionChannels: config IDs and Earfcns are not"
-                            + " found for subId: "
-                            + subId);
-                    sendUpdateSystemSelectionChannelsResult(
-                            SATELLITE_RESULT_INVALID_TELEPHONY_STATE, null);
-                    return;
-                }
-
-                SatelliteRegionalConfig satelliteRegionalConfig = satelliteRegionalConfigMap.get(
-                        mRegionalConfigId);
-                if (satelliteRegionalConfig == null) {
-                    plogd("handleCmdUpdateSystemSelectionChannels: "
-                            + "Earfcns for satellite config Id: " + mRegionalConfigId
-                            + " not found");
-                    sendUpdateSystemSelectionChannelsResult(
-                            SATELLITE_RESULT_INVALID_TELEPHONY_STATE, null);
-                    return;
-                }
-                IntArray bands = new IntArray();
-                IntArray earfcns = new IntArray();
-                for (Integer value : satelliteRegionalConfig.getEarfcns()) {
-                    earfcns.add(value);
-                }
-
-                mSatelliteController.updateSystemSelectionChannels(
-                        new SystemSelectionSpecifier(mccmnc, bands, earfcns),
-                        mInternalUpdateSystemSelectionChannelsResultReceiver);
+            final Integer[] regionalConfigId = new Integer[1];
+            regionalConfigId[0] = getSelectedRegionalConfigId();
+            if (regionalConfigId[0] != null
+                    && regionalConfigId[0] == UNKNOWN_REGIONAL_SATELLITE_CONFIG_ID) {
+                // The geofence file with old format return UNKNOWN_REGIONAL_SATELLITE_CONFIG_ID
+                // for an S2 cell present in the file.
+                // For backward compatibility, we will use DEFAULT_REGIONAL_SATELLITE_CONFIG_ID
+                // for such cases.
+                regionalConfigId[0] = DEFAULT_REGIONAL_SATELLITE_CONFIG_ID;
             }
+            if (!SatelliteAccessConfigurationParser.isRegionalConfigIdValid(regionalConfigId[0])) {
+                plogd("handleCmdUpdateSystemSelectionChannels: mRegionalConfigId is not valid, "
+                        + "mRegionalConfig=" + getSelectedRegionalConfigId());
+                sendUpdateSystemSelectionChannelsResult(
+                        SATELLITE_RESULT_ACCESS_BARRED, null);
+                return;
+            }
+
+            SatelliteAccessConfiguration satelliteAccessConfiguration;
+            synchronized (mLock) {
+                satelliteAccessConfiguration = Optional.ofNullable(mSatelliteAccessConfigMap)
+                        .map(map -> map.get(regionalConfigId[0]))
+                        .orElse(null);
+            }
+            if (satelliteAccessConfiguration == null) {
+                plogd("handleCmdUpdateSystemSelectionChannels: satelliteAccessConfiguration "
+                        + "is not valid");
+                sendUpdateSystemSelectionChannelsResult(
+                        SATELLITE_RESULT_ACCESS_BARRED, null);
+                return;
+            }
+
+            List<SatelliteInfo> satelliteInfos =
+                    satelliteAccessConfiguration.getSatelliteInfos();
+            List<Integer> bandList = new ArrayList<>();
+            List<Integer> earfcnList = new ArrayList<>();
+            for (SatelliteInfo satelliteInfo : satelliteInfos) {
+                bandList.addAll(satelliteInfo.getBands());
+                List<EarfcnRange> earfcnRangeList = satelliteInfo.getEarfcnRanges();
+                earfcnRangeList.stream().flatMapToInt(
+                        earfcnRange -> IntStream.of(earfcnRange.getStartEarfcn(),
+                                earfcnRange.getEndEarfcn())).boxed().forEach(earfcnList::add);
+            }
+
+            IntArray bands = new IntArray(bandList.size());
+            bands.addAll(bandList.stream().mapToInt(Integer::intValue).toArray());
+            IntArray earfcns = new IntArray(
+                    Math.min(earfcnList.size(), MAX_EARFCN_ARRAY_LENGTH));
+            for (int i = 0; i < Math.min(earfcnList.size(), MAX_EARFCN_ARRAY_LENGTH); i++) {
+                earfcns.add(earfcnList.get(i));
+            }
+            IntArray tagIds = new IntArray(satelliteAccessConfiguration.getTagIds().size());
+            tagIds.addAll(satelliteAccessConfiguration.getTagIds().stream().mapToInt(
+                    Integer::intValue).toArray());
+
+            List<SystemSelectionSpecifier> selectionSpecifiers = new ArrayList<>();
+            selectionSpecifiers.add(new SystemSelectionSpecifier(mccmnc, bands, earfcns,
+                    satelliteInfos.toArray(new SatelliteInfo[0]), tagIds));
+            mSatelliteController.updateSystemSelectionChannels(selectionSpecifiers,
+                    mInternalUpdateSystemSelectionChannelsResultReceiver);
         }
     }
 
@@ -2258,6 +2323,28 @@
         return accessAllowed;
     }
 
+
+    @Nullable
+    protected String getSatelliteConfigurationFileNameFromOverlayConfig(
+            @NonNull Context context) {
+        String satelliteAccessControlInfoFile = null;
+
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            logd("mFeatureFlags: carrierRoamingNbIotNtn is disabled");
+            return satelliteAccessControlInfoFile;
+        }
+
+        try {
+            satelliteAccessControlInfoFile = context.getResources().getString(
+                    com.android.internal.R.string.satellite_access_config_file);
+        } catch (Resources.NotFoundException ex) {
+            loge("getSatelliteConfigurationFileNameFromOverlayConfig: got ex=" + ex);
+        }
+
+        logd("satelliteAccessControlInfoFile =" + satelliteAccessControlInfoFile);
+        return satelliteAccessControlInfoFile;
+    }
+
     @Nullable
     private static String getSatelliteS2CellFileFromOverlayConfig(@NonNull Context context) {
         String s2CellFile = null;
@@ -2487,7 +2574,9 @@
                 }
                 synchronized (mLock) {
                     SatelliteAccessConfiguration satelliteAccessConfig =
-                            mSatelliteAccessConfigMap.get(mRegionalConfigId);
+                            Optional.ofNullable(mSatelliteAccessConfigMap)
+                                    .map(map -> map.get(mRegionalConfigId))
+                                    .orElse(null);
                     callback.onSatelliteAccessConfigurationChanged(satelliteAccessConfig);
                     logd("registerForCommunicationAllowedStateChanged: satelliteAccessConfig: "
                             + satelliteAccessConfig + " of mRegionalConfigId: "
@@ -2526,9 +2615,9 @@
      * Returns integer array of disallowed reasons of satellite.
      *
      * @return Integer array of disallowed reasons of satellite.
-     *
      */
-    @NonNull public List<Integer> getSatelliteDisallowedReasons() {
+    @NonNull
+    public List<Integer> getSatelliteDisallowedReasons() {
         if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
             plogd("getSatelliteDisallowedReasons: carrierRoamingNbIotNtn is disabled");
             return new ArrayList<>();
@@ -2545,7 +2634,6 @@
      * 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) {
@@ -2579,7 +2667,7 @@
      *
      * @param callback The callback that was passed to
      *                 {@link #registerForSatelliteDisallowedReasonsChanged(
-     *                 ISatelliteDisallowedReasonsCallback)}.
+     *ISatelliteDisallowedReasonsCallback)}.
      */
     public void unregisterForSatelliteDisallowedReasonsChanged(
             @NonNull ISatelliteDisallowedReasonsCallback callback) {
@@ -2737,7 +2825,7 @@
         Rlog.w(TAG, log);
     }
 
-    private static void loge(@NonNull String log) {
+    protected static void loge(@NonNull String log) {
         Rlog.e(TAG, log);
     }
 
@@ -2850,6 +2938,13 @@
         evaluatePossibleChangeInDefaultSmsApp(context);
     }
 
+    @Nullable
+    private Integer getSelectedRegionalConfigId() {
+        synchronized (mLock) {
+            return mRegionalConfigId;
+        }
+    }
+
     private void plogv(@NonNull String log) {
         Rlog.v(TAG, log);
         if (mPersistentLogger != null) {
diff --git a/tests/src/com/android/phone/satellite/accesscontrol/SatelliteAccessConfigurationParserTest.java b/tests/src/com/android/phone/satellite/accesscontrol/SatelliteAccessConfigurationParserTest.java
new file mode 100644
index 0000000..d577a63
--- /dev/null
+++ b/tests/src/com/android/phone/satellite/accesscontrol/SatelliteAccessConfigurationParserTest.java
@@ -0,0 +1,314 @@
+/*
+ * 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.phone.satellite.accesscontrol;
+
+import static com.android.phone.satellite.accesscontrol.SatelliteAccessConfigurationParser.SATELLITE_ACCESS_CONTROL_CONFIGS;
+import static com.android.phone.satellite.accesscontrol.SatelliteAccessConfigurationParser.SATELLITE_CONFIG_ID;
+import static com.android.phone.satellite.accesscontrol.SatelliteAccessConfigurationParser.SATELLITE_INFOS;
+import static com.android.phone.satellite.accesscontrol.SatelliteAccessConfigurationParser.isRegionalConfigIdValid;
+import static com.android.phone.satellite.accesscontrol.SatelliteAccessConfigurationParser.parseSatelliteBandList;
+import static com.android.phone.satellite.accesscontrol.SatelliteAccessConfigurationParser.parseSatelliteEarfcnRangeList;
+import static com.android.phone.satellite.accesscontrol.SatelliteAccessConfigurationParser.parseSatelliteId;
+import static com.android.phone.satellite.accesscontrol.SatelliteAccessConfigurationParser.parseSatelliteInfoList;
+import static com.android.phone.satellite.accesscontrol.SatelliteAccessConfigurationParser.parseSatellitePosition;
+import static com.android.phone.satellite.accesscontrol.SatelliteAccessConfigurationParser.parseSatelliteTagIdList;
+import static com.android.phone.satellite.accesscontrol.SatelliteAccessConfigurationParser.readJsonStringFromFile;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.content.Context;
+import android.telephony.satellite.EarfcnRange;
+import android.telephony.satellite.SatelliteAccessConfiguration;
+import android.telephony.satellite.SatelliteInfo;
+import android.telephony.satellite.SatellitePosition;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+/** Unit test for {@link SatelliteAccessConfigurationParser} */
+@RunWith(AndroidJUnit4.class)
+public class SatelliteAccessConfigurationParserTest {
+    private static final String TAG = "SatelliteAccessConfigurationParserTest";
+
+    private static final String TEST_FILE_NAME = "test.json";
+    private static final String TEST_INVALID_FILE_NAME = "nonexistent_file.json";
+
+    private static final String TEST_SATELLITE_UUID1 = "5d0cc4f8-9223-4196-ad7a-803002db7af7";
+    private static final String TEST_SATELLITE_UUID2 = "0d30312e-a73f-444d-b99b-a893dfb42ee9";
+    private static final String TEST_SATELLITE_UUID3 = "01a0b0ca-11bc-4777-87ae-f39afbbec1e9";
+
+    private static final String VALID_JSON_STRING =
+            """
+            {
+             "access_control_configs": [
+               {
+                 "config_id": 123,
+                 "satellite_infos": [
+                   {
+                     "satellite_id": "5d0cc4f8-9223-4196-ad7a-803002db7af7",
+                     "satellite_position": {
+                       "longitude": 45.5,
+                       "altitude": 35786000
+                     },
+                     "bands": [
+                       1234,
+                       5678
+                     ],
+                     "earfcn_ranges": [
+                       {
+                         "start_earfcn": 1500,
+                         "end_earfcn": 1800
+                       }
+                     ]
+                   },
+                   {
+                     "satellite_id": "0d30312e-a73f-444d-b99b-a893dfb42ee9",
+                     "satellite_position": {
+                       "longitude": -120.3,
+                       "altitude": 35786000
+                     },
+                     "bands": [
+                       3456,
+                       7890
+                     ],
+                     "earfcn_ranges": [
+                       {
+                         "start_earfcn": 2000,
+                         "end_earfcn": 2300
+                       }
+                     ]
+                   }
+                 ],
+                 "tag_ids": [
+                   7,
+                   10
+                 ]
+               },
+               {
+                 "config_id": 890,
+                 "satellite_infos": [
+                   {
+                     "satellite_id": "01a0b0ca-11bc-4777-87ae-f39afbbec1e9",
+                     "satellite_position": {
+                       "longitude": -120,
+                       "altitude": 1234567
+                     },
+                     "bands": [
+                       13579,
+                       24680
+                     ],
+                     "earfcn_ranges": [
+                       {
+                         "start_earfcn": 6420,
+                         "end_earfcn": 15255
+                       }
+                     ]
+                   }
+                 ],
+                 "tag_ids": [
+                   6420,
+                   15255
+                 ]
+               }
+             ]
+             }
+            """;
+
+
+    // Mandatory : config_id ( >= 0)
+    // SatelliteInfoList : NonNull
+    // UUID (0-9, a-f and hyphen : '_' and 'z' are invalid)
+    // longitude (-180 ~ 180)
+    // altitude ( >= 0)
+    private static final String INVALID_JSON_STRING =
+            """
+            {
+              "access_control_configs": [
+                {
+                  "config_id": -100,
+                  "satellite_infos": [
+                    {
+                      "satellite_id": "01z0b0ca-11bc-4777_87ae-f39afbbec1e9",
+                      "satellite_position": {
+                        "longitude": -181,
+                        "altitude": -1
+                      },
+                      "earfcn_ranges": [
+                        {
+                          "start_earfcn": -1,
+                          "end_earfcn": 65536
+                        }
+                      ]
+                    }
+                  ]
+                }
+              ]
+            }
+            """;
+
+    @Before
+    public void setUp() throws Exception {
+        Log.d(TAG, "setUp");
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        Log.d(TAG, "tearDown");
+    }
+
+    @AfterClass
+    public static void afterClass() throws Exception {
+    }
+
+    @BeforeClass
+    public static void beforeClass() throws Exception {
+    }
+
+    private static File createTestJsonFile(@NonNull String content) throws Exception {
+        Log.d(TAG, "createTestJsonFile");
+        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        File testFile = new File(context.getCacheDir(), TEST_FILE_NAME);
+        try (FileOutputStream fos = new FileOutputStream(testFile)) {
+            fos.write(content.getBytes(StandardCharsets.UTF_8));
+        }
+        return testFile;
+    }
+
+    @Test
+    public void testLoadJsonFile() throws Exception {
+        Log.d(TAG, "testLoadJsonFile");
+        assertNull(readJsonStringFromFile(TEST_INVALID_FILE_NAME));
+        assertNull(readJsonStringFromFile(null));
+
+        File file = createTestJsonFile(VALID_JSON_STRING);
+        assertEquals(VALID_JSON_STRING, readJsonStringFromFile(file.getPath()));
+
+        assertTrue(file.delete());
+    }
+
+
+    private SatelliteInfo getSatelliteInfo(UUID id, SatellitePosition position,
+            List<Integer> bandList, List<EarfcnRange> rangeList) {
+        return new SatelliteInfo(id, position, bandList, rangeList);
+    }
+
+    private Map<Integer, SatelliteAccessConfiguration> getExpectedMap() {
+        List<SatelliteInfo> satelliteInfoList1 = new ArrayList<>();
+        satelliteInfoList1.add(
+                getSatelliteInfo(UUID.fromString(TEST_SATELLITE_UUID1),
+                        new SatellitePosition(45.5, 35786000),
+                        List.of(1234, 5678),
+                        new ArrayList<>(List.of(new EarfcnRange(1500, 1800)))
+                ));
+        satelliteInfoList1.add(
+                getSatelliteInfo(UUID.fromString(TEST_SATELLITE_UUID2),
+                        new SatellitePosition(-120.3, 35786000),
+                        List.of(3456, 7890),
+                        new ArrayList<>(List.of(new EarfcnRange(2000, 2300)))
+                ));
+
+        List<Integer> tagIdList1 = List.of(7, 10);
+        SatelliteAccessConfiguration satelliteAccessConfiguration1 =
+                new SatelliteAccessConfiguration(satelliteInfoList1, tagIdList1);
+
+        HashMap<Integer, SatelliteAccessConfiguration> expectedResult = new HashMap<>();
+        expectedResult.put(123, satelliteAccessConfiguration1);
+
+        List<SatelliteInfo> satelliteInfoList2 = new ArrayList<>();
+        List<Integer> tagIdList2 = List.of(6420, 15255);
+        satelliteInfoList2.add(
+                getSatelliteInfo(UUID.fromString(TEST_SATELLITE_UUID3),
+                        new SatellitePosition(-120, 1234567),
+                        List.of(13579, 24680),
+                        new ArrayList<>(List.of(new EarfcnRange(6420, 15255)))
+                ));
+        SatelliteAccessConfiguration satelliteAccessConfiguration2 =
+                new SatelliteAccessConfiguration(satelliteInfoList2, tagIdList2);
+        expectedResult.put(890, satelliteAccessConfiguration2);
+        return expectedResult;
+    }
+
+
+    @Test
+    public void testParsingValidSatelliteAccessConfiguration() throws Exception {
+        Log.d(TAG, "testParsingValidSatelliteAccessConfiguration");
+        File file = createTestJsonFile(VALID_JSON_STRING);
+        assertEquals(getExpectedMap(),
+                SatelliteAccessConfigurationParser.parse(file.getCanonicalPath()));
+    }
+
+    @Test
+    public void testParsingInvalidSatelliteAccessConfiguration() throws Exception {
+        Log.d(TAG, "testParsingInvalidSatelliteAccessConfiguration");
+        File file = createTestJsonFile(INVALID_JSON_STRING);
+        String jsonString = readJsonStringFromFile(file.getCanonicalPath());
+        JSONObject satelliteAccessConfigJsonObject = new JSONObject(jsonString);
+        JSONArray configurationArrayJson = satelliteAccessConfigJsonObject.optJSONArray(
+                SATELLITE_ACCESS_CONTROL_CONFIGS);
+
+        for (int i = 0; i < configurationArrayJson.length(); i++) {
+            JSONObject configJson = configurationArrayJson.getJSONObject(i);
+
+            int configId = configJson.optInt(SATELLITE_CONFIG_ID, -1);
+            assertFalse(isRegionalConfigIdValid(configId));
+
+            JSONArray satelliteInfoArray = configJson.getJSONArray(SATELLITE_INFOS);
+            List<SatelliteInfo> satelliteInfoList = parseSatelliteInfoList(satelliteInfoArray);
+            assertNotNull(satelliteInfoList);
+            assertTrue(satelliteInfoList.isEmpty());
+
+            for (int j = 0; j < satelliteInfoArray.length(); j++) {
+                JSONObject infoJson = satelliteInfoArray.getJSONObject(i);
+                assertNull(parseSatelliteId(infoJson));
+                assertNull(parseSatellitePosition(infoJson));
+                assertTrue(parseSatelliteEarfcnRangeList(infoJson).isEmpty());
+                assertNotNull(parseSatelliteBandList(infoJson));
+                assertEquals(0, parseSatelliteBandList(infoJson).size());
+            }
+
+            List<Integer> tagIdList = parseSatelliteTagIdList(configJson);
+            assertNotNull(tagIdList);
+        }
+    }
+}
diff --git a/tests/src/com/android/phone/satellite/accesscontrol/SatelliteAccessControllerTest.java b/tests/src/com/android/phone/satellite/accesscontrol/SatelliteAccessControllerTest.java
index b388954..a47095d 100644
--- a/tests/src/com/android/phone/satellite/accesscontrol/SatelliteAccessControllerTest.java
+++ b/tests/src/com/android/phone/satellite/accesscontrol/SatelliteAccessControllerTest.java
@@ -22,11 +22,13 @@
 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_RESULT_ACCESS_BARRED;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_ERROR;
 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_MODEM_ERROR;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_NOT_SUPPORTED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_NO_RESOURCES;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
 
@@ -43,6 +45,7 @@
 import static com.android.phone.satellite.accesscontrol.SatelliteAccessController.GOOGLE_US_SAN_SAT_S2_FILE_NAME;
 import static com.android.phone.satellite.accesscontrol.SatelliteAccessController.UNKNOWN_REGIONAL_SATELLITE_CONFIG_ID;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
@@ -53,6 +56,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyList;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
@@ -97,10 +101,13 @@
 import android.telecom.TelecomManager;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.satellite.EarfcnRange;
 import android.telephony.satellite.ISatelliteCommunicationAllowedStateCallback;
 import android.telephony.satellite.SatelliteAccessConfiguration;
 import android.telephony.satellite.SatelliteInfo;
 import android.telephony.satellite.SatelliteManager;
+import android.telephony.satellite.SatellitePosition;
+import android.telephony.satellite.SystemSelectionSpecifier;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.Log;
@@ -135,11 +142,14 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 
 /** Unit test for {@link SatelliteAccessController} */
 @RunWith(AndroidTestingRunner.class)
@@ -287,6 +297,22 @@
         }
     };
 
+    private int mQueriedSystemSelectionChannelUpdatedResultCode = SATELLITE_RESULT_SUCCESS;
+    private Semaphore mSystemSelectionChannelUpdatedSemaphore = new Semaphore(0);
+    private ResultReceiver mSystemSelectionChannelUpdatedReceiver = new ResultReceiver(null) {
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            mQueriedSystemSelectionChannelUpdatedResultCode = resultCode;
+            try {
+                mSystemSelectionChannelUpdatedSemaphore.release();
+            } catch (Exception ex) {
+                fail("mSystemSelectionChannelUpdatedReceiver: Got exception in releasing "
+                        + "semaphore, ex="
+                        + ex);
+            }
+        }
+    };
+
     @Before
     public void setUp() throws Exception {
         super.setUp();
@@ -619,10 +645,9 @@
 
         verify(mockResultReceiver, times(1)).send(resultCodeCaptor.capture(),
                 bundleCaptor.capture());
-        assertEquals(SATELLITE_RESULT_SUCCESS, (int) resultCodeCaptor.getValue());
-        assertTrue(bundleCaptor.getValue().containsKey(KEY_SATELLITE_ACCESS_CONFIGURATION));
-        assertNull(bundleCaptor.getValue().getParcelable(KEY_SATELLITE_ACCESS_CONFIGURATION,
-                SatelliteAccessConfiguration.class));
+        assertEquals(SATELLITE_RESULT_NO_RESOURCES, (int) resultCodeCaptor.getValue());
+        assertNull(bundleCaptor.getValue());
+
         verify(mockSatelliteAllowedStateCallback, times(1))
                 .onSatelliteAccessConfigurationChanged(
                         satelliteAccessConfigurationCaptor.capture());
@@ -1410,6 +1435,33 @@
     }
 
     @Test
+    public void testLoadSatelliteAccessConfigurationFromDeviceConfig() {
+        when(mMockFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(false);
+        assertNull(mSatelliteAccessControllerUT
+                .getSatelliteConfigurationFileNameFromOverlayConfig(mMockContext));
+
+        when(mMockFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
+        when(mMockContext.getResources()).thenReturn(mMockResources);
+        when(mMockResources
+                .getString(eq(com.android.internal.R.string.satellite_access_config_file)))
+                .thenReturn("test_satellite_file.json");
+        assertEquals("test_satellite_file.json", mSatelliteAccessControllerUT
+                .getSatelliteConfigurationFileNameFromOverlayConfig(mMockContext));
+
+        when(mMockResources
+                .getString(eq(com.android.internal.R.string.satellite_access_config_file)))
+                .thenReturn(null);
+        assertNull(mSatelliteAccessControllerUT
+                .getSatelliteConfigurationFileNameFromOverlayConfig(mMockContext));
+        try {
+            mSatelliteAccessControllerUT.loadSatelliteAccessConfigurationFromDeviceConfig();
+        } catch (Exception e) {
+            fail("Unexpected exception thrown: " + e.getMessage());
+        }
+    }
+
+
+    @Test
     public void testUpdateSatelliteConfigData() throws Exception {
         verify(mMockSatelliteController).registerForConfigUpdateChanged(
                 mConfigUpdateHandlerCaptor.capture(), mConfigUpdateIntCaptor.capture(),
@@ -1503,7 +1555,7 @@
         // Captor and Verify if the mockReceiver and mocContext is registered well
         verify(mMockContext, times(2))
                 .registerReceiver(mLocationBroadcastReceiverCaptor.capture(),
-                mIntentFilterCaptor.capture());
+                        mIntentFilterCaptor.capture());
 
         // When the intent action is not MODE_CHANGED_ACTION,
         // verify if the location manager never invoke isLocationEnabled()
@@ -1683,6 +1735,311 @@
                 any(Consumer.class));
     }
 
+    @Test
+    public void testUpdateSystemSelectionChannels() {
+        // Set non-emergency case
+        when(mMockFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
+        when(mMockFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
+        when(mMockCountryDetector.getCurrentNetworkCountryIso()).thenReturn(EMPTY_STRING_LIST);
+
+        // Invoke when regional config ID is not set.
+        mSatelliteAccessControllerUT.setRegionalConfigId(null);
+        mSatelliteAccessControllerUT.updateSystemSelectionChannels(
+                mSystemSelectionChannelUpdatedReceiver);
+        mTestableLooper.processAllMessages();
+        assertTrue(waitForRequestUpdateSystemSelectionChannelResult(
+                mSystemSelectionChannelUpdatedSemaphore, 1));
+        assertEquals(SATELLITE_RESULT_ACCESS_BARRED,
+                mQueriedSystemSelectionChannelUpdatedResultCode);
+
+        // Invoke when mSatelliteAccessConfigMap does not have data for given regional config ID
+        int satelliteRegionalConfigId = DEFAULT_REGIONAL_SATELLITE_CONFIG_ID;
+        mSatelliteAccessControllerUT.setRegionalConfigId(satelliteRegionalConfigId);
+        mSatelliteAccessControllerUT.resetSatelliteAccessConfigMap();
+        mSatelliteAccessControllerUT.updateSystemSelectionChannels(
+                mSystemSelectionChannelUpdatedReceiver);
+        mTestableLooper.processAllMessages();
+        assertTrue(waitForRequestUpdateSystemSelectionChannelResult(
+                mSystemSelectionChannelUpdatedSemaphore, 1));
+        assertEquals(SATELLITE_RESULT_ACCESS_BARRED,
+                mQueriedSystemSelectionChannelUpdatedResultCode);
+
+        // Invoke when mSatelliteAccessConfigMap does not have data and given data is old format.
+        satelliteRegionalConfigId = UNKNOWN_REGIONAL_SATELLITE_CONFIG_ID;
+        mSatelliteAccessControllerUT.setRegionalConfigId(satelliteRegionalConfigId);
+        mSatelliteAccessControllerUT.resetSatelliteAccessConfigMap();
+        mSatelliteAccessControllerUT.updateSystemSelectionChannels(
+                mSystemSelectionChannelUpdatedReceiver);
+        mTestableLooper.processAllMessages();
+        assertTrue(waitForRequestUpdateSystemSelectionChannelResult(
+                mSystemSelectionChannelUpdatedSemaphore, 1));
+        assertEquals(SATELLITE_RESULT_ACCESS_BARRED,
+                mQueriedSystemSelectionChannelUpdatedResultCode);
+
+        satelliteRegionalConfigId = DEFAULT_REGIONAL_SATELLITE_CONFIG_ID;
+        // Return success when SatelliteController.updateSystemSelectionChannels was invoked
+        setupResponseForUpdateSystemSelectionChannels(SATELLITE_RESULT_SUCCESS);
+
+        // Invoke updateSystemSelectionChannels when there is corresponding satellite access config.
+        // Create satellite info 1
+        String seed1 = "test-seed-satellite1";
+        UUID uuid1 = UUID.nameUUIDFromBytes(seed1.getBytes());
+        SatellitePosition satellitePosition1 = new SatellitePosition(0, 35876);
+        int[] bands1 = {200, 201, 202};
+        EarfcnRange earfcnRange1 = new EarfcnRange(300, 301);
+        EarfcnRange earfcnRange2 = new EarfcnRange(310, 311);
+        List<EarfcnRange> earfcnRangeList1 = new ArrayList<>(
+                Arrays.asList(earfcnRange1, earfcnRange2));
+        SatelliteInfo satelliteInfo1 = new SatelliteInfo(uuid1, satellitePosition1, Arrays.stream(
+                bands1).boxed().collect(Collectors.toList()), earfcnRangeList1);
+        // Create satellite info 2
+        String seed2 = "test-seed-satellite2";
+        UUID uuid2 = UUID.nameUUIDFromBytes(seed2.getBytes());
+        SatellitePosition satellitePosition2 = new SatellitePosition(120, 35876);
+        int[] bands2 = {210, 211, 212};
+        EarfcnRange earfcnRange3 = new EarfcnRange(320, 321);
+        EarfcnRange earfcnRange4 = new EarfcnRange(330, 331);
+        List<EarfcnRange> earfcnRangeList2 = new ArrayList<>(
+                Arrays.asList(earfcnRange3, earfcnRange4));
+        SatelliteInfo satelliteInfo2 = new SatelliteInfo(uuid2, satellitePosition2, Arrays.stream(
+                bands2).boxed().collect(Collectors.toList()), earfcnRangeList2);
+        // Create satellite info 3
+        String seed3 = "test-seed-satellite3";
+        UUID uuid3 = UUID.nameUUIDFromBytes(seed3.getBytes());
+        SatellitePosition satellitePosition3 = new SatellitePosition(120, 35876);
+        int[] bands3 = {220, 221, 222};
+        EarfcnRange earfcnRange5 = new EarfcnRange(340, 341);
+        EarfcnRange earfcnRange6 = new EarfcnRange(350, 351);
+        List<EarfcnRange> earfcnRangeList3 = new ArrayList<>(
+                Arrays.asList(earfcnRange5, earfcnRange6));
+        SatelliteInfo satelliteInfo3 = new SatelliteInfo(uuid3, satellitePosition3, Arrays.stream(
+                bands3).boxed().collect(Collectors.toList()), earfcnRangeList3);
+
+        int[] tagIds = {1, 2, 3};
+        SatelliteAccessConfiguration satelliteAccessConfiguration =
+                new SatelliteAccessConfiguration(new ArrayList<>(
+                        Arrays.asList(satelliteInfo1, satelliteInfo2, satelliteInfo3)),
+                        Arrays.stream(tagIds).boxed().collect(Collectors.toList()));
+
+        // Add satellite access configuration to map
+        mSatelliteAccessControllerUT.setSatelliteAccessConfigMap(satelliteRegionalConfigId,
+                satelliteAccessConfiguration);
+
+        // Invoke updateSystemSelectionChannel
+        mSatelliteAccessControllerUT.updateSystemSelectionChannels(
+                mSystemSelectionChannelUpdatedReceiver);
+        mTestableLooper.processAllMessages();
+        assertTrue(waitForRequestUpdateSystemSelectionChannelResult(
+                mSystemSelectionChannelUpdatedSemaphore, 1));
+        assertEquals(SATELLITE_RESULT_SUCCESS,
+                mQueriedSystemSelectionChannelUpdatedResultCode);
+        ArgumentCaptor<List<SystemSelectionSpecifier>> systemSelectionSpecifierListCaptor =
+                ArgumentCaptor.forClass(List.class);
+        verify(mMockSatelliteController, times(1)).updateSystemSelectionChannels(
+                systemSelectionSpecifierListCaptor.capture(), any(ResultReceiver.class));
+        List<SystemSelectionSpecifier> capturedList = systemSelectionSpecifierListCaptor.getValue();
+        SystemSelectionSpecifier systemSelectionSpecifier = capturedList.getFirst();
+
+        // Verify the fields value of given systemSelectionSpecifier matched with expected.
+        int[] expectedBandsArray = IntStream.concat(
+                IntStream.concat(Arrays.stream(bands1), Arrays.stream(bands2)),
+                Arrays.stream(bands3)).toArray();
+        int[] actualBandsArray = IntStream.range(0, systemSelectionSpecifier.getBands().size()).map(
+                systemSelectionSpecifier.getBands()::get).toArray();
+        assertArrayEquals(expectedBandsArray, actualBandsArray);
+
+        int[] expectedEarfcnsArray = {300, 301, 310, 311, 320, 321, 330, 331, 340, 341, 350, 351};
+        int[] actualEarfcnsArray = IntStream.range(0,
+                systemSelectionSpecifier.getEarfcns().size()).map(
+                systemSelectionSpecifier.getEarfcns()::get).toArray();
+        assertArrayEquals(expectedEarfcnsArray, actualEarfcnsArray);
+
+        SatelliteInfo[] expectedSatelliteInfos = {satelliteInfo1, satelliteInfo2, satelliteInfo3};
+        assertArrayEquals(expectedSatelliteInfos, systemSelectionSpecifier.getSatelliteInfos());
+
+        int[] actualTagIdArray = IntStream.range(0,
+                systemSelectionSpecifier.getTagIds().size()).map(
+                systemSelectionSpecifier.getTagIds()::get).toArray();
+        assertArrayEquals(tagIds, actualTagIdArray);
+
+        // Verify backward compatibility when there is valid data for default regional config ID
+        satelliteRegionalConfigId = UNKNOWN_REGIONAL_SATELLITE_CONFIG_ID;
+        mSatelliteAccessControllerUT.setRegionalConfigId(satelliteRegionalConfigId);
+        mSatelliteAccessControllerUT.updateSystemSelectionChannels(
+                mSystemSelectionChannelUpdatedReceiver);
+        mTestableLooper.processAllMessages();
+
+        // updateSelectionChannelResult will be invoked with the data for default regional config ID
+        assertTrue(waitForRequestUpdateSystemSelectionChannelResult(
+                mSystemSelectionChannelUpdatedSemaphore, 1));
+        systemSelectionSpecifierListCaptor = ArgumentCaptor.forClass(List.class);
+        verify(mMockSatelliteController, times(2)).updateSystemSelectionChannels(
+                systemSelectionSpecifierListCaptor.capture(), any(ResultReceiver.class));
+        capturedList = systemSelectionSpecifierListCaptor.getValue();
+        systemSelectionSpecifier = capturedList.getFirst();
+
+        // Data will be same with default regional config ID
+
+        // Verify the fields value of given systemSelectionSpecifier matched with expected.
+        actualBandsArray = IntStream.range(0, systemSelectionSpecifier.getBands().size()).map(
+                systemSelectionSpecifier.getBands()::get).toArray();
+        assertArrayEquals(expectedBandsArray, actualBandsArray);
+
+        actualEarfcnsArray = IntStream.range(0,
+                systemSelectionSpecifier.getEarfcns().size()).map(
+                systemSelectionSpecifier.getEarfcns()::get).toArray();
+        assertArrayEquals(expectedEarfcnsArray, actualEarfcnsArray);
+
+        assertArrayEquals(expectedSatelliteInfos, systemSelectionSpecifier.getSatelliteInfos());
+
+        actualTagIdArray = IntStream.range(0,
+                systemSelectionSpecifier.getTagIds().size()).map(
+                systemSelectionSpecifier.getTagIds()::get).toArray();
+        assertArrayEquals(tagIds, actualTagIdArray);
+
+        mSatelliteAccessControllerUT.resetSatelliteAccessConfigMap();
+    }
+
+    @Test
+    public void testUpdateSystemSelectionChannels_HandleInvalidInput() {
+        // Set non-emergency case
+        when(mMockFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
+        when(mMockFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
+        when(mMockCountryDetector.getCurrentNetworkCountryIso()).thenReturn(EMPTY_STRING_LIST);
+        int satelliteRegionalConfigId = DEFAULT_REGIONAL_SATELLITE_CONFIG_ID;
+        mSatelliteAccessControllerUT.setRegionalConfigId(satelliteRegionalConfigId);
+        // Set return success when SatelliteController.updateSystemSelectionChannels was invoked
+        setupResponseForUpdateSystemSelectionChannels(SATELLITE_RESULT_SUCCESS);
+
+        // Create satellite info in which satellite position is null.
+        String seed1 = "test-seed-satellite1";
+        UUID uuid1 = UUID.nameUUIDFromBytes(seed1.getBytes());
+        SatellitePosition satellitePosition1 = null;
+        List<Integer> bandList1 = new ArrayList<>(List.of(200, 201, 202));
+        EarfcnRange earfcnRange1 = new EarfcnRange(300, 301);
+        EarfcnRange earfcnRange2 = new EarfcnRange(310, 311);
+        List<EarfcnRange> earfcnRangeList1 = new ArrayList<>(
+                Arrays.asList(earfcnRange1, earfcnRange2));
+        SatelliteInfo satelliteInfo1 = new SatelliteInfo(uuid1, satellitePosition1, bandList1,
+                earfcnRangeList1);
+
+        // Create satellite info in which band list is empty
+        String seed2 = "test-seed-satellite2";
+        UUID uuid2 = UUID.nameUUIDFromBytes(seed2.getBytes());
+        SatellitePosition satellitePosition2 = new SatellitePosition(120, 35876);
+        List<Integer> bandList2 = new ArrayList<>();
+        EarfcnRange earfcnRange3 = new EarfcnRange(320, 321);
+        EarfcnRange earfcnRange4 = new EarfcnRange(330, 331);
+        List<EarfcnRange> earfcnRangeList2 = new ArrayList<>(
+                Arrays.asList(earfcnRange3, earfcnRange4));
+        SatelliteInfo satelliteInfo2 = new SatelliteInfo(uuid2, satellitePosition2, bandList2,
+                earfcnRangeList2);
+
+        // Create satellite info 3, every field is valid
+        String seed3 = "test-seed-satellite3";
+        UUID uuid3 = UUID.nameUUIDFromBytes(seed3.getBytes());
+        SatellitePosition satellitePosition3 = new SatellitePosition(120, 35876);
+        List<Integer> bandList3 = new ArrayList<>(List.of(220, 221, 222));
+        EarfcnRange earfcnRange5 = new EarfcnRange(340, 341);
+        EarfcnRange earfcnRange6 = new EarfcnRange(350, 351);
+        List<EarfcnRange> earfcnRangeList3 = new ArrayList<>(
+                Arrays.asList(earfcnRange5, earfcnRange6));
+        SatelliteInfo satelliteInfo3 = new SatelliteInfo(uuid3, satellitePosition3, bandList3,
+                earfcnRangeList3);
+        // Add empty tagId list
+        List<Integer> tagIdList = new ArrayList<>();
+
+        // Create satelliteAccessConfiguration with some of files of added Satellite info are empty.
+        SatelliteAccessConfiguration satelliteAccessConfiguration1 =
+                new SatelliteAccessConfiguration(new ArrayList<>(
+                        Arrays.asList(satelliteInfo1, satelliteInfo2, satelliteInfo3)), tagIdList);
+
+        // Add satellite access configuration to map
+        mSatelliteAccessControllerUT.setSatelliteAccessConfigMap(satelliteRegionalConfigId,
+                satelliteAccessConfiguration1);
+
+        // Invoke updateSystemSelectionChannel
+        mSatelliteAccessControllerUT.updateSystemSelectionChannels(
+                mSystemSelectionChannelUpdatedReceiver);
+        mTestableLooper.processAllMessages();
+        assertTrue(waitForRequestUpdateSystemSelectionChannelResult(
+                mSystemSelectionChannelUpdatedSemaphore, 1));
+        assertEquals(SATELLITE_RESULT_SUCCESS,
+                mQueriedSystemSelectionChannelUpdatedResultCode);
+        ArgumentCaptor<List<SystemSelectionSpecifier>> systemSelectionSpecifierListCaptor =
+                ArgumentCaptor.forClass(List.class);
+        verify(mMockSatelliteController, times(1)).updateSystemSelectionChannels(
+                systemSelectionSpecifierListCaptor.capture(), any(ResultReceiver.class));
+        List<SystemSelectionSpecifier> capturedList = systemSelectionSpecifierListCaptor.getValue();
+        SystemSelectionSpecifier systemSelectionSpecifier = capturedList.getFirst();
+
+        // Verify the fields value of given systemSelectionSpecifier matched with expected.
+        List<Integer> expectedBandList = new ArrayList<>(bandList1);
+        expectedBandList.addAll(bandList2);
+        expectedBandList.addAll(bandList3);
+
+        List<Integer> actualBandList = IntStream.range(0,
+                systemSelectionSpecifier.getBands().size()).map(
+                systemSelectionSpecifier.getBands()::get).boxed().toList();
+        assertEquals(expectedBandList, actualBandList);
+
+        List<Integer> expectedEarfcnList = new ArrayList<>(
+                List.of(300, 301, 310, 311, 320, 321, 330, 331, 340, 341, 350, 351));
+        List<Integer> actualEarfcnList = IntStream.range(0,
+                systemSelectionSpecifier.getEarfcns().size()).map(
+                systemSelectionSpecifier.getEarfcns()::get).boxed().toList();
+        assertEquals(expectedEarfcnList, actualEarfcnList);
+
+        assertEquals(satelliteInfo1, systemSelectionSpecifier.getSatelliteInfos()[0]);
+        assertEquals(satelliteInfo2, systemSelectionSpecifier.getSatelliteInfos()[1]);
+        assertEquals(satelliteInfo3, systemSelectionSpecifier.getSatelliteInfos()[2]);
+
+        List<Integer> actualTagIdList = IntStream.range(0,
+                systemSelectionSpecifier.getTagIds().size()).map(
+                systemSelectionSpecifier.getTagIds()::get).boxed().toList();
+        assertEquals(tagIdList, actualTagIdList);
+
+        // Create satelliteAccessConfiguration with empty list of SatelliteInfo.
+        SatelliteAccessConfiguration satelliteAccessConfiguration2 =
+                new SatelliteAccessConfiguration(new ArrayList<>(), tagIdList);
+        mSatelliteAccessControllerUT.setSatelliteAccessConfigMap(
+                DEFAULT_REGIONAL_SATELLITE_CONFIG_ID, satelliteAccessConfiguration2);
+
+        // Invoke updateSystemSelectionChannel
+        mSatelliteAccessControllerUT.updateSystemSelectionChannels(
+                mSystemSelectionChannelUpdatedReceiver);
+        mTestableLooper.processAllMessages();
+        assertTrue(waitForRequestUpdateSystemSelectionChannelResult(
+                mSystemSelectionChannelUpdatedSemaphore, 1));
+        assertEquals(SATELLITE_RESULT_SUCCESS,
+                mQueriedSystemSelectionChannelUpdatedResultCode);
+        systemSelectionSpecifierListCaptor = ArgumentCaptor.forClass(List.class);
+        verify(mMockSatelliteController, times(2)).updateSystemSelectionChannels(
+                systemSelectionSpecifierListCaptor.capture(), any(ResultReceiver.class));
+        capturedList = systemSelectionSpecifierListCaptor.getValue();
+        systemSelectionSpecifier = capturedList.getFirst();
+
+        // Verify the fields value of given systemSelectionSpecifier matched with expected.
+        assertEquals(0, systemSelectionSpecifier.getBands().size());
+        assertEquals(0, systemSelectionSpecifier.getEarfcns().size());
+
+        SatelliteInfo[] expectedSatelliteInfoArray = new SatelliteInfo[0];
+        assertArrayEquals(expectedSatelliteInfoArray, systemSelectionSpecifier.getSatelliteInfos());
+
+        actualTagIdList = IntStream.range(0,
+                systemSelectionSpecifier.getTagIds().size()).map(
+                systemSelectionSpecifier.getTagIds()::get).boxed().toList();
+        assertEquals(tagIdList, actualTagIdList);
+
+        mSatelliteAccessControllerUT.resetSatelliteAccessConfigMap();
+    }
+
     private void sendSatelliteCommunicationAllowedEvent() {
         Pair<Integer, ResultReceiver> requestPair =
                 new Pair<>(DEFAULT_SUBSCRIPTION_ID,
@@ -1694,7 +2051,6 @@
         mTestableLooper.processAllMessages();
     }
 
-
     private void sendConfigUpdateChangedEvent(Context context) {
         Message msg = mSatelliteAccessControllerUT.obtainMessage(EVENT_CONFIG_DATA_UPDATED);
         msg.obj = new AsyncResult(context, SATELLITE_RESULT_SUCCESS, null);
@@ -1747,6 +2103,24 @@
         return true;
     }
 
+    private boolean waitForRequestUpdateSystemSelectionChannelResult(Semaphore semaphore,
+            int expectedNumberOfEvents) {
+        for (int i = 0; i < expectedNumberOfEvents; i++) {
+            try {
+                if (!semaphore.tryAcquire(TIMEOUT, TimeUnit.MILLISECONDS)) {
+                    logd("Timeout to receive "
+                            + "updateSystemSelectionChannel()"
+                            + " callback");
+                    return false;
+                }
+            } catch (Exception ex) {
+                logd("updateSystemSelectionChannel: Got exception=" + ex);
+                return false;
+            }
+        }
+        return true;
+    }
+
     private void sendLocationRequestResult(Location location) {
         mLocationRequestConsumerCaptor.getValue().accept(location);
         mTestableLooper.processAllMessages();
@@ -1783,6 +2157,16 @@
         }).when(mMockSatelliteController).requestIsSatelliteProvisioned(any(ResultReceiver.class));
     }
 
+    private void setupResponseForUpdateSystemSelectionChannels(
+            @SatelliteManager.SatelliteResult int error) {
+        doAnswer(invocation -> {
+            ResultReceiver resultReceiver = invocation.getArgument(1);
+            resultReceiver.send(error, null);
+            return null;
+        }).when(mMockSatelliteController).updateSystemSelectionChannels(anyList(),
+                any(ResultReceiver.class));
+    }
+
     @SafeVarargs
     private static <E> List<E> listOf(E... values) {
         return Arrays.asList(values);
@@ -1898,5 +2282,25 @@
                 mRegionalConfigId = regionalConfigId;
             }
         }
+
+        public void setSatelliteAccessConfigMap(int regionalConfigId,
+                SatelliteAccessConfiguration satelliteAccessConfiguration) {
+            synchronized (mLock) {
+                if (mSatelliteAccessConfigMap == null) {
+                    mSatelliteAccessConfigMap = new HashMap<>();
+                }
+                mSatelliteAccessConfigMap.put(regionalConfigId, satelliteAccessConfiguration);
+            }
+        }
+
+        public void resetSatelliteAccessConfigMap() {
+            synchronized (mLock) {
+                if (mSatelliteAccessConfigMap == null) {
+                    mSatelliteAccessConfigMap = new HashMap<>();
+                } else {
+                    mSatelliteAccessConfigMap.clear();
+                }
+            }
+        }
     }
 }