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