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