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 android.annotation.NonNull; 22 import android.content.Context; 23 import android.telephony.AccessNetworkConstants; 24 import android.telephony.NetworkRegistrationInfo; 25 import android.telephony.ServiceState; 26 import android.telephony.SmsCbMessage; 27 import android.telephony.TelephonyManager; 28 import android.util.Log; 29 30 import com.android.cellbroadcastreceiver.CellBroadcastAlertService.AlertType; 31 32 import java.util.ArrayList; 33 import java.util.Arrays; 34 import java.util.List; 35 36 /** 37 * CellBroadcastChannelManager handles the additional cell broadcast channels that 38 * carriers might enable through resources. 39 * Syntax: "<channel id range>:[type=<alert type>], [emergency=true/false]" 40 * For example, 41 * <string-array name="additional_cbs_channels_strings" translatable="false"> 42 * <item>"43008:type=earthquake, emergency=true"</item> 43 * <item>"0xAFEE:type=tsunami, emergency=true"</item> 44 * <item>"0xAC00-0xAFED:type=other"</item> 45 * <item>"1234-5678"</item> 46 * </string-array> 47 * If no tones are specified, the alert type will be set to DEFAULT. If emergency is not set, 48 * by default it's not emergency. 49 */ 50 public class CellBroadcastChannelManager { 51 52 private static final String TAG = "CBChannelManager"; 53 54 private static List<Integer> sCellBroadcastRangeResourceKeys = new ArrayList<>( 55 Arrays.asList(R.array.additional_cbs_channels_strings, 56 R.array.emergency_alerts_channels_range_strings, 57 R.array.cmas_presidential_alerts_channels_range_strings, 58 R.array.cmas_alert_extreme_channels_range_strings, 59 R.array.cmas_alerts_severe_range_strings, 60 R.array.cmas_amber_alerts_channels_range_strings, 61 R.array.required_monthly_test_range_strings, 62 R.array.exercise_alert_range_strings, 63 R.array.operator_defined_alert_range_strings, 64 R.array.etws_alerts_range_strings, 65 R.array.etws_test_alerts_range_strings, 66 R.array.public_safety_messages_channels_range_strings, 67 R.array.state_local_test_alert_range_strings 68 )); 69 70 private static ArrayList<CellBroadcastChannelRange> sAllCellBroadcastChannelRanges = null; 71 private static final Object channelRangesLock = new Object(); 72 73 private final Context mContext; 74 75 private final int mSubId; 76 77 /** 78 * Cell broadcast channel range 79 * A range is consisted by starting channel id, ending channel id, and the alert type 80 */ 81 public static class CellBroadcastChannelRange { 82 /** Defines the type of the alert. */ 83 private static final String KEY_TYPE = "type"; 84 /** Defines if the alert is emergency. */ 85 private static final String KEY_EMERGENCY = "emergency"; 86 /** Defines the network RAT for the alert. */ 87 private static final String KEY_RAT = "rat"; 88 /** Defines the scope of the alert. */ 89 private static final String KEY_SCOPE = "scope"; 90 /** Defines the vibration pattern of the alert. */ 91 private static final String KEY_VIBRATION = "vibration"; 92 /** Defines the duration of the alert. */ 93 private static final String KEY_ALERT_DURATION = "alert_duration"; 94 /** Defines if Do Not Disturb should be overridden for this alert */ 95 private static final String KEY_OVERRIDE_DND = "override_dnd"; 96 /** Defines whether writing alert message should exclude from SMS inbox. */ 97 private static final String KEY_EXCLUDE_FROM_SMS_INBOX = "exclude_from_sms_inbox"; 98 /** Define whether to display this cellbroadcast messages. */ 99 private static final String KEY_DISPLAY = "display"; 100 /** Define whether to enable this only in test/debug mode. */ 101 private static final String KEY_TESTING_MODE_ONLY = "testing_mode"; 102 /** Define the channels which not allow opt-out. */ 103 private static final String KEY_ALWAYS_ON = "always_on"; 104 /** Define the duration of screen on in milliseconds. */ 105 private static final String KEY_SCREEN_ON_DURATION = "screen_on_duration"; 106 /** Define whether to display warning icon in the alert dialog. */ 107 private static final String KEY_DISPLAY_ICON = "display_icon"; 108 /** Define whether to dismiss the alert dialog for outside touches */ 109 private static final String KEY_DISMISS_ON_OUTSIDE_TOUCH = "dismiss_on_outside_touch"; 110 /** Define the ISO-639-1 language code associated with the alert message. */ 111 private static final String KEY_LANGUAGE_CODE = "language"; 112 113 114 /** 115 * Defines whether the channel needs language filter or not. True indicates that the alert 116 * will only pop-up when the alert's language matches the device's language. 117 */ 118 private static final String KEY_FILTER_LANGUAGE = "filter_language"; 119 120 121 public static final int SCOPE_UNKNOWN = 0; 122 public static final int SCOPE_CARRIER = 1; 123 public static final int SCOPE_DOMESTIC = 2; 124 public static final int SCOPE_INTERNATIONAL = 3; 125 126 public static final int LEVEL_UNKNOWN = 0; 127 public static final int LEVEL_NOT_EMERGENCY = 1; 128 public static final int LEVEL_EMERGENCY = 2; 129 130 public int mStartId; 131 public int mEndId; 132 public AlertType mAlertType; 133 public int mEmergencyLevel; 134 public int mRanType; 135 public int mScope; 136 public int[] mVibrationPattern; 137 public boolean mFilterLanguage; 138 public boolean mDisplay; 139 public boolean mTestMode; 140 // by default no custom alert duration. play the alert tone with the tone's duration. 141 public int mAlertDuration = -1; 142 public boolean mOverrideDnd = false; 143 // If enable_write_alerts_to_sms_inbox is true, write to sms inbox is enabled by default 144 // for all channels except for channels which explicitly set to exclude from sms inbox. 145 public boolean mWriteToSmsInbox = true; 146 // only set to true for channels not allow opt-out. e.g, presidential alert. 147 public boolean mAlwaysOn = false; 148 // de default screen duration is 1min; 149 public int mScreenOnDuration = 60000; 150 // whether to display warning icon in the pop-up dialog; 151 public boolean mDisplayIcon = true; 152 // whether to dismiss the alert dialog on outside touch. Typically this should be false 153 // to avoid accidental dismisses of emergency messages 154 public boolean mDismissOnOutsideTouch = false; 155 // This is used to override dialog title language 156 public String mLanguageCode; 157 CellBroadcastChannelRange(Context context, int subId, String channelRange)158 public CellBroadcastChannelRange(Context context, int subId, String channelRange) { 159 160 mAlertType = AlertType.DEFAULT; 161 mEmergencyLevel = LEVEL_UNKNOWN; 162 mRanType = SmsCbMessage.MESSAGE_FORMAT_3GPP; 163 mScope = SCOPE_UNKNOWN; 164 mVibrationPattern = 165 CellBroadcastSettings.getResources(context, subId) 166 .getIntArray(R.array.default_vibration_pattern); 167 mFilterLanguage = false; 168 // by default all received messages should be displayed. 169 mDisplay = true; 170 mTestMode = false; 171 boolean hasVibrationPattern = false; 172 173 int colonIndex = channelRange.indexOf(':'); 174 if (colonIndex != -1) { 175 // Parse the alert type and emergency flag 176 String[] pairs = channelRange.substring(colonIndex + 1).trim().split(","); 177 for (String pair : pairs) { 178 pair = pair.trim(); 179 String[] tokens = pair.split("="); 180 if (tokens.length == 2) { 181 String key = tokens[0].trim(); 182 String value = tokens[1].trim(); 183 switch (key) { 184 case KEY_TYPE: 185 mAlertType = AlertType.valueOf(value.toUpperCase()); 186 break; 187 case KEY_EMERGENCY: 188 if (value.equalsIgnoreCase("true")) { 189 mEmergencyLevel = LEVEL_EMERGENCY; 190 } else if (value.equalsIgnoreCase("false")) { 191 mEmergencyLevel = LEVEL_NOT_EMERGENCY; 192 } 193 break; 194 case KEY_RAT: 195 mRanType = value.equalsIgnoreCase("cdma") 196 ? SmsCbMessage.MESSAGE_FORMAT_3GPP2 : 197 SmsCbMessage.MESSAGE_FORMAT_3GPP; 198 break; 199 case KEY_SCOPE: 200 if (value.equalsIgnoreCase("carrier")) { 201 mScope = SCOPE_CARRIER; 202 } else if (value.equalsIgnoreCase("domestic")) { 203 mScope = SCOPE_DOMESTIC; 204 } else if (value.equalsIgnoreCase("international")) { 205 mScope = SCOPE_INTERNATIONAL; 206 } 207 break; 208 case KEY_VIBRATION: 209 String[] vibration = value.split("\\|"); 210 if (vibration.length > 0) { 211 mVibrationPattern = new int[vibration.length]; 212 for (int i = 0; i < vibration.length; i++) { 213 mVibrationPattern[i] = Integer.parseInt(vibration[i]); 214 } 215 hasVibrationPattern = true; 216 } 217 break; 218 case KEY_FILTER_LANGUAGE: 219 if (value.equalsIgnoreCase("true")) { 220 mFilterLanguage = true; 221 } 222 break; 223 case KEY_ALERT_DURATION: 224 mAlertDuration = Integer.parseInt(value); 225 break; 226 case KEY_OVERRIDE_DND: 227 if (value.equalsIgnoreCase("true")) { 228 mOverrideDnd = true; 229 } 230 break; 231 case KEY_EXCLUDE_FROM_SMS_INBOX: 232 if (value.equalsIgnoreCase("true")) { 233 mWriteToSmsInbox = false; 234 } 235 break; 236 case KEY_DISPLAY: 237 if (value.equalsIgnoreCase("false")) { 238 mDisplay = false; 239 } 240 break; 241 case KEY_TESTING_MODE_ONLY: 242 if (value.equalsIgnoreCase("true")) { 243 mTestMode = true; 244 } 245 break; 246 case KEY_ALWAYS_ON: 247 if (value.equalsIgnoreCase("true")) { 248 mAlwaysOn = true; 249 } 250 break; 251 case KEY_SCREEN_ON_DURATION: 252 mScreenOnDuration = Integer.parseInt(value); 253 break; 254 case KEY_DISPLAY_ICON: 255 if (value.equalsIgnoreCase("false")) { 256 mDisplayIcon = false; 257 } 258 break; 259 case KEY_DISMISS_ON_OUTSIDE_TOUCH: 260 if (value.equalsIgnoreCase("true")) { 261 mDismissOnOutsideTouch = true; 262 } 263 break; 264 case KEY_LANGUAGE_CODE: 265 mLanguageCode = value; 266 break; 267 } 268 } 269 } 270 channelRange = channelRange.substring(0, colonIndex).trim(); 271 } 272 273 // If alert type is info, override vibration pattern 274 if (!hasVibrationPattern && mAlertType.equals(AlertType.INFO)) { 275 mVibrationPattern = CellBroadcastSettings.getResources(context, subId) 276 .getIntArray(R.array.default_notification_vibration_pattern); 277 } 278 279 // Parse the channel range 280 int dashIndex = channelRange.indexOf('-'); 281 if (dashIndex != -1) { 282 // range that has start id and end id 283 mStartId = Integer.decode(channelRange.substring(0, dashIndex).trim()); 284 mEndId = Integer.decode(channelRange.substring(dashIndex + 1).trim()); 285 } else { 286 // Not a range, only a single id 287 mStartId = mEndId = Integer.decode(channelRange); 288 } 289 } 290 291 @Override toString()292 public String toString() { 293 return "Range:[channels=" + mStartId + "-" + mEndId + ",emergency level=" 294 + mEmergencyLevel + ",type=" + mAlertType + ",scope=" + mScope + ",vibration=" 295 + Arrays.toString(mVibrationPattern) + ",alertDuration=" + mAlertDuration 296 + ",filter_language=" + mFilterLanguage + ",override_dnd=" + mOverrideDnd 297 + ",display=" + mDisplay + ",testMode=" + mTestMode + ",mAlwaysOn=" 298 + mAlwaysOn + ",ScreenOnDuration=" + mScreenOnDuration + ", displayIcon=" 299 + mDisplayIcon + "dismissOnOutsideTouch=" + mDismissOnOutsideTouch 300 + ", languageCode=" + mLanguageCode + "]"; 301 } 302 } 303 304 /** 305 * Constructor 306 * 307 * @param context Context 308 * @param subId Subscription index 309 */ CellBroadcastChannelManager(Context context, int subId)310 public CellBroadcastChannelManager(Context context, int subId) { 311 mContext = context; 312 mSubId = subId; 313 } 314 315 /** 316 * Get cell broadcast channels enabled by the carriers from resource key 317 * 318 * @param key Resource key 319 * 320 * @return The list of channel ranges enabled by the carriers. 321 */ getCellBroadcastChannelRanges(int key)322 public @NonNull ArrayList<CellBroadcastChannelRange> getCellBroadcastChannelRanges(int key) { 323 ArrayList<CellBroadcastChannelRange> result = new ArrayList<>(); 324 String[] ranges = 325 CellBroadcastSettings.getResources(mContext, mSubId).getStringArray(key); 326 if (ranges != null) { 327 for (String range : ranges) { 328 try { 329 result.add(new CellBroadcastChannelRange(mContext, mSubId, range)); 330 } catch (Exception e) { 331 loge("Failed to parse \"" + range + "\". e=" + e); 332 } 333 } 334 } 335 return result; 336 } 337 338 /** 339 * Get all cell broadcast channels 340 * 341 * @return all cell broadcast channels 342 */ getAllCellBroadcastChannelRanges()343 public @NonNull ArrayList<CellBroadcastChannelRange> getAllCellBroadcastChannelRanges() { 344 synchronized(channelRangesLock) { 345 if (sAllCellBroadcastChannelRanges != null) return sAllCellBroadcastChannelRanges; 346 347 Log.d(TAG, "Create new channel range list"); 348 ArrayList<CellBroadcastChannelRange> result = new ArrayList<>(); 349 350 for (int key : sCellBroadcastRangeResourceKeys) { 351 result.addAll(getCellBroadcastChannelRanges(key)); 352 } 353 354 sAllCellBroadcastChannelRanges = result; 355 return result; 356 } 357 } 358 359 /** 360 * Clear broadcast channel range list 361 */ clearAllCellBroadcastChannelRanges()362 public static void clearAllCellBroadcastChannelRanges() { 363 synchronized(channelRangesLock) { 364 if (sAllCellBroadcastChannelRanges != null) { 365 Log.d(TAG, "Clear channel range list"); 366 sAllCellBroadcastChannelRanges = null; 367 } 368 } 369 } 370 371 /** 372 * @param channel Cell broadcast message channel 373 * @param key Resource key 374 * 375 * @return {@code TRUE} if the input channel is within the channel range defined from resource. 376 * return {@code FALSE} otherwise 377 */ checkCellBroadcastChannelRange(int channel, int key)378 public boolean checkCellBroadcastChannelRange(int channel, int key) { 379 ArrayList<CellBroadcastChannelRange> ranges = getCellBroadcastChannelRanges(key); 380 381 for (CellBroadcastChannelRange range : ranges) { 382 if (channel >= range.mStartId && channel <= range.mEndId) { 383 return checkScope(range.mScope); 384 } 385 } 386 387 return false; 388 } 389 390 /** 391 * Check if the channel scope matches the current network condition. 392 * 393 * @param rangeScope Range scope. Must be SCOPE_CARRIER, SCOPE_DOMESTIC, or SCOPE_INTERNATIONAL. 394 * @return True if the scope matches the current network roaming condition. 395 */ checkScope(int rangeScope)396 public boolean checkScope(int rangeScope) { 397 if (rangeScope == CellBroadcastChannelRange.SCOPE_UNKNOWN) return true; 398 TelephonyManager tm = 399 (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); 400 tm = tm.createForSubscriptionId(mSubId); 401 ServiceState ss = tm.getServiceState(); 402 if (ss != null) { 403 NetworkRegistrationInfo regInfo = ss.getNetworkRegistrationInfo( 404 NetworkRegistrationInfo.DOMAIN_CS, 405 AccessNetworkConstants.TRANSPORT_TYPE_WWAN); 406 if (regInfo != null) { 407 if (regInfo.getRegistrationState() 408 == NetworkRegistrationInfo.REGISTRATION_STATE_HOME 409 || regInfo.getRegistrationState() 410 == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING 411 || regInfo.isEmergencyEnabled()) { 412 int voiceRoamingType = regInfo.getRoamingType(); 413 if (voiceRoamingType == ROAMING_TYPE_NOT_ROAMING) { 414 return true; 415 } else if (voiceRoamingType == ServiceState.ROAMING_TYPE_DOMESTIC 416 && rangeScope == CellBroadcastChannelRange.SCOPE_DOMESTIC) { 417 return true; 418 } else if (voiceRoamingType == ServiceState.ROAMING_TYPE_INTERNATIONAL 419 && rangeScope == CellBroadcastChannelRange.SCOPE_INTERNATIONAL) { 420 return true; 421 } 422 return false; 423 } 424 } 425 } 426 // If we can't determine the scope, for safe we should assume it's in. 427 return true; 428 } 429 430 /** 431 * Return corresponding cellbroadcast range where message belong to 432 * 433 * @param message Cell broadcast message 434 */ getCellBroadcastChannelRangeFromMessage(SmsCbMessage message)435 public CellBroadcastChannelRange getCellBroadcastChannelRangeFromMessage(SmsCbMessage message) { 436 if (mSubId != message.getSubscriptionId()) { 437 Log.e(TAG, "getCellBroadcastChannelRangeFromMessage: This manager is created for " 438 + "sub " + mSubId + ", should not be used for message from sub " 439 + message.getSubscriptionId()); 440 } 441 442 int channel = message.getServiceCategory(); 443 ArrayList<CellBroadcastChannelRange> ranges = null; 444 445 for (int key : sCellBroadcastRangeResourceKeys) { 446 if (checkCellBroadcastChannelRange(channel, key)) { 447 ranges = getCellBroadcastChannelRanges(key); 448 break; 449 } 450 } 451 if (ranges != null) { 452 for (CellBroadcastChannelRange range : ranges) { 453 if (range.mStartId <= message.getServiceCategory() 454 && range.mEndId >= message.getServiceCategory()) { 455 return range; 456 } 457 } 458 } 459 return null; 460 } 461 462 /** 463 * Check if the cell broadcast message is an emergency message or not 464 * 465 * @param message Cell broadcast message 466 * @return True if the message is an emergency message, otherwise false. 467 */ isEmergencyMessage(SmsCbMessage message)468 public boolean isEmergencyMessage(SmsCbMessage message) { 469 if (message == null) { 470 return false; 471 } 472 473 if (mSubId != message.getSubscriptionId()) { 474 Log.e(TAG, "This manager is created for sub " + mSubId 475 + ", should not be used for message from sub " + message.getSubscriptionId()); 476 } 477 478 int id = message.getServiceCategory(); 479 480 for (int key : sCellBroadcastRangeResourceKeys) { 481 ArrayList<CellBroadcastChannelRange> ranges = 482 getCellBroadcastChannelRanges(key); 483 for (CellBroadcastChannelRange range : ranges) { 484 if (range.mStartId <= id && range.mEndId >= id) { 485 switch (range.mEmergencyLevel) { 486 case CellBroadcastChannelRange.LEVEL_EMERGENCY: 487 Log.d(TAG, "isEmergencyMessage: true, message id = " + id); 488 return true; 489 case CellBroadcastChannelRange.LEVEL_NOT_EMERGENCY: 490 Log.d(TAG, "isEmergencyMessage: false, message id = " + id); 491 return false; 492 case CellBroadcastChannelRange.LEVEL_UNKNOWN: 493 default: 494 break; 495 } 496 break; 497 } 498 } 499 } 500 501 Log.d(TAG, "isEmergencyMessage: " + message.isEmergencyMessage() 502 + ", message id = " + id); 503 // If the configuration does not specify whether the alert is emergency or not, use the 504 // emergency property from the message itself, which is checking if the channel is between 505 // MESSAGE_ID_PWS_FIRST_IDENTIFIER (4352) and MESSAGE_ID_PWS_LAST_IDENTIFIER (6399). 506 return message.isEmergencyMessage(); 507 } 508 loge(String msg)509 private static void loge(String msg) { 510 Log.e(TAG, msg); 511 } 512 } 513