• 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.ArrayList;
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         if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
317                 R.array.cmas_presidential_alerts_channels_range_strings)) {
318             return R.string.sms_cb_sender_name_presidential;
319         }
320         if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
321                 R.array.emergency_alerts_channels_range_strings)) {
322             return R.string.sms_cb_sender_name_emergency;
323         }
324         if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
325                 R.array.public_safety_messages_channels_range_strings)) {
326             return R.string.sms_cb_sender_name_public_safety;
327         }
328         return R.string.sms_cb_sender_name_default;
329     }
330 
getDialogTitleResource(Context context, SmsCbMessage message)331     static int getDialogTitleResource(Context context, SmsCbMessage message) {
332         // ETWS warning types
333         SmsCbEtwsInfo etwsInfo = message.getEtwsWarningInfo();
334         if (etwsInfo != null) {
335             switch (etwsInfo.getWarningType()) {
336                 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE:
337                     return R.string.etws_earthquake_warning;
338 
339                 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_TSUNAMI:
340                     return R.string.etws_tsunami_warning;
341 
342                 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI:
343                     return R.string.etws_earthquake_and_tsunami_warning;
344 
345                 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE:
346                     return R.string.etws_test_message;
347 
348                 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_OTHER_EMERGENCY:
349                 default:
350                     return R.string.etws_other_emergency_type;
351             }
352         }
353 
354         SmsCbCmasInfo cmasInfo = message.getCmasWarningInfo();
355         int subId = message.getSubscriptionId();
356         CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager(
357                 context, subId);
358         final int serviceCategory = message.getServiceCategory();
359         if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
360                 R.array.emergency_alerts_channels_range_strings)) {
361             return R.string.pws_other_message_identifiers;
362         }
363         // CMAS warning types
364         if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
365                 R.array.cmas_presidential_alerts_channels_range_strings)) {
366             return R.string.cmas_presidential_level_alert;
367         }
368         if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
369                 R.array.cmas_alert_extreme_channels_range_strings)) {
370             if (message.isCmasMessage()) {
371                 if (cmasInfo.getSeverity() == SmsCbCmasInfo.CMAS_SEVERITY_EXTREME
372                         && cmasInfo.getUrgency() == SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE) {
373                     if (cmasInfo.getCertainty() == SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED) {
374                         return R.string.cmas_extreme_immediate_observed_alert;
375                     } else if (cmasInfo.getCertainty() == SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY) {
376                         return R.string.cmas_extreme_immediate_likely_alert;
377                     }
378                 }
379             }
380             return R.string.cmas_extreme_alert;
381         }
382         if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
383                 R.array.cmas_alerts_severe_range_strings)) {
384             return R.string.cmas_severe_alert;
385         }
386         if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
387                 R.array.cmas_amber_alerts_channels_range_strings)) {
388             return R.string.cmas_amber_alert;
389         }
390         if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
391                 R.array.required_monthly_test_range_strings)) {
392             return R.string.cmas_required_monthly_test;
393         }
394         if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
395                 R.array.exercise_alert_range_strings)) {
396             return R.string.cmas_exercise_alert;
397         }
398         if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
399                 R.array.operator_defined_alert_range_strings)) {
400             return R.string.cmas_operator_defined_alert;
401         }
402         if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
403                 R.array.public_safety_messages_channels_range_strings)) {
404             return R.string.public_safety_message;
405         }
406         if (channelManager.checkCellBroadcastChannelRange(serviceCategory,
407                 R.array.state_local_test_alert_range_strings)) {
408             return R.string.state_local_test_alert;
409         }
410 
411         if (channelManager.isEmergencyMessage(message)) {
412             ArrayList<CellBroadcastChannelRange> ranges =
413                     channelManager.getCellBroadcastChannelRanges(
414                             R.array.additional_cbs_channels_strings);
415             if (ranges != null) {
416                 for (CellBroadcastChannelRange range : ranges) {
417                     if (serviceCategory >= range.mStartId && serviceCategory <= range.mEndId) {
418                         // Apply the closest title to the specified tones.
419                         switch (range.mAlertType) {
420                             case DEFAULT:
421                                 return R.string.pws_other_message_identifiers;
422                             case ETWS_EARTHQUAKE:
423                                 return R.string.etws_earthquake_warning;
424                             case ETWS_TSUNAMI:
425                                 return R.string.etws_tsunami_warning;
426                             case TEST:
427                                 return R.string.etws_test_message;
428                             case ETWS_DEFAULT:
429                             case OTHER:
430                                 return R.string.etws_other_emergency_type;
431                         }
432                     }
433                 }
434 
435             }
436             return R.string.pws_other_message_identifiers;
437         } else {
438             return R.string.cb_other_message_identifiers;
439         }
440     }
441 
442     /**
443      * Choose pictogram resource according to etws type.
444      *
445      * @param context Application context
446      * @param message Cell broadcast message
447      *
448      * @return The resource of the pictogram, -1 if not available.
449      */
getDialogPictogramResource(Context context, SmsCbMessage message)450     static int getDialogPictogramResource(Context context, SmsCbMessage message) {
451         SmsCbEtwsInfo etwsInfo = message.getEtwsWarningInfo();
452         if (etwsInfo != null) {
453             switch (etwsInfo.getWarningType()) {
454                 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE:
455                 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI:
456                     return R.drawable.pict_icon_earthquake;
457                 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_TSUNAMI:
458                     return R.drawable.pict_icon_tsunami;
459             }
460         }
461 
462         final int serviceCategory = message.getServiceCategory();
463         int subId = message.getSubscriptionId();
464         CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager(
465                 context, subId);
466         if (channelManager.isEmergencyMessage(message)) {
467             ArrayList<CellBroadcastChannelRange> ranges =
468                     channelManager.getCellBroadcastChannelRanges(
469                             R.array.additional_cbs_channels_strings);
470             for (CellBroadcastChannelRange range : ranges) {
471                 if (serviceCategory >= range.mStartId && serviceCategory <= range.mEndId) {
472                     // Apply the closest title to the specified tones.
473                     switch (range.mAlertType) {
474                         case ETWS_EARTHQUAKE:
475                             return R.drawable.pict_icon_earthquake;
476                         case ETWS_TSUNAMI:
477                             return R.drawable.pict_icon_tsunami;
478                     }
479                 }
480             }
481             return -1;
482         }
483         return -1;
484     }
485 }
486