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