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