Home | History | Annotate | Download | only in net
      1 /*
      2  * Copyright (C) 2008 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 android.net;
     18 
     19 import android.content.BroadcastReceiver;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.IntentFilter;
     23 import android.os.RemoteException;
     24 import android.os.Handler;
     25 import android.os.ServiceManager;
     26 import android.os.SystemProperties;
     27 import com.android.internal.telephony.ITelephony;
     28 import com.android.internal.telephony.Phone;
     29 import com.android.internal.telephony.TelephonyIntents;
     30 import android.net.NetworkInfo.DetailedState;
     31 import android.telephony.TelephonyManager;
     32 import android.util.Log;
     33 import android.text.TextUtils;
     34 
     35 /**
     36  * Track the state of mobile data connectivity. This is done by
     37  * receiving broadcast intents from the Phone process whenever
     38  * the state of data connectivity changes.
     39  *
     40  * {@hide}
     41  */
     42 public class MobileDataStateTracker extends NetworkStateTracker {
     43 
     44     private static final String TAG = "MobileDataStateTracker";
     45     private static final boolean DBG = true;
     46 
     47     private Phone.DataState mMobileDataState;
     48     private ITelephony mPhoneService;
     49 
     50     private String mApnType;
     51     private String mApnTypeToWatchFor;
     52     private String mApnName;
     53     private boolean mEnabled;
     54     private BroadcastReceiver mStateReceiver;
     55 
     56     /**
     57      * Create a new MobileDataStateTracker
     58      * @param context the application context of the caller
     59      * @param target a message handler for getting callbacks about state changes
     60      * @param netType the ConnectivityManager network type
     61      * @param apnType the Phone apnType
     62      * @param tag the name of this network
     63      */
     64     public MobileDataStateTracker(Context context, Handler target, int netType, String tag) {
     65         super(context, target, netType,
     66                 TelephonyManager.getDefault().getNetworkType(), tag,
     67                 TelephonyManager.getDefault().getNetworkTypeName());
     68         mApnType = networkTypeToApnType(netType);
     69         if (TextUtils.equals(mApnType, Phone.APN_TYPE_HIPRI)) {
     70             mApnTypeToWatchFor = Phone.APN_TYPE_DEFAULT;
     71         } else {
     72             mApnTypeToWatchFor = mApnType;
     73         }
     74 
     75         mPhoneService = null;
     76         if(netType == ConnectivityManager.TYPE_MOBILE) {
     77             mEnabled = true;
     78         } else {
     79             mEnabled = false;
     80         }
     81 
     82         mDnsPropNames = new String[] {
     83                 "net.rmnet0.dns1",
     84                 "net.rmnet0.dns2",
     85                 "net.eth0.dns1",
     86                 "net.eth0.dns2",
     87                 "net.eth0.dns3",
     88                 "net.eth0.dns4",
     89                 "net.gprs.dns1",
     90                 "net.gprs.dns2",
     91                 "net.ppp0.dns1",
     92                 "net.ppp0.dns2"};
     93 
     94     }
     95 
     96     /**
     97      * Begin monitoring mobile data connectivity.
     98      */
     99     public void startMonitoring() {
    100         IntentFilter filter =
    101                 new IntentFilter(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
    102         filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED);
    103         filter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
    104 
    105         mStateReceiver = new MobileDataStateReceiver();
    106         Intent intent = mContext.registerReceiver(mStateReceiver, filter);
    107         if (intent != null)
    108             mMobileDataState = getMobileDataState(intent);
    109         else
    110             mMobileDataState = Phone.DataState.DISCONNECTED;
    111     }
    112 
    113     private Phone.DataState getMobileDataState(Intent intent) {
    114         String str = intent.getStringExtra(Phone.STATE_KEY);
    115         if (str != null) {
    116             String apnTypeList =
    117                     intent.getStringExtra(Phone.DATA_APN_TYPES_KEY);
    118             if (isApnTypeIncluded(apnTypeList)) {
    119                 return Enum.valueOf(Phone.DataState.class, str);
    120             }
    121         }
    122         return Phone.DataState.DISCONNECTED;
    123     }
    124 
    125     private boolean isApnTypeIncluded(String typeList) {
    126         /* comma seperated list - split and check */
    127         if (typeList == null)
    128             return false;
    129 
    130         String[] list = typeList.split(",");
    131         for(int i=0; i< list.length; i++) {
    132             if (TextUtils.equals(list[i], mApnTypeToWatchFor) ||
    133                 TextUtils.equals(list[i], Phone.APN_TYPE_ALL)) {
    134                 return true;
    135             }
    136         }
    137         return false;
    138     }
    139 
    140     private class MobileDataStateReceiver extends BroadcastReceiver {
    141         public void onReceive(Context context, Intent intent) {
    142             synchronized(this) {
    143                 if (intent.getAction().equals(TelephonyIntents.
    144                         ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) {
    145                     Phone.DataState state = getMobileDataState(intent);
    146                     String reason = intent.getStringExtra(Phone.STATE_CHANGE_REASON_KEY);
    147                     String apnName = intent.getStringExtra(Phone.DATA_APN_KEY);
    148                     String apnTypeList = intent.getStringExtra(Phone.DATA_APN_TYPES_KEY);
    149                     mApnName = apnName;
    150 
    151                     boolean unavailable = intent.getBooleanExtra(Phone.NETWORK_UNAVAILABLE_KEY,
    152                             false);
    153 
    154                     // set this regardless of the apnTypeList.  It's all the same radio/network
    155                     // underneath
    156                     mNetworkInfo.setIsAvailable(!unavailable);
    157 
    158                     if (isApnTypeIncluded(apnTypeList)) {
    159                         if (mEnabled == false) {
    160                             // if we're not enabled but the APN Type is supported by this connection
    161                             // we should record the interface name if one's provided.  If the user
    162                             // turns on this network we will need the interfacename but won't get
    163                             // a fresh connected message - TODO fix this when we get per-APN
    164                             // notifications
    165                             if (state == Phone.DataState.CONNECTED) {
    166                                 if (DBG) Log.d(TAG, "replacing old mInterfaceName (" +
    167                                         mInterfaceName + ") with " +
    168                                         intent.getStringExtra(Phone.DATA_IFACE_NAME_KEY) +
    169                                         " for " + mApnType);
    170                                 mInterfaceName = intent.getStringExtra(Phone.DATA_IFACE_NAME_KEY);
    171                             }
    172                             return;
    173                         }
    174                     } else {
    175                         return;
    176                     }
    177 
    178                     if (DBG) Log.d(TAG, mApnType + " Received state= " + state + ", old= " +
    179                             mMobileDataState + ", reason= " +
    180                             (reason == null ? "(unspecified)" : reason) +
    181                             ", apnTypeList= " + apnTypeList);
    182 
    183                     if (mMobileDataState != state) {
    184                         mMobileDataState = state;
    185                         switch (state) {
    186                             case DISCONNECTED:
    187                                 if(isTeardownRequested()) {
    188                                     mEnabled = false;
    189                                     setTeardownRequested(false);
    190                                 }
    191 
    192                                 setDetailedState(DetailedState.DISCONNECTED, reason, apnName);
    193                                 if (mInterfaceName != null) {
    194                                     NetworkUtils.resetConnections(mInterfaceName);
    195                                 }
    196                                 // can't do this here - ConnectivityService needs it to clear stuff
    197                                 // it's ok though - just leave it to be refreshed next time
    198                                 // we connect.
    199                                 //if (DBG) Log.d(TAG, "clearing mInterfaceName for "+ mApnType +
    200                                 //        " as it DISCONNECTED");
    201                                 //mInterfaceName = null;
    202                                 //mDefaultGatewayAddr = 0;
    203                                 break;
    204                             case CONNECTING:
    205                                 setDetailedState(DetailedState.CONNECTING, reason, apnName);
    206                                 break;
    207                             case SUSPENDED:
    208                                 setDetailedState(DetailedState.SUSPENDED, reason, apnName);
    209                                 break;
    210                             case CONNECTED:
    211                                 mInterfaceName = intent.getStringExtra(Phone.DATA_IFACE_NAME_KEY);
    212                                 if (mInterfaceName == null) {
    213                                     Log.d(TAG, "CONNECTED event did not supply interface name.");
    214                                 }
    215                                 setDetailedState(DetailedState.CONNECTED, reason, apnName);
    216                                 break;
    217                         }
    218                     }
    219                 } else if (intent.getAction().
    220                         equals(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED)) {
    221                     mEnabled = false;
    222                     String reason = intent.getStringExtra(Phone.FAILURE_REASON_KEY);
    223                     String apnName = intent.getStringExtra(Phone.DATA_APN_KEY);
    224                     if (DBG) Log.d(TAG, "Received " + intent.getAction() + " broadcast" +
    225                             reason == null ? "" : "(" + reason + ")");
    226                     setDetailedState(DetailedState.FAILED, reason, apnName);
    227                 }
    228                 TelephonyManager tm = TelephonyManager.getDefault();
    229                 setRoamingStatus(tm.isNetworkRoaming());
    230                 setSubtype(tm.getNetworkType(), tm.getNetworkTypeName());
    231             }
    232         }
    233     }
    234 
    235     private void getPhoneService(boolean forceRefresh) {
    236         if ((mPhoneService == null) || forceRefresh) {
    237             mPhoneService = ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
    238         }
    239     }
    240 
    241     /**
    242      * Report whether data connectivity is possible.
    243      */
    244     public boolean isAvailable() {
    245         getPhoneService(false);
    246 
    247         /*
    248          * If the phone process has crashed in the past, we'll get a
    249          * RemoteException and need to re-reference the service.
    250          */
    251         for (int retry = 0; retry < 2; retry++) {
    252             if (mPhoneService == null) break;
    253 
    254             try {
    255                 return mPhoneService.isDataConnectivityPossible();
    256             } catch (RemoteException e) {
    257                 // First-time failed, get the phone service again
    258                 if (retry == 0) getPhoneService(true);
    259             }
    260         }
    261 
    262         return false;
    263     }
    264 
    265     /**
    266      * {@inheritDoc}
    267      * The mobile data network subtype indicates what generation network technology is in effect,
    268      * e.g., GPRS, EDGE, UMTS, etc.
    269      */
    270     public int getNetworkSubtype() {
    271         return TelephonyManager.getDefault().getNetworkType();
    272     }
    273 
    274     /**
    275      * Return the system properties name associated with the tcp buffer sizes
    276      * for this network.
    277      */
    278     public String getTcpBufferSizesPropName() {
    279         String networkTypeStr = "unknown";
    280         TelephonyManager tm = new TelephonyManager(mContext);
    281         //TODO We have to edit the parameter for getNetworkType regarding CDMA
    282         switch(tm.getNetworkType()) {
    283         case TelephonyManager.NETWORK_TYPE_GPRS:
    284             networkTypeStr = "gprs";
    285             break;
    286         case TelephonyManager.NETWORK_TYPE_EDGE:
    287             networkTypeStr = "edge";
    288             break;
    289         case TelephonyManager.NETWORK_TYPE_UMTS:
    290             networkTypeStr = "umts";
    291             break;
    292         case TelephonyManager.NETWORK_TYPE_HSDPA:
    293             networkTypeStr = "hsdpa";
    294             break;
    295         case TelephonyManager.NETWORK_TYPE_HSUPA:
    296             networkTypeStr = "hsupa";
    297             break;
    298         case TelephonyManager.NETWORK_TYPE_HSPA:
    299             networkTypeStr = "hspa";
    300             break;
    301         case TelephonyManager.NETWORK_TYPE_CDMA:
    302             networkTypeStr = "cdma";
    303             break;
    304         case TelephonyManager.NETWORK_TYPE_1xRTT:
    305             networkTypeStr = "1xrtt";
    306             break;
    307         case TelephonyManager.NETWORK_TYPE_EVDO_0:
    308             networkTypeStr = "evdo";
    309             break;
    310         case TelephonyManager.NETWORK_TYPE_EVDO_A:
    311             networkTypeStr = "evdo";
    312             break;
    313         }
    314         return "net.tcp.buffersize." + networkTypeStr;
    315     }
    316 
    317     /**
    318      * Tear down mobile data connectivity, i.e., disable the ability to create
    319      * mobile data connections.
    320      */
    321     @Override
    322     public boolean teardown() {
    323         // since we won't get a notification currently (TODO - per APN notifications)
    324         // we won't get a disconnect message until all APN's on the current connection's
    325         // APN list are disabled.  That means privateRoutes for DNS and such will remain on -
    326         // not a problem since that's all shared with whatever other APN is still on, but
    327         // ugly.
    328         setTeardownRequested(true);
    329         return (setEnableApn(mApnType, false) != Phone.APN_REQUEST_FAILED);
    330     }
    331 
    332     /**
    333      * Re-enable mobile data connectivity after a {@link #teardown()}.
    334      */
    335     public boolean reconnect() {
    336         setTeardownRequested(false);
    337         switch (setEnableApn(mApnType, true)) {
    338             case Phone.APN_ALREADY_ACTIVE:
    339                 // TODO - remove this when we get per-apn notifications
    340                 mEnabled = true;
    341                 // need to set self to CONNECTING so the below message is handled.
    342                 mMobileDataState = Phone.DataState.CONNECTING;
    343                 setDetailedState(DetailedState.CONNECTING, Phone.REASON_APN_CHANGED, null);
    344                 //send out a connected message
    345                 Intent intent = new Intent(TelephonyIntents.
    346                         ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
    347                 intent.putExtra(Phone.STATE_KEY, Phone.DataState.CONNECTED.toString());
    348                 intent.putExtra(Phone.STATE_CHANGE_REASON_KEY, Phone.REASON_APN_CHANGED);
    349                 intent.putExtra(Phone.DATA_APN_TYPES_KEY, mApnTypeToWatchFor);
    350                 intent.putExtra(Phone.DATA_APN_KEY, mApnName);
    351                 intent.putExtra(Phone.DATA_IFACE_NAME_KEY, mInterfaceName);
    352                 intent.putExtra(Phone.NETWORK_UNAVAILABLE_KEY, false);
    353                 if (mStateReceiver != null) mStateReceiver.onReceive(mContext, intent);
    354                 break;
    355             case Phone.APN_REQUEST_STARTED:
    356                 mEnabled = true;
    357                 // no need to do anything - we're already due some status update intents
    358                 break;
    359             case Phone.APN_REQUEST_FAILED:
    360                 if (mPhoneService == null && mApnType == Phone.APN_TYPE_DEFAULT) {
    361                     // on startup we may try to talk to the phone before it's ready
    362                     // since the phone will come up enabled, go with that.
    363                     // TODO - this also comes up on telephony crash: if we think mobile data is
    364                     // off and the telephony stuff crashes and has to restart it will come up
    365                     // enabled (making a data connection).  We will then be out of sync.
    366                     // A possible solution is a broadcast when telephony restarts.
    367                     mEnabled = true;
    368                     return false;
    369                 }
    370                 // else fall through
    371             case Phone.APN_TYPE_NOT_AVAILABLE:
    372                 // Default is always available, but may be off due to
    373                 // AirplaneMode or E-Call or whatever..
    374                 if (mApnType != Phone.APN_TYPE_DEFAULT) {
    375                     mEnabled = false;
    376                 }
    377                 break;
    378             default:
    379                 Log.e(TAG, "Error in reconnect - unexpected response.");
    380                 mEnabled = false;
    381                 break;
    382         }
    383         return mEnabled;
    384     }
    385 
    386     /**
    387      * Turn on or off the mobile radio. No connectivity will be possible while the
    388      * radio is off. The operation is a no-op if the radio is already in the desired state.
    389      * @param turnOn {@code true} if the radio should be turned on, {@code false} if
    390      */
    391     public boolean setRadio(boolean turnOn) {
    392         getPhoneService(false);
    393         /*
    394          * If the phone process has crashed in the past, we'll get a
    395          * RemoteException and need to re-reference the service.
    396          */
    397         for (int retry = 0; retry < 2; retry++) {
    398             if (mPhoneService == null) {
    399                 Log.w(TAG,
    400                     "Ignoring mobile radio request because could not acquire PhoneService");
    401                 break;
    402             }
    403 
    404             try {
    405                 return mPhoneService.setRadio(turnOn);
    406             } catch (RemoteException e) {
    407                 if (retry == 0) getPhoneService(true);
    408             }
    409         }
    410 
    411         Log.w(TAG, "Could not set radio power to " + (turnOn ? "on" : "off"));
    412         return false;
    413     }
    414 
    415     /**
    416      * Tells the phone sub-system that the caller wants to
    417      * begin using the named feature. The only supported features at
    418      * this time are {@code Phone.FEATURE_ENABLE_MMS}, which allows an application
    419      * to specify that it wants to send and/or receive MMS data, and
    420      * {@code Phone.FEATURE_ENABLE_SUPL}, which is used for Assisted GPS.
    421      * @param feature the name of the feature to be used
    422      * @param callingPid the process ID of the process that is issuing this request
    423      * @param callingUid the user ID of the process that is issuing this request
    424      * @return an integer value representing the outcome of the request.
    425      * The interpretation of this value is feature-specific.
    426      * specific, except that the value {@code -1}
    427      * always indicates failure. For {@code Phone.FEATURE_ENABLE_MMS},
    428      * the other possible return values are
    429      * <ul>
    430      * <li>{@code Phone.APN_ALREADY_ACTIVE}</li>
    431      * <li>{@code Phone.APN_REQUEST_STARTED}</li>
    432      * <li>{@code Phone.APN_TYPE_NOT_AVAILABLE}</li>
    433      * <li>{@code Phone.APN_REQUEST_FAILED}</li>
    434      * </ul>
    435      */
    436     public int startUsingNetworkFeature(String feature, int callingPid, int callingUid) {
    437         return -1;
    438     }
    439 
    440     /**
    441      * Tells the phone sub-system that the caller is finished
    442      * using the named feature. The only supported feature at
    443      * this time is {@code Phone.FEATURE_ENABLE_MMS}, which allows an application
    444      * to specify that it wants to send and/or receive MMS data.
    445      * @param feature the name of the feature that is no longer needed
    446      * @param callingPid the process ID of the process that is issuing this request
    447      * @param callingUid the user ID of the process that is issuing this request
    448      * @return an integer value representing the outcome of the request.
    449      * The interpretation of this value is feature-specific, except that
    450      * the value {@code -1} always indicates failure.
    451      */
    452     public int stopUsingNetworkFeature(String feature, int callingPid, int callingUid) {
    453         return -1;
    454     }
    455 
    456     /**
    457      * Ensure that a network route exists to deliver traffic to the specified
    458      * host via the mobile data network.
    459      * @param hostAddress the IP address of the host to which the route is desired,
    460      * in network byte order.
    461      * @return {@code true} on success, {@code false} on failure
    462      */
    463     @Override
    464     public boolean requestRouteToHost(int hostAddress) {
    465         if (DBG) {
    466             Log.d(TAG, "Requested host route to " + Integer.toHexString(hostAddress) +
    467                     " for " + mApnType + "(" + mInterfaceName + ")");
    468         }
    469         if (mInterfaceName != null && hostAddress != -1) {
    470             return NetworkUtils.addHostRoute(mInterfaceName, hostAddress) == 0;
    471         } else {
    472             return false;
    473         }
    474     }
    475 
    476     @Override
    477     public String toString() {
    478         StringBuffer sb = new StringBuffer("Mobile data state: ");
    479 
    480         sb.append(mMobileDataState);
    481         return sb.toString();
    482     }
    483 
    484    /**
    485      * Internal method supporting the ENABLE_MMS feature.
    486      * @param apnType the type of APN to be enabled or disabled (e.g., mms)
    487      * @param enable {@code true} to enable the specified APN type,
    488      * {@code false} to disable it.
    489      * @return an integer value representing the outcome of the request.
    490      */
    491     private int setEnableApn(String apnType, boolean enable) {
    492         getPhoneService(false);
    493         /*
    494          * If the phone process has crashed in the past, we'll get a
    495          * RemoteException and need to re-reference the service.
    496          */
    497         for (int retry = 0; retry < 2; retry++) {
    498             if (mPhoneService == null) {
    499                 Log.w(TAG,
    500                     "Ignoring feature request because could not acquire PhoneService");
    501                 break;
    502             }
    503 
    504             try {
    505                 if (enable) {
    506                     return mPhoneService.enableApnType(apnType);
    507                 } else {
    508                     return mPhoneService.disableApnType(apnType);
    509                 }
    510             } catch (RemoteException e) {
    511                 if (retry == 0) getPhoneService(true);
    512             }
    513         }
    514 
    515         Log.w(TAG, "Could not " + (enable ? "enable" : "disable")
    516                 + " APN type \"" + apnType + "\"");
    517         return Phone.APN_REQUEST_FAILED;
    518     }
    519 
    520     public static String networkTypeToApnType(int netType) {
    521         switch(netType) {
    522             case ConnectivityManager.TYPE_MOBILE:
    523                 return Phone.APN_TYPE_DEFAULT;  // TODO - use just one of these
    524             case ConnectivityManager.TYPE_MOBILE_MMS:
    525                 return Phone.APN_TYPE_MMS;
    526             case ConnectivityManager.TYPE_MOBILE_SUPL:
    527                 return Phone.APN_TYPE_SUPL;
    528             case ConnectivityManager.TYPE_MOBILE_DUN:
    529                 return Phone.APN_TYPE_DUN;
    530             case ConnectivityManager.TYPE_MOBILE_HIPRI:
    531                 return Phone.APN_TYPE_HIPRI;
    532             default:
    533                 Log.e(TAG, "Error mapping networkType " + netType + " to apnType.");
    534                 return null;
    535         }
    536     }
    537 }
    538