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