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