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 )); 67 /** 68 * Cell broadcast channel range 69 * A range is consisted by starting channel id, ending channel id, and the alert type 70 */ 71 public static class CellBroadcastChannelRange { 72 73 private static final String KEY_TYPE = "type"; 74 private static final String KEY_EMERGENCY = "emergency"; 75 private static final String KEY_RAT = "rat"; 76 private static final String KEY_SCOPE = "scope"; 77 private static final String KEY_VIBRATION = "vibration"; 78 79 public static final int SCOPE_UNKNOWN = 0; 80 public static final int SCOPE_CARRIER = 1; 81 public static final int SCOPE_DOMESTIC = 2; 82 public static final int SCOPE_INTERNATIONAL = 3; 83 84 public static final int LEVEL_UNKNOWN = 0; 85 public static final int LEVEL_NOT_EMERGENCY = 1; 86 public static final int LEVEL_EMERGENCY = 2; 87 88 public int mStartId; 89 public int mEndId; 90 public AlertType mAlertType; 91 public int mEmergencyLevel; 92 public int mRat; 93 public int mScope; 94 public int[] mVibrationPattern; 95 CellBroadcastChannelRange(Context context, String channelRange)96 public CellBroadcastChannelRange(Context context, String channelRange) throws Exception { 97 98 mAlertType = AlertType.DEFAULT; 99 mEmergencyLevel = LEVEL_UNKNOWN; 100 mRat = SmsManager.CELL_BROADCAST_RAN_TYPE_GSM; 101 mScope = SCOPE_UNKNOWN; 102 mVibrationPattern = context.getResources().getIntArray( 103 R.array.default_vibration_pattern); 104 105 int colonIndex = channelRange.indexOf(':'); 106 if (colonIndex != -1) { 107 // Parse the alert type and emergency flag 108 String[] pairs = channelRange.substring(colonIndex + 1).trim().split(","); 109 for (String pair : pairs) { 110 pair = pair.trim(); 111 String[] tokens = pair.split("="); 112 if (tokens.length == 2) { 113 String key = tokens[0].trim(); 114 String value = tokens[1].trim(); 115 if (value == null) continue; 116 switch (key) { 117 case KEY_TYPE: 118 mAlertType = AlertType.valueOf(value.toUpperCase()); 119 break; 120 case KEY_EMERGENCY: 121 if (value.equalsIgnoreCase("true")) { 122 mEmergencyLevel = LEVEL_EMERGENCY; 123 } else if (value.equalsIgnoreCase("false")) { 124 mEmergencyLevel = LEVEL_NOT_EMERGENCY; 125 } 126 break; 127 case KEY_RAT: 128 mRat = value.equalsIgnoreCase("cdma") 129 ? SmsManager.CELL_BROADCAST_RAN_TYPE_CDMA : 130 SmsManager.CELL_BROADCAST_RAN_TYPE_GSM; 131 break; 132 case KEY_SCOPE: 133 if (value.equalsIgnoreCase("carrier")) { 134 mScope = SCOPE_CARRIER; 135 } else if (value.equalsIgnoreCase("domestic")) { 136 mScope = SCOPE_DOMESTIC; 137 } else if (value.equalsIgnoreCase("international")) { 138 mScope = SCOPE_INTERNATIONAL; 139 } 140 break; 141 case KEY_VIBRATION: 142 String[] vibration = value.split("\\|"); 143 if (!ArrayUtils.isEmpty(vibration)) { 144 mVibrationPattern = new int[vibration.length]; 145 for (int i = 0; i < vibration.length; i++) { 146 mVibrationPattern[i] = Integer.parseInt(vibration[i]); 147 } 148 } 149 break; 150 } 151 } 152 } 153 channelRange = channelRange.substring(0, colonIndex).trim(); 154 } 155 156 // Parse the channel range 157 int dashIndex = channelRange.indexOf('-'); 158 if (dashIndex != -1) { 159 // range that has start id and end id 160 mStartId = Integer.decode(channelRange.substring(0, dashIndex).trim()); 161 mEndId = Integer.decode(channelRange.substring(dashIndex + 1).trim()); 162 } else { 163 // Not a range, only a single id 164 mStartId = mEndId = Integer.decode(channelRange); 165 } 166 } 167 168 @Override toString()169 public String toString() { 170 return "Range:[channels=" + mStartId + "-" + mEndId + ",emergency level=" 171 + mEmergencyLevel + ",type=" + mAlertType + ",scope=" + mScope + ",vibration=" 172 + Arrays.toString(mVibrationPattern) + "]"; 173 } 174 } 175 176 /** 177 * Get the instance of the cell broadcast other channel manager 178 * @return The singleton instance 179 */ getInstance()180 public static CellBroadcastChannelManager getInstance() { 181 if (sInstance == null) { 182 sInstance = new CellBroadcastChannelManager(); 183 } 184 return sInstance; 185 } 186 187 /** 188 * Get cell broadcast channels enabled by the carriers from resource key 189 * @param context Application context 190 * @param key Resource key 191 * @return The list of channel ranges enabled by the carriers. 192 */ getCellBroadcastChannelRanges( Context context, int key)193 public static ArrayList<CellBroadcastChannelRange> getCellBroadcastChannelRanges( 194 Context context, int key) { 195 ArrayList<CellBroadcastChannelRange> result = new ArrayList<>(); 196 String[] ranges = context.getResources().getStringArray(key); 197 198 if (ranges != null) { 199 for (String range : ranges) { 200 try { 201 result.add(new CellBroadcastChannelRange(context, range)); 202 } catch (Exception e) { 203 loge("Failed to parse \"" + range + "\". e=" + e); 204 } 205 } 206 } 207 208 return result; 209 } 210 211 /** 212 * @param subId Subscription index 213 * @param channel Cell broadcast message channel 214 * @param context Application context 215 * @param key Resource key 216 * @return {@code TRUE} if the input channel is within the channel range defined from resource. 217 * return {@code FALSE} otherwise 218 */ checkCellBroadcastChannelRange(int subId, int channel, int key, Context context)219 public static boolean checkCellBroadcastChannelRange(int subId, int channel, int key, 220 Context context) { 221 ArrayList<CellBroadcastChannelRange> ranges = CellBroadcastChannelManager 222 .getInstance().getCellBroadcastChannelRanges(context, key); 223 if (ranges != null) { 224 for (CellBroadcastChannelRange range : ranges) { 225 if (channel >= range.mStartId && channel <= range.mEndId) { 226 return checkScope(context, subId, range.mScope); 227 } 228 } 229 } 230 return false; 231 } 232 233 /** 234 * Check if the channel scope matches the current network condition. 235 * 236 * @param subId Subscription id 237 * @param rangeScope Range scope. Must be SCOPE_CARRIER, SCOPE_DOMESTIC, or SCOPE_INTERNATIONAL. 238 * @return True if the scope matches the current network roaming condition. 239 */ checkScope(Context context, int subId, int rangeScope)240 public static boolean checkScope(Context context, int subId, int rangeScope) { 241 if (rangeScope == CellBroadcastChannelRange.SCOPE_UNKNOWN) return true; 242 if (context != null) { 243 TelephonyManager tm = 244 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 245 ServiceState ss = tm.getServiceStateForSubscriber(subId); 246 if (ss != null) { 247 if (ss.getVoiceRegState() == ServiceState.STATE_IN_SERVICE 248 || ss.getVoiceRegState() == ServiceState.STATE_EMERGENCY_ONLY) { 249 if (ss.getVoiceRoamingType() == ServiceState.ROAMING_TYPE_NOT_ROAMING) { 250 return true; 251 } else if (ss.getVoiceRoamingType() == ServiceState.ROAMING_TYPE_DOMESTIC 252 && rangeScope == CellBroadcastChannelRange.SCOPE_DOMESTIC) { 253 return true; 254 } else if (ss.getVoiceRoamingType() == ServiceState.ROAMING_TYPE_INTERNATIONAL 255 && rangeScope == CellBroadcastChannelRange.SCOPE_INTERNATIONAL) { 256 return true; 257 } 258 return false; 259 } 260 } 261 } 262 // If we can't determine the scope, for safe we should assume it's in. 263 return true; 264 } 265 266 /** 267 * Return corresponding cellbroadcast range where message belong to 268 * @param context Application context 269 * @param message Cell broadcast message 270 */ getCellBroadcastChannelRangeFromMessage( Context context, CellBroadcastMessage message)271 public static CellBroadcastChannelRange getCellBroadcastChannelRangeFromMessage( 272 Context context, CellBroadcastMessage message) { 273 int subId = message.getSubId(); 274 int channel = message.getServiceCategory(); 275 ArrayList<CellBroadcastChannelRange> ranges = null; 276 277 for (int key : sCellBroadcastRangeResourceKeys) { 278 if (checkCellBroadcastChannelRange(subId, channel, key, context)) { 279 ranges = getCellBroadcastChannelRanges(context, key); 280 break; 281 } 282 } 283 if (ranges != null) { 284 for (CellBroadcastChannelRange range : ranges) { 285 if (range.mStartId <= message.getServiceCategory() 286 && range.mEndId >= message.getServiceCategory()) { 287 return range; 288 } 289 } 290 } 291 return null; 292 } 293 294 /** 295 * Check if the cell broadcast message is an emergency message or not 296 * @param context Application context 297 * @param message Cell broadcast message 298 * @return True if the message is an emergency message, otherwise false. 299 */ isEmergencyMessage(Context context, CellBroadcastMessage message)300 public static boolean isEmergencyMessage(Context context, CellBroadcastMessage message) { 301 if (message == null) { 302 return false; 303 } 304 305 int id = message.getServiceCategory(); 306 307 for (int key : sCellBroadcastRangeResourceKeys) { 308 ArrayList<CellBroadcastChannelRange> ranges = 309 getCellBroadcastChannelRanges(context, key); 310 for (CellBroadcastChannelRange range : ranges) { 311 if (range.mStartId <= id && range.mEndId >= id) { 312 switch (range.mEmergencyLevel) { 313 case CellBroadcastChannelRange.LEVEL_EMERGENCY: 314 Log.d(TAG, "isEmergencyMessage: true, message id = " + id); 315 return true; 316 case CellBroadcastChannelRange.LEVEL_NOT_EMERGENCY: 317 Log.d(TAG, "isEmergencyMessage: false, message id = " + id); 318 return false; 319 case CellBroadcastChannelRange.LEVEL_UNKNOWN: 320 default: 321 break; 322 } 323 break; 324 } 325 } 326 } 327 328 Log.d(TAG, "isEmergencyMessage: " + message.isEmergencyAlertMessage() 329 + ", message id = " + id); 330 // If the configuration does not specify whether the alert is emergency or not, use the 331 // emergency property from the message itself, which is checking if the channel is between 332 // MESSAGE_ID_PWS_FIRST_IDENTIFIER (4352) and MESSAGE_ID_PWS_LAST_IDENTIFIER (6399). 333 return message.isEmergencyAlertMessage(); 334 } 335 log(String msg)336 private static void log(String msg) { 337 Log.d(TAG, msg); 338 } 339 loge(String msg)340 private static void loge(String msg) { 341 Log.e(TAG, msg); 342 } 343 } 344