1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.cellbroadcastreceiver; 18 19 import static android.text.format.DateUtils.DAY_IN_MILLIS; 20 21 import android.app.ActivityManager; 22 import android.app.Notification; 23 import android.app.NotificationChannel; 24 import android.app.NotificationManager; 25 import android.app.PendingIntent; 26 import android.app.Service; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.SharedPreferences; 30 import android.content.pm.PackageManager; 31 import android.content.res.Resources; 32 import android.os.Binder; 33 import android.os.Bundle; 34 import android.os.IBinder; 35 import android.os.PersistableBundle; 36 import android.os.PowerManager; 37 import android.os.RemoteException; 38 import android.os.SystemClock; 39 import android.os.SystemProperties; 40 import android.os.UserHandle; 41 import android.preference.PreferenceManager; 42 import android.provider.Telephony; 43 import android.telephony.CarrierConfigManager; 44 import android.telephony.CellBroadcastMessage; 45 import android.telephony.SmsCbEtwsInfo; 46 import android.telephony.SmsCbLocation; 47 import android.telephony.SmsCbMessage; 48 import android.telephony.SubscriptionManager; 49 import android.text.TextUtils; 50 import android.util.Log; 51 52 import com.android.cellbroadcastreceiver.CellBroadcastChannelManager.CellBroadcastChannelRange; 53 import com.android.internal.annotations.VisibleForTesting; 54 import com.android.internal.telephony.PhoneConstants; 55 56 import java.util.ArrayList; 57 import java.util.LinkedHashMap; 58 import java.util.Locale; 59 60 /** 61 * This service manages the display and animation of broadcast messages. 62 * Emergency messages display with a flashing animated exclamation mark icon, 63 * and an alert tone is played when the alert is first shown to the user 64 * (but not when the user views a previously received broadcast). 65 */ 66 public class CellBroadcastAlertService extends Service { 67 private static final String TAG = "CBAlertService"; 68 69 /** Intent action to display alert dialog/notification, after verifying the alert is new. */ 70 static final String SHOW_NEW_ALERT_ACTION = "cellbroadcastreceiver.SHOW_NEW_ALERT"; 71 72 /** Use the same notification ID for non-emergency alerts. */ 73 static final int NOTIFICATION_ID = 1; 74 75 /** 76 * Notification channel containing for non-emergency alerts. 77 */ 78 static final String NOTIFICATION_CHANNEL_NON_EMERGENCY_ALERTS = "broadcastMessagesNonEmergency"; 79 80 /** 81 * Notification channel for emergency alerts. This is used when users sneak out of the 82 * noisy pop-up for a real emergency and get a notification due to not officially acknowledged 83 * the alert and want to refer it back later. 84 */ 85 static final String NOTIFICATION_CHANNEL_EMERGENCY_ALERTS = "broadcastMessages"; 86 87 /** Sticky broadcast for latest area info broadcast received. */ 88 static final String CB_AREA_INFO_RECEIVED_ACTION = 89 "com.android.cellbroadcastreceiver.CB_AREA_INFO_RECEIVED"; 90 91 static final String SETTINGS_APP = "com.android.settings"; 92 93 /** Intent extra for passing a SmsCbMessage */ 94 private static final String EXTRA_MESSAGE = "message"; 95 96 /** 97 * Default message expiration time is 24 hours. Same message arrives within 24 hours will be 98 * treated as a duplicate. 99 */ 100 private static final long DEFAULT_EXPIRATION_TIME = DAY_IN_MILLIS; 101 102 /** 103 * Key for accessing message filter from SystemProperties. For testing use. 104 */ 105 private static final String MESSAGE_FILTER_PROPERTY_KEY = 106 "persist.cellbroadcast.message_filter"; 107 108 /** 109 * Alert type 110 */ 111 public enum AlertType { 112 DEFAULT, 113 ETWS_DEFAULT, 114 ETWS_EARTHQUAKE, 115 ETWS_TSUNAMI, 116 TEST, 117 AREA, 118 INFO, 119 OTHER 120 } 121 122 /** 123 * Container for service category, serial number, location, body hash code, and ETWS primary/ 124 * secondary information for duplication detection. 125 */ 126 private static final class MessageServiceCategoryAndScope { 127 private final int mServiceCategory; 128 private final int mSerialNumber; 129 private final SmsCbLocation mLocation; 130 private final int mBodyHash; 131 private final boolean mIsEtwsPrimary; 132 MessageServiceCategoryAndScope(int serviceCategory, int serialNumber, SmsCbLocation location, int bodyHash, boolean isEtwsPrimary)133 MessageServiceCategoryAndScope(int serviceCategory, int serialNumber, 134 SmsCbLocation location, int bodyHash, boolean isEtwsPrimary) { 135 mServiceCategory = serviceCategory; 136 mSerialNumber = serialNumber; 137 mLocation = location; 138 mBodyHash = bodyHash; 139 mIsEtwsPrimary = isEtwsPrimary; 140 } 141 142 @Override hashCode()143 public int hashCode() { 144 return mLocation.hashCode() + 5 * mServiceCategory + 7 * mSerialNumber + 13 * mBodyHash 145 + 17 * Boolean.hashCode(mIsEtwsPrimary); 146 } 147 148 @Override equals(Object o)149 public boolean equals(Object o) { 150 if (o == this) { 151 return true; 152 } 153 if (o instanceof MessageServiceCategoryAndScope) { 154 MessageServiceCategoryAndScope other = (MessageServiceCategoryAndScope) o; 155 return (mServiceCategory == other.mServiceCategory && 156 mSerialNumber == other.mSerialNumber && 157 mLocation.equals(other.mLocation) && 158 mBodyHash == other.mBodyHash && 159 mIsEtwsPrimary == other.mIsEtwsPrimary); 160 } 161 return false; 162 } 163 164 @Override toString()165 public String toString() { 166 return "{mServiceCategory: " + mServiceCategory + " serial number: " + mSerialNumber + 167 " location: " + mLocation.toString() + " body hash: " + mBodyHash + 168 " mIsEtwsPrimary: " + mIsEtwsPrimary + "}"; 169 } 170 } 171 172 /** Maximum number of message IDs to save before removing the oldest message ID. */ 173 private static final int MAX_MESSAGE_ID_SIZE = 1024; 174 175 /** Linked hash map of the message identities for duplication detection purposes. The key is the 176 * the collection of different message keys used for duplication detection, and the value 177 * is the timestamp of message arriving time. Some carriers may require shorter expiration time. 178 */ 179 private static final LinkedHashMap<MessageServiceCategoryAndScope, Long> sMessagesMap = 180 new LinkedHashMap<>(); 181 182 @Override onStartCommand(Intent intent, int flags, int startId)183 public int onStartCommand(Intent intent, int flags, int startId) { 184 String action = intent.getAction(); 185 Log.d(TAG, "onStartCommand: " + action); 186 if (Telephony.Sms.Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION.equals(action) || 187 Telephony.Sms.Intents.SMS_CB_RECEIVED_ACTION.equals(action)) { 188 handleCellBroadcastIntent(intent); 189 } else if (SHOW_NEW_ALERT_ACTION.equals(action)) { 190 try { 191 if (UserHandle.myUserId() == 192 ActivityManager.getService().getCurrentUser().id) { 193 showNewAlert(intent); 194 } else { 195 Log.d(TAG,"Not active user, ignore the alert display"); 196 } 197 } catch (RemoteException e) { 198 e.printStackTrace(); 199 } 200 } else { 201 Log.e(TAG, "Unrecognized intent action: " + action); 202 } 203 return START_NOT_STICKY; 204 } 205 206 /** 207 * Reset the duplicate detection map. 208 */ resetMessageDuplicateDetection()209 public static void resetMessageDuplicateDetection() { 210 Log.d(TAG, "Reset alert duplicate detection map."); 211 sMessagesMap.clear(); 212 } 213 214 /** 215 * Get the carrier specific message duplicate expiration time. 216 * 217 * @param subId Subscription index 218 * @return The expiration time in milliseconds. Small values like 0 (or negative values) 219 * indicate expiration immediately (meaning the duplicate will always be displayed), while large 220 * values indicate the duplicate will always be ignored. The default value would be 24 hours. 221 */ getDuplicateExpirationTime(int subId)222 private long getDuplicateExpirationTime(int subId) { 223 CarrierConfigManager configManager = (CarrierConfigManager) 224 getApplicationContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 225 Log.d(TAG, "manager = " + configManager); 226 if (configManager == null) { 227 Log.e(TAG, "carrier config is not available."); 228 return DEFAULT_EXPIRATION_TIME; 229 } 230 231 PersistableBundle b = configManager.getConfigForSubId(subId); 232 if (b == null) { 233 Log.e(TAG, "expiration key does not exist."); 234 return DEFAULT_EXPIRATION_TIME; 235 } 236 237 long time = b.getLong(CarrierConfigManager.KEY_MESSAGE_EXPIRATION_TIME_LONG, 238 DEFAULT_EXPIRATION_TIME); 239 return time; 240 } 241 242 /** 243 * Check if we should display the received cell broadcast message. 244 * 245 * @param cbm Cell broadcast message 246 * @return True if the message should be displayed to the user 247 */ shouldDisplayMessage(CellBroadcastMessage cbm)248 private boolean shouldDisplayMessage(CellBroadcastMessage cbm) { 249 // Check if the channel is enabled by the user or configuration. 250 if (!isChannelEnabled(cbm)) { 251 Log.d(TAG, "ignoring alert of type " + cbm.getServiceCategory() 252 + " by user preference"); 253 return false; 254 } 255 256 // Check if we need to perform language filtering. 257 CellBroadcastChannelRange range = CellBroadcastChannelManager 258 .getCellBroadcastChannelRangeFromMessage(getApplicationContext(), cbm); 259 if (range != null && range.mFilterLanguage) { 260 // If the message's language does not match device's message, we don't display the 261 // message. 262 String messageLanguage = cbm.getLanguageCode(); 263 String deviceLanguage = Locale.getDefault().getLanguage(); 264 if (!TextUtils.isEmpty(messageLanguage) 265 && !messageLanguage.equalsIgnoreCase(deviceLanguage)) { 266 Log.d(TAG, "ignoring the alert due to language mismatch. Message lang=" 267 + messageLanguage + ", device lang=" + deviceLanguage); 268 return false; 269 } 270 } 271 272 // Check for custom filtering 273 String messageFilters = SystemProperties.get(MESSAGE_FILTER_PROPERTY_KEY, ""); 274 if (!TextUtils.isEmpty(messageFilters)) { 275 String[] filters = messageFilters.split(","); 276 for (String filter : filters) { 277 if (!TextUtils.isEmpty(filter)) { 278 if (cbm.getMessageBody().toLowerCase().contains(filter)) { 279 Log.i(TAG, "Skipped message due to filter: " + filter); 280 return false; 281 } 282 } 283 } 284 } 285 286 return true; 287 } 288 handleCellBroadcastIntent(Intent intent)289 private void handleCellBroadcastIntent(Intent intent) { 290 Bundle extras = intent.getExtras(); 291 if (extras == null) { 292 Log.e(TAG, "received SMS_CB_RECEIVED_ACTION with no extras!"); 293 return; 294 } 295 296 SmsCbMessage message = (SmsCbMessage) extras.get(EXTRA_MESSAGE); 297 298 if (message == null) { 299 Log.e(TAG, "received SMS_CB_RECEIVED_ACTION with no message extra"); 300 return; 301 } 302 303 final CellBroadcastMessage cbm = new CellBroadcastMessage(message); 304 int subId = intent.getExtras().getInt(PhoneConstants.SUBSCRIPTION_KEY); 305 if (SubscriptionManager.isValidSubscriptionId(subId)) { 306 cbm.setSubId(subId); 307 } else { 308 Log.e(TAG, "Invalid subscription id"); 309 } 310 311 if (!shouldDisplayMessage(cbm)) { 312 return; 313 } 314 315 // Check if message body should be used for duplicate detection. 316 boolean shouldCompareMessageBody = 317 CellBroadcastSettings.getResourcesForDefaultSmsSubscriptionId( 318 getApplicationContext()) 319 .getBoolean(R.bool.duplicate_compare_body); 320 321 int hashCode = shouldCompareMessageBody ? message.getMessageBody().hashCode() : 0; 322 323 // If this is an ETWS message, we need to include primary/secondary message information to 324 // be a factor for duplication detection as well. Per 3GPP TS 23.041 section 8.2, 325 // duplicate message detection shall be performed independently for primary and secondary 326 // notifications. 327 boolean isEtwsPrimary = false; 328 if (message.isEtwsMessage()) { 329 SmsCbEtwsInfo etwsInfo = message.getEtwsWarningInfo(); 330 if (etwsInfo != null) { 331 isEtwsPrimary = etwsInfo.isPrimary(); 332 } else { 333 Log.w(TAG, "ETWS info is not available."); 334 } 335 } 336 337 // Check for duplicate message IDs according to CMAS carrier requirements. Message IDs 338 // are stored in volatile memory. If the maximum of 1024 messages is reached, the 339 // message ID of the oldest message is deleted from the list. 340 MessageServiceCategoryAndScope newCmasId = new MessageServiceCategoryAndScope( 341 message.getServiceCategory(), message.getSerialNumber(), message.getLocation(), 342 hashCode, isEtwsPrimary); 343 344 Log.d(TAG, "message ID = " + newCmasId); 345 346 long nowTime = SystemClock.elapsedRealtime(); 347 // Check if the identical message arrives again 348 if (sMessagesMap.get(newCmasId) != null) { 349 // And if the previous one has not expired yet, treat it as a duplicate message. 350 long previousTime = sMessagesMap.get(newCmasId); 351 long expirationTime = getDuplicateExpirationTime(subId); 352 if (nowTime - previousTime < expirationTime) { 353 Log.d(TAG, "ignoring the duplicate alert " + newCmasId + ", nowTime=" + nowTime 354 + ", previous=" + previousTime + ", expiration=" + expirationTime); 355 return; 356 } 357 // otherwise, we don't treat it as a duplicate and will show the same message again. 358 Log.d(TAG, "The same message shown up " + (nowTime - previousTime) 359 + " milliseconds ago. Not a duplicate."); 360 } else if (sMessagesMap.size() >= MAX_MESSAGE_ID_SIZE){ 361 // If we reach the maximum, remove the first inserted message key. 362 MessageServiceCategoryAndScope oldestCmasId = sMessagesMap.keySet().iterator().next(); 363 Log.d(TAG, "message ID limit reached, removing oldest message ID " + oldestCmasId); 364 sMessagesMap.remove(oldestCmasId); 365 } else { 366 Log.d(TAG, "New message. Not a duplicate. Map size = " + sMessagesMap.size()); 367 } 368 369 sMessagesMap.put(newCmasId, nowTime); 370 371 final Intent alertIntent = new Intent(SHOW_NEW_ALERT_ACTION); 372 alertIntent.setClass(this, CellBroadcastAlertService.class); 373 alertIntent.putExtra(EXTRA_MESSAGE, cbm); 374 375 // write to database on a background thread 376 new CellBroadcastContentProvider.AsyncCellBroadcastTask(getContentResolver()) 377 .execute(new CellBroadcastContentProvider.CellBroadcastOperation() { 378 @Override 379 public boolean execute(CellBroadcastContentProvider provider) { 380 if (provider.insertNewBroadcast(cbm)) { 381 // new message, show the alert or notification on UI thread 382 startService(alertIntent); 383 return true; 384 } else { 385 return false; 386 } 387 } 388 }); 389 } 390 showNewAlert(Intent intent)391 private void showNewAlert(Intent intent) { 392 Bundle extras = intent.getExtras(); 393 if (extras == null) { 394 Log.e(TAG, "received SHOW_NEW_ALERT_ACTION with no extras!"); 395 return; 396 } 397 398 CellBroadcastMessage cbm = (CellBroadcastMessage) intent.getParcelableExtra(EXTRA_MESSAGE); 399 400 if (cbm == null) { 401 Log.e(TAG, "received SHOW_NEW_ALERT_ACTION with no message extra"); 402 return; 403 } 404 405 if (CellBroadcastChannelManager.isEmergencyMessage(this, cbm)) { 406 // start alert sound / vibration / TTS and display full-screen alert 407 openEmergencyAlertNotification(cbm); 408 } else { 409 // add notification to the bar by passing the list of unread non-emergency 410 // CellBroadcastMessages 411 ArrayList<CellBroadcastMessage> messageList = CellBroadcastReceiverApp 412 .addNewMessageToList(cbm); 413 addToNotificationBar(cbm, messageList, this, false); 414 } 415 } 416 417 /** 418 * Check if the message's channel is enabled on the device. 419 * 420 * @param message the message to check 421 * @return true if the channel is enabled on the device, otherwise false. 422 */ isChannelEnabled(CellBroadcastMessage message)423 private boolean isChannelEnabled(CellBroadcastMessage message) { 424 425 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 426 // Check if all emergency alerts are disabled. 427 boolean emergencyAlertEnabled = 428 prefs.getBoolean(CellBroadcastSettings.KEY_ENABLE_ALERTS_MASTER_TOGGLE, true); 429 430 boolean enableAreaUpdateInfoAlerts = Resources.getSystem().getBoolean( 431 com.android.internal.R.bool.config_showAreaUpdateInfoSettings) 432 && prefs.getBoolean(CellBroadcastSettings.KEY_ENABLE_AREA_UPDATE_INFO_ALERTS, 433 true); 434 435 if (message.isEtwsTestMessage()) { 436 return emergencyAlertEnabled && 437 PreferenceManager.getDefaultSharedPreferences(this) 438 .getBoolean(CellBroadcastSettings.KEY_ENABLE_TEST_ALERTS, false); 439 } 440 441 if (message.isEtwsMessage()) { 442 // ETWS messages. 443 // Turn on/off emergency notifications is the only way to turn on/off ETWS messages. 444 return emergencyAlertEnabled; 445 446 } 447 448 int channel = message.getServiceCategory(); 449 450 // Check if the messages are on additional channels enabled by the resource config. 451 // If those channels are enabled by the carrier, but the device is actually roaming, we 452 // should not allow the messages. 453 ArrayList<CellBroadcastChannelRange> ranges = CellBroadcastChannelManager 454 .getInstance().getCellBroadcastChannelRanges(getApplicationContext(), 455 R.array.additional_cbs_channels_strings); 456 457 if (ranges != null) { 458 for (CellBroadcastChannelRange range : ranges) { 459 if (range.mStartId <= channel && range.mEndId >= channel) { 460 // Check if the channel is within the scope. If not, ignore the alert message. 461 if (!CellBroadcastChannelManager.checkScope(getApplicationContext(), 462 message.getSubId(), range.mScope)) { 463 Log.d(TAG, "The range [" + range.mStartId + "-" + range.mEndId 464 + "] is not within the scope. mScope = " + range.mScope); 465 return false; 466 } 467 468 // The area update information cell broadcast should not cause any pop-up. 469 // Instead the setting's app SIM status will show its information. 470 if (range.mAlertType == AlertType.AREA) { 471 if (enableAreaUpdateInfoAlerts) { 472 // save latest area info broadcast for Settings display and send as 473 // broadcast. 474 CellBroadcastReceiverApp.setLatestAreaInfo(message); 475 Intent intent = new Intent(CB_AREA_INFO_RECEIVED_ACTION); 476 intent.setPackage(SETTINGS_APP); 477 intent.putExtra(EXTRA_MESSAGE, message); 478 // Send broadcast twice, once for apps that have PRIVILEGED permission 479 // and once for those that have the runtime one. 480 sendBroadcastAsUser(intent, UserHandle.ALL, 481 android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE); 482 sendBroadcastAsUser(intent, UserHandle.ALL, 483 android.Manifest.permission.READ_PHONE_STATE); 484 // area info broadcasts are displayed in Settings status screen 485 } 486 return false; 487 } else if (range.mAlertType == AlertType.TEST) { 488 return emergencyAlertEnabled 489 && PreferenceManager.getDefaultSharedPreferences(this) 490 .getBoolean(CellBroadcastSettings.KEY_ENABLE_TEST_ALERTS, 491 false); 492 } 493 494 return emergencyAlertEnabled; 495 } 496 } 497 } 498 int subId = message.getSubId(); 499 if (CellBroadcastChannelManager.checkCellBroadcastChannelRange(subId, 500 channel, R.array.emergency_alerts_channels_range_strings, this)) { 501 return emergencyAlertEnabled 502 && PreferenceManager.getDefaultSharedPreferences(this).getBoolean( 503 CellBroadcastSettings.KEY_ENABLE_EMERGENCY_ALERTS, true); 504 } 505 // CMAS warning types 506 if (CellBroadcastChannelManager.checkCellBroadcastChannelRange(subId, 507 channel, R.array.cmas_presidential_alerts_channels_range_strings, this)) { 508 // always enabled 509 return true; 510 } 511 if (CellBroadcastChannelManager.checkCellBroadcastChannelRange(subId, 512 channel, R.array.cmas_alert_extreme_channels_range_strings, this)) { 513 return emergencyAlertEnabled 514 && PreferenceManager.getDefaultSharedPreferences(this).getBoolean( 515 CellBroadcastSettings.KEY_ENABLE_CMAS_EXTREME_THREAT_ALERTS, true); 516 } 517 if (CellBroadcastChannelManager.checkCellBroadcastChannelRange(subId, 518 channel, R.array.cmas_alerts_severe_range_strings, this)) { 519 return emergencyAlertEnabled 520 && PreferenceManager.getDefaultSharedPreferences(this).getBoolean( 521 CellBroadcastSettings.KEY_ENABLE_CMAS_SEVERE_THREAT_ALERTS, true); 522 } 523 if (CellBroadcastChannelManager.checkCellBroadcastChannelRange(subId, 524 channel, R.array.cmas_amber_alerts_channels_range_strings, this)) { 525 return emergencyAlertEnabled 526 && PreferenceManager.getDefaultSharedPreferences(this) 527 .getBoolean(CellBroadcastSettings.KEY_ENABLE_CMAS_AMBER_ALERTS, true); 528 } 529 530 if (CellBroadcastChannelManager.checkCellBroadcastChannelRange(subId, 531 channel, R.array.required_monthly_test_range_strings, this) 532 || CellBroadcastChannelManager.checkCellBroadcastChannelRange(subId, 533 channel, R.array.exercise_alert_range_strings, this) 534 || CellBroadcastChannelManager.checkCellBroadcastChannelRange(subId, 535 channel, R.array.operator_defined_alert_range_strings, this)) { 536 return emergencyAlertEnabled 537 && PreferenceManager.getDefaultSharedPreferences(this) 538 .getBoolean(CellBroadcastSettings.KEY_ENABLE_TEST_ALERTS, 539 false); 540 } 541 if (CellBroadcastChannelManager.checkCellBroadcastChannelRange(subId, 542 channel, R.array.public_safety_messages_channels_range_strings, this)) { 543 return emergencyAlertEnabled 544 && PreferenceManager.getDefaultSharedPreferences(this) 545 .getBoolean(CellBroadcastSettings.KEY_ENABLE_PUBLIC_SAFETY_MESSAGES, 546 true); 547 } 548 if (CellBroadcastChannelManager.checkCellBroadcastChannelRange(subId, 549 channel, R.array.state_local_test_alert_range_strings, this)) { 550 return emergencyAlertEnabled 551 && PreferenceManager.getDefaultSharedPreferences(this) 552 .getBoolean(CellBroadcastSettings.KEY_ENABLE_STATE_LOCAL_TEST_ALERTS, 553 false); 554 } 555 return true; 556 } 557 558 /** 559 * Display an alert message for emergency alerts. 560 * @param message the alert to display 561 */ openEmergencyAlertNotification(CellBroadcastMessage message)562 private void openEmergencyAlertNotification(CellBroadcastMessage message) { 563 // Acquire a screen bright wakelock until the alert dialog and audio start playing. 564 CellBroadcastAlertWakeLock.acquireScreenBrightWakeLock(this); 565 566 // Close dialogs and window shade 567 Intent closeDialogs = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 568 sendBroadcast(closeDialogs); 569 570 // start audio/vibration/speech service for emergency alerts 571 Intent audioIntent = new Intent(this, CellBroadcastAlertAudio.class); 572 audioIntent.setAction(CellBroadcastAlertAudio.ACTION_START_ALERT_AUDIO); 573 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 574 575 AlertType alertType = AlertType.DEFAULT; 576 if (message.isEtwsMessage()) { 577 alertType = AlertType.ETWS_DEFAULT; 578 579 if (message.getEtwsWarningInfo() != null) { 580 int warningType = message.getEtwsWarningInfo().getWarningType(); 581 582 switch (warningType) { 583 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE: 584 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI: 585 alertType = AlertType.ETWS_EARTHQUAKE; 586 break; 587 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_TSUNAMI: 588 alertType = AlertType.ETWS_TSUNAMI; 589 break; 590 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE: 591 alertType = AlertType.TEST; 592 break; 593 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_OTHER_EMERGENCY: 594 alertType = AlertType.OTHER; 595 break; 596 } 597 } 598 } else { 599 int channel = message.getServiceCategory(); 600 ArrayList<CellBroadcastChannelRange> ranges = CellBroadcastChannelManager 601 .getAllCellBroadcastChannelRanges(getApplicationContext()); 602 if (ranges != null) { 603 for (CellBroadcastChannelRange range : ranges) { 604 if (channel >= range.mStartId && channel <= range.mEndId) { 605 alertType = range.mAlertType; 606 break; 607 } 608 } 609 } 610 } 611 CellBroadcastChannelRange range = CellBroadcastChannelManager 612 .getCellBroadcastChannelRangeFromMessage(getApplicationContext(), message); 613 audioIntent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_TONE_TYPE, alertType); 614 audioIntent.putExtra( 615 CellBroadcastAlertAudio.ALERT_AUDIO_VIBRATION_PATTERN_EXTRA, 616 (range != null) 617 ? range.mVibrationPattern 618 : CellBroadcastSettings.getResourcesForDefaultSmsSubscriptionId( 619 getApplicationContext()) 620 .getIntArray(R.array.default_vibration_pattern)); 621 622 String messageBody = message.getMessageBody(); 623 624 if (prefs.getBoolean(CellBroadcastSettings.KEY_ENABLE_ALERT_SPEECH, true)) { 625 audioIntent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_MESSAGE_BODY, messageBody); 626 627 String language = message.getLanguageCode(); 628 629 Log.d(TAG, "Message language = " + language); 630 audioIntent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_MESSAGE_LANGUAGE, 631 language); 632 } 633 startService(audioIntent); 634 635 ArrayList<CellBroadcastMessage> messageList = new ArrayList<CellBroadcastMessage>(1); 636 messageList.add(message); 637 638 // For FEATURE_WATCH, the dialog doesn't make sense from a UI/UX perspective 639 if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) { 640 addToNotificationBar(message, messageList, this, false); 641 } else { 642 Intent alertDialogIntent = createDisplayMessageIntent(this, 643 CellBroadcastAlertDialog.class, messageList); 644 alertDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 645 PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); 646 // Wake up the device up regardless the scenario. (The device might be 647 // in screen saver mode that needs to be waken up otherwise the alert 648 // window can not be displayed.) 649 pm.wakeUp(SystemClock.uptimeMillis()); 650 startActivity(alertDialogIntent); 651 } 652 653 } 654 655 /** 656 * Add the new alert to the notification bar (non-emergency alerts), or launch a 657 * high-priority immediate intent for emergency alerts. 658 * @param message the alert to display 659 */ addToNotificationBar(CellBroadcastMessage message, ArrayList<CellBroadcastMessage> messageList, Context context, boolean fromSaveState)660 static void addToNotificationBar(CellBroadcastMessage message, 661 ArrayList<CellBroadcastMessage> messageList, Context context, 662 boolean fromSaveState) { 663 Resources res = CellBroadcastSettings.getResourcesForDefaultSmsSubscriptionId(context); 664 int channelTitleId = CellBroadcastResources.getDialogTitleResource(context, message); 665 CharSequence channelName = context.getText(channelTitleId); 666 String messageBody = message.getMessageBody(); 667 final NotificationManager notificationManager = NotificationManager.from(context); 668 createNotificationChannels(context); 669 670 // Create intent to show the new messages when user selects the notification. 671 Intent intent; 672 if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) { 673 // For FEATURE_WATCH we want to mark as read 674 intent = createMarkAsReadIntent(context, message.getDeliveryTime()); 675 } else { 676 // For anything else we handle it normally 677 intent = createDisplayMessageIntent(context, CellBroadcastAlertDialog.class, 678 messageList); 679 } 680 681 intent.putExtra(CellBroadcastAlertDialog.FROM_NOTIFICATION_EXTRA, true); 682 intent.putExtra(CellBroadcastAlertDialog.FROM_SAVE_STATE_NOTIFICATION_EXTRA, fromSaveState); 683 684 PendingIntent pi; 685 if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) { 686 pi = PendingIntent.getBroadcast(context, 0, intent, 0); 687 } else { 688 pi = PendingIntent.getActivity(context, NOTIFICATION_ID, intent, 689 PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT); 690 } 691 final String channelId = CellBroadcastChannelManager.isEmergencyMessage(context, message) 692 ? NOTIFICATION_CHANNEL_EMERGENCY_ALERTS : NOTIFICATION_CHANNEL_NON_EMERGENCY_ALERTS; 693 // use default sound/vibration/lights for non-emergency broadcasts 694 Notification.Builder builder = 695 new Notification.Builder(context, channelId) 696 .setSmallIcon(R.drawable.ic_warning_googred) 697 .setTicker(channelName) 698 .setWhen(System.currentTimeMillis()) 699 .setCategory(Notification.CATEGORY_SYSTEM) 700 .setPriority(Notification.PRIORITY_HIGH) 701 .setColor(res.getColor(R.color.notification_color)) 702 .setVisibility(Notification.VISIBILITY_PUBLIC) 703 .setOngoing(message.isEmergencyAlertMessage()); 704 705 if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) { 706 builder.setDeleteIntent(pi); 707 // FEATURE_WATCH/CWH devices see this as priority 708 builder.setVibrate(new long[]{0}); 709 } else { 710 builder.setContentIntent(pi); 711 // This will break vibration on FEATURE_WATCH, so use it for anything else 712 builder.setDefaults(Notification.DEFAULT_ALL); 713 } 714 715 // increment unread alert count (decremented when user dismisses alert dialog) 716 int unreadCount = messageList.size(); 717 if (unreadCount > 1) { 718 // use generic count of unread broadcasts if more than one unread 719 builder.setContentTitle(context.getString(R.string.notification_multiple_title)); 720 builder.setContentText(context.getString(R.string.notification_multiple, unreadCount)); 721 } else { 722 builder.setContentTitle(channelName) 723 .setContentText(messageBody) 724 .setStyle(new Notification.BigTextStyle() 725 .bigText(messageBody)); 726 } 727 728 notificationManager.notify(NOTIFICATION_ID, builder.build()); 729 730 // FEATURE_WATCH devices do not have global sounds for notifications; only vibrate. 731 // TW requires sounds for 911/919 732 // Emergency messages use a different audio playback and display path. Since we use 733 // addToNotification for the emergency display on FEATURE WATCH devices vs the 734 // Alert Dialog, it will call this and override the emergency audio tone. 735 if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH) 736 && !CellBroadcastChannelManager.isEmergencyMessage(context, message)) { 737 if (res.getBoolean(R.bool.watch_enable_non_emergency_audio)) { 738 // start audio/vibration/speech service for non emergency alerts 739 Intent audioIntent = new Intent(context, CellBroadcastAlertAudio.class); 740 audioIntent.setAction(CellBroadcastAlertAudio.ACTION_START_ALERT_AUDIO); 741 audioIntent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_TONE_TYPE, 742 AlertType.OTHER); 743 context.startService(audioIntent); 744 } 745 } 746 747 } 748 749 /** 750 * Creates the notification channel and registers it with NotificationManager. If a channel 751 * with the same ID is already registered, NotificationManager will ignore this call. 752 */ createNotificationChannels(Context context)753 static void createNotificationChannels(Context context) { 754 NotificationManager.from(context).createNotificationChannel( 755 new NotificationChannel( 756 NOTIFICATION_CHANNEL_EMERGENCY_ALERTS, 757 context.getString(R.string.notification_channel_emergency_alerts), 758 NotificationManager.IMPORTANCE_LOW)); 759 final NotificationChannel nonEmergency = new NotificationChannel( 760 NOTIFICATION_CHANNEL_NON_EMERGENCY_ALERTS, 761 context.getString(R.string.notification_channel_broadcast_messages), 762 NotificationManager.IMPORTANCE_DEFAULT); 763 nonEmergency.enableVibration(true); 764 NotificationManager.from(context).createNotificationChannel(nonEmergency); 765 } 766 createDisplayMessageIntent(Context context, Class intentClass, ArrayList<CellBroadcastMessage> messageList)767 static Intent createDisplayMessageIntent(Context context, Class intentClass, 768 ArrayList<CellBroadcastMessage> messageList) { 769 // Trigger the list activity to fire up a dialog that shows the received messages 770 Intent intent = new Intent(context, intentClass); 771 intent.putParcelableArrayListExtra(CellBroadcastMessage.SMS_CB_MESSAGE_EXTRA, messageList); 772 return intent; 773 } 774 775 /** 776 * Creates a delete intent that calls to the {@link CellBroadcastReceiver} in order to mark 777 * a message as read 778 * 779 * @param context context of the caller 780 * @param deliveryTime time the message was sent in order to mark as read 781 * @return delete intent to add to the pending intent 782 */ createMarkAsReadIntent(Context context, long deliveryTime)783 static Intent createMarkAsReadIntent(Context context, long deliveryTime) { 784 Intent deleteIntent = new Intent(context, CellBroadcastReceiver.class); 785 deleteIntent.setAction(CellBroadcastReceiver.ACTION_MARK_AS_READ); 786 deleteIntent.putExtra(CellBroadcastReceiver.EXTRA_DELIVERY_TIME, deliveryTime); 787 return deleteIntent; 788 } 789 790 @VisibleForTesting 791 @Override onBind(Intent intent)792 public IBinder onBind(Intent intent) { 793 return new LocalBinder(); 794 } 795 796 @VisibleForTesting 797 class LocalBinder extends Binder { getService()798 public CellBroadcastAlertService getService() { 799 return CellBroadcastAlertService.this; 800 } 801 } 802 } 803