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