MobileDataStateTracker.java revision 153b4374d067ee92a0b9c712e54300cc49db017c
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.IBinder; 26import android.os.Message; 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.net.NetworkInfo; 33import android.net.NetworkProperties; 34import android.telephony.TelephonyManager; 35import android.util.Log; 36import android.text.TextUtils; 37 38/** 39 * Track the state of mobile data connectivity. This is done by 40 * receiving broadcast intents from the Phone process whenever 41 * the state of data connectivity changes. 42 * 43 * {@hide} 44 */ 45public class MobileDataStateTracker implements NetworkStateTracker { 46 47 private static final String TAG = "MobileDataStateTracker"; 48 private static final boolean DBG = true; 49 50 private Phone.DataState mMobileDataState; 51 private ITelephony mPhoneService; 52 53 private String mApnType; 54 private static String[] sDnsPropNames; 55 private NetworkInfo mNetworkInfo; 56 private boolean mTeardownRequested = false; 57 private Handler mTarget; 58 private Context mContext; 59 private NetworkProperties mNetworkProperties; 60 private boolean mPrivateDnsRouteSet = false; 61 private int mDefaultGatewayAddr = 0; 62 private boolean mDefaultRouteSet = false; 63 64 // DEFAULT and HIPRI are the same connection. If we're one of these we need to check if 65 // the other is also disconnected before we reset sockets 66 private boolean mIsDefaultOrHipri = false; 67 68 /** 69 * Create a new MobileDataStateTracker 70 * @param context the application context of the caller 71 * @param target a message handler for getting callbacks about state changes 72 * @param netType the ConnectivityManager network type 73 * @param apnType the Phone apnType 74 * @param tag the name of this network 75 */ 76 public MobileDataStateTracker(Context context, Handler target, int netType, String tag) { 77 mTarget = target; 78 mContext = context; 79 mNetworkInfo = new NetworkInfo(netType, 80 TelephonyManager.getDefault().getNetworkType(), tag, 81 TelephonyManager.getDefault().getNetworkTypeName()); 82 mApnType = networkTypeToApnType(netType); 83 if (netType == ConnectivityManager.TYPE_MOBILE || 84 netType == ConnectivityManager.TYPE_MOBILE_HIPRI) { 85 mIsDefaultOrHipri = true; 86 } 87 88 mPhoneService = null; 89 90 sDnsPropNames = 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 * Return the IP addresses of the DNS servers available for the mobile data 105 * network interface. 106 * @return a list of DNS addresses, with no holes. 107 */ 108 public String[] getDnsPropNames() { 109 return sDnsPropNames; 110 } 111 112 public boolean isPrivateDnsRouteSet() { 113 return mPrivateDnsRouteSet; 114 } 115 116 public void privateDnsRouteSet(boolean enabled) { 117 mPrivateDnsRouteSet = enabled; 118 } 119 120 public NetworkInfo getNetworkInfo() { 121 return mNetworkInfo; 122 } 123 124 public int getDefaultGatewayAddr() { 125 return mDefaultGatewayAddr; 126 } 127 128 public boolean isDefaultRouteSet() { 129 return mDefaultRouteSet; 130 } 131 132 public void defaultRouteSet(boolean enabled) { 133 mDefaultRouteSet = enabled; 134 } 135 136 /** 137 * This is not implemented. 138 */ 139 public void releaseWakeLock() { 140 } 141 142 /** 143 * Begin monitoring mobile data connectivity. 144 */ 145 public void startMonitoring() { 146 IntentFilter filter = 147 new IntentFilter(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED); 148 filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED); 149 filter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED); 150 151 mContext.registerReceiver(new MobileDataStateReceiver(), filter); 152 mMobileDataState = Phone.DataState.DISCONNECTED; 153 } 154 155 /** 156 * Record the roaming status of the device, and if it is a change from the previous 157 * status, send a notification to any listeners. 158 * @param isRoaming {@code true} if the device is now roaming, {@code false} 159 * if it is no longer roaming. 160 */ 161 private void setRoamingStatus(boolean isRoaming) { 162 if (isRoaming != mNetworkInfo.isRoaming()) { 163 mNetworkInfo.setRoaming(isRoaming); 164 Message msg = mTarget.obtainMessage(EVENT_ROAMING_CHANGED, mNetworkInfo); 165 msg.sendToTarget(); 166 } 167 } 168 169 private void setSubtype(int subtype, String subtypeName) { 170 if (mNetworkInfo.isConnected()) { 171 int oldSubtype = mNetworkInfo.getSubtype(); 172 if (subtype != oldSubtype) { 173 mNetworkInfo.setSubtype(subtype, subtypeName); 174 Message msg = mTarget.obtainMessage( 175 EVENT_NETWORK_SUBTYPE_CHANGED, oldSubtype, 0, mNetworkInfo); 176 msg.sendToTarget(); 177 } 178 } 179 } 180 181 private class MobileDataStateReceiver extends BroadcastReceiver { 182 IConnectivityManager mConnectivityManager; 183 184 public void onReceive(Context context, Intent intent) { 185 if (intent.getAction().equals(TelephonyIntents. 186 ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) { 187 String apnType = intent.getStringExtra(Phone.DATA_APN_TYPE_KEY); 188 189 if (!TextUtils.equals(apnType, mApnType)) { 190 return; 191 } 192 Phone.DataState state = Enum.valueOf(Phone.DataState.class, 193 intent.getStringExtra(Phone.STATE_KEY)); 194 String reason = intent.getStringExtra(Phone.STATE_CHANGE_REASON_KEY); 195 String apnName = intent.getStringExtra(Phone.DATA_APN_KEY); 196 197 boolean unavailable = intent.getBooleanExtra(Phone.NETWORK_UNAVAILABLE_KEY, 198 false); 199 mNetworkInfo.setIsAvailable(!unavailable); 200 201 if (DBG) Log.d(TAG, mApnType + " Received state= " + state + ", old= " + 202 mMobileDataState + ", reason= " + 203 (reason == null ? "(unspecified)" : reason)); 204 205 if (mMobileDataState != state) { 206 mMobileDataState = state; 207 switch (state) { 208 case DISCONNECTED: 209 if(isTeardownRequested()) { 210 setTeardownRequested(false); 211 } 212 213 setDetailedState(DetailedState.DISCONNECTED, reason, apnName); 214 boolean doReset = true; 215 if (mIsDefaultOrHipri == true) { 216 // both default and hipri must go down before we reset 217 int typeToCheck = (Phone.APN_TYPE_DEFAULT.equals(mApnType) ? 218 ConnectivityManager.TYPE_MOBILE_HIPRI : 219 ConnectivityManager.TYPE_MOBILE); 220 if (mConnectivityManager == null) { 221 IBinder b = ServiceManager.getService( 222 mContext.CONNECTIVITY_SERVICE); 223 mConnectivityManager = IConnectivityManager.Stub.asInterface(b); 224 } 225 try { 226 if (mConnectivityManager != null) { 227 NetworkInfo info = mConnectivityManager.getNetworkInfo( 228 typeToCheck); 229 if (info.isConnected() == true) { 230 doReset = false; 231 } 232 } 233 } catch (RemoteException e) { 234 // just go ahead with the reset 235 Log.e(TAG, "Exception trying to contact ConnService: " 236 + e); 237 } 238 } 239 if (doReset && mNetworkProperties != null) { 240 String iface = mNetworkProperties.getInterfaceName(); 241 if (iface != null) NetworkUtils.resetConnections(iface); 242 } 243 // TODO - check this 244 // can't do this here - ConnectivityService needs it to clear stuff 245 // it's ok though - just leave it to be refreshed next time 246 // we connect. 247 //if (DBG) Log.d(TAG, "clearing mInterfaceName for "+ mApnType + 248 // " as it DISCONNECTED"); 249 //mInterfaceName = null; 250 //mDefaultGatewayAddr = 0; 251 break; 252 case CONNECTING: 253 setDetailedState(DetailedState.CONNECTING, reason, apnName); 254 break; 255 case SUSPENDED: 256 setDetailedState(DetailedState.SUSPENDED, reason, apnName); 257 break; 258 case CONNECTED: 259 mNetworkProperties = intent.getParcelableExtra( 260 Phone.DATA_NETWORK_PROPERTIES_KEY); 261 if (mNetworkProperties == null) { 262 Log.d(TAG, 263 "CONNECTED event did not supply network properties."); 264 } 265 setDetailedState(DetailedState.CONNECTED, reason, apnName); 266 break; 267 } 268 } 269 } else if (intent.getAction(). 270 equals(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED)) { 271 String apnType = intent.getStringExtra(Phone.DATA_APN_TYPE_KEY); 272 if (!TextUtils.equals(apnType, mApnType)) { 273 return; 274 } 275 String reason = intent.getStringExtra(Phone.FAILURE_REASON_KEY); 276 String apnName = intent.getStringExtra(Phone.DATA_APN_KEY); 277 if (DBG) Log.d(TAG, mApnType + "Received " + intent.getAction() + 278 " broadcast" + reason == null ? "" : "(" + reason + ")"); 279 setDetailedState(DetailedState.FAILED, reason, apnName); 280 } 281 TelephonyManager tm = TelephonyManager.getDefault(); 282 setRoamingStatus(tm.isNetworkRoaming()); 283 setSubtype(tm.getNetworkType(), tm.getNetworkTypeName()); 284 } 285 } 286 287 private void getPhoneService(boolean forceRefresh) { 288 if ((mPhoneService == null) || forceRefresh) { 289 mPhoneService = ITelephony.Stub.asInterface(ServiceManager.getService("phone")); 290 } 291 } 292 293 /** 294 * Report whether data connectivity is possible. 295 */ 296 public boolean isAvailable() { 297 getPhoneService(false); 298 299 /* 300 * If the phone process has crashed in the past, we'll get a 301 * RemoteException and need to re-reference the service. 302 */ 303 for (int retry = 0; retry < 2; retry++) { 304 if (mPhoneService == null) break; 305 306 try { 307 return mPhoneService.isDataConnectivityPossible(); 308 } catch (RemoteException e) { 309 // First-time failed, get the phone service again 310 if (retry == 0) getPhoneService(true); 311 } 312 } 313 314 return false; 315 } 316 317 /** 318 * {@inheritDoc} 319 * The mobile data network subtype indicates what generation network technology is in effect, 320 * e.g., GPRS, EDGE, UMTS, etc. 321 */ 322 public int getNetworkSubtype() { 323 return TelephonyManager.getDefault().getNetworkType(); 324 } 325 326 /** 327 * Return the system properties name associated with the tcp buffer sizes 328 * for this network. 329 */ 330 public String getTcpBufferSizesPropName() { 331 String networkTypeStr = "unknown"; 332 TelephonyManager tm = new TelephonyManager(mContext); 333 //TODO We have to edit the parameter for getNetworkType regarding CDMA 334 switch(tm.getNetworkType()) { 335 case TelephonyManager.NETWORK_TYPE_GPRS: 336 networkTypeStr = "gprs"; 337 break; 338 case TelephonyManager.NETWORK_TYPE_EDGE: 339 networkTypeStr = "edge"; 340 break; 341 case TelephonyManager.NETWORK_TYPE_UMTS: 342 networkTypeStr = "umts"; 343 break; 344 case TelephonyManager.NETWORK_TYPE_HSDPA: 345 networkTypeStr = "hsdpa"; 346 break; 347 case TelephonyManager.NETWORK_TYPE_HSUPA: 348 networkTypeStr = "hsupa"; 349 break; 350 case TelephonyManager.NETWORK_TYPE_HSPA: 351 networkTypeStr = "hspa"; 352 break; 353 case TelephonyManager.NETWORK_TYPE_CDMA: 354 networkTypeStr = "cdma"; 355 break; 356 case TelephonyManager.NETWORK_TYPE_1xRTT: 357 networkTypeStr = "1xrtt"; 358 break; 359 case TelephonyManager.NETWORK_TYPE_EVDO_0: 360 networkTypeStr = "evdo"; 361 break; 362 case TelephonyManager.NETWORK_TYPE_EVDO_A: 363 networkTypeStr = "evdo"; 364 break; 365 case TelephonyManager.NETWORK_TYPE_EVDO_B: 366 networkTypeStr = "evdo"; 367 break; 368 } 369 return "net.tcp.buffersize." + networkTypeStr; 370 } 371 372 /** 373 * Tear down mobile data connectivity, i.e., disable the ability to create 374 * mobile data connections. 375 * TODO - make async and return nothing? 376 */ 377 public boolean teardown() { 378 setTeardownRequested(true); 379 return (setEnableApn(mApnType, false) != Phone.APN_REQUEST_FAILED); 380 } 381 382 /** 383 * Record the detailed state of a network, and if it is a 384 * change from the previous state, send a notification to 385 * any listeners. 386 * @param state the new @{code DetailedState} 387 */ 388 private void setDetailedState(NetworkInfo.DetailedState state) { 389 setDetailedState(state, null, null); 390 } 391 392 /** 393 * Record the detailed state of a network, and if it is a 394 * change from the previous state, send a notification to 395 * any listeners. 396 * @param state the new @{code DetailedState} 397 * @param reason a {@code String} indicating a reason for the state change, 398 * if one was supplied. May be {@code null}. 399 * @param extraInfo optional {@code String} providing extra information about the state change 400 */ 401 private void setDetailedState(NetworkInfo.DetailedState state, String reason, String extraInfo) { 402 if (DBG) Log.d(TAG, "setDetailed state, old =" 403 + mNetworkInfo.getDetailedState() + " and new state=" + state); 404 if (state != mNetworkInfo.getDetailedState()) { 405 boolean wasConnecting = (mNetworkInfo.getState() == NetworkInfo.State.CONNECTING); 406 String lastReason = mNetworkInfo.getReason(); 407 /* 408 * If a reason was supplied when the CONNECTING state was entered, and no 409 * reason was supplied for entering the CONNECTED state, then retain the 410 * reason that was supplied when going to CONNECTING. 411 */ 412 if (wasConnecting && state == NetworkInfo.DetailedState.CONNECTED && reason == null 413 && lastReason != null) 414 reason = lastReason; 415 mNetworkInfo.setDetailedState(state, reason, extraInfo); 416 Message msg = mTarget.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo); 417 msg.sendToTarget(); 418 } 419 } 420 421 private void setDetailedStateInternal(NetworkInfo.DetailedState state) { 422 mNetworkInfo.setDetailedState(state, null, null); 423 } 424 425 public void setTeardownRequested(boolean isRequested) { 426 mTeardownRequested = isRequested; 427 } 428 429 public boolean isTeardownRequested() { 430 return mTeardownRequested; 431 } 432 433 /** 434 * Re-enable mobile data connectivity after a {@link #teardown()}. 435 * TODO - make async and always get a notification? 436 */ 437 public boolean reconnect() { 438 boolean retValue = false; //connected or expect to be? 439 setTeardownRequested(false); 440 switch (setEnableApn(mApnType, true)) { 441 case Phone.APN_ALREADY_ACTIVE: 442 // need to set self to CONNECTING so the below message is handled. 443 retValue = true; 444 break; 445 case Phone.APN_REQUEST_STARTED: 446 // no need to do anything - we're already due some status update intents 447 retValue = true; 448 break; 449 case Phone.APN_REQUEST_FAILED: 450 case Phone.APN_TYPE_NOT_AVAILABLE: 451 break; 452 default: 453 Log.e(TAG, "Error in reconnect - unexpected response."); 454 break; 455 } 456 return retValue; 457 } 458 459 /** 460 * Turn on or off the mobile radio. No connectivity will be possible while the 461 * radio is off. The operation is a no-op if the radio is already in the desired state. 462 * @param turnOn {@code true} if the radio should be turned on, {@code false} if 463 */ 464 public boolean setRadio(boolean turnOn) { 465 getPhoneService(false); 466 /* 467 * If the phone process has crashed in the past, we'll get a 468 * RemoteException and need to re-reference the service. 469 */ 470 for (int retry = 0; retry < 2; retry++) { 471 if (mPhoneService == null) { 472 Log.w(TAG, 473 "Ignoring mobile radio request because could not acquire PhoneService"); 474 break; 475 } 476 477 try { 478 return mPhoneService.setRadio(turnOn); 479 } catch (RemoteException e) { 480 if (retry == 0) getPhoneService(true); 481 } 482 } 483 484 Log.w(TAG, "Could not set radio power to " + (turnOn ? "on" : "off")); 485 return false; 486 } 487 488 /** 489 * Tells the phone sub-system that the caller wants to 490 * begin using the named feature. The only supported features at 491 * this time are {@code Phone.FEATURE_ENABLE_MMS}, which allows an application 492 * to specify that it wants to send and/or receive MMS data, and 493 * {@code Phone.FEATURE_ENABLE_SUPL}, which is used for Assisted GPS. 494 * @param feature the name of the feature to be used 495 * @param callingPid the process ID of the process that is issuing this request 496 * @param callingUid the user ID of the process that is issuing this request 497 * @return an integer value representing the outcome of the request. 498 * The interpretation of this value is feature-specific. 499 * specific, except that the value {@code -1} 500 * always indicates failure. For {@code Phone.FEATURE_ENABLE_MMS}, 501 * the other possible return values are 502 * <ul> 503 * <li>{@code Phone.APN_ALREADY_ACTIVE}</li> 504 * <li>{@code Phone.APN_REQUEST_STARTED}</li> 505 * <li>{@code Phone.APN_TYPE_NOT_AVAILABLE}</li> 506 * <li>{@code Phone.APN_REQUEST_FAILED}</li> 507 * </ul> 508 */ 509 public int startUsingNetworkFeature(String feature, int callingPid, int callingUid) { 510 return -1; 511 } 512 513 /** 514 * Tells the phone sub-system that the caller is finished 515 * using the named feature. The only supported feature at 516 * this time is {@code Phone.FEATURE_ENABLE_MMS}, which allows an application 517 * to specify that it wants to send and/or receive MMS data. 518 * @param feature the name of the feature that is no longer needed 519 * @param callingPid the process ID of the process that is issuing this request 520 * @param callingUid the user ID of the process that is issuing this request 521 * @return an integer value representing the outcome of the request. 522 * The interpretation of this value is feature-specific, except that 523 * the value {@code -1} always indicates failure. 524 */ 525 public int stopUsingNetworkFeature(String feature, int callingPid, int callingUid) { 526 return -1; 527 } 528 529 /** 530 * This is not supported. 531 */ 532 public void interpretScanResultsAvailable() { 533 } 534 535 @Override 536 public String toString() { 537 StringBuffer sb = new StringBuffer("Mobile data state: "); 538 539 sb.append(mMobileDataState); 540 return sb.toString(); 541 } 542 543 /** 544 * Internal method supporting the ENABLE_MMS feature. 545 * @param apnType the type of APN to be enabled or disabled (e.g., mms) 546 * @param enable {@code true} to enable the specified APN type, 547 * {@code false} to disable it. 548 * @return an integer value representing the outcome of the request. 549 */ 550 private int setEnableApn(String apnType, boolean enable) { 551 getPhoneService(false); 552 /* 553 * If the phone process has crashed in the past, we'll get a 554 * RemoteException and need to re-reference the service. 555 */ 556 for (int retry = 0; retry < 2; retry++) { 557 if (mPhoneService == null) { 558 Log.w(TAG, 559 "Ignoring feature request because could not acquire PhoneService"); 560 break; 561 } 562 563 try { 564 if (enable) { 565 return mPhoneService.enableApnType(apnType); 566 } else { 567 return mPhoneService.disableApnType(apnType); 568 } 569 } catch (RemoteException e) { 570 if (retry == 0) getPhoneService(true); 571 } 572 } 573 574 Log.w(TAG, "Could not " + (enable ? "enable" : "disable") 575 + " APN type \"" + apnType + "\""); 576 return Phone.APN_REQUEST_FAILED; 577 } 578 579 public static String networkTypeToApnType(int netType) { 580 switch(netType) { 581 case ConnectivityManager.TYPE_MOBILE: 582 return Phone.APN_TYPE_DEFAULT; // TODO - use just one of these 583 case ConnectivityManager.TYPE_MOBILE_MMS: 584 return Phone.APN_TYPE_MMS; 585 case ConnectivityManager.TYPE_MOBILE_SUPL: 586 return Phone.APN_TYPE_SUPL; 587 case ConnectivityManager.TYPE_MOBILE_DUN: 588 return Phone.APN_TYPE_DUN; 589 case ConnectivityManager.TYPE_MOBILE_HIPRI: 590 return Phone.APN_TYPE_HIPRI; 591 default: 592 Log.e(TAG, "Error mapping networkType " + netType + " to apnType."); 593 return null; 594 } 595 } 596 597 public NetworkProperties getNetworkProperties() { 598 return mNetworkProperties; 599 } 600} 601