• 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             ));
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