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.telephony.SmsCbMessage.MESSAGE_FORMAT_3GPP; 20 import static android.telephony.SmsCbMessage.MESSAGE_FORMAT_3GPP2; 21 22 import android.annotation.NonNull; 23 import android.app.ActivityManager; 24 import android.app.Notification; 25 import android.app.NotificationChannel; 26 import android.app.NotificationManager; 27 import android.app.PendingIntent; 28 import android.app.Service; 29 import android.bluetooth.BluetoothDevice; 30 import android.bluetooth.BluetoothManager; 31 import android.content.ContentValues; 32 import android.content.Context; 33 import android.content.Intent; 34 import android.content.SharedPreferences; 35 import android.content.pm.PackageManager; 36 import android.content.res.Resources; 37 import android.net.Uri; 38 import android.os.Binder; 39 import android.os.Bundle; 40 import android.os.Handler; 41 import android.os.IBinder; 42 import android.os.Looper; 43 import android.os.SystemProperties; 44 import android.os.UserHandle; 45 import android.preference.PreferenceManager; 46 import android.provider.Telephony; 47 import android.service.notification.StatusBarNotification; 48 import android.telephony.PhoneStateListener; 49 import android.telephony.SmsCbEtwsInfo; 50 import android.telephony.SmsCbMessage; 51 import android.telephony.TelephonyManager; 52 import android.text.TextUtils; 53 import android.util.Log; 54 55 import com.android.cellbroadcastreceiver.CellBroadcastChannelManager.CellBroadcastChannelRange; 56 import com.android.cellbroadcastservice.CellBroadcastStatsLog; 57 import com.android.internal.annotations.VisibleForTesting; 58 59 import java.util.ArrayList; 60 import java.util.Locale; 61 import java.util.Set; 62 63 /** 64 * This service manages the display and animation of broadcast messages. 65 * Emergency messages display with a flashing animated exclamation mark icon, 66 * and an alert tone is played when the alert is first shown to the user 67 * (but not when the user views a previously received broadcast). 68 */ 69 public class CellBroadcastAlertService extends Service { 70 private static final String TAG = "CBAlertService"; 71 72 /** Intent action to display alert dialog/notification, after verifying the alert is new. */ 73 @VisibleForTesting 74 public static final String SHOW_NEW_ALERT_ACTION = "cellbroadcastreceiver.SHOW_NEW_ALERT"; 75 76 /** Identifier for getExtra() when adding this object to an Intent. */ 77 public static final String SMS_CB_MESSAGE_EXTRA = 78 "com.android.cellbroadcastreceiver.SMS_CB_MESSAGE"; 79 80 /** Intent extra indicate this intent is to dismiss the alert dialog */ 81 public static final String DISMISS_DIALOG = "com.android.cellbroadcastreceiver.DIMISS_DIALOG"; 82 83 /** 84 * Use different request code to create distinct pendingIntent for notification deleteIntent 85 * and contentIntent. 86 */ 87 private static final int REQUEST_CODE_CONTENT_INTENT = 1; 88 private static final int REQUEST_CODE_DELETE_INTENT = 2; 89 90 /** Use the same notification ID for non-emergency alerts. */ 91 @VisibleForTesting 92 public static final int NOTIFICATION_ID = 1; 93 public static final int SETTINGS_CHANGED_NOTIFICATION_ID = 2; 94 95 /** 96 * Notification channel containing for non-emergency alerts. 97 */ 98 static final String NOTIFICATION_CHANNEL_NON_EMERGENCY_ALERTS = "broadcastMessagesNonEmergency"; 99 100 /** 101 * Notification channel for notifications accompanied by the alert dialog. 102 * e.g, only show when the device has active connections to companion devices. 103 */ 104 static final String NOTIFICATION_CHANNEL_EMERGENCY_ALERTS = "broadcastMessages"; 105 106 /** 107 * Notification channel for emergency alerts. This is used when users dismiss the alert 108 * dialog without officially hitting "OK" (e.g. by pressing the home button). In this case we 109 * pop up a notification for them to refer to later. 110 * 111 * This notification channel is HIGH_PRIORITY. 112 */ 113 static final String NOTIFICATION_CHANNEL_HIGH_PRIORITY_EMERGENCY_ALERTS = 114 "broadcastMessagesHighPriority"; 115 116 /** 117 * Notification channel for emergency alerts during voice call. This is used when users in a 118 * voice call, emergency alert will be displayed in a notification format rather than playing 119 * alert tone. 120 */ 121 static final String NOTIFICATION_CHANNEL_EMERGENCY_ALERTS_IN_VOICECALL = 122 "broadcastMessagesInVoiceCall"; 123 124 /** 125 * Notification channel for informing the user when a new Carrier's WEA settings have been 126 * automatically applied. 127 */ 128 static final String NOTIFICATION_CHANNEL_SETTINGS_UPDATES = "settingsUpdates"; 129 130 /** Intent extra for passing a SmsCbMessage */ 131 private static final String EXTRA_MESSAGE = "message"; 132 133 /** 134 * Key for accessing message filter from SystemProperties. For testing use. 135 */ 136 private static final String MESSAGE_FILTER_PROPERTY_KEY = 137 "persist.cellbroadcast.message_filter"; 138 139 private Context mContext; 140 141 /** 142 * Alert type 143 */ 144 public enum AlertType { 145 DEFAULT, 146 ETWS_DEFAULT, 147 ETWS_EARTHQUAKE, 148 ETWS_TSUNAMI, 149 TEST, 150 AREA, 151 INFO, 152 OTHER 153 } 154 155 private TelephonyManager mTelephonyManager; 156 157 /** 158 * Do not preempt active voice call, instead post notifications and play the ringtone/vibrate 159 * when the voicecall finish 160 */ 161 private static boolean sRemindAfterCallFinish = false; 162 163 164 @Override onStartCommand(Intent intent, int flags, int startId)165 public int onStartCommand(Intent intent, int flags, int startId) { 166 mContext = getApplicationContext(); 167 String action = intent.getAction(); 168 Log.d(TAG, "onStartCommand: " + action); 169 if (Telephony.Sms.Intents.ACTION_SMS_EMERGENCY_CB_RECEIVED.equals(action) || 170 Telephony.Sms.Intents.SMS_CB_RECEIVED_ACTION.equals(action)) { 171 handleCellBroadcastIntent(intent); 172 } else if (SHOW_NEW_ALERT_ACTION.equals(action)) { 173 if (UserHandle.myUserId() == ((ActivityManager) getSystemService( 174 Context.ACTIVITY_SERVICE)).getCurrentUser()) { 175 showNewAlert(intent); 176 } else { 177 Log.d(TAG, "Not active user, ignore the alert display"); 178 } 179 } else { 180 Log.e(TAG, "Unrecognized intent action: " + action); 181 } 182 return START_NOT_STICKY; 183 } 184 185 @Override onCreate()186 public void onCreate() { 187 mTelephonyManager = (TelephonyManager) 188 getApplicationContext().getSystemService(Context.TELEPHONY_SERVICE); 189 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); 190 } 191 192 @Override onDestroy()193 public void onDestroy() { 194 // Stop listening for incoming calls. 195 mTelephonyManager.listen(mPhoneStateListener, 0); 196 197 } 198 199 /** 200 * Check if the enabled message should be displayed to users in the form of pop-up dialog. 201 * 202 * @param message 203 * @return True if the full screen alert should be displayed to the users. False otherwise. 204 */ shouldDisplayFullScreenMessage(@onNull SmsCbMessage message)205 public boolean shouldDisplayFullScreenMessage(@NonNull SmsCbMessage message) { 206 CellBroadcastChannelManager channelManager = 207 new CellBroadcastChannelManager(mContext, message.getSubscriptionId()); 208 // check the full-screen message settings to hide or show message to users. 209 if (channelManager.checkCellBroadcastChannelRange(message.getServiceCategory(), 210 R.array.public_safety_messages_channels_range_strings)) { 211 return PreferenceManager.getDefaultSharedPreferences(this) 212 .getBoolean(CellBroadcastSettings.KEY_ENABLE_PUBLIC_SAFETY_MESSAGES_FULL_SCREEN, 213 true); 214 } 215 // if no separate full-screen message settings exists, then display the message by default. 216 return true; 217 } 218 219 /** 220 * Check if we should display the received cell broadcast message. 221 * 222 * @param message Cell broadcast message 223 * @return True if the message should be displayed to the user. 224 */ 225 @VisibleForTesting shouldDisplayMessage(SmsCbMessage message)226 public boolean shouldDisplayMessage(SmsCbMessage message) { 227 TelephonyManager tm = ((TelephonyManager) mContext.getSystemService( 228 Context.TELEPHONY_SERVICE)).createForSubscriptionId(message.getSubscriptionId()); 229 if (tm.getEmergencyCallbackMode() && CellBroadcastSettings.getResources( 230 mContext, message.getSubscriptionId()).getBoolean(R.bool.ignore_messages_in_ecbm)) { 231 // Ignore the message in ECBM. 232 // It is for LTE only mode. For 1xRTT, incoming pages should be ignored in the modem. 233 Log.d(TAG, "ignoring alert of type " + message.getServiceCategory() + " in ECBM"); 234 return false; 235 } 236 // Check if the channel is enabled by the user or configuration. 237 if (!isChannelEnabled(message)) { 238 Log.d(TAG, "ignoring alert of type " + message.getServiceCategory() 239 + " by user preference"); 240 return false; 241 } 242 243 // Check if message body is empty 244 String msgBody = message.getMessageBody(); 245 if (msgBody == null || msgBody.length() == 0) { 246 Log.e(TAG, "Empty content or Unsupported charset"); 247 return false; 248 } 249 250 // Check if we need to perform language filtering. 251 CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager(mContext, 252 message.getSubscriptionId()); 253 CellBroadcastChannelRange range = channelManager 254 .getCellBroadcastChannelRangeFromMessage(message); 255 String messageLanguage = message.getLanguageCode(); 256 if (range != null && range.mFilterLanguage) { 257 // language filtering based on CBR second language settings 258 final String secondLanguageCode = CellBroadcastSettings.getResources(mContext, 259 message.getSubscriptionId()) 260 .getString(R.string.emergency_alert_second_language_code); 261 if (!secondLanguageCode.isEmpty()) { 262 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 263 boolean receiveInSecondLanguage = prefs.getBoolean( 264 CellBroadcastSettings.KEY_RECEIVE_CMAS_IN_SECOND_LANGUAGE, false); 265 // For DCS values that bit 6 is 1 and bit 7 is 0, language field is not defined so 266 // ap receives it as null value and so alert is not shown to the user. 267 // bypass language filter in this case. 268 if (!TextUtils.isEmpty(messageLanguage) 269 && !secondLanguageCode.equalsIgnoreCase(messageLanguage)) { 270 Log.w(TAG, "Ignoring message in the unspecified second language:" 271 + messageLanguage); 272 return false; 273 } else if (!receiveInSecondLanguage) { 274 Log.d(TAG, "Ignoring message in second language because setting is off"); 275 return false; 276 } 277 } else { 278 // language filtering based on device language settings. 279 String deviceLanguage = Locale.getDefault().getLanguage(); 280 // Apply If the message's language does not match device's message, we don't 281 // display the message. 282 if (!TextUtils.isEmpty(messageLanguage) 283 && !messageLanguage.equalsIgnoreCase(deviceLanguage)) { 284 Log.d(TAG, "ignoring the alert due to language mismatch. Message lang=" 285 + messageLanguage + ", device lang=" + deviceLanguage); 286 return false; 287 } 288 } 289 } 290 291 // If the alert is set for test-mode only, then we should check if device is currently under 292 // testing mode (testing mode can be enabled by dialer code *#*#CMAS#*#*. 293 if (range != null && range.mTestMode && !CellBroadcastReceiver.isTestingMode(mContext)) { 294 Log.d(TAG, "ignoring the alert due to not in testing mode"); 295 return false; 296 } 297 298 // Check for custom filtering 299 String messageFilters = SystemProperties.get(MESSAGE_FILTER_PROPERTY_KEY, ""); 300 if (!TextUtils.isEmpty(messageFilters)) { 301 String[] filters = messageFilters.split(","); 302 for (String filter : filters) { 303 if (!TextUtils.isEmpty(filter)) { 304 if (message.getMessageBody().toLowerCase().contains(filter)) { 305 Log.i(TAG, "Skipped message due to filter: " + filter); 306 return false; 307 } 308 } 309 } 310 } 311 312 return true; 313 } 314 handleCellBroadcastIntent(Intent intent)315 private void handleCellBroadcastIntent(Intent intent) { 316 Bundle extras = intent.getExtras(); 317 if (extras == null) { 318 Log.e(TAG, "received SMS_CB_RECEIVED_ACTION with no extras!"); 319 return; 320 } 321 322 SmsCbMessage message = (SmsCbMessage) extras.get(EXTRA_MESSAGE); 323 324 if (message == null) { 325 Log.e(TAG, "received SMS_CB_RECEIVED_ACTION with no message extra"); 326 return; 327 } 328 329 if (message.getMessageFormat() == MESSAGE_FORMAT_3GPP) { 330 CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_REPORTED, 331 CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_REPORTED__TYPE__GSM, 332 CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_REPORTED__SOURCE__CB_RECEIVER_APP); 333 } else if (message.getMessageFormat() == MESSAGE_FORMAT_3GPP2) { 334 CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_REPORTED, 335 CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_REPORTED__TYPE__CDMA, 336 CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_REPORTED__SOURCE__CB_RECEIVER_APP); 337 } 338 339 if (!shouldDisplayMessage(message)) { 340 return; 341 } 342 343 final Intent alertIntent = new Intent(SHOW_NEW_ALERT_ACTION); 344 alertIntent.setClass(this, CellBroadcastAlertService.class); 345 alertIntent.putExtra(EXTRA_MESSAGE, message); 346 347 // write to database on a background thread 348 new CellBroadcastContentProvider.AsyncCellBroadcastTask(getContentResolver()) 349 .execute((CellBroadcastContentProvider.CellBroadcastOperation) provider -> { 350 CellBroadcastChannelManager channelManager = 351 new CellBroadcastChannelManager(mContext, message.getSubscriptionId()); 352 CellBroadcastChannelRange range = channelManager 353 .getCellBroadcastChannelRangeFromMessage(message); 354 // Check if the message was marked as do not display. Some channels 355 // are reserved for biz purpose where the msg should be routed as a data SMS 356 // rather than being displayed as pop-up or notification. However, 357 // per requirements those messages might also need to write to sms inbox... 358 boolean ret = false; 359 if (range != null && range.mDisplay == true) { 360 if (provider.insertNewBroadcast(message)) { 361 // new message, show the alert or notification on UI thread 362 // if not display.. 363 startService(alertIntent); 364 // mark the message as displayed to the user. 365 markMessageDisplayed(message); 366 ret = true; 367 } 368 } else { 369 Log.d(TAG, "ignoring the alert due to configured channels was marked " 370 + "as do not display"); 371 } 372 if (CellBroadcastSettings.getResources(mContext, message.getSubscriptionId()) 373 .getBoolean(R.bool.enable_write_alerts_to_sms_inbox)) { 374 if (CellBroadcastReceiver.isTestingMode(getApplicationContext()) 375 || (range != null && range.mWriteToSmsInbox)) { 376 provider.writeMessageToSmsInbox(message, mContext); 377 } 378 } 379 return ret; 380 }); 381 } 382 383 /** 384 * Mark the message as displayed in cell broadcast service's database. 385 * 386 * @param message The cell broadcast message. 387 */ markMessageDisplayed(SmsCbMessage message)388 private void markMessageDisplayed(SmsCbMessage message) { 389 mContext.getContentResolver().update( 390 Uri.withAppendedPath(Telephony.CellBroadcasts.CONTENT_URI,"displayed"), 391 new ContentValues(), 392 Telephony.CellBroadcasts.RECEIVED_TIME + "=?", 393 new String[] {Long.toString(message.getReceivedTime())}); 394 } 395 showNewAlert(Intent intent)396 private void showNewAlert(Intent intent) { 397 Bundle extras = intent.getExtras(); 398 if (extras == null) { 399 Log.e(TAG, "received SHOW_NEW_ALERT_ACTION with no extras!"); 400 return; 401 } 402 403 SmsCbMessage cbm = intent.getParcelableExtra(EXTRA_MESSAGE); 404 405 if (cbm == null) { 406 Log.e(TAG, "received SHOW_NEW_ALERT_ACTION with no message extra"); 407 return; 408 } 409 410 if (mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE 411 && CellBroadcastSettings.getResources(mContext, cbm.getSubscriptionId()) 412 .getBoolean(R.bool.enable_alert_handling_during_call)) { 413 Log.d(TAG, "CMAS received in dialing/during voicecall."); 414 sRemindAfterCallFinish = true; 415 } 416 417 // Either shown the dialog, adding it to notification (non emergency, or delayed emergency), 418 CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager( 419 mContext, cbm.getSubscriptionId()); 420 if (channelManager.isEmergencyMessage(cbm) && !sRemindAfterCallFinish) { 421 // start alert sound / vibration / TTS and display full-screen alert 422 openEmergencyAlertNotification(cbm); 423 Resources res = CellBroadcastSettings.getResources(mContext, cbm.getSubscriptionId()); 424 // KR carriers mandate to always show notifications along with alert dialog. 425 if (res.getBoolean(R.bool.show_alert_dialog_with_notification) || 426 // to support emergency alert on companion devices use flag 427 // show_notification_if_connected_to_companion_devices instead. 428 (res.getBoolean(R.bool.show_notification_if_connected_to_companion_devices) 429 && isConnectedToCompanionDevices())) { 430 // add notification to the bar by passing the list of unread non-emergency 431 // cell broadcast messages. The notification should be of LOW_IMPORTANCE if the 432 // notification is shown together with full-screen dialog. 433 addToNotificationBar(cbm, CellBroadcastReceiverApp.addNewMessageToList(cbm), 434 this, false, true, shouldDisplayFullScreenMessage(cbm)); 435 } 436 } else { 437 // add notification to the bar by passing the list of unread non-emergency 438 // cell broadcast messages 439 ArrayList<SmsCbMessage> messageList = CellBroadcastReceiverApp 440 .addNewMessageToList(cbm); 441 addToNotificationBar(cbm, messageList, this, false, true, false); 442 } 443 } 444 445 /** 446 * Check if the message's channel is enabled on the device. 447 * 448 * @param message the message to check 449 * @return true if the channel is enabled on the device, otherwise false. 450 */ isChannelEnabled(SmsCbMessage message)451 private boolean isChannelEnabled(SmsCbMessage message) { 452 CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager(mContext, 453 message.getSubscriptionId()); 454 CellBroadcastChannelRange chanelrange = channelManager 455 .getCellBroadcastChannelRangeFromMessage(message); 456 Resources res = CellBroadcastSettings.getResources(mContext, message.getSubscriptionId()); 457 if (chanelrange != null && chanelrange.mAlwaysOn) { 458 Log.d(TAG, "channel is enabled due to always-on, ignoring preference check"); 459 return true; 460 } 461 462 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 463 // Check if all emergency alerts are disabled. 464 boolean emergencyAlertEnabled = 465 prefs.getBoolean(CellBroadcastSettings.KEY_ENABLE_ALERTS_MASTER_TOGGLE, true); 466 int channel = message.getServiceCategory(); 467 468 SmsCbEtwsInfo etwsInfo = message.getEtwsWarningInfo(); 469 if ((etwsInfo != null && etwsInfo.getWarningType() 470 == SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE) 471 || channelManager.checkCellBroadcastChannelRange(channel, 472 R.array.etws_test_alerts_range_strings)) { 473 return emergencyAlertEnabled 474 && CellBroadcastSettings.isTestAlertsToggleVisible(getApplicationContext()) 475 && PreferenceManager.getDefaultSharedPreferences(this) 476 .getBoolean(CellBroadcastSettings.KEY_ENABLE_TEST_ALERTS, false); 477 } 478 479 if (message.isEtwsMessage() || channelManager.checkCellBroadcastChannelRange(channel, 480 R.array.etws_alerts_range_strings)) { 481 // ETWS messages. 482 // Turn on/off emergency notifications is the only way to turn on/off ETWS messages. 483 return emergencyAlertEnabled; 484 } 485 486 // Check if the messages are on additional channels enabled by the resource config. 487 // If those channels are enabled by the carrier, but the device is actually roaming, we 488 // should not allow the messages. 489 ArrayList<CellBroadcastChannelRange> ranges = channelManager.getCellBroadcastChannelRanges( 490 R.array.additional_cbs_channels_strings); 491 492 for (CellBroadcastChannelRange range : ranges) { 493 if (range.mStartId <= channel && range.mEndId >= channel) { 494 // Check if the channel is within the scope. If not, ignore the alert message. 495 if (!channelManager.checkScope(range.mScope)) { 496 Log.d(TAG, "The range [" + range.mStartId + "-" + range.mEndId 497 + "] is not within the scope. mScope = " + range.mScope); 498 return false; 499 } 500 501 if (range.mAlertType == AlertType.TEST) { 502 return emergencyAlertEnabled 503 && CellBroadcastSettings.isTestAlertsToggleVisible( 504 getApplicationContext()) 505 && PreferenceManager.getDefaultSharedPreferences(this) 506 .getBoolean(CellBroadcastSettings.KEY_ENABLE_TEST_ALERTS, 507 false); 508 } 509 if (range.mAlertType == AlertType.AREA) { 510 return emergencyAlertEnabled && PreferenceManager 511 .getDefaultSharedPreferences(this) 512 .getBoolean(CellBroadcastSettings.KEY_ENABLE_AREA_UPDATE_INFO_ALERTS, 513 false); 514 } 515 516 return emergencyAlertEnabled; 517 } 518 } 519 520 if (channelManager.checkCellBroadcastChannelRange(channel, 521 R.array.emergency_alerts_channels_range_strings)) { 522 return emergencyAlertEnabled 523 && PreferenceManager.getDefaultSharedPreferences(this).getBoolean( 524 CellBroadcastSettings.KEY_ENABLE_EMERGENCY_ALERTS, true); 525 } 526 // CMAS warning types 527 if (channelManager.checkCellBroadcastChannelRange(channel, 528 R.array.cmas_presidential_alerts_channels_range_strings)) { 529 // always enabled 530 return true; 531 } 532 if (channelManager.checkCellBroadcastChannelRange(channel, 533 R.array.cmas_alert_extreme_channels_range_strings)) { 534 return emergencyAlertEnabled 535 && PreferenceManager.getDefaultSharedPreferences(this).getBoolean( 536 CellBroadcastSettings.KEY_ENABLE_CMAS_EXTREME_THREAT_ALERTS, true); 537 } 538 if (channelManager.checkCellBroadcastChannelRange(channel, 539 R.array.cmas_alerts_severe_range_strings)) { 540 return emergencyAlertEnabled 541 && PreferenceManager.getDefaultSharedPreferences(this).getBoolean( 542 CellBroadcastSettings.KEY_ENABLE_CMAS_SEVERE_THREAT_ALERTS, true); 543 } 544 if (channelManager.checkCellBroadcastChannelRange(channel, 545 R.array.cmas_amber_alerts_channels_range_strings)) { 546 return emergencyAlertEnabled 547 && PreferenceManager.getDefaultSharedPreferences(this) 548 .getBoolean(CellBroadcastSettings.KEY_ENABLE_CMAS_AMBER_ALERTS, true); 549 } 550 551 if (channelManager.checkCellBroadcastChannelRange( 552 channel, R.array.exercise_alert_range_strings) && 553 res.getBoolean(R.bool.show_separate_exercise_settings)) { 554 return emergencyAlertEnabled && PreferenceManager.getDefaultSharedPreferences(this) 555 .getBoolean(CellBroadcastSettings.KEY_ENABLE_EXERCISE_ALERTS, false); 556 } 557 558 if (channelManager.checkCellBroadcastChannelRange( 559 channel, R.array.operator_defined_alert_range_strings) && 560 res.getBoolean(R.bool.show_separate_operator_defined_settings)) { 561 return emergencyAlertEnabled && PreferenceManager.getDefaultSharedPreferences(this) 562 .getBoolean(CellBroadcastSettings.KEY_OPERATOR_DEFINED_ALERTS, false); 563 } 564 565 if (channelManager.checkCellBroadcastChannelRange(channel, 566 R.array.required_monthly_test_range_strings) 567 || channelManager.checkCellBroadcastChannelRange(channel, 568 R.array.exercise_alert_range_strings) 569 || channelManager.checkCellBroadcastChannelRange(channel, 570 R.array.operator_defined_alert_range_strings)) { 571 return emergencyAlertEnabled 572 && CellBroadcastSettings.isTestAlertsToggleVisible(getApplicationContext()) 573 && PreferenceManager.getDefaultSharedPreferences(this) 574 .getBoolean(CellBroadcastSettings.KEY_ENABLE_TEST_ALERTS, 575 false); 576 } 577 578 if (channelManager.checkCellBroadcastChannelRange(channel, 579 R.array.public_safety_messages_channels_range_strings)) { 580 return emergencyAlertEnabled 581 && PreferenceManager.getDefaultSharedPreferences(this) 582 .getBoolean(CellBroadcastSettings.KEY_ENABLE_PUBLIC_SAFETY_MESSAGES, 583 true); 584 } 585 586 if (channelManager.checkCellBroadcastChannelRange(channel, 587 R.array.state_local_test_alert_range_strings)) { 588 return emergencyAlertEnabled 589 && PreferenceManager.getDefaultSharedPreferences(this) 590 .getBoolean(CellBroadcastSettings.KEY_ENABLE_STATE_LOCAL_TEST_ALERTS, 591 false); 592 } 593 594 Log.e(TAG, "received undefined channels: " + channel); 595 return false; 596 } 597 598 /** 599 * Display an alert message for emergency alerts. 600 * @param message the alert to display 601 */ openEmergencyAlertNotification(SmsCbMessage message)602 private void openEmergencyAlertNotification(SmsCbMessage message) { 603 if (!shouldDisplayFullScreenMessage(message)) { 604 Log.d(TAG, "openEmergencyAlertNotification: do not show full screen alert " 605 + "due to user preference"); 606 return; 607 } 608 // Close dialogs and window shade 609 Intent closeDialogs = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 610 sendBroadcast(closeDialogs); 611 612 // start audio/vibration/speech service for emergency alerts 613 Intent audioIntent = new Intent(this, CellBroadcastAlertAudio.class); 614 audioIntent.setAction(CellBroadcastAlertAudio.ACTION_START_ALERT_AUDIO); 615 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 616 617 CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager( 618 mContext, message.getSubscriptionId()); 619 620 AlertType alertType = AlertType.DEFAULT; 621 if (message.isEtwsMessage()) { 622 alertType = AlertType.ETWS_DEFAULT; 623 624 if (message.getEtwsWarningInfo() != null) { 625 int warningType = message.getEtwsWarningInfo().getWarningType(); 626 627 switch (warningType) { 628 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE: 629 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI: 630 alertType = AlertType.ETWS_EARTHQUAKE; 631 break; 632 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_TSUNAMI: 633 alertType = AlertType.ETWS_TSUNAMI; 634 break; 635 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE: 636 alertType = AlertType.TEST; 637 break; 638 case SmsCbEtwsInfo.ETWS_WARNING_TYPE_OTHER_EMERGENCY: 639 alertType = AlertType.OTHER; 640 break; 641 } 642 } 643 } else { 644 int channel = message.getServiceCategory(); 645 ArrayList<CellBroadcastChannelRange> ranges = channelManager 646 .getAllCellBroadcastChannelRanges(); 647 for (CellBroadcastChannelRange range : ranges) { 648 if (channel >= range.mStartId && channel <= range.mEndId) { 649 alertType = range.mAlertType; 650 break; 651 } 652 } 653 } 654 CellBroadcastChannelRange range = channelManager 655 .getCellBroadcastChannelRangeFromMessage(message); 656 audioIntent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_TONE_TYPE, alertType); 657 audioIntent.putExtra( 658 CellBroadcastAlertAudio.ALERT_AUDIO_VIBRATION_PATTERN_EXTRA, 659 (range != null) 660 ? range.mVibrationPattern 661 : CellBroadcastSettings.getResources(mContext, message.getSubscriptionId()) 662 .getIntArray(R.array.default_vibration_pattern)); 663 // read key_override_dnd only when the toggle is visible. 664 // range.mOverrideDnd is per channel configuration. override_dnd is the main config 665 // applied for all channels. 666 Resources res = CellBroadcastSettings.getResources(mContext, message.getSubscriptionId()); 667 if ((res.getBoolean(R.bool.show_override_dnd_settings) 668 && prefs.getBoolean(CellBroadcastSettings.KEY_OVERRIDE_DND, false)) 669 || (range != null && range.mOverrideDnd) || res.getBoolean(R.bool.override_dnd)) { 670 audioIntent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_OVERRIDE_DND_EXTRA, true); 671 } 672 673 String messageBody = message.getMessageBody(); 674 675 if (!CellBroadcastSettings.getResourcesForDefaultSubId(mContext) 676 .getBoolean(R.bool.show_alert_speech_setting) 677 || prefs.getBoolean(CellBroadcastSettings.KEY_ENABLE_ALERT_SPEECH, true)) { 678 audioIntent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_MESSAGE_BODY, messageBody); 679 680 String language = message.getLanguageCode(); 681 682 Log.d(TAG, "Message language = " + language); 683 audioIntent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_MESSAGE_LANGUAGE, 684 language); 685 } 686 687 audioIntent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_SUB_INDEX, 688 message.getSubscriptionId()); 689 audioIntent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_DURATION, 690 (range != null) ? range.mAlertDuration : -1); 691 startService(audioIntent); 692 693 ArrayList<SmsCbMessage> messageList = new ArrayList<>(); 694 messageList.add(message); 695 696 // For FEATURE_WATCH, the dialog doesn't make sense from a UI/UX perspective 697 if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) { 698 addToNotificationBar(message, messageList, this, false, true, false); 699 } else { 700 Intent alertDialogIntent = createDisplayMessageIntent(this, 701 CellBroadcastAlertDialog.class, messageList); 702 alertDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 703 startActivity(alertDialogIntent); 704 } 705 706 } 707 708 /** 709 * Add the new alert to the notification bar (non-emergency alerts), launch a 710 * high-priority immediate intent for emergency alerts or notifications for companion devices. 711 * @param message the alert to display 712 * @param shouldAlert only notify once if set to {@code false}. 713 * @param fromDialog if {@code true} indicate this notification is coming from the alert dialog 714 * with following behaviors: 715 * 1. display when alert is shown in the foreground. 716 * 2. dismiss when foreground alert is gone. 717 * 3. dismiss foreground alert when swipe away the notification. 718 * 4. no dialog open when tap the notification. 719 */ addToNotificationBar(SmsCbMessage message, ArrayList<SmsCbMessage> messageList, Context context, boolean fromSaveState, boolean shouldAlert, boolean fromDialog)720 static void addToNotificationBar(SmsCbMessage message, 721 ArrayList<SmsCbMessage> messageList, Context context, 722 boolean fromSaveState, boolean shouldAlert, boolean fromDialog) { 723 Resources res = CellBroadcastSettings.getResources(context, message.getSubscriptionId()); 724 int channelTitleId = CellBroadcastResources.getDialogTitleResource(context, message); 725 CharSequence channelName = context.getText(channelTitleId); 726 String messageBody = message.getMessageBody(); 727 final NotificationManager notificationManager = 728 (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); 729 createNotificationChannels(context); 730 731 boolean isWatch = context.getPackageManager() 732 .hasSystemFeature(PackageManager.FEATURE_WATCH); 733 // Create intent to show the new messages when user selects the notification. 734 Intent intent; 735 if (isWatch) { 736 // For FEATURE_WATCH we want to mark as read 737 intent = createMarkAsReadIntent(context, message.getReceivedTime()); 738 } else { 739 // For anything else we handle it normally 740 intent = createDisplayMessageIntent(context, CellBroadcastAlertDialog.class, 741 messageList); 742 } 743 744 // if this is an notification from on-going alert alert, do not clear the notification when 745 // tap the notification. the notification should be gone either when users swipe away or 746 // when the foreground dialog dismissed. 747 intent.putExtra(CellBroadcastAlertDialog.DISMISS_NOTIFICATION_EXTRA, !fromDialog); 748 intent.putExtra(CellBroadcastAlertDialog.FROM_SAVE_STATE_NOTIFICATION_EXTRA, fromSaveState); 749 750 PendingIntent pi; 751 if (isWatch) { 752 pi = PendingIntent.getBroadcast(context, 0, intent, 0); 753 } else { 754 pi = PendingIntent.getActivity(context, REQUEST_CODE_CONTENT_INTENT, intent, 755 PendingIntent.FLAG_ONE_SHOT 756 | PendingIntent.FLAG_UPDATE_CURRENT 757 | PendingIntent.FLAG_IMMUTABLE); 758 } 759 CellBroadcastChannelManager channelManager = new CellBroadcastChannelManager( 760 context, message.getSubscriptionId()); 761 762 String channelId; 763 if (!channelManager.isEmergencyMessage(message)) { 764 channelId = NOTIFICATION_CHANNEL_NON_EMERGENCY_ALERTS; 765 } else if (sRemindAfterCallFinish) { 766 channelId = NOTIFICATION_CHANNEL_EMERGENCY_ALERTS_IN_VOICECALL; 767 } else if (fromDialog) { 768 channelId = NOTIFICATION_CHANNEL_EMERGENCY_ALERTS; 769 } else { 770 channelId = NOTIFICATION_CHANNEL_HIGH_PRIORITY_EMERGENCY_ALERTS; 771 } 772 773 boolean nonSwipeableNotification = message.isEmergencyMessage() 774 && CellBroadcastSettings.getResources(context, message.getSubscriptionId()) 775 .getBoolean(R.bool.non_swipeable_notification) || sRemindAfterCallFinish; 776 777 // use default sound/vibration/lights for non-emergency broadcasts 778 Notification.Builder builder = 779 new Notification.Builder(context, channelId) 780 .setSmallIcon(R.drawable.ic_warning_googred) 781 .setTicker(channelName) 782 .setWhen(System.currentTimeMillis()) 783 .setCategory(Notification.CATEGORY_SYSTEM) 784 .setPriority(Notification.PRIORITY_HIGH) 785 .setColor(res.getColor(R.color.notification_color)) 786 .setVisibility(Notification.VISIBILITY_PUBLIC) 787 .setOngoing(nonSwipeableNotification) 788 .setOnlyAlertOnce(!shouldAlert); 789 790 if (isWatch) { 791 builder.setDeleteIntent(pi); 792 // FEATURE_WATCH/CWH devices see this as priority 793 builder.setVibrate(new long[]{0}); 794 } else { 795 // If this is a notification coming from the foreground dialog, should dismiss the 796 // foreground alert dialog when swipe the notification. This is needed 797 // when receiving emergency alerts on companion devices are supported, so that users 798 // swipe away notification on companion devices will synced to the parent devices 799 // with the foreground dialog/sound/vibration dismissed and stopped. Delete intent is 800 // also needed for regular notifications (e.g, pressing home button) to stop the 801 // sound, vibration and alert reminder. 802 Intent deleteIntent = new Intent(intent); 803 deleteIntent.putExtra(CellBroadcastAlertService.DISMISS_DIALOG, true); 804 builder.setDeleteIntent(PendingIntent.getActivity(context, REQUEST_CODE_DELETE_INTENT, 805 deleteIntent, PendingIntent.FLAG_ONE_SHOT 806 | PendingIntent.FLAG_UPDATE_CURRENT 807 | PendingIntent.FLAG_IMMUTABLE)); 808 809 builder.setContentIntent(pi); 810 // This will break vibration on FEATURE_WATCH, so use it for anything else 811 builder.setDefaults(Notification.DEFAULT_ALL); 812 } 813 814 // increment unread alert count (decremented when user dismisses alert dialog) 815 int unreadCount = messageList.size(); 816 if (unreadCount > 1) { 817 // use generic count of unread broadcasts if more than one unread 818 builder.setContentTitle(context.getString(R.string.notification_multiple_title)); 819 builder.setContentText(context.getString(R.string.notification_multiple, unreadCount)); 820 } else { 821 builder.setContentTitle(channelName) 822 .setContentText(messageBody) 823 .setStyle(new Notification.BigTextStyle() 824 .bigText(messageBody)); 825 } 826 827 notificationManager.notify(NOTIFICATION_ID, builder.build()); 828 829 // FEATURE_WATCH devices do not have global sounds for notifications; only vibrate. 830 // TW requires sounds for 911/919 831 // Emergency messages use a different audio playback and display path. Since we use 832 // addToNotification for the emergency display on FEATURE WATCH devices vs the 833 // Alert Dialog, it will call this and override the emergency audio tone. 834 if (isWatch && !channelManager.isEmergencyMessage(message)) { 835 if (res.getBoolean(R.bool.watch_enable_non_emergency_audio)) { 836 // start audio/vibration/speech service for non emergency alerts 837 Intent audioIntent = new Intent(context, CellBroadcastAlertAudio.class); 838 audioIntent.setAction(CellBroadcastAlertAudio.ACTION_START_ALERT_AUDIO); 839 audioIntent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_TONE_TYPE, 840 AlertType.OTHER); 841 context.startService(audioIntent); 842 } 843 } 844 845 } 846 847 /** 848 * Creates the notification channel and registers it with NotificationManager. If a channel 849 * with the same ID is already registered, NotificationManager will ignore this call. 850 */ createNotificationChannels(Context context)851 static void createNotificationChannels(Context context) { 852 NotificationManager notificationManager = 853 (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); 854 notificationManager.createNotificationChannel( 855 new NotificationChannel( 856 NOTIFICATION_CHANNEL_HIGH_PRIORITY_EMERGENCY_ALERTS, 857 context.getString( 858 R.string.notification_channel_emergency_alerts_high_priority), 859 NotificationManager.IMPORTANCE_HIGH)); 860 notificationManager.createNotificationChannel( 861 new NotificationChannel( 862 NOTIFICATION_CHANNEL_EMERGENCY_ALERTS, 863 context.getString(R.string.notification_channel_emergency_alerts), 864 NotificationManager.IMPORTANCE_LOW)); 865 final NotificationChannel nonEmergency = new NotificationChannel( 866 NOTIFICATION_CHANNEL_NON_EMERGENCY_ALERTS, 867 context.getString(R.string.notification_channel_broadcast_messages), 868 NotificationManager.IMPORTANCE_DEFAULT); 869 nonEmergency.enableVibration(true); 870 notificationManager.createNotificationChannel(nonEmergency); 871 872 final NotificationChannel emergencyAlertInVoiceCall = new NotificationChannel( 873 NOTIFICATION_CHANNEL_EMERGENCY_ALERTS_IN_VOICECALL, 874 context.getString(R.string.notification_channel_broadcast_messages_in_voicecall), 875 NotificationManager.IMPORTANCE_HIGH); 876 emergencyAlertInVoiceCall.enableVibration(true); 877 notificationManager.createNotificationChannel(emergencyAlertInVoiceCall); 878 879 final NotificationChannel settingsUpdate = new NotificationChannel( 880 NOTIFICATION_CHANNEL_SETTINGS_UPDATES, 881 context.getString(R.string.notification_channel_settings_updates), 882 NotificationManager.IMPORTANCE_DEFAULT); 883 notificationManager.createNotificationChannel(settingsUpdate); 884 } 885 886 createDisplayMessageIntent(Context context, Class intentClass, ArrayList<SmsCbMessage> messageList)887 private static Intent createDisplayMessageIntent(Context context, Class intentClass, 888 ArrayList<SmsCbMessage> messageList) { 889 // Trigger the list activity to fire up a dialog that shows the received messages 890 Intent intent = new Intent(context, intentClass); 891 intent.putParcelableArrayListExtra(CellBroadcastAlertService.SMS_CB_MESSAGE_EXTRA, 892 messageList); 893 intent.addFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION); 894 return intent; 895 } 896 897 /** 898 * Creates a delete intent that calls to the {@link CellBroadcastReceiver} in order to mark 899 * a message as read 900 * 901 * @param context context of the caller 902 * @param deliveryTime time the message was sent in order to mark as read 903 * @return delete intent to add to the pending intent 904 */ createMarkAsReadIntent(Context context, long deliveryTime)905 static Intent createMarkAsReadIntent(Context context, long deliveryTime) { 906 Intent deleteIntent = new Intent(context, CellBroadcastInternalReceiver.class); 907 deleteIntent.setAction(CellBroadcastReceiver.ACTION_MARK_AS_READ); 908 deleteIntent.putExtra(CellBroadcastReceiver.EXTRA_DELIVERY_TIME, deliveryTime); 909 return deleteIntent; 910 } 911 912 @VisibleForTesting 913 @Override onBind(Intent intent)914 public IBinder onBind(Intent intent) { 915 return new LocalBinder(); 916 } 917 918 @VisibleForTesting 919 class LocalBinder extends Binder { getService()920 public CellBroadcastAlertService getService() { 921 return CellBroadcastAlertService.this; 922 } 923 } 924 925 /** 926 * Remove previous unread notifications and play stored unread 927 * emergency messages after voice call finish. 928 */ 929 private final PhoneStateListener mPhoneStateListener = new PhoneStateListener( 930 new Handler(Looper.getMainLooper())::post) { 931 @Override 932 public void onCallStateChanged(int state, String incomingNumber) { 933 934 switch (state) { 935 case TelephonyManager.CALL_STATE_IDLE: 936 Log.d(TAG, "onCallStateChanged: CALL_STATE_IDLE"); 937 playPendingAlert(); 938 break; 939 940 default: 941 Log.d(TAG, "onCallStateChanged: other state = " + state); 942 break; 943 } 944 } 945 }; 946 playPendingAlert()947 private void playPendingAlert() { 948 if (sRemindAfterCallFinish) { 949 sRemindAfterCallFinish = false; 950 NotificationManager notificationManager = (NotificationManager) 951 getApplicationContext().getSystemService( 952 Context.NOTIFICATION_SERVICE); 953 954 StatusBarNotification[] notificationList = 955 notificationManager.getActiveNotifications(); 956 957 if(notificationList != null && notificationList.length >0) { 958 notificationManager.cancel(CellBroadcastAlertService.NOTIFICATION_ID); 959 ArrayList<SmsCbMessage> newMessageList = 960 CellBroadcastReceiverApp.getNewMessageList(); 961 962 for (int i = 0; i < newMessageList.size(); i++) { 963 openEmergencyAlertNotification(newMessageList.get(i)); 964 } 965 } 966 CellBroadcastReceiverApp.clearNewMessageList(); 967 } 968 } 969 isConnectedToCompanionDevices()970 private boolean isConnectedToCompanionDevices() { 971 BluetoothManager bluetoothMgr = getSystemService(BluetoothManager.class); 972 Set<BluetoothDevice> devices; 973 try { 974 devices = bluetoothMgr.getAdapter().getBondedDevices(); 975 } catch (SecurityException ex) { 976 // running on S+ will need runtime permission grant 977 // always return true here assuming there is connected devices to show alert in case 978 // of permission denial. 979 return true; 980 } 981 982 // TODO: filter out specific device types like wearable. no API support now. 983 for (BluetoothDevice device : devices) { 984 if (device.isConnected()) { 985 Log.d(TAG, "connected to device: " + device.getName()); 986 return true; 987 } 988 } 989 return false; 990 } 991 } 992