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