1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * 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, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 
17 package com.android.cellbroadcastreceiver;
18 
19 import android.content.Context;
20 import android.graphics.Typeface;
21 import android.telephony.SmsCbCmasInfo;
22 import android.telephony.SmsCbEtwsInfo;
23 import android.telephony.SmsCbMessage;
24 import android.text.Spannable;
25 import android.text.SpannableStringBuilder;
26 import android.text.TextUtils;
27 import android.text.style.StyleSpan;
28 
29 import com.android.cellbroadcastreceiver.CellBroadcastChannelManager.CellBroadcastChannelRange;
30 
31 import java.text.DateFormat;
32 import java.util.ArrayList;
33 
34 /**
35  * Returns the string resource ID's for CMAS and ETWS emergency alerts.
36  */
37 public class CellBroadcastResources {
38 
CellBroadcastResources()39     private CellBroadcastResources() {
40     }
41 
42     /**
43      * Returns a styled CharSequence containing the message date/time and alert details.
44      * @param context a Context for resource string access
45      * @param showDebugInfo {@code true} if adding more information for debugging purposes.
46      * @param message The cell broadcast message.
47      * @param locationCheckTime The EPOCH time in milliseconds that Device-based Geo-fencing (DBGF)
48      * was last performed. 0 if the message does not have DBGF information.
49      * @param isDisplayed {@code true} if the message is displayed to the user.
50      * @param geometry Geometry string for device-based geo-fencing message.
51      *
52      * @return a CharSequence for display in the broadcast alert dialog
53      */
getMessageDetails(Context context, boolean showDebugInfo, SmsCbMessage message, long locationCheckTime, boolean isDisplayed, String geometry)54     public static CharSequence getMessageDetails(Context context, boolean showDebugInfo,
55                                                  SmsCbMessage message, long locationCheckTime,
56                                                  boolean isDisplayed, String geometry) {
57         SpannableStringBuilder buf = new SpannableStringBuilder();
58         // Alert date/time
59         appendMessageDetail(context, buf, R.string.delivery_time_heading,
60                 DateFormat.getDateTimeInstance().format(message.getReceivedTime()));
61 
62         // Message id
63         if (showDebugInfo) {
64             appendMessageDetail(context, buf, R.string.message_identifier,
65                     Integer.toString(message.getServiceCategory()));
66             appendMessageDetail(context, buf, R.string.message_serial_number,
67                     Integer.toString(message.getSerialNumber()));
68         }
69 
70         if (message.isCmasMessage()) {
71             // CMAS category, response type, severity, urgency, certainty
72             appendCmasAlertDetails(context, buf, message.getCmasWarningInfo());
73         }
74 
75         if (showDebugInfo) {
76             appendMessageDetail(context, buf, R.string.data_coding_scheme,
77                     Integer.toString(message.getDataCodingScheme()));
78 
79             appendMessageDetail(context, buf, R.string.message_content, message.getMessageBody());
80 
81             appendMessageDetail(context, buf, R.string.location_check_time, locationCheckTime == -1
82                     ? "N/A"
83                     : DateFormat.getDateTimeInstance().format(locationCheckTime));
84 
85             appendMessageDetail(context, buf, R.string.maximum_waiting_time,
86                     message.getMaximumWaitingDuration() + " "
87                             + context.getString(R.string.seconds));
88 
89             appendMessageDetail(context, buf, R.string.message_displayed,
90                     Boolean.toString(isDisplayed));
91 
92             appendMessageDetail(context, buf, R.string.message_coordinates,
93                     TextUtils.isEmpty(geometry) ? "N/A" : geometry);
94         }
95 
96         return buf;
97     }
98 
appendCmasAlertDetails(Context context, SpannableStringBuilder buf, SmsCbCmasInfo cmasInfo)99     private static void appendCmasAlertDetails(Context context, SpannableStringBuilder buf,
100             SmsCbCmasInfo cmasInfo) {
101         // CMAS category
102         int categoryId = getCmasCategoryResId(cmasInfo);
103         if (categoryId != 0) {
104             appendMessageDetail(context, buf, R.string.cmas_category_heading,
105                     context.getString(categoryId));
106         }
107 
108         // CMAS response type
109         int responseId = getCmasResponseResId(cmasInfo);
110         if (responseId != 0) {
111             appendMessageDetail(context, buf, R.string.cmas_response_heading,
112                     context.getString(responseId));
113         }
114 
115         // CMAS severity
116         int severityId = getCmasSeverityResId(cmasInfo);
117         if (severityId != 0) {
118             appendMessageDetail(context, buf, R.string.cmas_severity_heading,
119                     context.getString(severityId));
120         }
121 
122         // CMAS urgency
123         int urgencyId = getCmasUrgencyResId(cmasInfo);
124         if (urgencyId != 0) {
125             appendMessageDetail(context, buf, R.string.cmas_urgency_heading,
126                     context.getString(urgencyId));
127         }
128 
129         // CMAS certainty
130         int certaintyId = getCmasCertaintyResId(cmasInfo);
131         if (certaintyId != 0) {
132             appendMessageDetail(context, buf, R.string.cmas_certainty_heading,
133                     context.getString(certaintyId));
134         }
135     }
136 
appendMessageDetail(Context context, SpannableStringBuilder buf, int typeId, String value)137     private static void appendMessageDetail(Context context, SpannableStringBuilder buf,
138                                            int typeId, String value) {
139         if (buf.length() != 0) {
140             buf.append("\n");
141         }
142         int start = buf.length();
143         buf.append(context.getString(typeId));
144         int end = buf.length();
145         buf.setSpan(new StyleSpan(Typeface.BOLD), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
146         buf.append(" ");
147         buf.append(value);
148     }
149 
150     /**
151      * Returns the string resource ID for the CMAS category.
152      * @return a string resource ID, or 0 if the CMAS category is unknown or not present
153      */
getCmasCategoryResId(SmsCbCmasInfo cmasInfo)154     private static int getCmasCategoryResId(SmsCbCmasInfo cmasInfo) {
155         switch (cmasInfo.getCategory()) {
156             case SmsCbCmasInfo.CMAS_CATEGORY_GEO:
157                 return R.string.cmas_category_geo;
158 
159             case SmsCbCmasInfo.CMAS_CATEGORY_MET:
160                 return R.string.cmas_category_met;
161 
162             case SmsCbCmasInfo.CMAS_CATEGORY_SAFETY:
163                 return R.string.cmas_category_safety;
164 
165             case SmsCbCmasInfo.CMAS_CATEGORY_SECURITY:
166                 return R.string.cmas_category_security;
167 
168             case SmsCbCmasInfo.CMAS_CATEGORY_RESCUE:
169                 return R.string.cmas_category_rescue;
170 
171             case SmsCbCmasInfo.CMAS_CATEGORY_FIRE:
172                 return R.string.cmas_category_fire;
173 
174             case SmsCbCmasInfo.CMAS_CATEGORY_HEALTH:
175                 return R.string.cmas_category_health;
176 
177             case SmsCbCmasInfo.CMAS_CATEGORY_ENV:
178                 return R.string.cmas_category_env;
179 
180             case SmsCbCmasInfo.CMAS_CATEGORY_TRANSPORT:
181                 return R.string.cmas_category_transport;
182 
183             case SmsCbCmasInfo.CMAS_CATEGORY_INFRA:
184                 return R.string.cmas_category_infra;
185 
186             case SmsCbCmasInfo.CMAS_CATEGORY_CBRNE:
187                 return R.string.cmas_category_cbrne;
188 
189             case SmsCbCmasInfo.CMAS_CATEGORY_OTHER:
190                 return R.string.cmas_category_other;
191 
192             default:
193                 return 0;
194         }
195     }
196 
197     /**
198      * Returns the string resource ID for the CMAS response type.
199      * @return a string resource ID, or 0 if the CMAS response type is unknown or not present
200      */
getCmasResponseResId(SmsCbCmasInfo cmasInfo)201     private static int getCmasResponseResId(SmsCbCmasInfo cmasInfo) {
202         switch (cmasInfo.getResponseType()) {
203             case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_SHELTER:
204                 return R.string.cmas_response_shelter;
205 
206             case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_EVACUATE:
207                 return R.string.cmas_response_evacuate;
208 
209             case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_PREPARE:
210                 return R.string.cmas_response_prepare;
211 
212             case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_EXECUTE:
213                 return R.string.cmas_response_execute;
214 
215             case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_MONITOR:
216                 return R.string.cmas_response_monitor;
217 
218             case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_AVOID:
219                 return R.string.cmas_response_avoid;
220 
221             case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_ASSESS:
222                 return R.string.cmas_response_assess;
223 
224             case SmsCbCmasInfo.CMAS_RESPONSE_TYPE_NONE:
225                 return R.string.cmas_response_none;
226 
227             default:
228                 return 0;
229         }
230     }
231 
232     /**
233      * Returns the string resource ID for the CMAS severity.
234      * @return a string resource ID, or 0 if the CMAS severity is unknown or not present
235      */
getCmasSeverityResId(SmsCbCmasInfo cmasInfo)236     private static int getCmasSeverityResId(SmsCbCmasInfo cmasInfo) {
237         switch (cmasInfo.getSeverity()) {
238             case SmsCbCmasInfo.CMAS_SEVERITY_EXTREME:
239                 return R.string.cmas_severity_extreme;
240 
241             case SmsCbCmasInfo.CMAS_SEVERITY_SEVERE:
242                 return R.string.cmas_severity_severe;
243 
244             default:
245                 return 0;
246         }
247     }
248 
249     /**
250      * Returns the string resource ID for the CMAS urgency.
251      * @return a string resource ID, or 0 if the CMAS urgency is unknown or not present
252      */
getCmasUrgencyResId(SmsCbCmasInfo cmasInfo)253     private static int getCmasUrgencyResId(SmsCbCmasInfo cmasInfo) {
254         switch (cmasInfo.getUrgency()) {
255             case SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE:
256                 return R.string.cmas_urgency_immediate;
257 
258             case SmsCbCmasInfo.CMAS_URGENCY_EXPECTED:
259                 return R.string.cmas_urgency_expected;
260 
261             default:
262                 return 0;
263         }
264     }
265 
266     /**
267      * Returns the string resource ID for the CMAS certainty.
268      * @return a string resource ID, or 0 if the CMAS certainty is unknown or not present
269      */
getCmasCertaintyResId(SmsCbCmasInfo cmasInfo)270     private static int getCmasCertaintyResId(SmsCbCmasInfo cmasInfo) {
271         switch (cmasInfo.getCertainty()) {
272             case SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED:
273                 return R.string.cmas_certainty_observed;
274 
275             case SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY:
276                 return R.string.cmas_certainty_likely;
277 
278             default:
279                 return 0;
280         }
281     }
282 
getDialogTitleResource(Context context, SmsCbMessage message)283     static int getDialogTitleResource(Context context, SmsCbMessage message) {
284         // ETWS warning types
285         SmsCbEtwsInfo etwsInfo = message.getEtwsWarningInfo();
286         if (etwsInfo != null) {
287             switch (etwsInfo.getWarningType()) {
288                 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE:
289                     return R.string.etws_earthquake_warning;
290 
291                 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_TSUNAMI:
292                     return R.string.etws_tsunami_warning;
293 
294                 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI:
295                     return R.string.etws_earthquake_and_tsunami_warning;
296 
297                 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE:
298                     return R.string.etws_test_message;
299 
300                 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_OTHER_EMERGENCY:
301                 default:
302                     return R.string.etws_other_emergency_type;
303             }
304         }
305 
306         SmsCbCmasInfo cmasInfo = message.getCmasWarningInfo();
307         int subId = message.getSubscriptionId();
308         CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager(
309                 context, subId);
310         final int serviceCategory = message.getServiceCategory();
311         if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
312                 R.array.emergency_alerts_channels_range_strings)) {
313             return R.string.pws_other_message_identifiers;
314         }
315         // CMAS warning types
316         if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
317                 R.array.cmas_presidential_alerts_channels_range_strings)) {
318             return R.string.cmas_presidential_level_alert;
319         }
320         if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
321                 R.array.cmas_alert_extreme_channels_range_strings)) {
322             if (message.isCmasMessage()) {
323                 if (cmasInfo.getSeverity() == SmsCbCmasInfo.CMAS_SEVERITY_EXTREME
324                         && cmasInfo.getUrgency() == SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE) {
325                     if (cmasInfo.getCertainty() == SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED) {
326                         return R.string.cmas_extreme_immediate_observed_alert;
327                     } else if (cmasInfo.getCertainty() == SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY) {
328                         return R.string.cmas_extreme_immediate_likely_alert;
329                     }
330                 }
331             }
332             return R.string.cmas_extreme_alert;
333         }
334         if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
335                 R.array.cmas_alerts_severe_range_strings)) {
336             return R.string.cmas_severe_alert;
337         }
338         if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
339                 R.array.cmas_amber_alerts_channels_range_strings)) {
340             return R.string.cmas_amber_alert;
341         }
342         if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
343                 R.array.required_monthly_test_range_strings)) {
344             return R.string.cmas_required_monthly_test;
345         }
346         if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
347                 R.array.exercise_alert_range_strings)) {
348             return R.string.cmas_exercise_alert;
349         }
350         if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
351                 R.array.operator_defined_alert_range_strings)) {
352             return R.string.cmas_operator_defined_alert;
353         }
354         if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
355                 R.array.public_safety_messages_channels_range_strings)) {
356             return R.string.public_safety_message;
357         }
358         if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
359                 R.array.state_local_test_alert_range_strings)) {
360             return R.string.state_local_test_alert;
361         }
362 
363         if (channelManager.isEmergencyMessage(message)) {
364             ArrayList<CellBroadcastChannelRange> ranges =
365                     channelManager.getCellBroadcastChannelRanges(
366                             R.array.additional_cbs_channels_strings);
367             if (ranges != null) {
368                 for (CellBroadcastChannelRange range : ranges) {
369                     if (serviceCategory >= range.mStartId && serviceCategory <= range.mEndId) {
370                         // Apply the closest title to the specified tones.
371                         switch (range.mAlertType) {
372                             case DEFAULT:
373                                 return R.string.pws_other_message_identifiers;
374                             case ETWS_EARTHQUAKE:
375                                 return R.string.etws_earthquake_warning;
376                             case ETWS_TSUNAMI:
377                                 return R.string.etws_tsunami_warning;
378                             case TEST:
379                                 return R.string.etws_test_message;
380                             case ETWS_DEFAULT:
381                             case OTHER:
382                                 return R.string.etws_other_emergency_type;
383                         }
384                     }
385                 }
386 
387             }
388             return R.string.pws_other_message_identifiers;
389         } else {
390             return R.string.cb_other_message_identifiers;
391         }
392     }
393 
394     /**
395      * Choose pictogram resource according to etws type.
396      *
397      * @param context Application context
398      * @param message Cell broadcast message
399      *
400      * @return The resource of the pictogram, -1 if not available.
401      */
getDialogPictogramResource(Context context, SmsCbMessage message)402     static int getDialogPictogramResource(Context context, SmsCbMessage message) {
403         SmsCbEtwsInfo etwsInfo = message.getEtwsWarningInfo();
404         if (etwsInfo != null) {
405             switch (etwsInfo.getWarningType()) {
406                 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE:
407                 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI:
408                     return R.drawable.pict_icon_earthquake;
409                 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_TSUNAMI:
410                     return R.drawable.pict_icon_tsunami;
411             }
412         }
413 
414         final int serviceCategory = message.getServiceCategory();
415         int subId = message.getSubscriptionId();
416         CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager(
417                 context, subId);
418         if (channelManager.isEmergencyMessage(message)) {
419             ArrayList<CellBroadcastChannelRange> ranges =
420                     channelManager.getCellBroadcastChannelRanges(
421                             R.array.additional_cbs_channels_strings);
422             for (CellBroadcastChannelRange range : ranges) {
423                 if (serviceCategory >= range.mStartId && serviceCategory <= range.mEndId) {
424                     // Apply the closest title to the specified tones.
425                     switch (range.mAlertType) {
426                         case ETWS_EARTHQUAKE:
427                             return R.drawable.pict_icon_earthquake;
428                         case ETWS_TSUNAMI:
429                             return R.drawable.pict_icon_tsunami;
430                     }
431                 }
432             }
433             return -1;
434         }
435         return -1;
436     }
437 }
438