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