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