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