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 72 private final Context mContext; 73 74 private final int mSubId; 75 76 /** 77 * Cell broadcast channel range 78 * A range is consisted by starting channel id, ending channel id, and the alert type 79 */ 80 public static class CellBroadcastChannelRange { 81 /** Defines the type of the alert. */ 82 private static final String KEY_TYPE = "type"; 83 /** Defines if the alert is emergency. */ 84 private static final String KEY_EMERGENCY = "emergency"; 85 /** Defines the network RAT for the alert. */ 86 private static final String KEY_RAT = "rat"; 87 /** Defines the scope of the alert. */ 88 private static final String KEY_SCOPE = "scope"; 89 /** Defines the vibration pattern of the alert. */ 90 private static final String KEY_VIBRATION = "vibration"; 91 /** Defines the duration of the alert. */ 92 private static final String KEY_ALERT_DURATION = "alert_duration"; 93 /** Defines if Do Not Disturb should be overridden for this alert */ 94 private static final String KEY_OVERRIDE_DND = "override_dnd"; 95 /** Defines whether writing alert message should exclude from SMS inbox. */ 96 private static final String KEY_EXCLUDE_FROM_SMS_INBOX = "exclude_from_sms_inbox"; 97 98 /** 99 * Defines whether the channel needs language filter or not. True indicates that the alert 100 * will only pop-up when the alert's language matches the device's language. 101 */ 102 private static final String KEY_FILTER_LANGUAGE = "filter_language"; 103 104 105 public static final int SCOPE_UNKNOWN = 0; 106 public static final int SCOPE_CARRIER = 1; 107 public static final int SCOPE_DOMESTIC = 2; 108 public static final int SCOPE_INTERNATIONAL = 3; 109 110 public static final int LEVEL_UNKNOWN = 0; 111 public static final int LEVEL_NOT_EMERGENCY = 1; 112 public static final int LEVEL_EMERGENCY = 2; 113 114 public int mStartId; 115 public int mEndId; 116 public AlertType mAlertType; 117 public int mEmergencyLevel; 118 public int mRanType; 119 public int mScope; 120 public int[] mVibrationPattern; 121 public boolean mFilterLanguage; 122 // by default no custom alert duration. play the alert tone with the tone's duration. 123 public int mAlertDuration = -1; 124 public boolean mOverrideDnd = false; 125 // If enable_write_alerts_to_sms_inbox is true, write to sms inbox is enabled by default 126 // for all channels except for channels which explicitly set to exclude from sms inbox. 127 public boolean mWriteToSmsInbox = true; 128 CellBroadcastChannelRange(Context context, int subId, String channelRange)129 public CellBroadcastChannelRange(Context context, int subId, String channelRange) { 130 131 mAlertType = AlertType.DEFAULT; 132 mEmergencyLevel = LEVEL_UNKNOWN; 133 mRanType = SmsCbMessage.MESSAGE_FORMAT_3GPP; 134 mScope = SCOPE_UNKNOWN; 135 mVibrationPattern = 136 CellBroadcastSettings.getResources(context, subId) 137 .getIntArray(R.array.default_vibration_pattern); 138 mFilterLanguage = false; 139 140 int colonIndex = channelRange.indexOf(':'); 141 if (colonIndex != -1) { 142 // Parse the alert type and emergency flag 143 String[] pairs = channelRange.substring(colonIndex + 1).trim().split(","); 144 for (String pair : pairs) { 145 pair = pair.trim(); 146 String[] tokens = pair.split("="); 147 if (tokens.length == 2) { 148 String key = tokens[0].trim(); 149 String value = tokens[1].trim(); 150 switch (key) { 151 case KEY_TYPE: 152 mAlertType = AlertType.valueOf(value.toUpperCase()); 153 break; 154 case KEY_EMERGENCY: 155 if (value.equalsIgnoreCase("true")) { 156 mEmergencyLevel = LEVEL_EMERGENCY; 157 } else if (value.equalsIgnoreCase("false")) { 158 mEmergencyLevel = LEVEL_NOT_EMERGENCY; 159 } 160 break; 161 case KEY_RAT: 162 mRanType = value.equalsIgnoreCase("cdma") 163 ? SmsCbMessage.MESSAGE_FORMAT_3GPP2 : 164 SmsCbMessage.MESSAGE_FORMAT_3GPP; 165 break; 166 case KEY_SCOPE: 167 if (value.equalsIgnoreCase("carrier")) { 168 mScope = SCOPE_CARRIER; 169 } else if (value.equalsIgnoreCase("domestic")) { 170 mScope = SCOPE_DOMESTIC; 171 } else if (value.equalsIgnoreCase("international")) { 172 mScope = SCOPE_INTERNATIONAL; 173 } 174 break; 175 case KEY_VIBRATION: 176 String[] vibration = value.split("\\|"); 177 if (vibration.length > 0) { 178 mVibrationPattern = new int[vibration.length]; 179 for (int i = 0; i < vibration.length; i++) { 180 mVibrationPattern[i] = Integer.parseInt(vibration[i]); 181 } 182 } 183 break; 184 case KEY_FILTER_LANGUAGE: 185 if (value.equalsIgnoreCase("true")) { 186 mFilterLanguage = true; 187 } 188 break; 189 case KEY_ALERT_DURATION: 190 mAlertDuration = Integer.parseInt(value); 191 break; 192 case KEY_OVERRIDE_DND: 193 if (value.equalsIgnoreCase("true")) { 194 mOverrideDnd = true; 195 } 196 break; 197 case KEY_EXCLUDE_FROM_SMS_INBOX: 198 if (value.equalsIgnoreCase("true")) { 199 mWriteToSmsInbox = false; 200 } 201 break; 202 } 203 } 204 } 205 channelRange = channelRange.substring(0, colonIndex).trim(); 206 } 207 208 // Parse the channel range 209 int dashIndex = channelRange.indexOf('-'); 210 if (dashIndex != -1) { 211 // range that has start id and end id 212 mStartId = Integer.decode(channelRange.substring(0, dashIndex).trim()); 213 mEndId = Integer.decode(channelRange.substring(dashIndex + 1).trim()); 214 } else { 215 // Not a range, only a single id 216 mStartId = mEndId = Integer.decode(channelRange); 217 } 218 } 219 220 @Override toString()221 public String toString() { 222 return "Range:[channels=" + mStartId + "-" + mEndId + ",emergency level=" 223 + mEmergencyLevel + ",type=" + mAlertType + ",scope=" + mScope + ",vibration=" 224 + Arrays.toString(mVibrationPattern) + ",alertDuration=" + mAlertDuration 225 + ",filter_language=" + mFilterLanguage + ",override_dnd=" + mOverrideDnd + "]"; 226 } 227 } 228 229 /** 230 * Constructor 231 * 232 * @param context Context 233 * @param subId Subscription index 234 */ CellBroadcastChannelManager(Context context, int subId)235 public CellBroadcastChannelManager(Context context, int subId) { 236 mContext = context; 237 mSubId = subId; 238 } 239 240 /** 241 * Get cell broadcast channels enabled by the carriers from resource key 242 * 243 * @param key Resource key 244 * 245 * @return The list of channel ranges enabled by the carriers. 246 */ getCellBroadcastChannelRanges(int key)247 public @NonNull ArrayList<CellBroadcastChannelRange> getCellBroadcastChannelRanges(int key) { 248 ArrayList<CellBroadcastChannelRange> result = new ArrayList<>(); 249 String[] ranges = 250 CellBroadcastSettings.getResources(mContext, mSubId).getStringArray(key); 251 252 for (String range : ranges) { 253 try { 254 result.add(new CellBroadcastChannelRange(mContext, mSubId, range)); 255 } catch (Exception e) { 256 loge("Failed to parse \"" + range + "\". e=" + e); 257 } 258 } 259 260 return result; 261 } 262 263 /** 264 * Get all cell broadcast channels 265 * 266 * @return all cell broadcast channels 267 */ getAllCellBroadcastChannelRanges()268 public @NonNull ArrayList<CellBroadcastChannelRange> getAllCellBroadcastChannelRanges() { 269 if (sAllCellBroadcastChannelRanges != null) return sAllCellBroadcastChannelRanges; 270 271 ArrayList<CellBroadcastChannelRange> result = new ArrayList<>(); 272 273 for (int key : sCellBroadcastRangeResourceKeys) { 274 result.addAll(getCellBroadcastChannelRanges(key)); 275 } 276 277 sAllCellBroadcastChannelRanges = result; 278 return result; 279 } 280 281 /** 282 * @param channel Cell broadcast message channel 283 * @param key Resource key 284 * 285 * @return {@code TRUE} if the input channel is within the channel range defined from resource. 286 * return {@code FALSE} otherwise 287 */ checkCellBroadcastChannelRange(int channel, int key)288 public boolean checkCellBroadcastChannelRange(int channel, int key) { 289 ArrayList<CellBroadcastChannelRange> ranges = getCellBroadcastChannelRanges(key); 290 291 for (CellBroadcastChannelRange range : ranges) { 292 if (channel >= range.mStartId && channel <= range.mEndId) { 293 return checkScope(range.mScope); 294 } 295 } 296 297 return false; 298 } 299 300 /** 301 * Check if the channel scope matches the current network condition. 302 * 303 * @param rangeScope Range scope. Must be SCOPE_CARRIER, SCOPE_DOMESTIC, or SCOPE_INTERNATIONAL. 304 * @return True if the scope matches the current network roaming condition. 305 */ checkScope(int rangeScope)306 public boolean checkScope(int rangeScope) { 307 if (rangeScope == CellBroadcastChannelRange.SCOPE_UNKNOWN) return true; 308 TelephonyManager tm = 309 (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); 310 tm = tm.createForSubscriptionId(mSubId); 311 ServiceState ss = tm.getServiceState(); 312 if (ss != null) { 313 NetworkRegistrationInfo regInfo = ss.getNetworkRegistrationInfo( 314 NetworkRegistrationInfo.DOMAIN_CS, 315 AccessNetworkConstants.TRANSPORT_TYPE_WWAN); 316 if (regInfo != null) { 317 if (regInfo.getRegistrationState() 318 == NetworkRegistrationInfo.REGISTRATION_STATE_HOME 319 || regInfo.getRegistrationState() 320 == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING 321 || regInfo.isEmergencyEnabled()) { 322 int voiceRoamingType = regInfo.getRoamingType(); 323 if (voiceRoamingType == ROAMING_TYPE_NOT_ROAMING) { 324 return true; 325 } else if (voiceRoamingType == ServiceState.ROAMING_TYPE_DOMESTIC 326 && rangeScope == CellBroadcastChannelRange.SCOPE_DOMESTIC) { 327 return true; 328 } else if (voiceRoamingType == ServiceState.ROAMING_TYPE_INTERNATIONAL 329 && rangeScope == CellBroadcastChannelRange.SCOPE_INTERNATIONAL) { 330 return true; 331 } 332 return false; 333 } 334 } 335 } 336 // If we can't determine the scope, for safe we should assume it's in. 337 return true; 338 } 339 340 /** 341 * Return corresponding cellbroadcast range where message belong to 342 * 343 * @param message Cell broadcast message 344 */ getCellBroadcastChannelRangeFromMessage(SmsCbMessage message)345 public CellBroadcastChannelRange getCellBroadcastChannelRangeFromMessage(SmsCbMessage message) { 346 if (mSubId != message.getSubscriptionId()) { 347 Log.e(TAG, "getCellBroadcastChannelRangeFromMessage: This manager is created for " 348 + "sub " + mSubId + ", should not be used for message from sub " 349 + message.getSubscriptionId()); 350 } 351 352 int channel = message.getServiceCategory(); 353 ArrayList<CellBroadcastChannelRange> ranges = null; 354 355 for (int key : sCellBroadcastRangeResourceKeys) { 356 if (checkCellBroadcastChannelRange(channel, key)) { 357 ranges = getCellBroadcastChannelRanges(key); 358 break; 359 } 360 } 361 if (ranges != null) { 362 for (CellBroadcastChannelRange range : ranges) { 363 if (range.mStartId <= message.getServiceCategory() 364 && range.mEndId >= message.getServiceCategory()) { 365 return range; 366 } 367 } 368 } 369 return null; 370 } 371 372 /** 373 * Check if the cell broadcast message is an emergency message or not 374 * 375 * @param message Cell broadcast message 376 * @return True if the message is an emergency message, otherwise false. 377 */ isEmergencyMessage(SmsCbMessage message)378 public boolean isEmergencyMessage(SmsCbMessage message) { 379 if (message == null) { 380 return false; 381 } 382 383 if (mSubId != message.getSubscriptionId()) { 384 Log.e(TAG, "This manager is created for sub " + mSubId 385 + ", should not be used for message from sub " + message.getSubscriptionId()); 386 } 387 388 int id = message.getServiceCategory(); 389 390 for (int key : sCellBroadcastRangeResourceKeys) { 391 ArrayList<CellBroadcastChannelRange> ranges = 392 getCellBroadcastChannelRanges(key); 393 for (CellBroadcastChannelRange range : ranges) { 394 if (range.mStartId <= id && range.mEndId >= id) { 395 switch (range.mEmergencyLevel) { 396 case CellBroadcastChannelRange.LEVEL_EMERGENCY: 397 Log.d(TAG, "isEmergencyMessage: true, message id = " + id); 398 return true; 399 case CellBroadcastChannelRange.LEVEL_NOT_EMERGENCY: 400 Log.d(TAG, "isEmergencyMessage: false, message id = " + id); 401 return false; 402 case CellBroadcastChannelRange.LEVEL_UNKNOWN: 403 default: 404 break; 405 } 406 break; 407 } 408 } 409 } 410 411 Log.d(TAG, "isEmergencyMessage: " + message.isEmergencyMessage() 412 + ", message id = " + id); 413 // If the configuration does not specify whether the alert is emergency or not, use the 414 // emergency property from the message itself, which is checking if the channel is between 415 // MESSAGE_ID_PWS_FIRST_IDENTIFIER (4352) and MESSAGE_ID_PWS_LAST_IDENTIFIER (6399). 416 return message.isEmergencyMessage(); 417 } 418 log(String msg)419 private static void log(String msg) { 420 Log.d(TAG, msg); 421 } 422 loge(String msg)423 private static void loge(String msg) { 424 Log.e(TAG, msg); 425 } 426 } 427