Home | History | Annotate | Download | only in telephony
      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 
     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
     98     public ContentObserver getContentObserver() {
     99         return mPrefNetworkModeObserver;
    100     }
    101 
    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 
    113     private void unregisterPrefNetworkModeObserver() {
    114         mPhone.getContext().getContentResolver().unregisterContentObserver(
    115                 mPrefNetworkModeObserver);
    116     }
    117 
    118     /**
    119      * Returns mNotificationTypeMap
    120      */
    121     @VisibleForTesting
    122     public Map<Integer, NotificationType> getNotificationTypeMap() {
    123         return mNotificationTypeMap;
    124     }
    125 
    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
    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 
    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 
    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 
    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
    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      */
    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 
    208     private void handleConfigChanges() {
    209         for (Map.Entry<Integer, NotificationType> entry : mNotificationTypeMap.entrySet()) {
    210             NotificationType notificationType = entry.getValue();
    211             evaluateSendingMessageOrCancelNotification(notificationType);
    212         }
    213     }
    214 
    215     private void handlePrefNetworkModeChanged() {
    216         NotificationType notificationType = mNotificationTypeMap.get(NOTIFICATION_PREF_NETWORK);
    217         if (notificationType != null) {
    218             evaluateSendingMessageOrCancelNotification(notificationType);
    219         }
    220     }
    221 
    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
    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
    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
    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
    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
    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      **/
    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      */
    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          **/
    325         boolean sendMessage();
    326 
    327         /**
    328          * returns the interval by which the message is delayed.
    329          **/
    330         int getDelay();
    331 
    332         /** sets the interval by which the message is delayed.
    333          * @param bundle PersistableBundle
    334         **/
    335         void setDelay(PersistableBundle bundle);
    336 
    337         /**
    338          * returns notification type id.
    339          **/
    340         int getTypeId();
    341 
    342         /**
    343          * returns the notification builder, for the notification to be displayed.
    344          **/
    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 
    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          **/
    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 
    374         public int getDelay() {
    375             return mDelay;
    376         }
    377 
    378         public int getTypeId() {
    379             return mTypeId;
    380         }
    381 
    382         /**
    383          * Contains logic on sending notifications.
    384          */
    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          */
    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 
    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          **/
    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 
    444         public int getDelay() {
    445             return mDelay;
    446         }
    447 
    448         public int getTypeId() {
    449             return mTypeId;
    450         }
    451 
    452         /**
    453          * Contains logic on sending notifications,
    454          */
    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          */
    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