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