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