• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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