• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.cellbroadcastreceiver;
18 
19 import static android.telephony.ServiceState.ROAMING_TYPE_NOT_ROAMING;
20 
21 import static com.android.cellbroadcastreceiver.CellBroadcastReceiver.VDBG;
22 
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.content.Context;
26 import android.content.res.Resources;
27 import android.os.SystemProperties;
28 import android.telephony.AccessNetworkConstants;
29 import android.telephony.NetworkRegistrationInfo;
30 import android.telephony.ServiceState;
31 import android.telephony.SmsCbMessage;
32 import android.telephony.TelephonyManager;
33 import android.text.TextUtils;
34 import android.util.ArrayMap;
35 import android.util.Log;
36 import android.util.Pair;
37 
38 import androidx.annotation.VisibleForTesting;
39 
40 import com.android.cellbroadcastreceiver.CellBroadcastAlertService.AlertType;
41 
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.List;
45 import java.util.Map;
46 
47 /**
48  * CellBroadcastChannelManager handles the additional cell broadcast channels that
49  * carriers might enable through resources.
50  * Syntax: "<channel id range>:[type=<alert type>], [emergency=true/false]"
51  * For example,
52  * <string-array name="additional_cbs_channels_strings" translatable="false">
53  *     <item>"43008:type=earthquake, emergency=true"</item>
54  *     <item>"0xAFEE:type=tsunami, emergency=true"</item>
55  *     <item>"0xAC00-0xAFED:type=other"</item>
56  *     <item>"1234-5678"</item>
57  * </string-array>
58  * If no tones are specified, the alert type will be set to DEFAULT. If emergency is not set,
59  * by default it's not emergency.
60  */
61 public class CellBroadcastChannelManager {
62 
63     private static final String TAG = "CBChannelManager";
64 
65     private static final int MAX_CACHE_SIZE = 3;
66     private static List<Integer> sCellBroadcastRangeResourceKeys = new ArrayList<>(
67             Arrays.asList(R.array.additional_cbs_channels_strings,
68                     R.array.emergency_alerts_channels_range_strings,
69                     R.array.cmas_presidential_alerts_channels_range_strings,
70                     R.array.cmas_alert_extreme_channels_range_strings,
71                     R.array.cmas_alerts_severe_range_strings,
72                     R.array.cmas_amber_alerts_channels_range_strings,
73                     R.array.required_monthly_test_range_strings,
74                     R.array.exercise_alert_range_strings,
75                     R.array.operator_defined_alert_range_strings,
76                     R.array.etws_alerts_range_strings,
77                     R.array.etws_test_alerts_range_strings,
78                     R.array.public_safety_messages_channels_range_strings,
79                     R.array.state_local_test_alert_range_strings,
80                     R.array.geo_fencing_trigger_messages_range_strings
81             ));
82 
83     private static Map<Integer, Map<Integer, List<CellBroadcastChannelRange>>>
84             sAllCellBroadcastChannelRangesPerSub = new ArrayMap<>();
85     private static Map<String, Map<Integer, List<CellBroadcastChannelRange>>>
86             sAllCellBroadcastChannelRangesPerOperator = new ArrayMap<>();
87 
88     private static final Object mChannelRangesLock = new Object();
89 
90     private final Context mContext;
91 
92     private final int mSubId;
93 
94     private final String mOperator;
95 
96     private boolean mIsDebugBuild = false;
97 
98     /**
99      * Cell broadcast channel range
100      * A range is consisted by starting channel id, ending channel id, and the alert type
101      */
102     public static class CellBroadcastChannelRange {
103         /** Defines the type of the alert. */
104         private static final String KEY_TYPE = "type";
105         /** Defines if the alert is emergency. */
106         private static final String KEY_EMERGENCY = "emergency";
107         /** Defines the network RAT for the alert. */
108         private static final String KEY_RAT = "rat";
109         /** Defines the scope of the alert. */
110         private static final String KEY_SCOPE = "scope";
111         /** Defines the vibration pattern of the alert. */
112         private static final String KEY_VIBRATION = "vibration";
113         /** Defines the duration of the alert. */
114         private static final String KEY_ALERT_DURATION = "alert_duration";
115         /** Defines if Do Not Disturb should be overridden for this alert */
116         private static final String KEY_OVERRIDE_DND = "override_dnd";
117         /** Defines whether writing alert message should exclude from SMS inbox. */
118         private static final String KEY_EXCLUDE_FROM_SMS_INBOX = "exclude_from_sms_inbox";
119         /** Define whether to display this cellbroadcast messages. */
120         private static final String KEY_DISPLAY = "display";
121         /** Define whether to enable this only in test/debug mode. */
122         private static final String KEY_TESTING_MODE_ONLY = "testing_mode";
123         /** Define the channels which not allow opt-out. */
124         private static final String KEY_ALWAYS_ON = "always_on";
125         /** Define the duration of screen on in milliseconds. */
126         private static final String KEY_SCREEN_ON_DURATION = "screen_on_duration";
127         /** Define whether to display warning icon in the alert dialog. */
128         private static final String KEY_DISPLAY_ICON = "display_icon";
129         /** Define whether to dismiss the alert dialog for outside touches */
130         private static final String KEY_DISMISS_ON_OUTSIDE_TOUCH = "dismiss_on_outside_touch";
131         /** Define whether to enable this only in userdebug/eng build. */
132         private static final String KEY_DEBUG_BUILD_ONLY = "debug_build";
133         /** Define the ISO-639-1 language code associated with the alert message. */
134         private static final String KEY_LANGUAGE_CODE = "language";
135         /** Define whether to display dialog and notification */
136         private static final String KEY_DIALOG_WITH_NOTIFICATION = "dialog_with_notification";
137 
138         /**
139          * Defines whether the channel needs language filter or not. True indicates that the alert
140          * will only pop-up when the alert's language matches the device's language.
141          */
142         private static final String KEY_FILTER_LANGUAGE = "filter_language";
143 
144 
145         public static final int SCOPE_UNKNOWN       = 0;
146         public static final int SCOPE_CARRIER       = 1;
147         public static final int SCOPE_DOMESTIC      = 2;
148         public static final int SCOPE_INTERNATIONAL = 3;
149 
150         public static final int LEVEL_UNKNOWN          = 0;
151         public static final int LEVEL_NOT_EMERGENCY    = 1;
152         public static final int LEVEL_EMERGENCY        = 2;
153 
154         public int mStartId;
155         public int mEndId;
156         public AlertType mAlertType;
157         public int mEmergencyLevel;
158         public int mRanType;
159         public int mScope;
160         public int[] mVibrationPattern;
161         public boolean mFilterLanguage;
162         public boolean mDisplay;
163         public boolean mTestMode;
164         // by default no custom alert duration. play the alert tone with the tone's duration.
165         public int mAlertDuration = -1;
166         public boolean mOverrideDnd = false;
167         // If enable_write_alerts_to_sms_inbox is true, write to sms inbox is enabled by default
168         // for all channels except for channels which explicitly set to exclude from sms inbox.
169         public boolean mWriteToSmsInbox = true;
170         // only set to true for channels not allow opt-out. e.g, presidential alert.
171         public boolean mAlwaysOn = false;
172         // de default screen duration is 1min;
173         public int mScreenOnDuration = 60000;
174         // whether to display warning icon in the pop-up dialog;
175         public boolean mDisplayIcon = true;
176         // whether to dismiss the alert dialog on outside touch. Typically this should be false
177         // to avoid accidental dismisses of emergency messages
178         public boolean mDismissOnOutsideTouch = false;
179         // Whether the channels are disabled
180         public boolean mIsDebugBuildOnly = false;
181         // This is used to override dialog title language
182         public String mLanguageCode;
183         // Display both ways dialog and notification
184         public boolean mDisplayDialogWithNotification = false;
185 
CellBroadcastChannelRange(Context context, int subId, Resources res, String channelRange)186         public CellBroadcastChannelRange(Context context, int subId,
187                 Resources res, String channelRange) {
188             mAlertType = AlertType.DEFAULT;
189             mEmergencyLevel = LEVEL_UNKNOWN;
190             mRanType = SmsCbMessage.MESSAGE_FORMAT_3GPP;
191             mScope = SCOPE_UNKNOWN;
192 
193             mVibrationPattern = res.getIntArray(R.array.default_vibration_pattern);
194             mFilterLanguage = false;
195             // by default all received messages should be displayed.
196             mDisplay = true;
197             mTestMode = false;
198             boolean hasVibrationPattern = false;
199 
200             int colonIndex = channelRange.indexOf(':');
201             if (colonIndex != -1) {
202                 // Parse the alert type and emergency flag
203                 String[] pairs = channelRange.substring(colonIndex + 1).trim().split(",");
204                 for (String pair : pairs) {
205                     pair = pair.trim();
206                     String[] tokens = pair.split("=");
207                     if (tokens.length == 2) {
208                         String key = tokens[0].trim();
209                         String value = tokens[1].trim();
210                         switch (key) {
211                             case KEY_TYPE:
212                                 mAlertType = AlertType.valueOf(value.toUpperCase());
213                                 break;
214                             case KEY_EMERGENCY:
215                                 if (value.equalsIgnoreCase("true")) {
216                                     mEmergencyLevel = LEVEL_EMERGENCY;
217                                 } else if (value.equalsIgnoreCase("false")) {
218                                     mEmergencyLevel = LEVEL_NOT_EMERGENCY;
219                                 }
220                                 break;
221                             case KEY_RAT:
222                                 mRanType = value.equalsIgnoreCase("cdma")
223                                         ? SmsCbMessage.MESSAGE_FORMAT_3GPP2 :
224                                         SmsCbMessage.MESSAGE_FORMAT_3GPP;
225                                 break;
226                             case KEY_SCOPE:
227                                 if (value.equalsIgnoreCase("carrier")) {
228                                     mScope = SCOPE_CARRIER;
229                                 } else if (value.equalsIgnoreCase("domestic")) {
230                                     mScope = SCOPE_DOMESTIC;
231                                 } else if (value.equalsIgnoreCase("international")) {
232                                     mScope = SCOPE_INTERNATIONAL;
233                                 }
234                                 break;
235                             case KEY_VIBRATION:
236                                 String[] vibration = value.split("\\|");
237                                 if (vibration.length > 0) {
238                                     mVibrationPattern = new int[vibration.length];
239                                     for (int i = 0; i < vibration.length; i++) {
240                                         mVibrationPattern[i] = Integer.parseInt(vibration[i]);
241                                     }
242                                     hasVibrationPattern = true;
243                                 }
244                                 break;
245                             case KEY_FILTER_LANGUAGE:
246                                 if (value.equalsIgnoreCase("true")) {
247                                     mFilterLanguage = true;
248                                 }
249                                 break;
250                             case KEY_ALERT_DURATION:
251                                 mAlertDuration = Integer.parseInt(value);
252                                 break;
253                             case KEY_OVERRIDE_DND:
254                                 if (value.equalsIgnoreCase("true")) {
255                                     mOverrideDnd = true;
256                                 }
257                                 break;
258                             case KEY_EXCLUDE_FROM_SMS_INBOX:
259                                 if (value.equalsIgnoreCase("true")) {
260                                     mWriteToSmsInbox = false;
261                                 }
262                                 break;
263                             case KEY_DISPLAY:
264                                 if (value.equalsIgnoreCase("false")) {
265                                     mDisplay = false;
266                                 }
267                                 break;
268                             case KEY_TESTING_MODE_ONLY:
269                                 if (value.equalsIgnoreCase("true")) {
270                                     mTestMode = true;
271                                 }
272                                 break;
273                             case KEY_ALWAYS_ON:
274                                 if (value.equalsIgnoreCase("true")) {
275                                     mAlwaysOn = true;
276                                 }
277                                 break;
278                             case KEY_SCREEN_ON_DURATION:
279                                 mScreenOnDuration = Integer.parseInt(value);
280                                 break;
281                             case KEY_DISPLAY_ICON:
282                                 if (value.equalsIgnoreCase("false")) {
283                                     mDisplayIcon = false;
284                                 }
285                                 break;
286                             case KEY_DISMISS_ON_OUTSIDE_TOUCH:
287                                 if (value.equalsIgnoreCase("true")) {
288                                     mDismissOnOutsideTouch = true;
289                                 }
290                                 break;
291                             case KEY_DEBUG_BUILD_ONLY:
292                                 if (value.equalsIgnoreCase("true")) {
293                                     mIsDebugBuildOnly = true;
294                                 }
295                                 break;
296                             case KEY_LANGUAGE_CODE:
297                                 mLanguageCode = value;
298                                 break;
299                             case KEY_DIALOG_WITH_NOTIFICATION:
300                                 if (value.equalsIgnoreCase("true")) {
301                                     mDisplayDialogWithNotification = true;
302                                 }
303                                 break;
304                         }
305                     }
306                 }
307                 channelRange = channelRange.substring(0, colonIndex).trim();
308             }
309 
310             // If alert type is info, override vibration pattern
311             if (!hasVibrationPattern && mAlertType.equals(AlertType.INFO)) {
312                 mVibrationPattern = res.getIntArray(R.array.default_notification_vibration_pattern);
313             }
314 
315             // Parse the channel range
316             int dashIndex = channelRange.indexOf('-');
317             if (dashIndex != -1) {
318                 // range that has start id and end id
319                 mStartId = Integer.decode(channelRange.substring(0, dashIndex).trim());
320                 mEndId = Integer.decode(channelRange.substring(dashIndex + 1).trim());
321             } else {
322                 // Not a range, only a single id
323                 mStartId = mEndId = Integer.decode(channelRange);
324             }
325         }
326 
327         @Override
toString()328         public String toString() {
329             return "Range:[channels=" + mStartId + "-" + mEndId + ",emergency level="
330                     + mEmergencyLevel + ",type=" + mAlertType + ",scope=" + mScope + ",vibration="
331                     + Arrays.toString(mVibrationPattern) + ",alertDuration=" + mAlertDuration
332                     + ",filter_language=" + mFilterLanguage + ",override_dnd=" + mOverrideDnd
333                     + ",display=" + mDisplay + ",testMode=" + mTestMode + ",mAlwaysOn="
334                     + mAlwaysOn + ",ScreenOnDuration=" + mScreenOnDuration + ", displayIcon="
335                     + mDisplayIcon + "dismissOnOutsideTouch=" + mDismissOnOutsideTouch
336                     + ", mIsDebugBuildOnly =" + mIsDebugBuildOnly
337                     + ", languageCode=" + mLanguageCode
338                     + ", mDisplayDialogWithNotification=" + mDisplayDialogWithNotification + "]";
339         }
340     }
341 
342     /**
343      * Constructor
344      *
345      * @param context Context
346      * @param subId Subscription index
347      */
CellBroadcastChannelManager(Context context, int subId)348     public CellBroadcastChannelManager(Context context, int subId) {
349         this(context, subId, CellBroadcastReceiver.getRoamingOperatorSupported(context),
350                 SystemProperties.getInt("ro.debuggable", 0) == 1);
351     }
352 
CellBroadcastChannelManager(Context context, int subId, @Nullable String operator)353     public CellBroadcastChannelManager(Context context, int subId, @Nullable String operator) {
354         this(context, subId, operator, SystemProperties.getInt("ro.debuggable", 0) == 1);
355     }
356 
357     @VisibleForTesting
CellBroadcastChannelManager(Context context, int subId, String operator, boolean isDebugBuild)358     public CellBroadcastChannelManager(Context context, int subId,
359             String operator, boolean isDebugBuild) {
360         mContext = context;
361         mSubId = subId;
362         mOperator = operator;
363         mIsDebugBuild = isDebugBuild;
364         initAsNeeded();
365     }
366 
367     /**
368      * Parse channel ranges from resources, and initialize the cache as needed
369      */
initAsNeeded()370     private void initAsNeeded() {
371         if (!TextUtils.isEmpty(mOperator)) {
372             synchronized (mChannelRangesLock) {
373                 if (!sAllCellBroadcastChannelRangesPerOperator.containsKey(mOperator)) {
374                     if (VDBG) {
375                         log("init for operator: " + mOperator);
376                     }
377                     if (sAllCellBroadcastChannelRangesPerOperator.size() == MAX_CACHE_SIZE) {
378                         sAllCellBroadcastChannelRangesPerOperator.clear();
379                     }
380                     sAllCellBroadcastChannelRangesPerOperator.put(mOperator,
381                             getChannelRangesMapFromResoures(CellBroadcastSettings
382                                     .getResourcesByOperator(mContext, mSubId, mOperator)));
383                 }
384             }
385         }
386 
387         synchronized (mChannelRangesLock) {
388             if (!sAllCellBroadcastChannelRangesPerSub.containsKey(mSubId)) {
389                 if (sAllCellBroadcastChannelRangesPerSub.size() == MAX_CACHE_SIZE) {
390                     sAllCellBroadcastChannelRangesPerSub.clear();
391                 }
392                 if (VDBG) {
393                     log("init for sub: " + mSubId);
394                 }
395                 sAllCellBroadcastChannelRangesPerSub.put(mSubId,
396                         getChannelRangesMapFromResoures(CellBroadcastSettings
397                                 .getResources(mContext, mSubId)));
398             }
399         }
400     }
401 
getChannelRangesMapFromResoures( @onNull Resources res)402     private @NonNull Map<Integer, List<CellBroadcastChannelRange>> getChannelRangesMapFromResoures(
403             @NonNull Resources res) {
404         Map<Integer, List<CellBroadcastChannelRange>> map = new ArrayMap<>();
405 
406         for (int key : sCellBroadcastRangeResourceKeys) {
407             String[] ranges = res.getStringArray(key);
408             if (ranges != null) {
409                 List<CellBroadcastChannelRange> rangesList = new ArrayList<>();
410                 for (String range : ranges) {
411                     try {
412                         if (VDBG) {
413                             log("parse channel range: " + range);
414                         }
415                         CellBroadcastChannelRange r =
416                                 new CellBroadcastChannelRange(mContext, mSubId, res, range);
417                         // Bypass if the range is disabled
418                         if (r.mIsDebugBuildOnly && !mIsDebugBuild) {
419                             continue;
420                         }
421                         rangesList.add(r);
422                     } catch (Exception e) {
423                         loge("Failed to parse \"" + range + "\". e=" + e);
424                     }
425                 }
426                 map.put(key, rangesList);
427             }
428         }
429 
430         return map;
431     }
432 
433     /**
434      * Get cell broadcast channels enabled by the carriers from resource key
435      *
436      * @param key Resource key
437      *
438      * @return The list of channel ranges enabled by the carriers.
439      */
getCellBroadcastChannelRanges(int key)440     public @NonNull List<CellBroadcastChannelRange> getCellBroadcastChannelRanges(int key) {
441         List<CellBroadcastChannelRange> result = null;
442 
443         synchronized (mChannelRangesLock) {
444             initAsNeeded();
445 
446             // Check the config per network first if applicable
447             if (!TextUtils.isEmpty(mOperator)) {
448                 result = sAllCellBroadcastChannelRangesPerOperator.get(mOperator).get(key);
449             }
450 
451             if (result == null) {
452                 result = sAllCellBroadcastChannelRangesPerSub.get(mSubId).get(key);
453             }
454         }
455 
456         return result == null ? new ArrayList<>() : result;
457     }
458 
459     /**
460      * Get all cell broadcast channels
461      *
462      * @return all cell broadcast channels
463      */
getAllCellBroadcastChannelRanges()464     public @NonNull List<CellBroadcastChannelRange> getAllCellBroadcastChannelRanges() {
465         final List<CellBroadcastChannelRange> result = new ArrayList<>();
466         synchronized (mChannelRangesLock) {
467             if (!TextUtils.isEmpty(mOperator)
468                     && sAllCellBroadcastChannelRangesPerOperator.containsKey(mOperator)) {
469                 sAllCellBroadcastChannelRangesPerOperator.get(mOperator).forEach(
470                         (k, v)->result.addAll(v));
471             }
472 
473             sAllCellBroadcastChannelRangesPerSub.get(mSubId).forEach((k, v)->result.addAll(v));
474         }
475         return result;
476     }
477 
478     /**
479      * Clear broadcast channel range list
480      */
clearAllCellBroadcastChannelRanges()481     public static void clearAllCellBroadcastChannelRanges() {
482         synchronized (mChannelRangesLock) {
483             Log.d(TAG, "Clear channel range list");
484             sAllCellBroadcastChannelRangesPerSub.clear();
485             sAllCellBroadcastChannelRangesPerOperator.clear();
486         }
487     }
488 
489     /**
490      * @param channel Cell broadcast message channel
491      * @param key Resource key
492      *
493      * @return {@code TRUE} if the input channel is within the channel range defined from resource.
494      * return {@code FALSE} otherwise
495      */
checkCellBroadcastChannelRange(int channel, int key)496     public boolean checkCellBroadcastChannelRange(int channel, int key) {
497         return getCellBroadcastChannelResourcesKey(channel) == key;
498     }
499 
500     /**
501      * Get the resources key for the channel
502      * @param channel Cell broadcast message channel
503      *
504      * @return 0 if the key is not found, otherwise the value of the resources key
505      */
getCellBroadcastChannelResourcesKey(int channel)506     public int getCellBroadcastChannelResourcesKey(int channel) {
507         Pair<Integer, CellBroadcastChannelRange> p = findChannelRange(channel);
508 
509         return p != null ? p.first : 0;
510     }
511 
512     /**
513      * Get the CellBroadcastChannelRange for the channel
514      * @param channel Cell broadcast message channel
515      *
516      * @return the CellBroadcastChannelRange for the channel, null if not found
517      */
getCellBroadcastChannelRange(int channel)518     public @Nullable CellBroadcastChannelRange getCellBroadcastChannelRange(int channel) {
519         Pair<Integer, CellBroadcastChannelRange> p = findChannelRange(channel);
520 
521         return p != null ? p.second : null;
522     }
523 
findChannelRange(int channel)524     private @Nullable Pair<Integer, CellBroadcastChannelRange> findChannelRange(int channel) {
525         if (!TextUtils.isEmpty(mOperator)) {
526             Pair<Integer, CellBroadcastChannelRange> p = findChannelRange(
527                     sAllCellBroadcastChannelRangesPerOperator.get(mOperator), channel);
528             if (p != null) {
529                 return p;
530             }
531         }
532 
533         return findChannelRange(sAllCellBroadcastChannelRangesPerSub.get(mSubId), channel);
534     }
535 
findChannelRange( Map<Integer, List<CellBroadcastChannelRange>> channelRangeMap, int channel)536     private @Nullable Pair<Integer, CellBroadcastChannelRange> findChannelRange(
537             Map<Integer, List<CellBroadcastChannelRange>> channelRangeMap, int channel) {
538         if (channelRangeMap != null) {
539             for (Map.Entry<Integer, List<CellBroadcastChannelRange>> entry
540                     : channelRangeMap.entrySet()) {
541                 for (CellBroadcastChannelRange range : entry.getValue()) {
542                     if (channel >= range.mStartId && channel <= range.mEndId
543                             && checkScope(range.mScope)) {
544                         return new Pair<>(entry.getKey(), range);
545                     }
546                 }
547             }
548         }
549         return null;
550     }
551 
552     /**
553      * Check if the channel scope matches the current network condition.
554      *
555      * @param rangeScope Range scope. Must be SCOPE_CARRIER, SCOPE_DOMESTIC, or SCOPE_INTERNATIONAL.
556      * @return True if the scope matches the current network roaming condition.
557      */
checkScope(int rangeScope)558     public boolean checkScope(int rangeScope) {
559         if (rangeScope == CellBroadcastChannelRange.SCOPE_UNKNOWN) return true;
560         TelephonyManager tm =
561                 (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
562         tm = tm.createForSubscriptionId(mSubId);
563         ServiceState ss = tm.getServiceState();
564         if (ss != null) {
565             NetworkRegistrationInfo regInfo = ss.getNetworkRegistrationInfo(
566                     NetworkRegistrationInfo.DOMAIN_CS,
567                     AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
568             if (regInfo != null) {
569                 if (regInfo.getRegistrationState()
570                         == NetworkRegistrationInfo.REGISTRATION_STATE_HOME
571                         || regInfo.getRegistrationState()
572                         == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING
573                         || regInfo.isEmergencyEnabled()) {
574                     int voiceRoamingType = regInfo.getRoamingType();
575                     if (voiceRoamingType == ROAMING_TYPE_NOT_ROAMING) {
576                         return true;
577                     } else if (voiceRoamingType == ServiceState.ROAMING_TYPE_DOMESTIC
578                             && rangeScope == CellBroadcastChannelRange.SCOPE_DOMESTIC) {
579                         return true;
580                     } else if (voiceRoamingType == ServiceState.ROAMING_TYPE_INTERNATIONAL
581                             && rangeScope == CellBroadcastChannelRange.SCOPE_INTERNATIONAL) {
582                         return true;
583                     }
584                     return false;
585                 }
586             }
587         }
588         // If we can't determine the scope, for safe we should assume it's in.
589         return true;
590     }
591 
592     /**
593      * Return corresponding cellbroadcast range where message belong to
594      *
595      * @param message Cell broadcast message
596      */
getCellBroadcastChannelRangeFromMessage(SmsCbMessage message)597     public CellBroadcastChannelRange getCellBroadcastChannelRangeFromMessage(SmsCbMessage message) {
598         if (mSubId != message.getSubscriptionId()) {
599             Log.e(TAG, "getCellBroadcastChannelRangeFromMessage: This manager is created for "
600                     + "sub " + mSubId + ", should not be used for message from sub "
601                     + message.getSubscriptionId());
602         }
603 
604         return getCellBroadcastChannelRange(message.getServiceCategory());
605     }
606 
607     /**
608      * Check if the cell broadcast message is an emergency message or not
609      *
610      * @param message Cell broadcast message
611      * @return True if the message is an emergency message, otherwise false.
612      */
isEmergencyMessage(SmsCbMessage message)613     public boolean isEmergencyMessage(SmsCbMessage message) {
614         if (message == null) {
615             return false;
616         }
617 
618         if (mSubId != message.getSubscriptionId()) {
619             Log.e(TAG, "This manager is created for sub " + mSubId
620                     + ", should not be used for message from sub " + message.getSubscriptionId());
621         }
622 
623         int id = message.getServiceCategory();
624         CellBroadcastChannelRange range = getCellBroadcastChannelRange(id);
625 
626         if (range != null) {
627             switch (range.mEmergencyLevel) {
628                 case CellBroadcastChannelRange.LEVEL_EMERGENCY:
629                     Log.d(TAG, "isEmergencyMessage: true, message id = " + id);
630                     return true;
631                 case CellBroadcastChannelRange.LEVEL_NOT_EMERGENCY:
632                     Log.d(TAG, "isEmergencyMessage: false, message id = " + id);
633                     return false;
634                 case CellBroadcastChannelRange.LEVEL_UNKNOWN:
635                 default:
636                     break;
637             }
638         }
639 
640         Log.d(TAG, "isEmergencyMessage: " + message.isEmergencyMessage()
641                 + ", message id = " + id);
642         // If the configuration does not specify whether the alert is emergency or not, use the
643         // emergency property from the message itself, which is checking if the channel is between
644         // MESSAGE_ID_PWS_FIRST_IDENTIFIER (4352) and MESSAGE_ID_PWS_LAST_IDENTIFIER (6399).
645         return message.isEmergencyMessage();
646     }
647 
log(String msg)648     private static void log(String msg) {
649         Log.d(TAG, msg);
650     }
651 
loge(String msg)652     private static void loge(String msg) {
653         Log.e(TAG, msg);
654     }
655 }
656