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