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