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