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