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