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