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