Support config for roaming network

Bug: 198618350
Test: atest CellBroadcastReceiverUnitTests
Test: manual
Change-Id: I1d666c1a3260328d1e3cf4eca3f3ac1a70af2bd9
Merged-Id: I1d666c1a3260328d1e3cf4eca3f3ac1a70af2bd9
diff --git a/Android.bp b/Android.bp
index 43bd173..4e45773 100644
--- a/Android.bp
+++ b/Android.bp
@@ -40,6 +40,7 @@
         "SettingsLibSettingsTheme",
         "SettingsLibCollapsingToolbarBaseActivity",
         "SettingsLibMainSwitchPreference",
+	"SettingsLibTopIntroPreference",
         "modules-utils-build_system",
     ],
     optimize: {
diff --git a/res/values/config.xml b/res/values/config.xml
index 9c2c0c2..ffc722a 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -181,6 +181,15 @@
     <!-- Channels to receive geo-fencing trigger messages -->
     <string-array name="geo_fencing_trigger_messages_range_strings" translatable="false"></string-array>
 
+    <!-- List of roaming network operator to be supported.
+         The item can be operator numeric as 3 digits of mcc, 6 or 5 digits of mcc+mnc, or special
+         characters as XXX to match any mcc, XXXXXX to match any mcc+mnc. If multiple items match
+         the roaming network, only the first one will take effect. -->
+    <string-array name="cmas_roaming_network_strings" translatable="false">
+        <!-- France -->
+        <item>208</item>
+    </string-array>
+
     <!-- Values that are retrieved from the ListPreference.
          These must match the alert_reminder_interval_entries list above. -->
     <string-array name="alert_reminder_interval_values" translatable="false">
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 77fae22..7c140dc 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -384,6 +384,11 @@
     <!-- Emergency alert message copied to clipboard. -->
     <string name="message_copied">Message copied</string>
 
+    <!-- The top introduction to show by default. -->
+    <string name="top_intro_default_text"></string>
+    <!-- The top introduction to show for roaming network support. -->
+    <string name="top_intro_roaming_text">While you\'re roaming, you may get some alerts that aren\'t included in these settings</string>
+
     <!-- Notification title and text when alerting user that their CB settings have changed -->
     <string name="notification_cb_settings_changed_title">Settings changed by carrier</string>
     <string name="notification_cb_settings_changed_text">Tap to see wireless emergency alert settings</string>
diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml
index 327e902..fc0649e 100644
--- a/res/xml/preferences.xml
+++ b/res/xml/preferences.xml
@@ -25,6 +25,9 @@
                 android:icon="@drawable/ic_info_outline_24dp"
                 android:selectable="false" />
 
+    <com.android.settingslib.widget.TopIntroPreference
+                      android:key="alert_prefs_top_intro"/>
+
     <com.android.settingslib.widget.MainSwitchPreference android:defaultValue="@bool/master_toggle_enabled_default"
                       android:key="enable_alerts_master_toggle"
                       android:summary="@string/enable_alerts_master_toggle_summary"
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertDialog.java b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertDialog.java
index c57faa4..a479d7a 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertDialog.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertDialog.java
@@ -715,7 +715,9 @@
         Context context = getApplicationContext();
         int titleId = CellBroadcastResources.getDialogTitleResource(context, message);
 
-        Resources res = CellBroadcastSettings.getResources(context, message.getSubscriptionId());
+        Resources res = CellBroadcastSettings.getResourcesByOperator(context,
+                message.getSubscriptionId(),
+                CellBroadcastReceiver.getRoamingOperatorSupported(context));
 
         CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager(
                 this, message.getSubscriptionId());
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertService.java b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertService.java
index b5edce2..85e6293 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertService.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertService.java
@@ -57,6 +57,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
+import java.util.List;
 import java.util.Locale;
 import java.util.Set;
 
@@ -206,8 +207,8 @@
         CellBroadcastChannelManager channelManager =
                 new CellBroadcastChannelManager(mContext, message.getSubscriptionId());
         // check the full-screen message settings to hide or show message to users.
-        if (channelManager.checkCellBroadcastChannelRange(message.getServiceCategory(),
-                R.array.public_safety_messages_channels_range_strings)) {
+        if (channelManager.getCellBroadcastChannelResourcesKey(message.getServiceCategory())
+                == R.array.public_safety_messages_channels_range_strings) {
             return PreferenceManager.getDefaultSharedPreferences(this)
                     .getBoolean(CellBroadcastSettings.KEY_ENABLE_PUBLIC_SAFETY_MESSAGES_FULL_SCREEN,
                             true);
@@ -455,35 +456,36 @@
      * @return true if the channel is enabled on the device, otherwise false.
      */
     private boolean isChannelEnabled(SmsCbMessage message) {
-        CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager(mContext,
-                message.getSubscriptionId());
+        int subId = message.getSubscriptionId();
+        CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager(
+                mContext, subId);
         CellBroadcastChannelRange chanelrange = channelManager
                 .getCellBroadcastChannelRangeFromMessage(message);
-        Resources res = CellBroadcastSettings.getResources(mContext, message.getSubscriptionId());
+        Resources res = CellBroadcastSettings.getResourcesByOperator(mContext, subId,
+                CellBroadcastReceiver.getRoamingOperatorSupported(this));
         if (chanelrange != null && chanelrange.mAlwaysOn) {
             Log.d(TAG, "channel is enabled due to always-on, ignoring preference check");
             return true;
         }
 
-        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
         // Check if all emergency alerts are disabled.
-        boolean emergencyAlertEnabled =
-                prefs.getBoolean(CellBroadcastSettings.KEY_ENABLE_ALERTS_MASTER_TOGGLE, true);
+        boolean emergencyAlertEnabled = checkAlertConfigEnabled(
+                subId, CellBroadcastSettings.KEY_ENABLE_ALERTS_MASTER_TOGGLE, true);
         int channel = message.getServiceCategory();
+        int resourcesKey = channelManager.getCellBroadcastChannelResourcesKey(channel);
+        CellBroadcastChannelRange range = channelManager.getCellBroadcastChannelRange(channel);
 
         SmsCbEtwsInfo etwsInfo = message.getEtwsWarningInfo();
         if ((etwsInfo != null && etwsInfo.getWarningType()
                 == SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE)
-                || channelManager.checkCellBroadcastChannelRange(channel,
-                R.array.etws_test_alerts_range_strings)) {
+                || resourcesKey == R.array.etws_test_alerts_range_strings) {
             return emergencyAlertEnabled
                     && CellBroadcastSettings.isTestAlertsToggleVisible(getApplicationContext())
-                    && PreferenceManager.getDefaultSharedPreferences(this)
-                    .getBoolean(CellBroadcastSettings.KEY_ENABLE_TEST_ALERTS, false);
+                    && checkAlertConfigEnabled(subId,
+                            CellBroadcastSettings.KEY_ENABLE_TEST_ALERTS, false);
         }
 
-        if (message.isEtwsMessage() || channelManager.checkCellBroadcastChannelRange(channel,
-                R.array.etws_alerts_range_strings)) {
+        if (message.isEtwsMessage() || resourcesKey == R.array.etws_alerts_range_strings) {
             // ETWS messages.
             // Turn on/off emergency notifications is the only way to turn on/off ETWS messages.
             return emergencyAlertEnabled;
@@ -492,109 +494,79 @@
         // Check if the messages are on additional channels enabled by the resource config.
         // If those channels are enabled by the carrier, but the device is actually roaming, we
         // should not allow the messages.
-        ArrayList<CellBroadcastChannelRange> ranges = channelManager.getCellBroadcastChannelRanges(
-                R.array.additional_cbs_channels_strings);
-
-        for (CellBroadcastChannelRange range : ranges) {
-            if (range.mStartId <= channel && range.mEndId >= channel) {
-                // Check if the channel is within the scope. If not, ignore the alert message.
-                if (!channelManager.checkScope(range.mScope)) {
-                    Log.d(TAG, "The range [" + range.mStartId + "-" + range.mEndId
-                            + "] is not within the scope. mScope = " + range.mScope);
-                    return false;
-                }
-
-                if (range.mAlertType == AlertType.TEST) {
-                    return emergencyAlertEnabled
-                            && CellBroadcastSettings.isTestAlertsToggleVisible(
-                                    getApplicationContext())
-                            && PreferenceManager.getDefaultSharedPreferences(this)
-                            .getBoolean(CellBroadcastSettings.KEY_ENABLE_TEST_ALERTS,
-                                    false);
-                }
-                if (range.mAlertType == AlertType.AREA) {
-                    return emergencyAlertEnabled && PreferenceManager
-                            .getDefaultSharedPreferences(this)
-                            .getBoolean(CellBroadcastSettings.KEY_ENABLE_AREA_UPDATE_INFO_ALERTS,
-                                    false);
-                }
-
-                return emergencyAlertEnabled;
+        if (resourcesKey == R.array.additional_cbs_channels_strings) {
+            // Check if the channel is within the scope. If not, ignore the alert message.
+            if (!channelManager.checkScope(range.mScope)) {
+                Log.d(TAG, "The range [" + range.mStartId + "-" + range.mEndId
+                        + "] is not within the scope. mScope = " + range.mScope);
+                return false;
             }
+
+            if (range.mAlertType == AlertType.TEST) {
+                return emergencyAlertEnabled
+                        && CellBroadcastSettings.isTestAlertsToggleVisible(getApplicationContext())
+                        && checkAlertConfigEnabled(subId,
+                                CellBroadcastSettings.KEY_ENABLE_TEST_ALERTS, false);
+            }
+            if (range.mAlertType == AlertType.AREA) {
+                return emergencyAlertEnabled && checkAlertConfigEnabled(subId,
+                        CellBroadcastSettings.KEY_ENABLE_AREA_UPDATE_INFO_ALERTS, false);
+            }
+
+            return emergencyAlertEnabled;
         }
 
-        if (channelManager.checkCellBroadcastChannelRange(channel,
-                R.array.emergency_alerts_channels_range_strings)) {
-            return emergencyAlertEnabled
-                    && PreferenceManager.getDefaultSharedPreferences(this).getBoolean(
-                            CellBroadcastSettings.KEY_ENABLE_EMERGENCY_ALERTS, true);
+        if (resourcesKey == R.array.emergency_alerts_channels_range_strings) {
+            return emergencyAlertEnabled && checkAlertConfigEnabled(
+                    subId, CellBroadcastSettings.KEY_ENABLE_EMERGENCY_ALERTS, true);
         }
         // CMAS warning types
-        if (channelManager.checkCellBroadcastChannelRange(channel,
-                R.array.cmas_presidential_alerts_channels_range_strings)) {
+        if (resourcesKey == R.array.cmas_presidential_alerts_channels_range_strings) {
             // always enabled
             return true;
         }
-        if (channelManager.checkCellBroadcastChannelRange(channel,
-                R.array.cmas_alert_extreme_channels_range_strings)) {
-            return emergencyAlertEnabled
-                    && PreferenceManager.getDefaultSharedPreferences(this).getBoolean(
-                            CellBroadcastSettings.KEY_ENABLE_CMAS_EXTREME_THREAT_ALERTS, true);
+        if (resourcesKey == R.array.cmas_alert_extreme_channels_range_strings) {
+            return emergencyAlertEnabled && checkAlertConfigEnabled(
+                    subId, CellBroadcastSettings.KEY_ENABLE_CMAS_EXTREME_THREAT_ALERTS, true);
         }
-        if (channelManager.checkCellBroadcastChannelRange(channel,
-                R.array.cmas_alerts_severe_range_strings)) {
-            return emergencyAlertEnabled
-                    && PreferenceManager.getDefaultSharedPreferences(this).getBoolean(
-                            CellBroadcastSettings.KEY_ENABLE_CMAS_SEVERE_THREAT_ALERTS, true);
+        if (resourcesKey == R.array.cmas_alerts_severe_range_strings) {
+            return emergencyAlertEnabled && checkAlertConfigEnabled(
+                    subId, CellBroadcastSettings.KEY_ENABLE_CMAS_SEVERE_THREAT_ALERTS, true);
         }
-        if (channelManager.checkCellBroadcastChannelRange(channel,
-                R.array.cmas_amber_alerts_channels_range_strings)) {
-            return emergencyAlertEnabled
-                    && PreferenceManager.getDefaultSharedPreferences(this)
-                            .getBoolean(CellBroadcastSettings.KEY_ENABLE_CMAS_AMBER_ALERTS, true);
+        if (resourcesKey == R.array.cmas_amber_alerts_channels_range_strings) {
+            return emergencyAlertEnabled && checkAlertConfigEnabled(
+                    subId, CellBroadcastSettings.KEY_ENABLE_CMAS_AMBER_ALERTS, true);
         }
 
-        if (channelManager.checkCellBroadcastChannelRange(
-                channel, R.array.exercise_alert_range_strings) &&
-                res.getBoolean(R.bool.show_separate_exercise_settings)) {
-            return emergencyAlertEnabled && PreferenceManager.getDefaultSharedPreferences(this)
-                    .getBoolean(CellBroadcastSettings.KEY_ENABLE_EXERCISE_ALERTS, false);
+        if (resourcesKey == R.array.exercise_alert_range_strings
+                && res.getBoolean(R.bool.show_separate_exercise_settings)) {
+            return emergencyAlertEnabled && checkAlertConfigEnabled(
+                    subId, CellBroadcastSettings.KEY_ENABLE_EXERCISE_ALERTS, false);
         }
 
-        if (channelManager.checkCellBroadcastChannelRange(
-                channel, R.array.operator_defined_alert_range_strings) &&
-                res.getBoolean(R.bool.show_separate_operator_defined_settings)) {
-            return emergencyAlertEnabled && PreferenceManager.getDefaultSharedPreferences(this)
-                    .getBoolean(CellBroadcastSettings.KEY_OPERATOR_DEFINED_ALERTS, false);
+        if (resourcesKey == R.array.operator_defined_alert_range_strings
+                && res.getBoolean(R.bool.show_separate_operator_defined_settings)) {
+            return emergencyAlertEnabled && checkAlertConfigEnabled(
+                    subId, CellBroadcastSettings.KEY_OPERATOR_DEFINED_ALERTS, false);
         }
 
-        if (channelManager.checkCellBroadcastChannelRange(channel,
-                R.array.required_monthly_test_range_strings)
-                || channelManager.checkCellBroadcastChannelRange(channel,
-                R.array.exercise_alert_range_strings)
-                || channelManager.checkCellBroadcastChannelRange(channel,
-                R.array.operator_defined_alert_range_strings)) {
+        if (resourcesKey == R.array.required_monthly_test_range_strings
+                || resourcesKey == R.array.exercise_alert_range_strings
+                || resourcesKey == R.array.operator_defined_alert_range_strings) {
             return emergencyAlertEnabled
                     && CellBroadcastSettings.isTestAlertsToggleVisible(getApplicationContext())
-                    && PreferenceManager.getDefaultSharedPreferences(this)
-                            .getBoolean(CellBroadcastSettings.KEY_ENABLE_TEST_ALERTS,
-                                    false);
+                    && checkAlertConfigEnabled(
+                            subId, CellBroadcastSettings.KEY_ENABLE_TEST_ALERTS, false);
         }
 
-        if (channelManager.checkCellBroadcastChannelRange(channel,
-                R.array.public_safety_messages_channels_range_strings)) {
-            return emergencyAlertEnabled
-                    && PreferenceManager.getDefaultSharedPreferences(this)
-                    .getBoolean(CellBroadcastSettings.KEY_ENABLE_PUBLIC_SAFETY_MESSAGES,
-                            true);
+        if (resourcesKey == R.array.public_safety_messages_channels_range_strings) {
+            return emergencyAlertEnabled && checkAlertConfigEnabled(
+                    subId, CellBroadcastSettings.KEY_ENABLE_PUBLIC_SAFETY_MESSAGES, true);
         }
 
-        if (channelManager.checkCellBroadcastChannelRange(channel,
-                R.array.state_local_test_alert_range_strings)) {
-            return emergencyAlertEnabled
-                    && PreferenceManager.getDefaultSharedPreferences(this)
-                    .getBoolean(CellBroadcastSettings.KEY_ENABLE_STATE_LOCAL_TEST_ALERTS,
-                            false);
+        if (resourcesKey == R.array.state_local_test_alert_range_strings) {
+            return emergencyAlertEnabled && checkAlertConfigEnabled(
+                    subId, CellBroadcastSettings.KEY_ENABLE_STATE_LOCAL_TEST_ALERTS, false);
         }
 
         Log.e(TAG, "received undefined channels: " + channel);
@@ -648,7 +620,7 @@
             }
         } else {
             int channel = message.getServiceCategory();
-            ArrayList<CellBroadcastChannelRange> ranges = channelManager
+            List<CellBroadcastChannelRange> ranges = channelManager
                     .getAllCellBroadcastChannelRanges();
             for (CellBroadcastChannelRange range : ranges) {
                 if (channel >= range.mStartId && channel <= range.mEndId) {
@@ -996,4 +968,23 @@
         }
         return false;
     }
+
+    private boolean checkAlertConfigEnabled(int subId, String key, boolean defaultValue) {
+        boolean result = defaultValue;
+        String roamingOperator = CellBroadcastReceiver.getRoamingOperatorSupported(this);
+        // For roaming supported case
+        if (!roamingOperator.isEmpty()) {
+            int resId = CellBroadcastSettings.getResourcesIdForDefaultPrefValue(key);
+            if (resId != 0) {
+                result = CellBroadcastSettings.getResourcesByOperator(
+                        mContext, subId, roamingOperator).getBoolean(resId);
+                // For roaming support case, the channel can be enabled by the default config
+                // for the network even it is disabled by the preference
+                if (result) {
+                    return true;
+                }
+            }
+        }
+        return PreferenceManager.getDefaultSharedPreferences(this).getBoolean(key, defaultValue);
+    }
 }
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastChannelManager.java b/src/com/android/cellbroadcastreceiver/CellBroadcastChannelManager.java
index a56cd0a..22ececa 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastChannelManager.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastChannelManager.java
@@ -18,15 +18,22 @@
 
 import static android.telephony.ServiceState.ROAMING_TYPE_NOT_ROAMING;
 
+import static com.android.cellbroadcastreceiver.CellBroadcastReceiver.VDBG;
+
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
+import android.content.res.Resources;
 import android.os.SystemProperties;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.ServiceState;
 import android.telephony.SmsCbMessage;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.Log;
+import android.util.Pair;
 
 import androidx.annotation.VisibleForTesting;
 
@@ -35,6 +42,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 
 /**
  * CellBroadcastChannelManager handles the additional cell broadcast channels that
@@ -54,6 +62,7 @@
 
     private static final String TAG = "CBChannelManager";
 
+    private static final int MAX_CACHE_SIZE = 3;
     private static List<Integer> sCellBroadcastRangeResourceKeys = new ArrayList<>(
             Arrays.asList(R.array.additional_cbs_channels_strings,
                     R.array.emergency_alerts_channels_range_strings,
@@ -70,13 +79,19 @@
                     R.array.state_local_test_alert_range_strings
             ));
 
-    private static ArrayList<CellBroadcastChannelRange> sAllCellBroadcastChannelRanges = null;
-    private static final Object channelRangesLock = new Object();
+    private static Map<Integer, Map<Integer, List<CellBroadcastChannelRange>>>
+            sAllCellBroadcastChannelRangesPerSub = new ArrayMap<>();
+    private static Map<String, Map<Integer, List<CellBroadcastChannelRange>>>
+            sAllCellBroadcastChannelRangesPerOperator = new ArrayMap<>();
+
+    private static final Object mChannelRangesLock = new Object();
 
     private final Context mContext;
 
     private final int mSubId;
 
+    private final String mOperator;
+
     private boolean mIsDebugBuild = false;
 
     /**
@@ -167,14 +182,14 @@
         // Display both ways dialog and notification
         public boolean mDisplayDialogWithNotification = false;
 
-        public CellBroadcastChannelRange(Context context, int subId, String channelRange) {
+        public CellBroadcastChannelRange(Context context, int subId,
+                Resources res, String channelRange) {
             mAlertType = AlertType.DEFAULT;
             mEmergencyLevel = LEVEL_UNKNOWN;
             mRanType = SmsCbMessage.MESSAGE_FORMAT_3GPP;
             mScope = SCOPE_UNKNOWN;
-            mVibrationPattern =
-                    CellBroadcastSettings.getResources(context, subId)
-                            .getIntArray(R.array.default_vibration_pattern);
+
+            mVibrationPattern = res.getIntArray(R.array.default_vibration_pattern);
             mFilterLanguage = false;
             // by default all received messages should be displayed.
             mDisplay = true;
@@ -293,8 +308,7 @@
 
             // If alert type is info, override vibration pattern
             if (!hasVibrationPattern && mAlertType.equals(AlertType.INFO)) {
-                mVibrationPattern = CellBroadcastSettings.getResources(context, subId)
-                        .getIntArray(R.array.default_notification_vibration_pattern);
+                mVibrationPattern = res.getIntArray(R.array.default_notification_vibration_pattern);
             }
 
             // Parse the channel range
@@ -331,14 +345,88 @@
      * @param subId Subscription index
      */
     public CellBroadcastChannelManager(Context context, int subId) {
-        this(context, subId, SystemProperties.getInt("ro.debuggable", 0) == 1);
+        this(context, subId, CellBroadcastReceiver.getRoamingOperatorSupported(context),
+                SystemProperties.getInt("ro.debuggable", 0) == 1);
+    }
+
+    public CellBroadcastChannelManager(Context context, int subId, @Nullable String operator) {
+        this(context, subId, operator, SystemProperties.getInt("ro.debuggable", 0) == 1);
     }
 
     @VisibleForTesting
-    public CellBroadcastChannelManager(Context context, int subId, boolean isDebugBuild) {
+    public CellBroadcastChannelManager(Context context, int subId,
+            String operator, boolean isDebugBuild) {
         mContext = context;
         mSubId = subId;
+        mOperator = operator;
         mIsDebugBuild = isDebugBuild;
+        initAsNeeded();
+    }
+
+    /**
+     * Parse channel ranges from resources, and initialize the cache as needed
+     */
+    private void initAsNeeded() {
+        if (!TextUtils.isEmpty(mOperator)) {
+            synchronized (mChannelRangesLock) {
+                if (!sAllCellBroadcastChannelRangesPerOperator.containsKey(mOperator)) {
+                    if (VDBG) {
+                        log("init for operator: " + mOperator);
+                    }
+                    if (sAllCellBroadcastChannelRangesPerOperator.size() == MAX_CACHE_SIZE) {
+                        sAllCellBroadcastChannelRangesPerOperator.clear();
+                    }
+                    sAllCellBroadcastChannelRangesPerOperator.put(mOperator,
+                            getChannelRangesMapFromResoures(CellBroadcastSettings
+                                    .getResourcesByOperator(mContext, mSubId, mOperator)));
+                }
+            }
+        }
+
+        synchronized (mChannelRangesLock) {
+            if (!sAllCellBroadcastChannelRangesPerSub.containsKey(mSubId)) {
+                if (sAllCellBroadcastChannelRangesPerSub.size() == MAX_CACHE_SIZE) {
+                    sAllCellBroadcastChannelRangesPerSub.clear();
+                }
+                if (VDBG) {
+                    log("init for sub: " + mSubId);
+                }
+                sAllCellBroadcastChannelRangesPerSub.put(mSubId,
+                        getChannelRangesMapFromResoures(CellBroadcastSettings
+                                .getResources(mContext, mSubId)));
+            }
+        }
+    }
+
+    private @NonNull Map<Integer, List<CellBroadcastChannelRange>> getChannelRangesMapFromResoures(
+            @NonNull Resources res) {
+        Map<Integer, List<CellBroadcastChannelRange>> map = new ArrayMap<>();
+
+        for (int key : sCellBroadcastRangeResourceKeys) {
+            String[] ranges = res.getStringArray(key);
+            if (ranges != null) {
+                List<CellBroadcastChannelRange> rangesList = new ArrayList<>();
+                for (String range : ranges) {
+                    try {
+                        if (VDBG) {
+                            log("parse channel range: " + range);
+                        }
+                        CellBroadcastChannelRange r =
+                                new CellBroadcastChannelRange(mContext, mSubId, res, range);
+                        // Bypass if the range is disabled
+                        if (r.mIsDebugBuildOnly && !mIsDebugBuild) {
+                            continue;
+                        }
+                        rangesList.add(r);
+                    } catch (Exception e) {
+                        loge("Failed to parse \"" + range + "\". e=" + e);
+                    }
+                }
+                map.put(key, rangesList);
+            }
+        }
+
+        return map;
     }
 
     /**
@@ -348,26 +436,23 @@
      *
      * @return The list of channel ranges enabled by the carriers.
      */
-    public @NonNull ArrayList<CellBroadcastChannelRange> getCellBroadcastChannelRanges(int key) {
-        ArrayList<CellBroadcastChannelRange> result = new ArrayList<>();
-        String[] ranges =
-                CellBroadcastSettings.getResources(mContext, mSubId).getStringArray(key);
-        if (ranges != null) {
-            for (String range : ranges) {
-                try {
-                    CellBroadcastChannelRange r =
-                            new CellBroadcastChannelRange(mContext, mSubId, range);
-                    // Bypass if the range is disabled
-                    if (r.mIsDebugBuildOnly && !mIsDebugBuild) {
-                        continue;
-                    }
-                    result.add(r);
-                } catch (Exception e) {
-                    loge("Failed to parse \"" + range + "\". e=" + e);
-                }
+    public @NonNull List<CellBroadcastChannelRange> getCellBroadcastChannelRanges(int key) {
+        List<CellBroadcastChannelRange> result = null;
+
+        synchronized (mChannelRangesLock) {
+            initAsNeeded();
+
+            // Check the config per network first if applicable
+            if (!TextUtils.isEmpty(mOperator)) {
+                result = sAllCellBroadcastChannelRangesPerOperator.get(mOperator).get(key);
+            }
+
+            if (result == null) {
+                result = sAllCellBroadcastChannelRangesPerSub.get(mSubId).get(key);
             }
         }
-        return result;
+
+        return result == null ? new ArrayList<>() : result;
     }
 
     /**
@@ -375,31 +460,28 @@
      *
      * @return all cell broadcast channels
      */
-    public @NonNull ArrayList<CellBroadcastChannelRange> getAllCellBroadcastChannelRanges() {
-        synchronized(channelRangesLock) {
-            if (sAllCellBroadcastChannelRanges != null) return sAllCellBroadcastChannelRanges;
-
-            Log.d(TAG, "Create new channel range list");
-            ArrayList<CellBroadcastChannelRange> result = new ArrayList<>();
-
-            for (int key : sCellBroadcastRangeResourceKeys) {
-                result.addAll(getCellBroadcastChannelRanges(key));
+    public @NonNull List<CellBroadcastChannelRange> getAllCellBroadcastChannelRanges() {
+        final List<CellBroadcastChannelRange> result = new ArrayList<>();
+        synchronized (mChannelRangesLock) {
+            if (!TextUtils.isEmpty(mOperator)
+                    && sAllCellBroadcastChannelRangesPerOperator.containsKey(mOperator)) {
+                sAllCellBroadcastChannelRangesPerOperator.get(mOperator).forEach(
+                        (k, v)->result.addAll(v));
             }
 
-            sAllCellBroadcastChannelRanges = result;
-            return result;
+            sAllCellBroadcastChannelRangesPerSub.get(mSubId).forEach((k, v)->result.addAll(v));
         }
+        return result;
     }
 
     /**
      * Clear broadcast channel range list
      */
     public static void clearAllCellBroadcastChannelRanges() {
-        synchronized(channelRangesLock) {
-            if (sAllCellBroadcastChannelRanges != null) {
-                Log.d(TAG, "Clear channel range list");
-                sAllCellBroadcastChannelRanges = null;
-            }
+        synchronized (mChannelRangesLock) {
+            Log.d(TAG, "Clear channel range list");
+            sAllCellBroadcastChannelRangesPerSub.clear();
+            sAllCellBroadcastChannelRangesPerOperator.clear();
         }
     }
 
@@ -411,15 +493,59 @@
      * return {@code FALSE} otherwise
      */
     public boolean checkCellBroadcastChannelRange(int channel, int key) {
-        ArrayList<CellBroadcastChannelRange> ranges = getCellBroadcastChannelRanges(key);
+        return getCellBroadcastChannelResourcesKey(channel) == key;
+    }
 
-        for (CellBroadcastChannelRange range : ranges) {
-            if (channel >= range.mStartId && channel <= range.mEndId) {
-                return checkScope(range.mScope);
+    /**
+     * Get the resources key for the channel
+     * @param channel Cell broadcast message channel
+     *
+     * @return 0 if the key is not found, otherwise the value of the resources key
+     */
+    public int getCellBroadcastChannelResourcesKey(int channel) {
+        Pair<Integer, CellBroadcastChannelRange> p = findChannelRange(channel);
+
+        return p != null ? p.first : 0;
+    }
+
+    /**
+     * Get the CellBroadcastChannelRange for the channel
+     * @param channel Cell broadcast message channel
+     *
+     * @return the CellBroadcastChannelRange for the channel, null if not found
+     */
+    public @Nullable CellBroadcastChannelRange getCellBroadcastChannelRange(int channel) {
+        Pair<Integer, CellBroadcastChannelRange> p = findChannelRange(channel);
+
+        return p != null ? p.second : null;
+    }
+
+    private @Nullable Pair<Integer, CellBroadcastChannelRange> findChannelRange(int channel) {
+        if (!TextUtils.isEmpty(mOperator)) {
+            Pair<Integer, CellBroadcastChannelRange> p = findChannelRange(
+                    sAllCellBroadcastChannelRangesPerOperator.get(mOperator), channel);
+            if (p != null) {
+                return p;
             }
         }
 
-        return false;
+        return findChannelRange(sAllCellBroadcastChannelRangesPerSub.get(mSubId), channel);
+    }
+
+    private @Nullable Pair<Integer, CellBroadcastChannelRange> findChannelRange(
+            Map<Integer, List<CellBroadcastChannelRange>> channelRangeMap, int channel) {
+        if (channelRangeMap != null) {
+            for (Map.Entry<Integer, List<CellBroadcastChannelRange>> entry
+                    : channelRangeMap.entrySet()) {
+                for (CellBroadcastChannelRange range : entry.getValue()) {
+                    if (channel >= range.mStartId && channel <= range.mEndId
+                            && checkScope(range.mScope)) {
+                        return new Pair<>(entry.getKey(), range);
+                    }
+                }
+            }
+        }
+        return null;
     }
 
     /**
@@ -474,24 +600,7 @@
                     + message.getSubscriptionId());
         }
 
-        int channel = message.getServiceCategory();
-        ArrayList<CellBroadcastChannelRange> ranges = null;
-
-        for (int key : sCellBroadcastRangeResourceKeys) {
-            if (checkCellBroadcastChannelRange(channel, key)) {
-                ranges = getCellBroadcastChannelRanges(key);
-                break;
-            }
-        }
-        if (ranges != null) {
-            for (CellBroadcastChannelRange range : ranges) {
-                if (range.mStartId <= message.getServiceCategory()
-                        && range.mEndId >= message.getServiceCategory()) {
-                    return range;
-                }
-            }
-        }
-        return null;
+        return getCellBroadcastChannelRange(message.getServiceCategory());
     }
 
     /**
@@ -511,25 +620,19 @@
         }
 
         int id = message.getServiceCategory();
+        CellBroadcastChannelRange range = getCellBroadcastChannelRange(id);
 
-        for (int key : sCellBroadcastRangeResourceKeys) {
-            ArrayList<CellBroadcastChannelRange> ranges =
-                    getCellBroadcastChannelRanges(key);
-            for (CellBroadcastChannelRange range : ranges) {
-                if (range.mStartId <= id && range.mEndId >= id) {
-                    switch (range.mEmergencyLevel) {
-                        case CellBroadcastChannelRange.LEVEL_EMERGENCY:
-                            Log.d(TAG, "isEmergencyMessage: true, message id = " + id);
-                            return true;
-                        case CellBroadcastChannelRange.LEVEL_NOT_EMERGENCY:
-                            Log.d(TAG, "isEmergencyMessage: false, message id = " + id);
-                            return false;
-                        case CellBroadcastChannelRange.LEVEL_UNKNOWN:
-                        default:
-                            break;
-                    }
+        if (range != null) {
+            switch (range.mEmergencyLevel) {
+                case CellBroadcastChannelRange.LEVEL_EMERGENCY:
+                    Log.d(TAG, "isEmergencyMessage: true, message id = " + id);
+                    return true;
+                case CellBroadcastChannelRange.LEVEL_NOT_EMERGENCY:
+                    Log.d(TAG, "isEmergencyMessage: false, message id = " + id);
+                    return false;
+                case CellBroadcastChannelRange.LEVEL_UNKNOWN:
+                default:
                     break;
-                }
             }
         }
 
@@ -541,6 +644,10 @@
         return message.isEmergencyMessage();
     }
 
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+
     private static void loge(String msg) {
         Log.e(TAG, msg);
     }
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastConfigService.java b/src/com/android/cellbroadcastreceiver/CellBroadcastConfigService.java
index 9b9b75a..d00eef8 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastConfigService.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastConfigService.java
@@ -30,6 +30,7 @@
 import android.telephony.SmsManager;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
+import android.text.TextUtils;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
@@ -81,10 +82,13 @@
                         for (int subId : subIds) {
                             log("Enable CellBroadcast on sub " + subId);
                             enableCellBroadcastChannels(subId);
+                            enableCellBroadcastRoamingChannelsAsNeeded(subId);
                         }
                     } else {
                         // For no sim scenario.
                         enableCellBroadcastChannels(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
+                        enableCellBroadcastRoamingChannelsAsNeeded(
+                                SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
                     }
                 }
             } catch (Exception ex) {
@@ -164,7 +168,7 @@
         resetCellBroadcastChannels(subId);
 
         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
-        Resources res = CellBroadcastSettings.getResources(this, subId);
+        Resources res = getResources(subId, null);
 
         // boolean for each user preference checkbox, true for checked, false for unchecked
         // Note: If enableAlertsMasterToggle is false, it disables ALL emergency broadcasts
@@ -215,14 +219,29 @@
         boolean enableEmergencyAlerts = enableAlertsMasterToggle && prefs.getBoolean(
                 CellBroadcastSettings.KEY_ENABLE_EMERGENCY_ALERTS, true);
 
-        boolean enableGeoFencingTriggerMessage = true;
+        setCellBroadcastChannelsEnabled(subId, null, enableAlertsMasterToggle, enableEtwsAlerts,
+                enablePresidential, enableCmasExtremeAlerts, enableCmasSevereAlerts,
+                enableCmasAmberAlerts, enableTestAlerts, enableExerciseAlerts,
+                enableOperatorDefined, enableAreaUpdateInfoAlerts,
+                enablePublicSafetyMessagesChannelAlerts, enableStateLocalTestAlerts,
+                enableEmergencyAlerts, true);
+    }
+
+    private void setCellBroadcastChannelsEnabled(int subId, @NonNull String operator,
+            boolean enableAlertsMasterToggle, boolean enableEtwsAlerts, boolean enablePresidential,
+            boolean enableCmasExtremeAlerts, boolean enableCmasSevereAlerts,
+            boolean enableCmasAmberAlerts, boolean enableTestAlerts, boolean enableExerciseAlerts,
+            boolean enableOperatorDefined, boolean enableAreaUpdateInfoAlerts,
+            boolean enablePublicSafetyMessagesChannelAlerts, boolean enableStateLocalTestAlerts,
+            boolean enableEmergencyAlerts, boolean enableGeoFencingTriggerMessage) {
 
         if (VDBG) {
+            log("setCellBroadcastChannelsEnabled for " + subId + ", operator: " + operator);
             log("enableAlertsMasterToggle = " + enableAlertsMasterToggle);
             log("enableEtwsAlerts = " + enableEtwsAlerts);
             log("enablePresidential = " + enablePresidential);
             log("enableCmasExtremeAlerts = " + enableCmasExtremeAlerts);
-            log("enableCmasSevereAlerts = " + enableCmasExtremeAlerts);
+            log("enableCmasSevereAlerts = " + enableCmasSevereAlerts);
             log("enableCmasAmberAlerts = " + enableCmasAmberAlerts);
             log("enableTestAlerts = " + enableTestAlerts);
             log("enableExerciseAlerts = " + enableExerciseAlerts);
@@ -235,80 +254,81 @@
             log("enableGeoFencingTriggerMessage = " + enableGeoFencingTriggerMessage);
         }
 
+        boolean isEnableOnly = !TextUtils.isEmpty(operator);
         CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager(
-                getApplicationContext(), subId);
+                getApplicationContext(), subId, operator);
 
         /** Enable CMAS series messages. */
 
         // Enable/Disable Presidential messages.
-        setCellBroadcastRange(subId, enablePresidential,
+        setCellBroadcastRange(subId, isEnableOnly, enablePresidential,
                 channelManager.getCellBroadcastChannelRanges(
                         R.array.cmas_presidential_alerts_channels_range_strings));
 
         // Enable/Disable CMAS extreme messages.
-        setCellBroadcastRange(subId, enableCmasExtremeAlerts,
+        setCellBroadcastRange(subId, isEnableOnly, enableCmasExtremeAlerts,
                 channelManager.getCellBroadcastChannelRanges(
                         R.array.cmas_alert_extreme_channels_range_strings));
 
         // Enable/Disable CMAS severe messages.
-        setCellBroadcastRange(subId, enableCmasSevereAlerts,
+        setCellBroadcastRange(subId, isEnableOnly, enableCmasSevereAlerts,
                 channelManager.getCellBroadcastChannelRanges(
                         R.array.cmas_alerts_severe_range_strings));
 
         // Enable/Disable CMAS amber alert messages.
-        setCellBroadcastRange(subId, enableCmasAmberAlerts,
+        setCellBroadcastRange(subId, isEnableOnly, enableCmasAmberAlerts,
                 channelManager.getCellBroadcastChannelRanges(
                         R.array.cmas_amber_alerts_channels_range_strings));
 
         // Enable/Disable test messages.
-        setCellBroadcastRange(subId, enableTestAlerts,
+        setCellBroadcastRange(subId, isEnableOnly, enableTestAlerts,
                 channelManager.getCellBroadcastChannelRanges(
                         R.array.required_monthly_test_range_strings));
 
         // Enable/Disable exercise test messages.
         // This could either controlled by main test toggle or separate exercise test toggle.
-        setCellBroadcastRange(subId, enableTestAlerts || enableExerciseAlerts,
+        setCellBroadcastRange(subId, isEnableOnly, enableTestAlerts || enableExerciseAlerts,
                 channelManager.getCellBroadcastChannelRanges(
                         R.array.exercise_alert_range_strings));
 
         // Enable/Disable operator defined test messages.
         // This could either controlled by main test toggle or separate operator defined test toggle
-        setCellBroadcastRange(subId, enableTestAlerts || enableOperatorDefined,
+        setCellBroadcastRange(subId, isEnableOnly, enableTestAlerts || enableOperatorDefined,
                 channelManager.getCellBroadcastChannelRanges(
                         R.array.operator_defined_alert_range_strings));
 
         // Enable/Disable GSM ETWS messages.
-        setCellBroadcastRange(subId, enableEtwsAlerts,
+        setCellBroadcastRange(subId, isEnableOnly, enableEtwsAlerts,
                 channelManager.getCellBroadcastChannelRanges(
                         R.array.etws_alerts_range_strings));
 
         // Enable/Disable GSM ETWS test messages.
-        setCellBroadcastRange(subId, enableTestAlerts,
+        setCellBroadcastRange(subId, isEnableOnly, enableTestAlerts,
                 channelManager.getCellBroadcastChannelRanges(
                         R.array.etws_test_alerts_range_strings));
 
         // Enable/Disable GSM public safety messages.
-        setCellBroadcastRange(subId, enablePublicSafetyMessagesChannelAlerts,
+        setCellBroadcastRange(subId, isEnableOnly, enablePublicSafetyMessagesChannelAlerts,
                 channelManager.getCellBroadcastChannelRanges(
                         R.array.public_safety_messages_channels_range_strings));
 
         // Enable/Disable GSM state/local test alerts.
-        setCellBroadcastRange(subId, enableStateLocalTestAlerts,
+        setCellBroadcastRange(subId, isEnableOnly, enableStateLocalTestAlerts,
                 channelManager.getCellBroadcastChannelRanges(
                         R.array.state_local_test_alert_range_strings));
 
         // Enable/Disable GSM geo-fencing trigger messages.
-        setCellBroadcastRange(subId, enableGeoFencingTriggerMessage,
+        setCellBroadcastRange(subId, isEnableOnly, enableGeoFencingTriggerMessage,
                 channelManager.getCellBroadcastChannelRanges(
                         R.array.geo_fencing_trigger_messages_range_strings));
 
         // Enable non-CMAS series messages.
-        setCellBroadcastRange(subId, enableEmergencyAlerts,
+        setCellBroadcastRange(subId, isEnableOnly, enableEmergencyAlerts,
                 channelManager.getCellBroadcastChannelRanges(
                         R.array.emergency_alerts_channels_range_strings));
 
         // Enable/Disable additional channels based on carrier specific requirement.
-        ArrayList<CellBroadcastChannelRange> ranges =
+        List<CellBroadcastChannelRange> ranges =
                 channelManager.getCellBroadcastChannelRanges(
                         R.array.additional_cbs_channels_strings);
 
@@ -324,17 +344,88 @@
                 default:
                     enableAlerts = enableAlertsMasterToggle;
             }
-            setCellBroadcastRange(subId, enableAlerts, new ArrayList<>(Arrays.asList(range)));
+            setCellBroadcastRange(subId, isEnableOnly, enableAlerts,
+                    new ArrayList<>(Arrays.asList(range)));
         }
     }
+
+    /**
+     * Enable cell broadcast messages channels. Messages can be only received on the
+     * enabled channels.
+     *
+     * @param subId Subscription index
+     */
+    @VisibleForTesting
+    public void enableCellBroadcastRoamingChannelsAsNeeded(int subId) {
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+            subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+        }
+
+        String roamingOperator = CellBroadcastReceiver.getRoamingOperatorSupported(this);
+        if (roamingOperator.isEmpty()) {
+            return;
+        }
+
+        log("enableCellBroadcastRoamingChannels for roaming network:" + roamingOperator);
+        Resources res = getResources(subId, roamingOperator);
+
+        // Get default config for roaming network as the settings are based on sim
+        boolean enablePresidential = true;
+
+        boolean enableAlertsMasterToggle = res.getBoolean(R.bool.master_toggle_enabled_default);
+
+        boolean enableEtwsAlerts = enableAlertsMasterToggle;
+
+        boolean enableCmasExtremeAlerts = enableAlertsMasterToggle && res.getBoolean(
+                R.bool.extreme_threat_alerts_enabled_default);
+
+        boolean enableCmasSevereAlerts = enableAlertsMasterToggle && res.getBoolean(
+                R.bool.severe_threat_alerts_enabled_default);
+
+        boolean enableCmasAmberAlerts = enableAlertsMasterToggle && res.getBoolean(
+                R.bool.amber_alerts_enabled_default);
+
+        boolean enableTestAlerts = enableAlertsMasterToggle && CellBroadcastSettings
+                .isTestAlertsToggleVisible(getApplicationContext(), roamingOperator)
+                && res.getBoolean(R.bool.test_alerts_enabled_default);
+
+        boolean enableExerciseAlerts = enableAlertsMasterToggle
+                && res.getBoolean(R.bool.show_separate_exercise_settings)
+                && res.getBoolean(R.bool.test_exercise_alerts_enabled_default);
+
+        boolean enableOperatorDefined = enableAlertsMasterToggle
+                && res.getBoolean(R.bool.show_separate_operator_defined_settings)
+                && res.getBoolean(R.bool.test_operator_defined_alerts_enabled_default);
+
+        boolean enableAreaUpdateInfoAlerts = res.getBoolean(
+                R.bool.config_showAreaUpdateInfoSettings)
+                && res.getBoolean(R.bool.area_update_info_alerts_enabled_default);
+
+        boolean enablePublicSafetyMessagesChannelAlerts = enableAlertsMasterToggle
+                && res.getBoolean(R.bool.public_safety_messages_enabled_default);
+        boolean enableStateLocalTestAlerts = enableAlertsMasterToggle
+                && res.getBoolean(R.bool.state_local_test_alerts_enabled_default);
+
+        boolean enableEmergencyAlerts = enableAlertsMasterToggle && res.getBoolean(
+                R.bool.emergency_alerts_enabled_default);
+
+        setCellBroadcastChannelsEnabled(subId, roamingOperator, enableAlertsMasterToggle,
+                enableEtwsAlerts, enablePresidential, enableCmasExtremeAlerts,
+                enableCmasSevereAlerts, enableCmasAmberAlerts, enableTestAlerts,
+                enableExerciseAlerts, enableOperatorDefined, enableAreaUpdateInfoAlerts,
+                enablePublicSafetyMessagesChannelAlerts, enableStateLocalTestAlerts,
+                enableEmergencyAlerts, true);
+    }
+
     /**
      * Enable/disable cell broadcast with messages id range
      * @param subId Subscription index
-     * @param enable True for enabling cell broadcast with id range, otherwise for disabling.
+     * @param isEnableOnly, True for enabling channel only for roaming network
+     * @param enable True for enabling cell broadcast with id range, otherwise for disabling
      * @param ranges Cell broadcast id ranges
      */
-    private void setCellBroadcastRange(int subId, boolean enable,
-                                       List<CellBroadcastChannelRange> ranges) {
+    private void setCellBroadcastRange(int subId, boolean isEnableOnly,
+            boolean enable, List<CellBroadcastChannelRange> ranges) {
         SmsManager manager;
         if (subId != SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
             manager = SmsManager.getSmsManagerForSubscriptionId(subId);
@@ -349,15 +440,39 @@
                             + ":" + range.mEndId);
                     enable = true;
                 }
+
                 if (enable) {
+                    if (VDBG) {
+                        log("enableCellBroadcastRange[" + range.mStartId + "-" + range.mEndId
+                                + "], type:" + range.mRanType);
+                    }
                     manager.enableCellBroadcastRange(range.mStartId, range.mEndId, range.mRanType);
-                } else {
+                } else if (!isEnableOnly) {
+                    if (VDBG) {
+                        log("disableCellBroadcastRange[" + range.mStartId + "-" + range.mEndId
+                                + "], type:" + range.mRanType);
+                    }
                     manager.disableCellBroadcastRange(range.mStartId, range.mEndId, range.mRanType);
                 }
             }
         }
     }
 
+
+    /**
+     * Get resource according to the operator or subId
+     * @param subId Subscription index
+     * @param operator Operator numeric, the resource will be retrieved by it if it is no null,
+     * otherwise, by the sub id.
+     */
+    @VisibleForTesting
+    public Resources getResources(int subId, String operator) {
+        if (operator == null) {
+            return CellBroadcastSettings.getResources(this, subId);
+        }
+        return CellBroadcastSettings.getResourcesByOperator(this, subId, operator);
+    }
+
     private static void log(String msg) {
         Log.d(TAG, msg);
     }
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastReceiver.java b/src/com/android/cellbroadcastreceiver/CellBroadcastReceiver.java
index 0a4cf07..ae45c0e 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastReceiver.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastReceiver.java
@@ -32,7 +32,6 @@
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.UserManager;
-import androidx.preference.PreferenceManager;
 import android.provider.Telephony;
 import android.provider.Telephony.CellBroadcasts;
 import android.telephony.CarrierConfigManager;
@@ -46,12 +45,13 @@
 import android.widget.Toast;
 
 import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import androidx.preference.PreferenceManager;
 
 import com.android.cellbroadcastservice.CellBroadcastStatsLog;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
-
+import java.util.Arrays;
 
 public class CellBroadcastReceiver extends BroadcastReceiver {
     private static final String TAG = "CellBroadcastReceiver";
@@ -69,6 +69,9 @@
     // Key to access the shared preference of service state.
     private static final String SERVICE_STATE = "service_state";
 
+    // Key to access the shared preference of roaming operator.
+    private static final String ROAMING_OPERATOR_SUPPORTED = "roaming_operator_supported";
+
     // shared preference under developer settings
     private static final String ENABLE_ALERT_MASTER_PREF = "enable_alerts_master_toggle";
 
@@ -94,6 +97,11 @@
     public static final String ACTION_TESTING_MODE_CHANGED =
             "com.android.cellbroadcastreceiver.intent.ACTION_TESTING_MODE_CHANGED";
 
+    // System property to set roaming network config which can be multiple items split by
+    // comma, and matched in sequence. This config will insert before the overlay.
+    private static final String ROAMING_PLMN_SUPPORTED_PROPERTY_KEY =
+            "persist.cellbroadcast.roaming_plmn_supported";
+
     private Context mContext;
 
     /**
@@ -146,11 +154,7 @@
             // configurations once moving back from APM. This should be fixed in lower layer
             // going forward.
             int ss = intent.getIntExtra(EXTRA_VOICE_REG_STATE, ServiceState.STATE_IN_SERVICE);
-            if (ss != ServiceState.STATE_POWER_OFF
-                    && getServiceState(context) == ServiceState.STATE_POWER_OFF) {
-                startConfigServiceToEnableChannels();
-            }
-            setServiceState(ss);
+            onServiceStateChanged(context, res, ss);
         } else if (CELLBROADCAST_START_CONFIG_ACTION.equals(action)
                 || SubscriptionManager.ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED.equals(action)) {
             startConfigServiceToEnableChannels();
@@ -202,6 +206,61 @@
         }
     }
 
+    private void onServiceStateChanged(Context context, Resources res, int ss) {
+        logd("onServiceStateChanged, ss: " + ss);
+        // check whether to support roaming network
+        String roamingOperator = null;
+        if (ss == ServiceState.STATE_IN_SERVICE || ss == ServiceState.STATE_EMERGENCY_ONLY) {
+            TelephonyManager tm = context.getSystemService(TelephonyManager.class);
+            String networkOperator = tm.getNetworkOperator();
+            logd("networkOperator: " + networkOperator);
+
+            // check roaming config only if the network oprator is not empty as the config
+            // is based on operator numeric
+            if (!networkOperator.isEmpty()) {
+                // No roaming supported by default
+                roamingOperator = "";
+                if ((tm.isNetworkRoaming() || ss == ServiceState.STATE_EMERGENCY_ONLY)
+                        && !networkOperator.equals(tm.getSimOperator())) {
+                    String propRoamingPlmn = SystemProperties.get(
+                            ROAMING_PLMN_SUPPORTED_PROPERTY_KEY, "").trim();
+                    String[] roamingNetworks = propRoamingPlmn.isEmpty() ? res.getStringArray(
+                            R.array.cmas_roaming_network_strings) : propRoamingPlmn.split(",");
+                    logd("roamingNetworks: " + Arrays.toString(roamingNetworks));
+
+                    for (String r : roamingNetworks) {
+                        r = r.trim();
+                        if (r.equals("XXXXXX")) {
+                            //match any roaming network, store mcc+mnc
+                            roamingOperator = networkOperator;
+                            break;
+                        } else if (r.equals("XXX")) {
+                            //match any roaming network, only store mcc
+                            roamingOperator = networkOperator.substring(0, 3);
+                            break;
+                        }  else if (networkOperator.startsWith(r)) {
+                            roamingOperator = r;
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+
+        if ((ss != ServiceState.STATE_POWER_OFF
+                && getServiceState(context) == ServiceState.STATE_POWER_OFF)
+                || (roamingOperator != null && !roamingOperator.equals(
+                getRoamingOperatorSupported(context)))) {
+            startConfigServiceToEnableChannels();
+        }
+        setServiceState(ss);
+
+        if (roamingOperator != null) {
+            log("update supported roaming operator as " + roamingOperator);
+            setRoamingOperatorSupported(roamingOperator);
+        }
+    }
+
     /**
      * Send an intent to reset the users WEA settings if there is a new carrier on the default subId
      *
@@ -276,7 +335,7 @@
                 .apply();
     }
 
-        /**
+    /**
      * Enable/disable cell broadcast receiver testing mode.
      *
      * @param on {@code true} if testing mode is on, otherwise off.
@@ -307,6 +366,14 @@
     }
 
     /**
+     * Store the roaming operator
+     */
+    private void setRoamingOperatorSupported(String roamingOperator) {
+        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
+        sp.edit().putString(ROAMING_OPERATOR_SUPPORTED, roamingOperator).commit();
+    }
+
+    /**
      * @return the stored voice registration service state
      */
     private static int getServiceState(Context context) {
@@ -315,6 +382,14 @@
     }
 
     /**
+     * @return the supported roaming operator
+     */
+    public static String getRoamingOperatorSupported(Context context) {
+        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
+        return sp.getString(ROAMING_OPERATOR_SUPPORTED, "");
+    }
+
+    /**
      * update reminder interval
      */
     @VisibleForTesting
@@ -624,6 +699,10 @@
         Log.d(TAG, msg);
     }
 
+    private static void logd(String msg) {
+        if (DBG) Log.d(TAG, msg);
+    }
+
     private static void loge(String msg) {
         Log.e(TAG, msg);
     }
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastResources.java b/src/com/android/cellbroadcastreceiver/CellBroadcastResources.java
index 35d418d..16bbb69 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastResources.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastResources.java
@@ -31,7 +31,6 @@
 import com.android.cellbroadcastreceiver.CellBroadcastChannelManager.CellBroadcastChannelRange;
 
 import java.text.DateFormat;
-import java.util.ArrayList;
 import java.util.Locale;
 
 /**
@@ -313,18 +312,15 @@
                 context, message.getSubscriptionId());
         final int serviceCategory = message.getServiceCategory();
         // store to different SMS threads based on channel mappings.
-        if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
-                R.array.cmas_presidential_alerts_channels_range_strings)) {
-            return R.string.sms_cb_sender_name_presidential;
+        switch (channelManager.getCellBroadcastChannelResourcesKey(serviceCategory)) {
+            case R.array.cmas_presidential_alerts_channels_range_strings:
+                return R.string.sms_cb_sender_name_presidential;
+            case R.array.emergency_alerts_channels_range_strings:
+                return R.string.sms_cb_sender_name_emergency;
+            case R.array.public_safety_messages_channels_range_strings:
+                return R.string.sms_cb_sender_name_public_safety;
         }
-        if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
-                R.array.emergency_alerts_channels_range_strings)) {
-            return R.string.sms_cb_sender_name_emergency;
-        }
-        if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
-                R.array.public_safety_messages_channels_range_strings)) {
-            return R.string.sms_cb_sender_name_public_safety;
-        }
+
         return R.string.sms_cb_sender_name_default;
     }
 
@@ -356,82 +352,60 @@
         CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager(
                 context, subId);
         final int serviceCategory = message.getServiceCategory();
-        if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
-                R.array.emergency_alerts_channels_range_strings)) {
-            return R.string.pws_other_message_identifiers;
-        }
-        // CMAS warning types
-        if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
-                R.array.cmas_presidential_alerts_channels_range_strings)) {
-            return R.string.cmas_presidential_level_alert;
-        }
-        if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
-                R.array.cmas_alert_extreme_channels_range_strings)) {
-            if (message.isCmasMessage()) {
-                if (cmasInfo.getSeverity() == SmsCbCmasInfo.CMAS_SEVERITY_EXTREME
-                        && cmasInfo.getUrgency() == SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE) {
-                    if (cmasInfo.getCertainty() == SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED) {
-                        return R.string.cmas_extreme_immediate_observed_alert;
-                    } else if (cmasInfo.getCertainty() == SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY) {
-                        return R.string.cmas_extreme_immediate_likely_alert;
-                    }
-                }
-            }
-            return R.string.cmas_extreme_alert;
-        }
-        if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
-                R.array.cmas_alerts_severe_range_strings)) {
-            return R.string.cmas_severe_alert;
-        }
-        if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
-                R.array.cmas_amber_alerts_channels_range_strings)) {
-            return R.string.cmas_amber_alert;
-        }
-        if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
-                R.array.required_monthly_test_range_strings)) {
-            return R.string.cmas_required_monthly_test;
-        }
-        if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
-                R.array.exercise_alert_range_strings)) {
-            return R.string.cmas_exercise_alert;
-        }
-        if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
-                R.array.operator_defined_alert_range_strings)) {
-            return R.string.cmas_operator_defined_alert;
-        }
-        if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
-                R.array.public_safety_messages_channels_range_strings)) {
-            return R.string.public_safety_message;
-        }
-        if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
-                R.array.state_local_test_alert_range_strings)) {
-            return R.string.state_local_test_alert;
-        }
+        int resourcesKey = channelManager.getCellBroadcastChannelResourcesKey(serviceCategory);
+        CellBroadcastChannelRange range = channelManager
+                .getCellBroadcastChannelRange(serviceCategory);
 
-        if (channelManager.isEmergencyMessage(message)) {
-            ArrayList<CellBroadcastChannelRange> ranges =
-                    channelManager.getCellBroadcastChannelRanges(
-                            R.array.additional_cbs_channels_strings);
-            if (ranges != null) {
-                for (CellBroadcastChannelRange range : ranges) {
-                    if (serviceCategory >= range.mStartId && serviceCategory <= range.mEndId) {
-                        // Apply the closest title to the specified tones.
-                        switch (range.mAlertType) {
-                            case DEFAULT:
-                                return R.string.pws_other_message_identifiers;
-                            case ETWS_EARTHQUAKE:
-                                return R.string.etws_earthquake_warning;
-                            case ETWS_TSUNAMI:
-                                return R.string.etws_tsunami_warning;
-                            case TEST:
-                                return R.string.etws_test_message;
-                            case ETWS_DEFAULT:
-                            case OTHER:
-                                return R.string.etws_other_emergency_type;
+        switch (resourcesKey) {
+            case R.array.emergency_alerts_channels_range_strings:
+                return R.string.pws_other_message_identifiers;
+            case R.array.cmas_presidential_alerts_channels_range_strings:
+                return R.string.cmas_presidential_level_alert;
+            case R.array.cmas_alert_extreme_channels_range_strings:
+                if (message.isCmasMessage()) {
+                    if (cmasInfo.getSeverity() == SmsCbCmasInfo.CMAS_SEVERITY_EXTREME
+                            && cmasInfo.getUrgency() == SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE) {
+                        if (cmasInfo.getCertainty() == SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED) {
+                            return R.string.cmas_extreme_immediate_observed_alert;
+                        } else if (cmasInfo.getCertainty() == SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY) {
+                            return R.string.cmas_extreme_immediate_likely_alert;
                         }
                     }
                 }
+                return R.string.cmas_extreme_alert;
+            case R.array.cmas_alerts_severe_range_strings:
+                return R.string.cmas_severe_alert;
+            case R.array.cmas_amber_alerts_channels_range_strings:
+                return R.string.cmas_amber_alert;
+            case R.array.required_monthly_test_range_strings:
+                return R.string.cmas_required_monthly_test;
+            case R.array.exercise_alert_range_strings:
+                return R.string.cmas_exercise_alert;
+            case R.array.operator_defined_alert_range_strings:
+                return R.string.cmas_operator_defined_alert;
+            case R.array.public_safety_messages_channels_range_strings:
+                return R.string.public_safety_message;
+            case R.array.state_local_test_alert_range_strings:
+                return R.string.state_local_test_alert;
+        }
 
+        if (channelManager.isEmergencyMessage(message)) {
+            if (resourcesKey == R.array.additional_cbs_channels_strings) {
+                switch (range.mAlertType) {
+                    case DEFAULT:
+                        return R.string.pws_other_message_identifiers;
+                    case ETWS_EARTHQUAKE:
+                        return R.string.etws_earthquake_warning;
+                    case ETWS_TSUNAMI:
+                        return R.string.etws_tsunami_warning;
+                    case TEST:
+                        return R.string.etws_test_message;
+                    case ETWS_DEFAULT:
+                    case OTHER:
+                        return R.string.etws_other_emergency_type;
+                    default:
+                        break;
+                }
             }
             return R.string.pws_other_message_identifiers;
         } else {
@@ -464,18 +438,18 @@
         CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager(
                 context, subId);
         if (channelManager.isEmergencyMessage(message)) {
-            ArrayList<CellBroadcastChannelRange> ranges =
-                    channelManager.getCellBroadcastChannelRanges(
-                            R.array.additional_cbs_channels_strings);
-            for (CellBroadcastChannelRange range : ranges) {
-                if (serviceCategory >= range.mStartId && serviceCategory <= range.mEndId) {
-                    // Apply the closest title to the specified tones.
-                    switch (range.mAlertType) {
-                        case ETWS_EARTHQUAKE:
-                            return R.drawable.pict_icon_earthquake;
-                        case ETWS_TSUNAMI:
-                            return R.drawable.pict_icon_tsunami;
-                    }
+            if (channelManager.getCellBroadcastChannelResourcesKey(serviceCategory)
+                    == R.array.additional_cbs_channels_strings) {
+                CellBroadcastChannelRange range = channelManager
+                        .getCellBroadcastChannelRangeFromMessage(message);
+                // Apply the closest title to the specified tones.
+                switch (range.mAlertType) {
+                    case ETWS_EARTHQUAKE:
+                        return R.drawable.pict_icon_earthquake;
+                    case ETWS_TSUNAMI:
+                        return R.drawable.pict_icon_tsunami;
+                    default:
+                        break;
                 }
             }
             return -1;
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastSettings.java b/src/com/android/cellbroadcastreceiver/CellBroadcastSettings.java
index a5c9caa..a7b71a6 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastSettings.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastSettings.java
@@ -27,12 +27,11 @@
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.os.Bundle;
-import android.os.PersistableBundle;
 import android.os.UserManager;
 import android.os.Vibrator;
-import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.util.Log;
@@ -48,7 +47,6 @@
 import androidx.preference.PreferenceScreen;
 import androidx.preference.TwoStatePreference;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.modules.utils.build.SdkLevel;
 import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
 import com.android.settingslib.widget.MainSwitchPreference;
@@ -152,6 +150,9 @@
     // For watch layout
     private static final String KEY_WATCH_ALERT_REMINDER = "watch_alert_reminder";
 
+    // For top introduction info
+    private static final String KEY_PREFS_TOP_INTRO = "alert_prefs_top_intro";
+
     // Whether to receive alert in second language code
     public static final String KEY_RECEIVE_CMAS_IN_SECOND_LANGUAGE =
             "receive_cmas_in_second_language";
@@ -161,6 +162,9 @@
     // Resource cache
     private static final Map<Integer, Resources> sResourcesCache = new HashMap<>();
 
+    // Resource cache per operator
+    private static final Map<String, Resources> sResourcesCacheByOperator = new HashMap<>();
+
     // Intent sent from cellbroadcastreceiver to notify cellbroadcastservice that area info update
     // is disabled/enabled.
     private static final String AREA_INFO_UPDATE_ACTION =
@@ -326,6 +330,9 @@
         // on/off switch in settings for receiving alert in second language code
         private TwoStatePreference mReceiveCmasInSecondLanguageCheckBox;
 
+        // Show the top introduction
+        private Preference mTopIntroPreference;
+
         private final BroadcastReceiver mTestingModeChangedReceiver = new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
@@ -404,6 +411,7 @@
                 mAlertCategory = (PreferenceCategory)
                         findPreference(KEY_CATEGORY_EMERGENCY_ALERTS);
             }
+            mTopIntroPreference = findPreference(KEY_PREFS_TOP_INTRO);
         }
 
         @Override
@@ -576,8 +584,9 @@
         private void updatePreferenceVisibility() {
             Resources res = CellBroadcastSettings.getResourcesForDefaultSubId(getContext());
 
+            // The settings should be based on the config by the subscription
             CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager(
-                    getContext(), SubscriptionManager.getDefaultSubscriptionId());
+                    getContext(), SubscriptionManager.getDefaultSubscriptionId(), null);
 
             if (mMasterToggle != null) {
                 mMasterToggle.setVisible(res.getBoolean(R.bool.show_main_switch_settings));
@@ -701,6 +710,18 @@
                         || getActivity().getPackageManager()
                         .hasSystemFeature(PackageManager.FEATURE_WATCH));
             }
+
+            if (mTopIntroPreference != null) {
+                mTopIntroPreference.setTitle(getTopIntroduction());
+            }
+        }
+
+        private int getTopIntroduction() {
+            // Only set specific top introduction for roaming support now
+            if (!CellBroadcastReceiver.getRoamingOperatorSupported(getContext()).isEmpty()) {
+                return R.string.top_intro_roaming_text;
+            }
+            return R.string.top_intro_default_text;
         }
 
         private void initReminderIntervalList() {
@@ -806,9 +827,20 @@
     }
 
     public static boolean isTestAlertsToggleVisible(Context context) {
+        return isTestAlertsToggleVisible(context, null);
+    }
+
+    /**
+     * Check whether test alert toggle is visible
+     * @param context Context
+     * @param operator Opeator numeric
+     */
+    public static boolean isTestAlertsToggleVisible(Context context, String operator) {
         CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager(context,
-                SubscriptionManager.getDefaultSubscriptionId());
-        Resources res = CellBroadcastSettings.getResourcesForDefaultSubId(context);
+                SubscriptionManager.getDefaultSubscriptionId(), operator);
+        Resources res = operator == null ? getResourcesForDefaultSubId(context)
+                : getResourcesByOperator(context,
+                        SubscriptionManager.getDefaultSubscriptionId(), operator);
         boolean isTestAlertsAvailable = !channelManager.getCellBroadcastChannelRanges(
                 R.array.required_monthly_test_range_strings).isEmpty()
                 || (!channelManager.getCellBroadcastChannelRanges(
@@ -870,4 +902,83 @@
     public static @NonNull Resources getResourcesForDefaultSubId(@NonNull Context context) {
         return getResources(context, SubscriptionManager.getDefaultSubscriptionId());
     }
+
+    /**
+     * Get the resources per network operator
+     * @param context Context
+     * @param operator Opeator numeric
+     * @return the Resources based on network operator
+     */
+    public static @NonNull Resources getResourcesByOperator(
+            @NonNull Context context, int subId, @NonNull String operator) {
+        if (operator == null || operator.isEmpty()) {
+            return getResources(context, subId);
+        }
+
+        Resources res = sResourcesCacheByOperator.get(operator);
+        if (res != null) {
+            return res;
+        }
+
+        Configuration overrideConfig = new Configuration();
+        try {
+            int mcc = Integer.parseInt(operator.substring(0, 3));
+            int mnc = operator.length() > 3 ? Integer.parseInt(operator.substring(3))
+                    : Configuration.MNC_ZERO;
+
+            overrideConfig.mcc = mcc;
+            overrideConfig.mnc = mnc;
+        } catch (NumberFormatException e) {
+            // should not happen
+            Log.e(TAG, "invalid operator: " + operator);
+            return context.getResources();
+        }
+
+        Context newContext = context.createConfigurationContext(overrideConfig);
+        res = newContext.getResources();
+
+        sResourcesCacheByOperator.put(operator, res);
+        return res;
+    }
+
+    /**
+     * Get the resources id which is used for the default value of the preference
+     * @param key the preference key
+     * @return a valid resources id if the key is valid and the default value is
+     * defined, otherwise 0
+     */
+    public static int getResourcesIdForDefaultPrefValue(String key) {
+        switch (key) {
+            case KEY_ENABLE_ALERTS_MASTER_TOGGLE:
+                return R.bool.master_toggle_enabled_default;
+            case KEY_ENABLE_PUBLIC_SAFETY_MESSAGES:
+                return R.bool.public_safety_messages_enabled_default;
+            case KEY_ENABLE_PUBLIC_SAFETY_MESSAGES_FULL_SCREEN:
+                return R.bool.public_safety_messages_full_screen_enabled_default;
+            case KEY_ENABLE_EMERGENCY_ALERTS:
+                return R.bool.emergency_alerts_enabled_default;
+            case KEY_ENABLE_ALERT_SPEECH:
+                return R.bool.enable_alert_speech_default;
+            case KEY_OVERRIDE_DND:
+                return R.bool.override_dnd_default;
+            case KEY_ENABLE_CMAS_EXTREME_THREAT_ALERTS:
+                return R.bool.extreme_threat_alerts_enabled_default;
+            case KEY_ENABLE_CMAS_SEVERE_THREAT_ALERTS:
+                return R.bool.severe_threat_alerts_enabled_default;
+            case KEY_ENABLE_CMAS_AMBER_ALERTS:
+                return R.bool.amber_alerts_enabled_default;
+            case KEY_ENABLE_TEST_ALERTS:
+                return R.bool.test_alerts_enabled_default;
+            case KEY_ENABLE_EXERCISE_ALERTS:
+                return R.bool.test_exercise_alerts_enabled_default;
+            case KEY_OPERATOR_DEFINED_ALERTS:
+                return R.bool.test_operator_defined_alerts_enabled_default;
+            case KEY_ENABLE_STATE_LOCAL_TEST_ALERTS:
+                return R.bool.state_local_test_alerts_enabled_default;
+            case KEY_ENABLE_AREA_UPDATE_INFO_ALERTS:
+                return R.bool.area_update_info_alerts_enabled_default;
+            default:
+                return 0;
+        }
+    }
 }
diff --git a/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastAlertServiceTest.java b/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastAlertServiceTest.java
index 00cd8c9..fb45b88 100644
--- a/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastAlertServiceTest.java
+++ b/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastAlertServiceTest.java
@@ -21,6 +21,7 @@
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 
@@ -80,6 +81,8 @@
     @Before
     public void setUp() throws Exception {
         super.setUp();
+        // No roaming supported by default
+        doReturn("").when(mMockedSharedPreferences).getString(anyString(), anyString());
     }
 
     @After
diff --git a/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastChannelManagerTest.java b/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastChannelManagerTest.java
index cfd58fa..f916dd0 100644
--- a/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastChannelManagerTest.java
+++ b/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastChannelManagerTest.java
@@ -20,7 +20,13 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
 
+import android.telephony.SmsCbCmasInfo;
+import android.telephony.SmsCbLocation;
+import android.telephony.SmsCbMessage;
 import android.telephony.SubscriptionManager;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -35,42 +41,84 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 
 /**
  * APN retry manager tests
  */
 public class CellBroadcastChannelManagerTest extends CellBroadcastTest {
 
+    private static final String[] CHANNEL_CONFIG1 = {
+        "12:type=etws_earthquake, emergency=true, display=false, always_on=true",
+        "456:type=etws_tsunami, emergency=true, alert_duration=60000, scope=domestic",
+        "0xAC00-0xAFED:type=other, emergency=false, override_dnd=true, scope=carrier",
+        "54-60:emergency=true, testing_mode=true, dialog_with_notification=true",
+        "100-200",
+        "0xA804:type=test, emergency=true, exclude_from_sms_inbox=true, vibration=0|350|250|350",
+        "0x111E:debug_build=true"};
+    private static final String[] CHANNEL_CONFIG2 = {
+        "12:type=etws_earthquake, emergency=true, display=true, always_on=false",
+        "456:type=etws_tsunami, emergency=true, alert_duration=20000, scope=domestic",
+        "0xAC00-0xAEFF:type=other, emergency=false, override_dnd=true, scope=carrier"};
+    private static final String[] CHANNEL_CONFIG3 = {
+        "0xA804:type=test, emergency=true, exclude_from_sms_inbox=true, vibration=0|350|250|350"
+    };
+
+    private static final String OPERATOR = "123456";
+    private static final int SUB_ID = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+
+    // For SUB1 without roaming support
+    private CellBroadcastChannelManager mChannelManager1;
+    // For SUB1 with roaming support of OPERATOR
+    private CellBroadcastChannelManager mChannelManager2;
+
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
+
+        doReturn(null).when(mTelephonyManager).getServiceState();
+        doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
+        doReturn(mContext).when(mContext).createConfigurationContext(any());
+
+        CellBroadcastChannelManager.clearAllCellBroadcastChannelRanges();
+        // Init mChannelManager1 for SUB1
+        putResources(com.android.cellbroadcastreceiver.R.array.additional_cbs_channels_strings,
+                CHANNEL_CONFIG1);
+        mChannelManager1 = new CellBroadcastChannelManager(mContext, SUB_ID, null, false);
+
+        // Init mChannelManager2 for SUB2 and OPERATOR
+        putResources(com.android.cellbroadcastreceiver.R.array.additional_cbs_channels_strings,
+                CHANNEL_CONFIG2);
+        putResources(
+                com.android.cellbroadcastreceiver.R.array.emergency_alerts_channels_range_strings,
+                CHANNEL_CONFIG3);
+        mChannelManager2 = new CellBroadcastChannelManager(mContext, SUB_ID, OPERATOR, false);
     }
 
     @After
     public void tearDown() throws Exception {
         super.tearDown();
+        CellBroadcastChannelManager.clearAllCellBroadcastChannelRanges();
     }
 
     @Test
     @SmallTest
     public void testGetCellBroadcastChannelRanges() throws Exception {
-        putResources(com.android.cellbroadcastreceiver.R.array.additional_cbs_channels_strings,
-                new String[]{
-                        "12:type=etws_earthquake, emergency=true, display=false, always_on=true",
-                        "456:type=etws_tsunami, emergency=true, alert_duration=60000, "
-                                + "scope=domestic", "0xAC00-0xAFED:type=other, emergency=false, "
-                        + "override_dnd=true, scope=carrier", "54-60:emergency=true, "
-                        + "testing_mode=true, " + "dialog_with_notification=true",
-                        "100-200", "0xA804:type=test, "
-                        + "emergency=true, exclude_from_sms_inbox=true, vibration=0|350|250|350",
-                        "0x111E:debug_build=true"});
-
-        CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager(mContext,
-                SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, false);
-
-        ArrayList<CellBroadcastChannelRange> list = channelManager.getCellBroadcastChannelRanges(
+        List<CellBroadcastChannelRange> list = mChannelManager1.getCellBroadcastChannelRanges(
                 com.android.cellbroadcastreceiver.R.array.additional_cbs_channels_strings);
 
+        verifyChannelRangesForConfig1(list);
+
+        list = mChannelManager2.getCellBroadcastChannelRanges(
+                com.android.cellbroadcastreceiver.R.array.additional_cbs_channels_strings);
+
+        verifyChannelRangesForConfig2(list);
+    }
+
+    private void verifyChannelRangesForConfig1(List<CellBroadcastChannelRange> list)
+            throws Exception {
+        assertEquals(6, list.size());
+
         assertEquals(12, list.get(0).mStartId);
         assertEquals(12, list.get(0).mEndId);
         assertEquals(AlertType.ETWS_EARTHQUAKE, list.get(0).mAlertType);
@@ -140,7 +188,124 @@
         assertTrue(Arrays.equals(new int[]{0, 350, 250, 350}, list.get(5).mVibrationPattern));
         assertNotEquals(list.get(4).toString(), list.get(5).toString());
         assertFalse(list.get(5).mDisplayDialogWithNotification);
+    }
 
-        assertEquals(6, list.size());
+    private void verifyChannelRangesForConfig2(List<CellBroadcastChannelRange> list)
+            throws Exception {
+        assertEquals(3, list.size());
+
+        assertEquals(12, list.get(0).mStartId);
+        assertEquals(12, list.get(0).mEndId);
+        assertEquals(AlertType.ETWS_EARTHQUAKE, list.get(0).mAlertType);
+        assertEquals(CellBroadcastChannelRange.LEVEL_EMERGENCY, list.get(0).mEmergencyLevel);
+        assertFalse(list.get(0).mAlwaysOn);
+        assertTrue(list.get(0).mDisplay);
+        assertFalse(list.get(0).mOverrideDnd);
+        assertTrue(list.get(0).mWriteToSmsInbox);
+        assertFalse(list.get(0).mTestMode);
+        assertFalse(list.get(0).mDisplayDialogWithNotification);
+
+        assertEquals(456, list.get(1).mStartId);
+        assertEquals(456, list.get(1).mEndId);
+        assertEquals(AlertType.ETWS_TSUNAMI, list.get(1).mAlertType);
+        assertEquals(CellBroadcastChannelRange.LEVEL_EMERGENCY, list.get(1).mEmergencyLevel);
+        assertFalse(list.get(1).mAlwaysOn);
+        assertTrue(list.get(1).mDisplay);
+        assertFalse(list.get(1).mOverrideDnd);
+        assertTrue(list.get(1).mWriteToSmsInbox);
+        assertFalse(list.get(1).mTestMode);
+        assertEquals(20000, list.get(1).mAlertDuration);
+        assertFalse(list.get(1).mDisplayDialogWithNotification);
+
+        assertEquals(0xAC00, list.get(2).mStartId);
+        assertEquals(0xAEFF, list.get(2).mEndId);
+        assertEquals(AlertType.OTHER, list.get(2).mAlertType);
+        assertEquals(CellBroadcastChannelRange.LEVEL_NOT_EMERGENCY, list.get(2).mEmergencyLevel);
+        assertFalse(list.get(2).mAlwaysOn);
+        assertTrue(list.get(2).mDisplay);
+        assertTrue(list.get(2).mOverrideDnd);
+        assertTrue(list.get(2).mWriteToSmsInbox);
+        assertFalse(list.get(2).mTestMode);
+        assertEquals(list.get(2).mScope, CellBroadcastChannelRange.SCOPE_CARRIER);
+        assertFalse(list.get(2).mDisplayDialogWithNotification);
+    }
+
+    @Test
+    @SmallTest
+    public void testGetCellBroadcastChannelResourcesKey() throws Exception {
+        assertEquals(mChannelManager1.getCellBroadcastChannelResourcesKey(0xA804),
+                com.android.cellbroadcastreceiver.R.array.additional_cbs_channels_strings);
+
+        assertEquals(mChannelManager2.getCellBroadcastChannelResourcesKey(0xA804),
+                com.android.cellbroadcastreceiver.R.array.emergency_alerts_channels_range_strings);
+        // It should hit the channel ranges for sub as no config for the operator
+        assertEquals(mChannelManager2.getCellBroadcastChannelResourcesKey(0xAFED),
+                com.android.cellbroadcastreceiver.R.array.additional_cbs_channels_strings);
+    }
+
+    @Test
+    @SmallTest
+    public void testGetCellBroadcastChannelRange() throws Exception {
+        CellBroadcastChannelRange channelRange = mChannelManager1
+                .getCellBroadcastChannelRange(0xAC00);
+
+        assertEquals(0xAC00, channelRange.mStartId);
+        assertEquals(0xAFED, channelRange.mEndId);
+
+        channelRange = mChannelManager2.getCellBroadcastChannelRange(0xAC00);
+
+        assertEquals(0xAC00, channelRange.mStartId);
+        assertEquals(0xAEFF, channelRange.mEndId);
+    }
+
+    @Test
+    @SmallTest
+    public void testGetAllCellBroadcastChannelRanges() throws Exception {
+        List<CellBroadcastChannelRange> ranges =
+                mChannelManager1.getAllCellBroadcastChannelRanges();
+
+        verifyChannelRangesForConfig1(ranges);
+
+        ranges = mChannelManager2.getAllCellBroadcastChannelRanges();
+
+        assertEquals(10, ranges.size());
+        verifyChannelRangesForConfig2(new ArrayList<>(ranges).subList(0, 3));
+        verifyChannelRangesForConfig1(new ArrayList<>(ranges).subList(4, 10));
+    }
+
+    @Test
+    @SmallTest
+    public void testGetCellBroadcastChannelRangeFromMessage() throws Exception {
+        SmsCbMessage msg = createMessageForCmasMessageClass(1, 0xAC00, 0);
+
+        CellBroadcastChannelRange range = mChannelManager1
+                .getCellBroadcastChannelRangeFromMessage(msg);
+
+        assertEquals(0xAC00, range.mStartId);
+        assertEquals(0xAFED, range.mEndId);
+
+        range = mChannelManager2.getCellBroadcastChannelRangeFromMessage(msg);
+
+        assertEquals(0xAC00, range.mStartId);
+        assertEquals(0xAEFF, range.mEndId);
+    }
+
+    @Test
+    @SmallTest
+    public void testIsEmergencyMessage() throws Exception {
+        assertFalse(mChannelManager1.isEmergencyMessage(null));
+
+        SmsCbMessage msg = createMessageForCmasMessageClass(1, 0xA804, 0);
+
+        assertTrue(mChannelManager1.isEmergencyMessage(msg));
+    }
+
+    private SmsCbMessage createMessageForCmasMessageClass(int serialNumber,
+            int serviceCategory, int cmasMessageClass) {
+        return new SmsCbMessage(1, 2, serialNumber, new SmsCbLocation(), serviceCategory,
+                "language", "body",
+                SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, null,
+                new SmsCbCmasInfo(cmasMessageClass, 2, 3, 4, 5, 6),
+                0, SUB_ID);
     }
 }
diff --git a/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastConfigServiceTest.java b/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastConfigServiceTest.java
index 72d802f..6d60b8c 100644
--- a/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastConfigServiceTest.java
+++ b/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastConfigServiceTest.java
@@ -17,18 +17,24 @@
 package com.android.cellbroadcastreceiver.unit;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.Intent;
 import android.content.SharedPreferences;
 import android.telephony.SmsCbMessage;
+import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -62,6 +68,15 @@
     @Mock
     SharedPreferences mMockedSharedPreferences;
 
+    @Mock
+    SubscriptionManager mMockSubscriptionManager;
+
+    @Mock
+    SubscriptionInfo mMockSubscriptionInfo;
+
+    @Mock
+    Intent mIntent;
+
     private CellBroadcastConfigService mConfigService;
 
     @Before
@@ -140,20 +155,20 @@
         TelephonyManager.enableServiceHandleCaching();
     }
 
-    private void setCellBroadcastRange(int subId, boolean enable,
-                                       List<CellBroadcastChannelRange> ranges)
-            throws Exception {
+    private void setCellBroadcastRange(int subId, boolean isEnableOnly,
+            boolean enable, List<CellBroadcastChannelRange> ranges) throws Exception {
 
-        Class[] cArgs = new Class[3];
+        Class[] cArgs = new Class[4];
         cArgs[0] = Integer.TYPE;
         cArgs[1] = Boolean.TYPE;
-        cArgs[2] = List.class;
+        cArgs[2] = Boolean.TYPE;
+        cArgs[3] = List.class;
 
         Method method =
                 CellBroadcastConfigService.class.getDeclaredMethod("setCellBroadcastRange", cArgs);
         method.setAccessible(true);
 
-        method.invoke(mConfigService, subId, enable, ranges);
+        method.invoke(mConfigService, subId, isEnableOnly, enable, ranges);
     }
 
     /**
@@ -164,8 +179,8 @@
     public void testEnableCellBroadcastRange() throws Exception {
         ArrayList<CellBroadcastChannelRange> result = new ArrayList<>();
         result.add(new CellBroadcastChannelRange(mContext,
-                SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, "10-20"));
-        setCellBroadcastRange(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, true, result);
+                SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, mResources, "10-20"));
+        setCellBroadcastRange(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, false, true, result);
         ArgumentCaptor<Integer> captorStart = ArgumentCaptor.forClass(Integer.class);
         ArgumentCaptor<Integer> captorEnd = ArgumentCaptor.forClass(Integer.class);
         ArgumentCaptor<Integer> captorType = ArgumentCaptor.forClass(Integer.class);
@@ -176,6 +191,14 @@
         assertEquals(10, captorStart.getValue().intValue());
         assertEquals(20, captorEnd.getValue().intValue());
         assertEquals(1, captorType.getValue().intValue());
+
+        setCellBroadcastRange(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, true, true, result);
+
+        verify(mMockedSmsService, times(2)).enableCellBroadcastRangeForSubscriber(anyInt(),
+                captorStart.capture(), captorEnd.capture(), captorType.capture());
+        assertEquals(10, captorStart.getValue().intValue());
+        assertEquals(20, captorEnd.getValue().intValue());
+        assertEquals(1, captorType.getValue().intValue());
     }
 
     /**
@@ -186,8 +209,8 @@
     public void testDisableCellBroadcastRange() throws Exception {
         ArrayList<CellBroadcastChannelRange> result = new ArrayList<>();
         result.add(new CellBroadcastChannelRange(mContext,
-                SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, "10-20"));
-        setCellBroadcastRange(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, false, result);
+                SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, mResources, "10-20"));
+        setCellBroadcastRange(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, false, false, result);
         ArgumentCaptor<Integer> captorStart = ArgumentCaptor.forClass(Integer.class);
         ArgumentCaptor<Integer> captorEnd = ArgumentCaptor.forClass(Integer.class);
         ArgumentCaptor<Integer> captorType = ArgumentCaptor.forClass(Integer.class);
@@ -198,6 +221,11 @@
         assertEquals(10, captorStart.getValue().intValue());
         assertEquals(20, captorEnd.getValue().intValue());
         assertEquals(1, captorType.getValue().intValue());
+
+        setCellBroadcastRange(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, true, false, result);
+
+        verify(mMockedSmsService, times(1)).disableCellBroadcastRangeForSubscriber(anyInt(),
+                captorStart.capture(), captorEnd.capture(), captorType.capture());
     }
 
     private void setPreference(String pref, boolean value) {
@@ -634,4 +662,171 @@
                 eq(SmsCbConstants.MESSAGE_ID_ETWS_OTHER_EMERGENCY_TYPE),
                 eq(SmsCbMessage.MESSAGE_FORMAT_3GPP));
     }
+
+    /**
+     * Test handling the intent to enable channels
+     */
+    @Test
+    @SmallTest
+    public void testOnHandleIntentActionEnableChannels() throws Exception {
+        List<SubscriptionInfo> sl = new ArrayList<>();
+        sl.add(mMockSubscriptionInfo);
+        doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID).when(
+                mMockSubscriptionInfo).getSubscriptionId();
+        doReturn(mContext).when(mConfigService).getApplicationContext();
+        doReturn(mMockSubscriptionManager).when(mContext).getSystemService(anyString());
+        doReturn(sl).when(mMockSubscriptionManager).getActiveSubscriptionInfoList();
+        doReturn(CellBroadcastConfigService.ACTION_ENABLE_CHANNELS).when(mIntent).getAction();
+        doNothing().when(mConfigService).enableCellBroadcastChannels(anyInt());
+        doNothing().when(mConfigService).enableCellBroadcastRoamingChannelsAsNeeded(anyInt());
+
+        Method method = CellBroadcastConfigService.class.getDeclaredMethod(
+                "onHandleIntent", new Class[]{Intent.class});
+        method.setAccessible(true);
+        method.invoke(mConfigService, mIntent);
+
+        verify(mConfigService, times(1)).enableCellBroadcastChannels(
+                eq(SubscriptionManager.INVALID_SUBSCRIPTION_ID));
+        verify(mConfigService, times(1)).enableCellBroadcastRoamingChannelsAsNeeded(
+                eq(SubscriptionManager.INVALID_SUBSCRIPTION_ID));
+    }
+
+    /**
+     * Test enabling cell broadcast roaming channels as needed
+     */
+    @Test
+    @SmallTest
+    public void testEnableCellBroadcastRoamingChannelsAsNeeded() throws Exception {
+        doReturn("").when(mMockedSharedPreferences).getString(anyString(), anyString());
+
+        mConfigService.enableCellBroadcastRoamingChannelsAsNeeded(
+                SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
+
+        //do nothing if operator is empty
+        verify(mConfigService, never()).getResources(anyInt(), anyString());
+
+        Context mockContext = mock(Context.class);
+        doReturn(mResources).when(mockContext).getResources();
+        doReturn(mockContext).when(mContext).createConfigurationContext(any());
+        doReturn("123").when(mMockedSharedPreferences).getString(anyString(), anyString());
+        doReturn(mResources).when(mConfigService).getResources(anyInt(), anyString());
+        putResources(com.android.cellbroadcastreceiver.R.bool.master_toggle_enabled_default, true);
+        putResources(com.android.cellbroadcastreceiver.R.bool
+                .extreme_threat_alerts_enabled_default, true);
+        putResources(com.android.cellbroadcastreceiver.R.bool
+                .severe_threat_alerts_enabled_default, true);
+        putResources(com.android.cellbroadcastreceiver.R.bool.amber_alerts_enabled_default, true);
+        putResources(com.android.cellbroadcastreceiver.R.bool.show_test_settings, true);
+        putResources(com.android.cellbroadcastreceiver.R.bool.test_alerts_enabled_default, true);
+        putResources(com.android.cellbroadcastreceiver.R.bool
+                .test_exercise_alerts_enabled_default, true);
+        putResources(com.android.cellbroadcastreceiver.R.bool
+                .test_operator_defined_alerts_enabled_default, true);
+        putResources(com.android.cellbroadcastreceiver.R.bool
+                .area_update_info_alerts_enabled_default, true);
+        putResources(com.android.cellbroadcastreceiver.R.bool
+                .public_safety_messages_enabled_default, true);
+        putResources(com.android.cellbroadcastreceiver.R.bool
+                .state_local_test_alerts_enabled_default, true);
+        putResources(com.android.cellbroadcastreceiver.R.bool
+                .emergency_alerts_enabled_default, true);
+
+        mConfigService.enableCellBroadcastRoamingChannelsAsNeeded(
+                SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
+
+        // should not disable channel
+        verify(mMockedSmsService, never()).disableCellBroadcastRangeForSubscriber(
+                anyInt(), anyInt(), anyInt(), anyInt());
+
+        verify(mMockedSmsService, times(1)).enableCellBroadcastRangeForSubscriber(
+                eq(0),
+                eq(SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT),
+                eq(SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT),
+                eq(SmsCbMessage.MESSAGE_FORMAT_3GPP2));
+
+        verify(mMockedSmsService, times(1)).enableCellBroadcastRangeForSubscriber(
+                eq(0),
+                eq(SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT),
+                eq(SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT),
+                eq(SmsCbMessage.MESSAGE_FORMAT_3GPP2));
+
+        verify(mMockedSmsService, times(1)).enableCellBroadcastRangeForSubscriber(
+                eq(0),
+                eq(SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT),
+                eq(SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT),
+                eq(SmsCbMessage.MESSAGE_FORMAT_3GPP2));
+
+        verify(mMockedSmsService, times(1)).enableCellBroadcastRangeForSubscriber(
+                eq(0),
+                eq(SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY),
+                eq(SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY),
+                eq(SmsCbMessage.MESSAGE_FORMAT_3GPP2));
+
+        verify(mMockedSmsService, times(1)).enableCellBroadcastRangeForSubscriber(
+                eq(0),
+                eq(SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE),
+                eq(SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE),
+                eq(SmsCbMessage.MESSAGE_FORMAT_3GPP2));
+
+        // GSM
+        verify(mMockedSmsService, times(1)).enableCellBroadcastRangeForSubscriber(
+                eq(0),
+                eq(SmsCbConstants.MESSAGE_ID_ETWS_EARTHQUAKE_WARNING),
+                eq(SmsCbConstants.MESSAGE_ID_ETWS_EARTHQUAKE_AND_TSUNAMI_WARNING),
+                eq(SmsCbMessage.MESSAGE_FORMAT_3GPP));
+
+        verify(mMockedSmsService, times(1)).enableCellBroadcastRangeForSubscriber(
+                eq(0),
+                eq(SmsCbConstants.MESSAGE_ID_ETWS_OTHER_EMERGENCY_TYPE),
+                eq(SmsCbConstants.MESSAGE_ID_ETWS_OTHER_EMERGENCY_TYPE),
+                eq(SmsCbMessage.MESSAGE_FORMAT_3GPP));
+
+        verify(mMockedSmsService, times(1)).enableCellBroadcastRangeForSubscriber(
+                eq(0),
+                eq(SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL),
+                eq(SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL),
+                eq(SmsCbMessage.MESSAGE_FORMAT_3GPP));
+
+        verify(mMockedSmsService, times(1)).enableCellBroadcastRangeForSubscriber(
+                eq(0),
+                eq(SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED),
+                eq(SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY),
+                eq(SmsCbMessage.MESSAGE_FORMAT_3GPP));
+
+        verify(mMockedSmsService, times(1)).enableCellBroadcastRangeForSubscriber(
+                eq(0),
+                eq(SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED),
+                eq(SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY),
+                eq(SmsCbMessage.MESSAGE_FORMAT_3GPP));
+
+        verify(mMockedSmsService, times(1)).enableCellBroadcastRangeForSubscriber(
+                eq(0),
+                eq(SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY),
+                eq(SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY),
+                eq(SmsCbMessage.MESSAGE_FORMAT_3GPP));
+
+        verify(mMockedSmsService, times(1)).enableCellBroadcastRangeForSubscriber(
+                eq(0),
+                eq(SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST),
+                eq(SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST),
+                eq(SmsCbMessage.MESSAGE_FORMAT_3GPP));
+
+        verify(mMockedSmsService, times(1)).enableCellBroadcastRangeForSubscriber(
+                eq(0),
+                eq(SmsCbConstants.MESSAGE_ID_ETWS_TEST_MESSAGE),
+                eq(SmsCbConstants.MESSAGE_ID_ETWS_TEST_MESSAGE),
+                eq(SmsCbMessage.MESSAGE_FORMAT_3GPP));
+
+        verify(mMockedSmsService, times(1)).enableCellBroadcastRangeForSubscriber(
+                eq(0),
+                eq(SmsCbConstants.MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE),
+                eq(SmsCbConstants.MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE),
+                eq(SmsCbMessage.MESSAGE_FORMAT_3GPP));
+
+        verify(mMockedSmsService, times(1)).enableCellBroadcastRangeForSubscriber(
+                eq(0),
+                eq(SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXERCISE),
+                eq(SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXERCISE),
+                eq(SmsCbMessage.MESSAGE_FORMAT_3GPP));
+    }
 }
diff --git a/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastReceiverTest.java b/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastReceiverTest.java
index 76b37c9..8b6377c 100644
--- a/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastReceiverTest.java
+++ b/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastReceiverTest.java
@@ -28,6 +28,7 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.content.ContentResolver;
@@ -43,6 +44,7 @@
 import android.os.UserManager;
 import android.provider.Telephony;
 import android.telephony.CarrierConfigManager;
+import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.cdma.CdmaSmsCbProgramData;
@@ -470,6 +472,128 @@
         mCellBroadcastReceiver.onReceive(mContext, mIntent);
     }
 
+    @Test
+    public void testOnServiceStateChange() {
+        mFakeSharedPreferences.putInt("service_state", ServiceState.STATE_OUT_OF_SERVICE);
+        mFakeSharedPreferences.putString("roaming_operator_supported", "");
+        mockTelephonyManager();
+        doReturn("android.intent.action.SERVICE_STATE").when(mIntent).getAction();
+        doReturn(ServiceState.STATE_IN_SERVICE).when(mIntent).getIntExtra(anyString(), anyInt());
+        doReturn(false).when(mMockTelephonyManager).isNetworkRoaming();
+        doReturn("123456").when(mMockTelephonyManager).getNetworkOperator();
+
+        mCellBroadcastReceiver.onReceive(mContext, mIntent);
+
+        verify(mCellBroadcastReceiver, never()).startConfigServiceToEnableChannels();
+        assertThat(mFakeSharedPreferences.getInt("service_state", ServiceState.STATE_POWER_OFF))
+                .isEqualTo(ServiceState.STATE_IN_SERVICE);
+
+        mFakeSharedPreferences.putInt("service_state", ServiceState.STATE_POWER_OFF);
+
+        mCellBroadcastReceiver.onReceive(mContext, mIntent);
+
+        verify(mCellBroadcastReceiver).startConfigServiceToEnableChannels();
+        assertThat(mFakeSharedPreferences.getInt("service_state", ServiceState.STATE_POWER_OFF))
+                .isEqualTo(ServiceState.STATE_IN_SERVICE);
+    }
+
+
+    @Test
+    public void testOnNetworkRoamingChange() {
+        mFakeSharedPreferences.putInt("service_state", ServiceState.STATE_IN_SERVICE);
+        mFakeSharedPreferences.putString("roaming_operator_supported", "");
+        mockTelephonyManager();
+        doReturn("android.intent.action.SERVICE_STATE").when(mIntent).getAction();
+        doReturn(ServiceState.STATE_IN_SERVICE).when(mIntent).getIntExtra(anyString(), anyInt());
+        doReturn("123456").when(mMockTelephonyManager).getNetworkOperator();
+
+        // not roaming, verify not to store the network operator, or call enable channel
+        doReturn(false).when(mMockTelephonyManager).isNetworkRoaming();
+
+        mCellBroadcastReceiver.onReceive(mContext, mIntent);
+
+        verify(mCellBroadcastReceiver, never()).startConfigServiceToEnableChannels();
+        assertThat(mFakeSharedPreferences.getString(
+                "roaming_operator_supported", "123456")).isEqualTo("");
+
+        // roaming and network operator changed with wild match, verify to
+        // update the network operator, and call enable channel
+        doReturn(true).when(mMockTelephonyManager).isNetworkRoaming();
+        doReturn(new String[] {"XXXXXX"}).when(mResources).getStringArray(anyInt());
+        doReturn("654321").when(mMockTelephonyManager).getSimOperator();
+
+        mCellBroadcastReceiver.onReceive(mContext, mIntent);
+
+        verify(mCellBroadcastReceiver, times(1)).startConfigServiceToEnableChannels();
+        assertThat(mFakeSharedPreferences.getString(
+                "roaming_operator_supported", "")).isEqualTo("123456");
+
+        // roaming to home case, verify to call enable channel
+        doReturn(false).when(mMockTelephonyManager).isNetworkRoaming();
+
+        mCellBroadcastReceiver.onReceive(mContext, mIntent);
+
+        verify(mCellBroadcastReceiver, times(2)).startConfigServiceToEnableChannels();
+        assertThat(mFakeSharedPreferences.getString(
+                "roaming_operator_supported", "123456")).isEqualTo("");
+
+        // roaming and network operator changed with exact mcc match, verify to
+        // update the network operator, and call enable channel
+        doReturn(true).when(mMockTelephonyManager).isNetworkRoaming();
+        doReturn(new String[] {"123"}).when(mResources).getStringArray(anyInt());
+        doReturn("654321").when(mMockTelephonyManager).getSimOperator();
+
+        mCellBroadcastReceiver.onReceive(mContext, mIntent);
+
+        verify(mCellBroadcastReceiver, times(3)).startConfigServiceToEnableChannels();
+        assertThat(mFakeSharedPreferences.getString(
+                "roaming_operator_supported", "")).isEqualTo("123");
+
+        // roaming to network operator with same mcc and configured as exact mcc match,
+        // verify to update the network operator, but not call enable channel
+        doReturn("123654").when(mMockTelephonyManager).getNetworkOperator();
+
+        mCellBroadcastReceiver.onReceive(mContext, mIntent);
+
+        verify(mCellBroadcastReceiver, times(3)).startConfigServiceToEnableChannels();
+        assertThat(mFakeSharedPreferences.getString(
+                "roaming_operator_supported", "")).isEqualTo("123");
+
+        // roaming and network operator changed with exact match, verify to
+        // update the network operator, and call enable channel
+        doReturn(new String[] {"123456"}).when(mResources).getStringArray(anyInt());
+        doReturn("123456").when(mMockTelephonyManager).getNetworkOperator();
+
+        mCellBroadcastReceiver.onReceive(mContext, mIntent);
+
+        verify(mCellBroadcastReceiver, times(4)).startConfigServiceToEnableChannels();
+        assertThat(mFakeSharedPreferences.getString(
+                "roaming_operator_supported", "")).isEqualTo("123456");
+
+        // roaming to network operator with different mcc and configured as any mcc match,
+        // verify to update the network operator, and call enable channel
+        doReturn("321456").when(mMockTelephonyManager).getNetworkOperator();
+        doReturn(new String[] {"XXX"}).when(mResources).getStringArray(anyInt());
+
+        mCellBroadcastReceiver.onReceive(mContext, mIntent);
+
+        verify(mCellBroadcastReceiver, times(5)).startConfigServiceToEnableChannels();
+        assertThat(mFakeSharedPreferences.getString(
+                "roaming_operator_supported", "")).isEqualTo("321");
+
+        // roaming to network operator which does not match the configuration,
+        // verify to update the network operator to empty, and call enable channel
+        doReturn("321456").when(mMockTelephonyManager).getNetworkOperator();
+        doReturn(new String[] {"123"}).when(mResources).getStringArray(anyInt());
+
+        mCellBroadcastReceiver.onReceive(mContext, mIntent);
+
+        verify(mCellBroadcastReceiver, times(6)).startConfigServiceToEnableChannels();
+        assertThat(mFakeSharedPreferences.getString(
+                "roaming_operator_supported", "321")).isEqualTo("");
+    }
+
+
     @After
     public void tearDown() throws Exception {
         super.tearDown();
diff --git a/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastServiceTestCase.java b/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastServiceTestCase.java
index 66ae7b1..2e767fe 100644
--- a/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastServiceTestCase.java
+++ b/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastServiceTestCase.java
@@ -37,7 +37,7 @@
 import android.telephony.TelephonyManager;
 import android.test.ServiceTestCase;
 
-import com.android.cellbroadcastreceiver.CellBroadcastSettings;
+import com.android.cellbroadcastreceiver.CellBroadcastChannelManager;
 import com.android.internal.telephony.ISub;
 
 import org.junit.After;
@@ -173,11 +173,13 @@
 
         mContext = new TestContextWrapper(getContext());
         setContext(mContext);
+        CellBroadcastChannelManager.clearAllCellBroadcastChannelRanges();
     }
 
     @After
     public void tearDown() throws Exception {
         mMockedServiceManager.restoreAllServices();
+        CellBroadcastChannelManager.clearAllCellBroadcastChannelRanges();
     }
 
     void putResources(int id, String[] values) {
diff --git a/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastSettingsTest.java b/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastSettingsTest.java
index ca34ac6..f893f8c 100644
--- a/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastSettingsTest.java
+++ b/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastSettingsTest.java
@@ -33,6 +33,7 @@
 import android.app.Instrumentation;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.os.Looper;
 import android.os.RemoteException;
@@ -52,6 +53,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 
 @RunWith(AndroidJUnit4.class)
 public class CellBroadcastSettingsTest extends
@@ -201,6 +203,34 @@
         verify(mockContext2, times(1)).getResources();
     }
 
+    @Test
+    public void testGetResourcesByOperator() {
+        Context mockContext = mock(Context.class);
+        Resources mockResources = mock(Resources.class);
+        doReturn(mockResources).when(mockContext).getResources();
+
+        CellBroadcastSettings.getResourcesByOperator(mockContext,
+                SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, "");
+        verify(mockContext, never()).createConfigurationContext(any());
+        verify(mockContext, times(1)).getResources();
+
+        int mcc = 123;
+        int mnc = 456;
+        Context mockContext2 = mock(Context.class);
+        ArgumentCaptor<Configuration> captorConfig = ArgumentCaptor.forClass(Configuration.class);
+        doReturn(mockResources).when(mockContext2).getResources();
+        doReturn(mockContext2).when(mockContext).createConfigurationContext(any());
+
+        CellBroadcastSettings.getResourcesByOperator(mockContext,
+                SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                Integer.toString(mcc) + Integer.toString(mnc));
+        verify(mockContext, times(1)).getResources();
+        verify(mockContext2, times(1)).getResources();
+        verify(mockContext, times(1)).createConfigurationContext(captorConfig.capture());
+        assertEquals(mcc, captorConfig.getValue().mcc);
+        assertEquals(mnc, captorConfig.getValue().mnc);
+    }
+
     public void waitUntilDialogOpens(Runnable r, long maxWaitMs) {
         long waitTime = 0;
         while (waitTime < maxWaitMs) {
diff --git a/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastTest.java b/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastTest.java
index 54c6329..e4c80a3 100644
--- a/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastTest.java
+++ b/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastTest.java
@@ -22,6 +22,7 @@
 import static org.mockito.Mockito.eq;
 
 import android.content.Context;
+import android.content.SharedPreferences;
 import android.content.res.Resources;
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
@@ -30,6 +31,7 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import com.android.cellbroadcastreceiver.CellBroadcastChannelManager;
 import com.android.internal.telephony.ISub;
 
 import org.mockito.Mock;
@@ -53,10 +55,14 @@
     Resources mResources;
     @Mock
     ISub.Stub mSubService;
+    @Mock
+    SharedPreferences mSharedPreferences;
 
     protected void setUp(String tag) throws Exception {
         TAG = tag;
         MockitoAnnotations.initMocks(this);
+        doReturn(mSharedPreferences).when(mContext).getSharedPreferences(anyString(), anyInt());
+        doReturn(null).when(mSharedPreferences).getString(anyString(), anyString());
         // A hack to return mResources from static method
         // CellBroadcastSettings.getResources(context).
         doReturn(mSubService).when(mSubService).queryLocalInterface(anyString());
@@ -70,6 +76,7 @@
         SubscriptionManager.disableCaching();
 
         initContext();
+        CellBroadcastChannelManager.clearAllCellBroadcastChannelRanges();
     }
 
     private void initContext() {
@@ -97,8 +104,13 @@
         doReturn(values).when(mResources).getStringArray(eq(id));
     }
 
+    void putResources(int id, boolean values) {
+        doReturn(values).when(mResources).getBoolean(eq(id));
+    }
+
     protected void tearDown() throws Exception {
         mMockedServiceManager.restoreAllServices();
+        CellBroadcastChannelManager.clearAllCellBroadcastChannelRanges();
     }
 
     protected static void logd(String s) {