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