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