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