• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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