1 /*
2  * Copyright (C) 2016 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.internal.telephony;
18 
19 import android.app.Notification;
20 import android.app.NotificationManager;
21 import android.app.PendingIntent;
22 import android.content.BroadcastReceiver;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.IntentFilter;
26 import android.content.res.Resources;
27 import android.database.ContentObserver;
28 import android.os.Handler;
29 import android.os.Message;
30 import android.os.PersistableBundle;
31 import android.provider.Settings;
32 import android.telephony.CarrierConfigManager;
33 import android.telephony.ServiceState;
34 import android.telephony.SubscriptionManager;
35 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
36 
37 import com.android.internal.annotations.VisibleForTesting;
38 import com.android.internal.telephony.util.NotificationChannelController;
39 import com.android.telephony.Rlog;
40 
41 import java.util.HashMap;
42 import java.util.Map;
43 
44 /**
45  * This contains Carrier specific logic based on the states/events
46  * managed in ServiceStateTracker.
47  * {@hide}
48  */
49 public class CarrierServiceStateTracker extends Handler {
50     private static final String LOG_TAG = "CSST";
51     protected static final int CARRIER_EVENT_BASE = 100;
52     protected static final int CARRIER_EVENT_VOICE_REGISTRATION = CARRIER_EVENT_BASE + 1;
53     protected static final int CARRIER_EVENT_VOICE_DEREGISTRATION = CARRIER_EVENT_BASE + 2;
54     protected static final int CARRIER_EVENT_DATA_REGISTRATION = CARRIER_EVENT_BASE + 3;
55     protected static final int CARRIER_EVENT_DATA_DEREGISTRATION = CARRIER_EVENT_BASE + 4;
56     protected static final int CARRIER_EVENT_IMS_CAPABILITIES_CHANGED = CARRIER_EVENT_BASE + 5;
57 
58     private static final int UNINITIALIZED_DELAY_VALUE = -1;
59     private Phone mPhone;
60     private ServiceStateTracker mSST;
61     private final Map<Integer, NotificationType> mNotificationTypeMap = new HashMap<>();
62     private int mPreviousSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
63     public static final int NOTIFICATION_PREF_NETWORK = 1000;
64     public static final int NOTIFICATION_EMERGENCY_NETWORK = 1001;
65 
66     @VisibleForTesting
67     public static final String EMERGENCY_NOTIFICATION_TAG = "EmergencyNetworkNotification";
68 
69     @VisibleForTesting
70     public static final String PREF_NETWORK_NOTIFICATION_TAG = "PrefNetworkNotification";
71 
CarrierServiceStateTracker(Phone phone, ServiceStateTracker sst)72     public CarrierServiceStateTracker(Phone phone, ServiceStateTracker sst) {
73         this.mPhone = phone;
74         this.mSST = sst;
75         phone.getContext().registerReceiver(mBroadcastReceiver, new IntentFilter(
76                 CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
77         // Listen for subscriber changes
78         SubscriptionManager.from(mPhone.getContext()).addOnSubscriptionsChangedListener(
79                 new OnSubscriptionsChangedListener(this.getLooper()) {
80                     @Override
81                     public void onSubscriptionsChanged() {
82                         int subId = mPhone.getSubId();
83                         if (mPreviousSubId != subId) {
84                             mPreviousSubId = subId;
85                             registerPrefNetworkModeObserver();
86                         }
87                     }
88                 });
89 
90         registerNotificationTypes();
91         registerPrefNetworkModeObserver();
92     }
93 
94     private ContentObserver mPrefNetworkModeObserver = new ContentObserver(this) {
95         @Override
96         public void onChange(boolean selfChange) {
97             handlePrefNetworkModeChanged();
98         }
99     };
100 
101     /**
102      * Return preferred network mode observer
103      */
104     @VisibleForTesting
getContentObserver()105     public ContentObserver getContentObserver() {
106         return mPrefNetworkModeObserver;
107     }
108 
registerPrefNetworkModeObserver()109     private void registerPrefNetworkModeObserver() {
110         int subId = mPhone.getSubId();
111         unregisterPrefNetworkModeObserver();
112         if (SubscriptionManager.isValidSubscriptionId(subId)) {
113             mPhone.getContext().getContentResolver().registerContentObserver(
114                     Settings.Global.getUriFor(Settings.Global.PREFERRED_NETWORK_MODE + subId),
115                     true,
116                     mPrefNetworkModeObserver);
117         }
118     }
119 
unregisterPrefNetworkModeObserver()120     private void unregisterPrefNetworkModeObserver() {
121         mPhone.getContext().getContentResolver().unregisterContentObserver(
122                 mPrefNetworkModeObserver);
123     }
124 
125     /**
126      * Returns mNotificationTypeMap
127      */
128     @VisibleForTesting
getNotificationTypeMap()129     public Map<Integer, NotificationType> getNotificationTypeMap() {
130         return mNotificationTypeMap;
131     }
132 
registerNotificationTypes()133     private void registerNotificationTypes() {
134         mNotificationTypeMap.put(NOTIFICATION_PREF_NETWORK,
135                 new PrefNetworkNotification(NOTIFICATION_PREF_NETWORK));
136         mNotificationTypeMap.put(NOTIFICATION_EMERGENCY_NETWORK,
137                 new EmergencyNetworkNotification(NOTIFICATION_EMERGENCY_NETWORK));
138     }
139 
140     @Override
handleMessage(Message msg)141     public void handleMessage(Message msg) {
142         switch (msg.what) {
143             case CARRIER_EVENT_VOICE_REGISTRATION:
144             case CARRIER_EVENT_DATA_REGISTRATION:
145             case CARRIER_EVENT_VOICE_DEREGISTRATION:
146             case CARRIER_EVENT_DATA_DEREGISTRATION:
147                 handleConfigChanges();
148                 break;
149             case CARRIER_EVENT_IMS_CAPABILITIES_CHANGED:
150                 handleImsCapabilitiesChanged();
151                 break;
152             case NOTIFICATION_EMERGENCY_NETWORK:
153             case NOTIFICATION_PREF_NETWORK:
154                 Rlog.d(LOG_TAG, "sending notification after delay: " + msg.what);
155                 NotificationType notificationType = mNotificationTypeMap.get(msg.what);
156                 if (notificationType != null) {
157                     sendNotification(notificationType);
158                 }
159                 break;
160         }
161     }
162 
isPhoneStillRegistered()163     private boolean isPhoneStillRegistered() {
164         if (mSST.mSS == null) {
165             return true; //something has gone wrong, return true and not show the notification.
166         }
167         return (mSST.mSS.getState() == ServiceState.STATE_IN_SERVICE
168                 || mSST.mSS.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE);
169     }
170 
isPhoneRegisteredForWifiCalling()171     private boolean isPhoneRegisteredForWifiCalling() {
172         Rlog.d(LOG_TAG, "isPhoneRegisteredForWifiCalling: " + mPhone.isWifiCallingEnabled());
173         return mPhone.isWifiCallingEnabled();
174     }
175 
176     /**
177      * Returns true if the radio is off or in Airplane Mode else returns false.
178      */
179     @VisibleForTesting
isRadioOffOrAirplaneMode()180     public boolean isRadioOffOrAirplaneMode() {
181         Context context = mPhone.getContext();
182         int airplaneMode = -1;
183         try {
184             airplaneMode = Settings.Global.getInt(context.getContentResolver(),
185                     Settings.Global.AIRPLANE_MODE_ON, 0);
186         } catch (Exception e) {
187             Rlog.e(LOG_TAG, "Unable to get AIRPLACE_MODE_ON.");
188             return true;
189         }
190         return (!mSST.isRadioOn() || (airplaneMode != 0));
191     }
192 
193     /**
194      * Returns true if the preferred network is set to 'Global'.
195      */
isGlobalMode()196     private boolean isGlobalMode() {
197         Context context = mPhone.getContext();
198         int preferredNetworkSetting = -1;
199         try {
200             preferredNetworkSetting =
201                     android.provider.Settings.Global.getInt(context.getContentResolver(),
202                             android.provider.Settings.Global.PREFERRED_NETWORK_MODE
203                                     + mPhone.getSubId(), Phone.PREFERRED_NT_MODE);
204         } catch (Exception e) {
205             Rlog.e(LOG_TAG, "Unable to get PREFERRED_NETWORK_MODE.");
206             return true;
207         }
208         return (preferredNetworkSetting == RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA);
209     }
210 
handleConfigChanges()211     private void handleConfigChanges() {
212         for (Map.Entry<Integer, NotificationType> entry : mNotificationTypeMap.entrySet()) {
213             NotificationType notificationType = entry.getValue();
214             evaluateSendingMessageOrCancelNotification(notificationType);
215         }
216     }
217 
handlePrefNetworkModeChanged()218     private void handlePrefNetworkModeChanged() {
219         NotificationType notificationType = mNotificationTypeMap.get(NOTIFICATION_PREF_NETWORK);
220         if (notificationType != null) {
221             evaluateSendingMessageOrCancelNotification(notificationType);
222         }
223     }
224 
handleImsCapabilitiesChanged()225     private void handleImsCapabilitiesChanged() {
226         NotificationType notificationType = mNotificationTypeMap
227                 .get(NOTIFICATION_EMERGENCY_NETWORK);
228         if (notificationType != null) {
229             evaluateSendingMessageOrCancelNotification(notificationType);
230         }
231     }
232 
evaluateSendingMessageOrCancelNotification(NotificationType notificationType)233     private void evaluateSendingMessageOrCancelNotification(NotificationType notificationType) {
234         if (evaluateSendingMessage(notificationType)) {
235             Message notificationMsg = obtainMessage(notificationType.getTypeId(), null);
236             Rlog.i(LOG_TAG, "starting timer for notifications." + notificationType.getTypeId());
237             sendMessageDelayed(notificationMsg, getDelay(notificationType));
238         } else {
239             cancelNotification(notificationType);
240             Rlog.i(LOG_TAG, "canceling notifications: " + notificationType.getTypeId());
241         }
242     }
243 
244     /**
245      * This method adds a level of indirection, and was created so we can unit the class.
246      **/
247     @VisibleForTesting
evaluateSendingMessage(NotificationType notificationType)248     public boolean evaluateSendingMessage(NotificationType notificationType) {
249         return notificationType.sendMessage();
250     }
251 
252     /**
253      * This method adds a level of indirection, and was created so we can unit the class.
254      **/
255     @VisibleForTesting
getDelay(NotificationType notificationType)256     public int getDelay(NotificationType notificationType) {
257         return notificationType.getDelay();
258     }
259 
260     /**
261      * This method adds a level of indirection, and was created so we can unit the class.
262      **/
263     @VisibleForTesting
getNotificationBuilder(NotificationType notificationType)264     public Notification.Builder getNotificationBuilder(NotificationType notificationType) {
265         return notificationType.getNotificationBuilder();
266     }
267 
268     /**
269      * This method adds a level of indirection, and was created so we can unit the class.
270      **/
271     @VisibleForTesting
getNotificationManager(Context context)272     public NotificationManager getNotificationManager(Context context) {
273         return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
274     }
275 
276     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
277         @Override
278         public void onReceive(Context context, Intent intent) {
279             CarrierConfigManager carrierConfigManager = (CarrierConfigManager)
280                     context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
281             PersistableBundle b = carrierConfigManager.getConfigForSubId(mPhone.getSubId());
282 
283             for (Map.Entry<Integer, NotificationType> entry : mNotificationTypeMap.entrySet()) {
284                 NotificationType notificationType = entry.getValue();
285                 notificationType.setDelay(b);
286             }
287             handleConfigChanges();
288         }
289     };
290 
291     /**
292      * Post a notification to the NotificationManager for changing network type.
293      */
294     @VisibleForTesting
sendNotification(NotificationType notificationType)295     public void sendNotification(NotificationType notificationType) {
296         if (!evaluateSendingMessage(notificationType)) {
297             return;
298         }
299 
300         Context context = mPhone.getContext();
301         Notification.Builder builder = getNotificationBuilder(notificationType);
302         // set some common attributes
303         builder.setWhen(System.currentTimeMillis())
304                 .setAutoCancel(true)
305                 .setSmallIcon(com.android.internal.R.drawable.stat_sys_warning)
306                 .setColor(context.getResources().getColor(
307                        com.android.internal.R.color.system_notification_accent_color));
308         getNotificationManager(context).notify(notificationType.getNotificationTag(),
309                 notificationType.getNotificationId(), builder.build());
310     }
311 
312     /**
313      * Cancel notifications if a registration is pending or has been sent.
314      **/
cancelNotification(NotificationType notificationType)315     public void cancelNotification(NotificationType notificationType) {
316         Context context = mPhone.getContext();
317         removeMessages(notificationType.getTypeId());
318         getNotificationManager(context).cancel(
319                 notificationType.getNotificationTag(), notificationType.getNotificationId());
320     }
321 
322     /**
323      * Dispose the CarrierServiceStateTracker.
324      */
dispose()325     public void dispose() {
326         unregisterPrefNetworkModeObserver();
327     }
328 
329     /**
330      * Class that defines the different types of notifications.
331      */
332     public interface NotificationType {
333 
334         /**
335          * decides if the message should be sent, Returns boolean
336          **/
sendMessage()337         boolean sendMessage();
338 
339         /**
340          * returns the interval by which the message is delayed.
341          **/
getDelay()342         int getDelay();
343 
344         /** sets the interval by which the message is delayed.
345          * @param bundle PersistableBundle
346         **/
setDelay(PersistableBundle bundle)347         void setDelay(PersistableBundle bundle);
348 
349         /**
350          * returns notification type id.
351          **/
getTypeId()352         int getTypeId();
353 
354         /**
355          * returns notification id.
356          **/
getNotificationId()357         int getNotificationId();
358 
359         /**
360          * returns notification tag.
361          **/
getNotificationTag()362         String getNotificationTag();
363 
364         /**
365          * returns the notification builder, for the notification to be displayed.
366          **/
getNotificationBuilder()367         Notification.Builder getNotificationBuilder();
368     }
369 
370     /**
371      * Class that defines the network notification, which is shown when the phone cannot camp on
372      * a network, and has 'preferred mode' set to global.
373      */
374     public class PrefNetworkNotification implements NotificationType {
375 
376         private final int mTypeId;
377         private int mDelay = UNINITIALIZED_DELAY_VALUE;
378 
PrefNetworkNotification(int typeId)379         PrefNetworkNotification(int typeId) {
380             this.mTypeId = typeId;
381         }
382 
383         /** sets the interval by which the message is delayed.
384          * @param bundle PersistableBundle
385          **/
setDelay(PersistableBundle bundle)386         public void setDelay(PersistableBundle bundle) {
387             if (bundle == null) {
388                 Rlog.e(LOG_TAG, "bundle is null");
389                 return;
390             }
391             this.mDelay = bundle.getInt(
392                     CarrierConfigManager.KEY_PREF_NETWORK_NOTIFICATION_DELAY_INT);
393             Rlog.i(LOG_TAG, "reading time to delay notification pref network: " + mDelay);
394         }
395 
getDelay()396         public int getDelay() {
397             return mDelay;
398         }
399 
getTypeId()400         public int getTypeId() {
401             return mTypeId;
402         }
403 
getNotificationId()404         public int getNotificationId() {
405             return mPhone.getSubId();
406         }
407 
getNotificationTag()408         public String getNotificationTag() {
409             return PREF_NETWORK_NOTIFICATION_TAG;
410         }
411 
412         /**
413          * Contains logic on sending notifications.
414          */
sendMessage()415         public boolean sendMessage() {
416             Rlog.i(LOG_TAG, "PrefNetworkNotification: sendMessage() w/values: "
417                     + "," + isPhoneStillRegistered() + "," + mDelay + "," + isGlobalMode()
418                     + "," + mSST.isRadioOn());
419             if (mDelay == UNINITIALIZED_DELAY_VALUE || isPhoneStillRegistered() || isGlobalMode()
420                     || isRadioOffOrAirplaneMode()) {
421                 return false;
422             }
423             return true;
424         }
425 
426         /**
427          * Builds a partial notificaiton builder, and returns it.
428          */
getNotificationBuilder()429         public Notification.Builder getNotificationBuilder() {
430             Context context = mPhone.getContext();
431             Intent notificationIntent = new Intent(Settings.ACTION_DATA_ROAMING_SETTINGS);
432             notificationIntent.putExtra("expandable", true);
433             PendingIntent settingsIntent = PendingIntent.getActivity(context, 0, notificationIntent,
434                     PendingIntent.FLAG_ONE_SHOT);
435             Resources res = SubscriptionManager.getResourcesForSubId(context, mPhone.getSubId());
436             CharSequence title = res.getText(
437                     com.android.internal.R.string.NetworkPreferenceSwitchTitle);
438             CharSequence details = res.getText(
439                     com.android.internal.R.string.NetworkPreferenceSwitchSummary);
440             return new Notification.Builder(context)
441                     .setContentTitle(title)
442                     .setStyle(new Notification.BigTextStyle().bigText(details))
443                     .setContentText(details)
444                     .setChannelId(NotificationChannelController.CHANNEL_ID_ALERT)
445                     .setContentIntent(settingsIntent);
446         }
447     }
448 
449     /**
450      * Class that defines the emergency notification, which is shown when Wi-Fi Calling is
451      * available.
452      */
453     public class EmergencyNetworkNotification implements NotificationType {
454 
455         private final int mTypeId;
456         private int mDelay = UNINITIALIZED_DELAY_VALUE;
457 
EmergencyNetworkNotification(int typeId)458         EmergencyNetworkNotification(int typeId) {
459             this.mTypeId = typeId;
460         }
461 
462         /** sets the interval by which the message is delayed.
463          * @param bundle PersistableBundle
464          **/
setDelay(PersistableBundle bundle)465         public void setDelay(PersistableBundle bundle) {
466             if (bundle == null) {
467                 Rlog.e(LOG_TAG, "bundle is null");
468                 return;
469             }
470             this.mDelay = bundle.getInt(
471                     CarrierConfigManager.KEY_EMERGENCY_NOTIFICATION_DELAY_INT);
472             Rlog.i(LOG_TAG, "reading time to delay notification emergency: " + mDelay);
473         }
474 
getDelay()475         public int getDelay() {
476             return mDelay;
477         }
478 
getTypeId()479         public int getTypeId() {
480             return mTypeId;
481         }
482 
getNotificationId()483         public int getNotificationId() {
484             return mPhone.getSubId();
485         }
486 
getNotificationTag()487         public String getNotificationTag() {
488             return EMERGENCY_NOTIFICATION_TAG;
489         }
490 
491         /**
492          * Contains logic on sending notifications,
493          */
sendMessage()494         public boolean sendMessage() {
495             Rlog.i(LOG_TAG, "EmergencyNetworkNotification: sendMessage() w/values: "
496                     + "," + mDelay + "," + isPhoneRegisteredForWifiCalling() + ","
497                     + mSST.isRadioOn());
498             if (mDelay == UNINITIALIZED_DELAY_VALUE || !isPhoneRegisteredForWifiCalling()) {
499                 return false;
500             }
501             return true;
502         }
503 
504         /**
505          * Builds a partial notificaiton builder, and returns it.
506          */
getNotificationBuilder()507         public Notification.Builder getNotificationBuilder() {
508             Context context = mPhone.getContext();
509             Resources res = SubscriptionManager.getResourcesForSubId(context, mPhone.getSubId());
510             CharSequence title = res.getText(
511                     com.android.internal.R.string.EmergencyCallWarningTitle);
512             CharSequence details = res.getText(
513                     com.android.internal.R.string.EmergencyCallWarningSummary);
514             return new Notification.Builder(context)
515                     .setContentTitle(title)
516                     .setStyle(new Notification.BigTextStyle().bigText(details))
517                     .setContentText(details)
518                     .setFlag(Notification.FLAG_NO_CLEAR, true)
519                     .setChannelId(NotificationChannelController.CHANNEL_ID_WFC);
520         }
521     }
522 }
523