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