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 android.content.Context; 20 import android.telephony.CellBroadcastMessage; 21 import android.telephony.ServiceState; 22 import android.telephony.SmsManager; 23 import android.telephony.TelephonyManager; 24 import android.util.Log; 25 26 import com.android.cellbroadcastreceiver.CellBroadcastAlertService.AlertType; 27 import com.android.internal.util.ArrayUtils; 28 29 import java.util.ArrayList; 30 import java.util.Arrays; 31 import java.util.List; 32 33 /** 34 * CellBroadcastChannelManager handles the additional cell broadcast channels that 35 * carriers might enable through resources. 36 * Syntax: "<channel id range>:[type=<alert type>], [emergency=true/false]" 37 * For example, 38 * <string-array name="additional_cbs_channels_strings" translatable="false"> 39 * <item>"43008:type=earthquake, emergency=true"</item> 40 * <item>"0xAFEE:type=tsunami, emergency=true"</item> 41 * <item>"0xAC00-0xAFED:type=other"</item> 42 * <item>"1234-5678"</item> 43 * </string-array> 44 * If no tones are specified, the alert type will be set to DEFAULT. If emergency is not set, 45 * by default it's not emergency. 46 */ 47 public class CellBroadcastChannelManager { 48 49 private static final String TAG = "CBChannelManager"; 50 51 private static CellBroadcastChannelManager sInstance = null; 52 53 private static List<Integer> sCellBroadcastRangeResourceKeys = new ArrayList<>( 54 Arrays.asList(R.array.additional_cbs_channels_strings, 55 R.array.emergency_alerts_channels_range_strings, 56 R.array.cmas_presidential_alerts_channels_range_strings, 57 R.array.cmas_alert_extreme_channels_range_strings, 58 R.array.cmas_alerts_severe_range_strings, 59 R.array.cmas_amber_alerts_channels_range_strings, 60 R.array.required_monthly_test_range_strings, 61 R.array.exercise_alert_range_strings, 62 R.array.operator_defined_alert_range_strings, 63 R.array.etws_alerts_range_strings, 64 R.array.etws_test_alerts_range_strings, 65 R.array.public_safety_messages_channels_range_strings, 66 R.array.state_local_test_alert_range_strings 67 )); 68 69 private static ArrayList<CellBroadcastChannelRange> sAllCellBroadcastChannelRanges = null; 70 71 /** 72 * Cell broadcast channel range 73 * A range is consisted by starting channel id, ending channel id, and the alert type 74 */ 75 public static class CellBroadcastChannelRange { 76 /** Defines the type of the alert. */ 77 private static final String KEY_TYPE = "type"; 78 /** Defines if the alert is emergency. */ 79 private static final String KEY_EMERGENCY = "emergency"; 80 /** Defines the network RAT for the alert. */ 81 private static final String KEY_RAT = "rat"; 82 /** Defines the scope of the alert. */ 83 private static final String KEY_SCOPE = "scope"; 84 /** Defines the vibration pattern of the alert. */ 85 private static final String KEY_VIBRATION = "vibration"; 86 /** 87 * Defines whether the channel needs language filter or not. True indicates that the alert 88 * will only pop-up when the alert's language matches the device's language. 89 */ 90 private static final String KEY_FILTER_LANGUAGE = "filter_language"; 91 92 93 public static final int SCOPE_UNKNOWN = 0; 94 public static final int SCOPE_CARRIER = 1; 95 public static final int SCOPE_DOMESTIC = 2; 96 public static final int SCOPE_INTERNATIONAL = 3; 97 98 public static final int LEVEL_UNKNOWN = 0; 99 public static final int LEVEL_NOT_EMERGENCY = 1; 100 public static final int LEVEL_EMERGENCY = 2; 101 102 public int mStartId; 103 public int mEndId; 104 public AlertType mAlertType; 105 public int mEmergencyLevel; 106 public int mRat; 107 public int mScope; 108 public int[] mVibrationPattern; 109 public boolean mFilterLanguage; 110 CellBroadcastChannelRange(Context context, String channelRange)111 public CellBroadcastChannelRange(Context context, String channelRange) throws Exception { 112 113 mAlertType = AlertType.DEFAULT; 114 mEmergencyLevel = LEVEL_UNKNOWN; 115 mRat = SmsManager.CELL_BROADCAST_RAN_TYPE_GSM; 116 mScope = SCOPE_UNKNOWN; 117 mVibrationPattern = 118 CellBroadcastSettings.getResourcesForDefaultSmsSubscriptionId(context) 119 .getIntArray(R.array.default_vibration_pattern); 120 mFilterLanguage = false; 121 122 int colonIndex = channelRange.indexOf(':'); 123 if (colonIndex != -1) { 124 // Parse the alert type and emergency flag 125 String[] pairs = channelRange.substring(colonIndex + 1).trim().split(","); 126 for (String pair : pairs) { 127 pair = pair.trim(); 128 String[] tokens = pair.split("="); 129 if (tokens.length == 2) { 130 String key = tokens[0].trim(); 131 String value = tokens[1].trim(); 132 if (value == null) continue; 133 switch (key) { 134 case KEY_TYPE: 135 mAlertType = AlertType.valueOf(value.toUpperCase()); 136 break; 137 case KEY_EMERGENCY: 138 if (value.equalsIgnoreCase("true")) { 139 mEmergencyLevel = LEVEL_EMERGENCY; 140 } else if (value.equalsIgnoreCase("false")) { 141 mEmergencyLevel = LEVEL_NOT_EMERGENCY; 142 } 143 break; 144 case KEY_RAT: 145 mRat = value.equalsIgnoreCase("cdma") 146 ? SmsManager.CELL_BROADCAST_RAN_TYPE_CDMA : 147 SmsManager.CELL_BROADCAST_RAN_TYPE_GSM; 148 break; 149 case KEY_SCOPE: 150 if (value.equalsIgnoreCase("carrier")) { 151 mScope = SCOPE_CARRIER; 152 } else if (value.equalsIgnoreCase("domestic")) { 153 mScope = SCOPE_DOMESTIC; 154 } else if (value.equalsIgnoreCase("international")) { 155 mScope = SCOPE_INTERNATIONAL; 156 } 157 break; 158 case KEY_VIBRATION: 159 String[] vibration = value.split("\\|"); 160 if (!ArrayUtils.isEmpty(vibration)) { 161 mVibrationPattern = new int[vibration.length]; 162 for (int i = 0; i < vibration.length; i++) { 163 mVibrationPattern[i] = Integer.parseInt(vibration[i]); 164 } 165 } 166 break; 167 case KEY_FILTER_LANGUAGE: 168 if (value.equalsIgnoreCase("true")) { 169 mFilterLanguage = true; 170 } 171 break; 172 } 173 } 174 } 175 channelRange = channelRange.substring(0, colonIndex).trim(); 176 } 177 178 // Parse the channel range 179 int dashIndex = channelRange.indexOf('-'); 180 if (dashIndex != -1) { 181 // range that has start id and end id 182 mStartId = Integer.decode(channelRange.substring(0, dashIndex).trim()); 183 mEndId = Integer.decode(channelRange.substring(dashIndex + 1).trim()); 184 } else { 185 // Not a range, only a single id 186 mStartId = mEndId = Integer.decode(channelRange); 187 } 188 } 189 190 @Override toString()191 public String toString() { 192 return "Range:[channels=" + mStartId + "-" + mEndId + ",emergency level=" 193 + mEmergencyLevel + ",type=" + mAlertType + ",scope=" + mScope + ",vibration=" 194 + Arrays.toString(mVibrationPattern) + "]"; 195 } 196 } 197 198 /** 199 * Get the instance of the cell broadcast other channel manager 200 * @return The singleton instance 201 */ getInstance()202 public static CellBroadcastChannelManager getInstance() { 203 if (sInstance == null) { 204 sInstance = new CellBroadcastChannelManager(); 205 } 206 return sInstance; 207 } 208 209 /** 210 * Get cell broadcast channels enabled by the carriers from resource key 211 * @param context Application context 212 * @param key Resource key 213 * @return The list of channel ranges enabled by the carriers. 214 */ getCellBroadcastChannelRanges( Context context, int key)215 public static ArrayList<CellBroadcastChannelRange> getCellBroadcastChannelRanges( 216 Context context, int key) { 217 ArrayList<CellBroadcastChannelRange> result = new ArrayList<>(); 218 String[] ranges = 219 CellBroadcastSettings.getResourcesForDefaultSmsSubscriptionId(context) 220 .getStringArray(key); 221 222 if (ranges != null) { 223 for (String range : ranges) { 224 try { 225 result.add(new CellBroadcastChannelRange(context, range)); 226 } catch (Exception e) { 227 loge("Failed to parse \"" + range + "\". e=" + e); 228 } 229 } 230 } 231 232 return result; 233 } 234 235 /** 236 * Get all cell broadcast channels 237 * 238 * @param context Application context 239 * @return all cell broadcast channels 240 */ getAllCellBroadcastChannelRanges( Context context)241 public static ArrayList<CellBroadcastChannelRange> getAllCellBroadcastChannelRanges( 242 Context context) { 243 if (sAllCellBroadcastChannelRanges != null) return sAllCellBroadcastChannelRanges; 244 245 ArrayList<CellBroadcastChannelRange> result = new ArrayList<>(); 246 247 for (int key : sCellBroadcastRangeResourceKeys) { 248 result.addAll(getCellBroadcastChannelRanges(context, key)); 249 } 250 251 sAllCellBroadcastChannelRanges = result; 252 return result; 253 } 254 255 /** 256 * @param subId Subscription index 257 * @param channel Cell broadcast message channel 258 * @param context Application context 259 * @param key Resource key 260 * @return {@code TRUE} if the input channel is within the channel range defined from resource. 261 * return {@code FALSE} otherwise 262 */ checkCellBroadcastChannelRange(int subId, int channel, int key, Context context)263 public static boolean checkCellBroadcastChannelRange(int subId, int channel, int key, 264 Context context) { 265 ArrayList<CellBroadcastChannelRange> ranges = CellBroadcastChannelManager 266 .getInstance().getCellBroadcastChannelRanges(context, key); 267 if (ranges != null) { 268 for (CellBroadcastChannelRange range : ranges) { 269 if (channel >= range.mStartId && channel <= range.mEndId) { 270 return checkScope(context, subId, range.mScope); 271 } 272 } 273 } 274 return false; 275 } 276 277 /** 278 * Check if the channel scope matches the current network condition. 279 * 280 * @param subId Subscription id 281 * @param rangeScope Range scope. Must be SCOPE_CARRIER, SCOPE_DOMESTIC, or SCOPE_INTERNATIONAL. 282 * @return True if the scope matches the current network roaming condition. 283 */ checkScope(Context context, int subId, int rangeScope)284 public static boolean checkScope(Context context, int subId, int rangeScope) { 285 if (rangeScope == CellBroadcastChannelRange.SCOPE_UNKNOWN) return true; 286 if (context != null) { 287 TelephonyManager tm = 288 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 289 ServiceState ss = tm.getServiceStateForSubscriber(subId); 290 if (ss != null) { 291 if (ss.getVoiceRegState() == ServiceState.STATE_IN_SERVICE 292 || ss.getVoiceRegState() == ServiceState.STATE_EMERGENCY_ONLY) { 293 if (ss.getVoiceRoamingType() == ServiceState.ROAMING_TYPE_NOT_ROAMING) { 294 return true; 295 } else if (ss.getVoiceRoamingType() == ServiceState.ROAMING_TYPE_DOMESTIC 296 && rangeScope == CellBroadcastChannelRange.SCOPE_DOMESTIC) { 297 return true; 298 } else if (ss.getVoiceRoamingType() == ServiceState.ROAMING_TYPE_INTERNATIONAL 299 && rangeScope == CellBroadcastChannelRange.SCOPE_INTERNATIONAL) { 300 return true; 301 } 302 return false; 303 } 304 } 305 } 306 // If we can't determine the scope, for safe we should assume it's in. 307 return true; 308 } 309 310 /** 311 * Return corresponding cellbroadcast range where message belong to 312 * @param context Application context 313 * @param message Cell broadcast message 314 */ getCellBroadcastChannelRangeFromMessage( Context context, CellBroadcastMessage message)315 public static CellBroadcastChannelRange getCellBroadcastChannelRangeFromMessage( 316 Context context, CellBroadcastMessage message) { 317 int subId = message.getSubId(); 318 int channel = message.getServiceCategory(); 319 ArrayList<CellBroadcastChannelRange> ranges = null; 320 321 for (int key : sCellBroadcastRangeResourceKeys) { 322 if (checkCellBroadcastChannelRange(subId, channel, key, context)) { 323 ranges = getCellBroadcastChannelRanges(context, key); 324 break; 325 } 326 } 327 if (ranges != null) { 328 for (CellBroadcastChannelRange range : ranges) { 329 if (range.mStartId <= message.getServiceCategory() 330 && range.mEndId >= message.getServiceCategory()) { 331 return range; 332 } 333 } 334 } 335 return null; 336 } 337 338 /** 339 * Check if the cell broadcast message is an emergency message or not 340 * @param context Application context 341 * @param message Cell broadcast message 342 * @return True if the message is an emergency message, otherwise false. 343 */ isEmergencyMessage(Context context, CellBroadcastMessage message)344 public static boolean isEmergencyMessage(Context context, CellBroadcastMessage message) { 345 if (message == null) { 346 return false; 347 } 348 349 int id = message.getServiceCategory(); 350 351 for (int key : sCellBroadcastRangeResourceKeys) { 352 ArrayList<CellBroadcastChannelRange> ranges = 353 getCellBroadcastChannelRanges(context, key); 354 for (CellBroadcastChannelRange range : ranges) { 355 if (range.mStartId <= id && range.mEndId >= id) { 356 switch (range.mEmergencyLevel) { 357 case CellBroadcastChannelRange.LEVEL_EMERGENCY: 358 Log.d(TAG, "isEmergencyMessage: true, message id = " + id); 359 return true; 360 case CellBroadcastChannelRange.LEVEL_NOT_EMERGENCY: 361 Log.d(TAG, "isEmergencyMessage: false, message id = " + id); 362 return false; 363 case CellBroadcastChannelRange.LEVEL_UNKNOWN: 364 default: 365 break; 366 } 367 break; 368 } 369 } 370 } 371 372 Log.d(TAG, "isEmergencyMessage: " + message.isEmergencyAlertMessage() 373 + ", message id = " + id); 374 // If the configuration does not specify whether the alert is emergency or not, use the 375 // emergency property from the message itself, which is checking if the channel is between 376 // MESSAGE_ID_PWS_FIRST_IDENTIFIER (4352) and MESSAGE_ID_PWS_LAST_IDENTIFIER (6399). 377 return message.isEmergencyAlertMessage(); 378 } 379 log(String msg)380 private static void log(String msg) { 381 Log.d(TAG, msg); 382 } 383 loge(String msg)384 private static void loge(String msg) { 385 Log.e(TAG, msg); 386 } 387 } 388