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