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