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