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