MobileDataStateTracker.java revision ed9c02b56c6f56a583aa272f82818ef039face75
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.Slog; 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) { 166 log(String.format("Broadcast received: ACTION_ANY_DATA_CONNECTION_STATE_CHANGED" 167 + "mApnType=%s %s received apnType=%s", mApnType, 168 TextUtils.equals(apnType, mApnType) ? "==" : "!=", apnType)); 169 } 170 if (!TextUtils.equals(apnType, mApnType)) { 171 return; 172 } 173 Phone.DataState state = Enum.valueOf(Phone.DataState.class, 174 intent.getStringExtra(Phone.STATE_KEY)); 175 String reason = intent.getStringExtra(Phone.STATE_CHANGE_REASON_KEY); 176 String apnName = intent.getStringExtra(Phone.DATA_APN_KEY); 177 178 mNetworkInfo.setIsAvailable(!intent.getBooleanExtra(Phone.NETWORK_UNAVAILABLE_KEY, 179 false)); 180 181 if (DBG) { 182 log(mApnType + " Received state=" + state + ", old=" + mMobileDataState + 183 ", reason=" + (reason == null ? "(unspecified)" : reason)); 184 } 185 if (mMobileDataState != state) { 186 mMobileDataState = state; 187 switch (state) { 188 case DISCONNECTED: 189 if(isTeardownRequested()) { 190 setTeardownRequested(false); 191 } 192 193 setDetailedState(DetailedState.DISCONNECTED, reason, apnName); 194 boolean doReset = true; 195 if (mIsDefaultOrHipri == true) { 196 // both default and hipri must go down before we reset 197 int typeToCheck = (Phone.APN_TYPE_DEFAULT.equals(mApnType) ? 198 ConnectivityManager.TYPE_MOBILE_HIPRI : 199 ConnectivityManager.TYPE_MOBILE); 200 if (mConnectivityManager == null) { 201 IBinder b = ServiceManager.getService( 202 Context.CONNECTIVITY_SERVICE); 203 mConnectivityManager = IConnectivityManager.Stub.asInterface(b); 204 } 205 try { 206 if (mConnectivityManager != null) { 207 NetworkInfo info = mConnectivityManager.getNetworkInfo( 208 typeToCheck); 209 if (info.isConnected() == true) { 210 doReset = false; 211 } 212 } 213 } catch (RemoteException e) { 214 // just go ahead with the reset 215 loge("Exception trying to contact ConnService: " + 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("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("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("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) { 259 log(String.format( 260 "Broadcast received: ACTION_ANY_DATA_CONNECTION_FAILED ignore, " + 261 "mApnType=%s != received apnType=%s", mApnType, apnType)); 262 } 263 return; 264 } 265 String reason = intent.getStringExtra(Phone.FAILURE_REASON_KEY); 266 String apnName = intent.getStringExtra(Phone.DATA_APN_KEY); 267 if (DBG) { 268 log(mApnType + "Received " + intent.getAction() + 269 " broadcast" + reason == null ? "" : "(" + reason + ")"); 270 } 271 setDetailedState(DetailedState.FAILED, reason, apnName); 272 } else { 273 if (DBG) log("Broadcast received: ignore " + intent.getAction()); 274 } 275 } 276 } 277 278 private void getPhoneService(boolean forceRefresh) { 279 if ((mPhoneService == null) || forceRefresh) { 280 mPhoneService = ITelephony.Stub.asInterface(ServiceManager.getService("phone")); 281 } 282 } 283 284 /** 285 * Report whether data connectivity is possible. 286 */ 287 public boolean isAvailable() { 288 return mNetworkInfo.isAvailable(); 289 } 290 291 /** 292 * Return the system properties name associated with the tcp buffer sizes 293 * for this network. 294 */ 295 public String getTcpBufferSizesPropName() { 296 String networkTypeStr = "unknown"; 297 TelephonyManager tm = new TelephonyManager(mContext); 298 //TODO We have to edit the parameter for getNetworkType regarding CDMA 299 switch(tm.getNetworkType()) { 300 case TelephonyManager.NETWORK_TYPE_GPRS: 301 networkTypeStr = "gprs"; 302 break; 303 case TelephonyManager.NETWORK_TYPE_EDGE: 304 networkTypeStr = "edge"; 305 break; 306 case TelephonyManager.NETWORK_TYPE_UMTS: 307 networkTypeStr = "umts"; 308 break; 309 case TelephonyManager.NETWORK_TYPE_HSDPA: 310 networkTypeStr = "hsdpa"; 311 break; 312 case TelephonyManager.NETWORK_TYPE_HSUPA: 313 networkTypeStr = "hsupa"; 314 break; 315 case TelephonyManager.NETWORK_TYPE_HSPA: 316 networkTypeStr = "hspa"; 317 break; 318 case TelephonyManager.NETWORK_TYPE_CDMA: 319 networkTypeStr = "cdma"; 320 break; 321 case TelephonyManager.NETWORK_TYPE_1xRTT: 322 networkTypeStr = "1xrtt"; 323 break; 324 case TelephonyManager.NETWORK_TYPE_EVDO_0: 325 networkTypeStr = "evdo"; 326 break; 327 case TelephonyManager.NETWORK_TYPE_EVDO_A: 328 networkTypeStr = "evdo"; 329 break; 330 case TelephonyManager.NETWORK_TYPE_EVDO_B: 331 networkTypeStr = "evdo"; 332 break; 333 case TelephonyManager.NETWORK_TYPE_IDEN: 334 networkTypeStr = "iden"; 335 break; 336 case TelephonyManager.NETWORK_TYPE_LTE: 337 networkTypeStr = "lte"; 338 break; 339 case TelephonyManager.NETWORK_TYPE_EHRPD: 340 networkTypeStr = "ehrpd"; 341 break; 342 default: 343 loge("unknown network type: " + tm.getNetworkType()); 344 } 345 return "net.tcp.buffersize." + networkTypeStr; 346 } 347 348 /** 349 * Tear down mobile data connectivity, i.e., disable the ability to create 350 * mobile data connections. 351 * TODO - make async and return nothing? 352 */ 353 public boolean teardown() { 354 setTeardownRequested(true); 355 return (setEnableApn(mApnType, false) != Phone.APN_REQUEST_FAILED); 356 } 357 358 /** 359 * Record the detailed state of a network, and if it is a 360 * change from the previous state, send a notification to 361 * any listeners. 362 * @param state the new @{code DetailedState} 363 * @param reason a {@code String} indicating a reason for the state change, 364 * if one was supplied. May be {@code null}. 365 * @param extraInfo optional {@code String} providing extra information about the state change 366 */ 367 private void setDetailedState(NetworkInfo.DetailedState state, String reason, 368 String extraInfo) { 369 if (DBG) log("setDetailed state, old =" 370 + mNetworkInfo.getDetailedState() + " and new state=" + state); 371 if (state != mNetworkInfo.getDetailedState()) { 372 boolean wasConnecting = (mNetworkInfo.getState() == NetworkInfo.State.CONNECTING); 373 String lastReason = mNetworkInfo.getReason(); 374 /* 375 * If a reason was supplied when the CONNECTING state was entered, and no 376 * reason was supplied for entering the CONNECTED state, then retain the 377 * reason that was supplied when going to CONNECTING. 378 */ 379 if (wasConnecting && state == NetworkInfo.DetailedState.CONNECTED && reason == null 380 && lastReason != null) 381 reason = lastReason; 382 mNetworkInfo.setDetailedState(state, reason, extraInfo); 383 Message msg = mTarget.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo); 384 msg.sendToTarget(); 385 } 386 } 387 388 public void setTeardownRequested(boolean isRequested) { 389 mTeardownRequested = isRequested; 390 } 391 392 public boolean isTeardownRequested() { 393 return mTeardownRequested; 394 } 395 396 /** 397 * Re-enable mobile data connectivity after a {@link #teardown()}. 398 * TODO - make async and always get a notification? 399 */ 400 public boolean reconnect() { 401 boolean retValue = false; //connected or expect to be? 402 setTeardownRequested(false); 403 switch (setEnableApn(mApnType, true)) { 404 case Phone.APN_ALREADY_ACTIVE: 405 // need to set self to CONNECTING so the below message is handled. 406 retValue = true; 407 break; 408 case Phone.APN_REQUEST_STARTED: 409 // no need to do anything - we're already due some status update intents 410 retValue = true; 411 break; 412 case Phone.APN_REQUEST_FAILED: 413 case Phone.APN_TYPE_NOT_AVAILABLE: 414 break; 415 default: 416 loge("Error in reconnect - unexpected response."); 417 break; 418 } 419 return retValue; 420 } 421 422 /** 423 * Turn on or off the mobile radio. No connectivity will be possible while the 424 * radio is off. The operation is a no-op if the radio is already in the desired state. 425 * @param turnOn {@code true} if the radio should be turned on, {@code false} if 426 */ 427 public boolean setRadio(boolean turnOn) { 428 getPhoneService(false); 429 /* 430 * If the phone process has crashed in the past, we'll get a 431 * RemoteException and need to re-reference the service. 432 */ 433 for (int retry = 0; retry < 2; retry++) { 434 if (mPhoneService == null) { 435 log("Ignoring mobile radio request because could not acquire PhoneService"); 436 break; 437 } 438 439 try { 440 return mPhoneService.setRadio(turnOn); 441 } catch (RemoteException e) { 442 if (retry == 0) getPhoneService(true); 443 } 444 } 445 446 log("Could not set radio power to " + (turnOn ? "on" : "off")); 447 return false; 448 } 449 450 /** 451 * Tells the phone sub-system that the caller wants to 452 * begin using the named feature. The only supported features at 453 * this time are {@code Phone.FEATURE_ENABLE_MMS}, which allows an application 454 * to specify that it wants to send and/or receive MMS data, and 455 * {@code Phone.FEATURE_ENABLE_SUPL}, which is used for Assisted GPS. 456 * @param feature the name of the feature to be used 457 * @param callingPid the process ID of the process that is issuing this request 458 * @param callingUid the user ID of the process that is issuing this request 459 * @return an integer value representing the outcome of the request. 460 * The interpretation of this value is feature-specific. 461 * specific, except that the value {@code -1} 462 * always indicates failure. For {@code Phone.FEATURE_ENABLE_MMS}, 463 * the other possible return values are 464 * <ul> 465 * <li>{@code Phone.APN_ALREADY_ACTIVE}</li> 466 * <li>{@code Phone.APN_REQUEST_STARTED}</li> 467 * <li>{@code Phone.APN_TYPE_NOT_AVAILABLE}</li> 468 * <li>{@code Phone.APN_REQUEST_FAILED}</li> 469 * </ul> 470 */ 471 public int startUsingNetworkFeature(String feature, int callingPid, int callingUid) { 472 return -1; 473 } 474 475 /** 476 * Tells the phone sub-system that the caller is finished 477 * using the named feature. The only supported feature at 478 * this time is {@code Phone.FEATURE_ENABLE_MMS}, which allows an application 479 * to specify that it wants to send and/or receive MMS data. 480 * @param feature the name of the feature that is no longer needed 481 * @param callingPid the process ID of the process that is issuing this request 482 * @param callingUid the user ID of the process that is issuing this request 483 * @return an integer value representing the outcome of the request. 484 * The interpretation of this value is feature-specific, except that 485 * the value {@code -1} always indicates failure. 486 */ 487 public int stopUsingNetworkFeature(String feature, int callingPid, int callingUid) { 488 return -1; 489 } 490 491 @Override 492 public String toString() { 493 StringBuffer sb = new StringBuffer("Mobile data state: "); 494 495 sb.append(mMobileDataState); 496 return sb.toString(); 497 } 498 499 /** 500 * Internal method supporting the ENABLE_MMS feature. 501 * @param apnType the type of APN to be enabled or disabled (e.g., mms) 502 * @param enable {@code true} to enable the specified APN type, 503 * {@code false} to disable it. 504 * @return an integer value representing the outcome of the request. 505 */ 506 private int setEnableApn(String apnType, boolean enable) { 507 getPhoneService(false); 508 /* 509 * If the phone process has crashed in the past, we'll get a 510 * RemoteException and need to re-reference the service. 511 */ 512 for (int retry = 0; retry < 2; retry++) { 513 if (mPhoneService == null) { 514 log("Ignoring feature request because could not acquire PhoneService"); 515 break; 516 } 517 518 try { 519 if (enable) { 520 return mPhoneService.enableApnType(apnType); 521 } else { 522 return mPhoneService.disableApnType(apnType); 523 } 524 } catch (RemoteException e) { 525 if (retry == 0) getPhoneService(true); 526 } 527 } 528 529 log("Could not " + (enable ? "enable" : "disable") + " APN type \"" + apnType + "\""); 530 return Phone.APN_REQUEST_FAILED; 531 } 532 533 public static String networkTypeToApnType(int netType) { 534 switch(netType) { 535 case ConnectivityManager.TYPE_MOBILE: 536 return Phone.APN_TYPE_DEFAULT; // TODO - use just one of these 537 case ConnectivityManager.TYPE_MOBILE_MMS: 538 return Phone.APN_TYPE_MMS; 539 case ConnectivityManager.TYPE_MOBILE_SUPL: 540 return Phone.APN_TYPE_SUPL; 541 case ConnectivityManager.TYPE_MOBILE_DUN: 542 return Phone.APN_TYPE_DUN; 543 case ConnectivityManager.TYPE_MOBILE_HIPRI: 544 return Phone.APN_TYPE_HIPRI; 545 default: 546 loge("Error mapping networkType " + netType + " to apnType."); 547 return null; 548 } 549 } 550 551 /** 552 * @see android.net.NetworkStateTracker#getLinkProperties() 553 */ 554 public LinkProperties getLinkProperties() { 555 return new LinkProperties(mLinkProperties); 556 } 557 558 /** 559 * @see android.net.NetworkStateTracker#getLinkCapabilities() 560 */ 561 public LinkCapabilities getLinkCapabilities() { 562 return new LinkCapabilities(mLinkCapabilities); 563 } 564 565 static private void log(String s) { 566 Slog.d(TAG, s); 567 } 568 569 static private void loge(String s) { 570 Slog.e(TAG, s); 571 } 572} 573