MobileDataStateTracker.java revision 17415b9292d3da64d6776906954bcf31421d00f2
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 mNetworkInfo.setSubtype(TelephonyManager.getDefault().getNetworkType(), 225 TelephonyManager.getDefault().getNetworkTypeName()); 226 Phone.DataState state = Enum.valueOf(Phone.DataState.class, 227 intent.getStringExtra(Phone.STATE_KEY)); 228 String reason = intent.getStringExtra(Phone.STATE_CHANGE_REASON_KEY); 229 String apnName = intent.getStringExtra(Phone.DATA_APN_KEY); 230 231 mNetworkInfo.setIsAvailable(!intent.getBooleanExtra(Phone.NETWORK_UNAVAILABLE_KEY, 232 false)); 233 234 if (DBG) { 235 log("Received state=" + state + ", old=" + mMobileDataState + 236 ", reason=" + (reason == null ? "(unspecified)" : reason)); 237 } 238 if (mMobileDataState != state) { 239 mMobileDataState = state; 240 switch (state) { 241 case DISCONNECTED: 242 if(isTeardownRequested()) { 243 setTeardownRequested(false); 244 } 245 246 setDetailedState(DetailedState.DISCONNECTED, reason, apnName); 247 boolean doReset = true; 248 if (mIsDefaultOrHipri == true) { 249 // both default and hipri must go down before we reset 250 int typeToCheck = (Phone.APN_TYPE_DEFAULT.equals(mApnType) ? 251 ConnectivityManager.TYPE_MOBILE_HIPRI : 252 ConnectivityManager.TYPE_MOBILE); 253 if (mConnectivityManager == null) { 254 IBinder b = ServiceManager.getService( 255 Context.CONNECTIVITY_SERVICE); 256 mConnectivityManager = IConnectivityManager.Stub.asInterface(b); 257 } 258 try { 259 if (mConnectivityManager != null) { 260 NetworkInfo info = mConnectivityManager.getNetworkInfo( 261 typeToCheck); 262 if (info.isConnected() == true) { 263 doReset = false; 264 } 265 } 266 } catch (RemoteException e) { 267 // just go ahead with the reset 268 loge("Exception trying to contact ConnService: " + e); 269 } 270 } 271 if (doReset && mLinkProperties != null) { 272 String iface = mLinkProperties.getInterfaceName(); 273 if (iface != null) NetworkUtils.resetConnections(iface); 274 } 275 // TODO - check this 276 // can't do this here - ConnectivityService needs it to clear stuff 277 // it's ok though - just leave it to be refreshed next time 278 // we connect. 279 //if (DBG) log("clearing mInterfaceName for "+ mApnType + 280 // " as it DISCONNECTED"); 281 //mInterfaceName = null; 282 //mDefaultGatewayAddr = 0; 283 break; 284 case CONNECTING: 285 setDetailedState(DetailedState.CONNECTING, reason, apnName); 286 break; 287 case SUSPENDED: 288 setDetailedState(DetailedState.SUSPENDED, reason, apnName); 289 break; 290 case CONNECTED: 291 mLinkProperties = intent.getParcelableExtra( 292 Phone.DATA_LINK_PROPERTIES_KEY); 293 if (mLinkProperties == null) { 294 log("CONNECTED event did not supply link properties."); 295 mLinkProperties = new LinkProperties(); 296 } 297 mLinkCapabilities = intent.getParcelableExtra( 298 Phone.DATA_LINK_CAPABILITIES_KEY); 299 if (mLinkCapabilities == null) { 300 log("CONNECTED event did not supply link capabilities."); 301 mLinkCapabilities = new LinkCapabilities(); 302 } 303 setDetailedState(DetailedState.CONNECTED, reason, apnName); 304 break; 305 } 306 } 307 } else if (intent.getAction(). 308 equals(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED)) { 309 String apnType = intent.getStringExtra(Phone.DATA_APN_TYPE_KEY); 310 if (!TextUtils.equals(apnType, mApnType)) { 311 if (DBG) { 312 log(String.format( 313 "Broadcast received: ACTION_ANY_DATA_CONNECTION_FAILED ignore, " + 314 "mApnType=%s != received apnType=%s", mApnType, apnType)); 315 } 316 return; 317 } 318 String reason = intent.getStringExtra(Phone.FAILURE_REASON_KEY); 319 String apnName = intent.getStringExtra(Phone.DATA_APN_KEY); 320 if (DBG) { 321 log("Received " + intent.getAction() + 322 " broadcast" + reason == null ? "" : "(" + reason + ")"); 323 } 324 setDetailedState(DetailedState.FAILED, reason, apnName); 325 } else if (intent.getAction(). 326 equals(DataConnectionTracker.ACTION_DATA_CONNECTION_TRACKER_MESSENGER)) { 327 if (DBG) log(mApnType + " got ACTION_DATA_CONNECTION_TRACKER_MESSENGER"); 328 mMessenger = intent.getParcelableExtra(DataConnectionTracker.EXTRA_MESSENGER); 329 AsyncChannel ac = new AsyncChannel(); 330 ac.connect(mContext, MobileDataStateTracker.this.mHandler, mMessenger); 331 } else { 332 if (DBG) log("Broadcast received: ignore " + intent.getAction()); 333 } 334 } 335 } 336 337 private void getPhoneService(boolean forceRefresh) { 338 if ((mPhoneService == null) || forceRefresh) { 339 mPhoneService = ITelephony.Stub.asInterface(ServiceManager.getService("phone")); 340 } 341 } 342 343 /** 344 * Report whether data connectivity is possible. 345 */ 346 public boolean isAvailable() { 347 return mNetworkInfo.isAvailable(); 348 } 349 350 /** 351 * Return the system properties name associated with the tcp buffer sizes 352 * for this network. 353 */ 354 public String getTcpBufferSizesPropName() { 355 String networkTypeStr = "unknown"; 356 TelephonyManager tm = new TelephonyManager(mContext); 357 //TODO We have to edit the parameter for getNetworkType regarding CDMA 358 switch(tm.getNetworkType()) { 359 case TelephonyManager.NETWORK_TYPE_GPRS: 360 networkTypeStr = "gprs"; 361 break; 362 case TelephonyManager.NETWORK_TYPE_EDGE: 363 networkTypeStr = "edge"; 364 break; 365 case TelephonyManager.NETWORK_TYPE_UMTS: 366 networkTypeStr = "umts"; 367 break; 368 case TelephonyManager.NETWORK_TYPE_HSDPA: 369 networkTypeStr = "hsdpa"; 370 break; 371 case TelephonyManager.NETWORK_TYPE_HSUPA: 372 networkTypeStr = "hsupa"; 373 break; 374 case TelephonyManager.NETWORK_TYPE_HSPA: 375 networkTypeStr = "hspa"; 376 break; 377 case TelephonyManager.NETWORK_TYPE_CDMA: 378 networkTypeStr = "cdma"; 379 break; 380 case TelephonyManager.NETWORK_TYPE_1xRTT: 381 networkTypeStr = "1xrtt"; 382 break; 383 case TelephonyManager.NETWORK_TYPE_EVDO_0: 384 networkTypeStr = "evdo"; 385 break; 386 case TelephonyManager.NETWORK_TYPE_EVDO_A: 387 networkTypeStr = "evdo"; 388 break; 389 case TelephonyManager.NETWORK_TYPE_EVDO_B: 390 networkTypeStr = "evdo"; 391 break; 392 case TelephonyManager.NETWORK_TYPE_IDEN: 393 networkTypeStr = "iden"; 394 break; 395 case TelephonyManager.NETWORK_TYPE_LTE: 396 networkTypeStr = "lte"; 397 break; 398 case TelephonyManager.NETWORK_TYPE_EHRPD: 399 networkTypeStr = "ehrpd"; 400 break; 401 default: 402 loge("unknown network type: " + tm.getNetworkType()); 403 } 404 return "net.tcp.buffersize." + networkTypeStr; 405 } 406 407 /** 408 * Tear down mobile data connectivity, i.e., disable the ability to create 409 * mobile data connections. 410 * TODO - make async and return nothing? 411 */ 412 public boolean teardown() { 413 setTeardownRequested(true); 414 return (setEnableApn(mApnType, false) != Phone.APN_REQUEST_FAILED); 415 } 416 417 /** 418 * Record the detailed state of a network, and if it is a 419 * change from the previous state, send a notification to 420 * any listeners. 421 * @param state the new @{code DetailedState} 422 * @param reason a {@code String} indicating a reason for the state change, 423 * if one was supplied. May be {@code null}. 424 * @param extraInfo optional {@code String} providing extra information about the state change 425 */ 426 private void setDetailedState(NetworkInfo.DetailedState state, String reason, 427 String extraInfo) { 428 if (DBG) log("setDetailed state, old =" 429 + mNetworkInfo.getDetailedState() + " and new state=" + state); 430 if (state != mNetworkInfo.getDetailedState()) { 431 boolean wasConnecting = (mNetworkInfo.getState() == NetworkInfo.State.CONNECTING); 432 String lastReason = mNetworkInfo.getReason(); 433 /* 434 * If a reason was supplied when the CONNECTING state was entered, and no 435 * reason was supplied for entering the CONNECTED state, then retain the 436 * reason that was supplied when going to CONNECTING. 437 */ 438 if (wasConnecting && state == NetworkInfo.DetailedState.CONNECTED && reason == null 439 && lastReason != null) 440 reason = lastReason; 441 mNetworkInfo.setDetailedState(state, reason, extraInfo); 442 Message msg = mTarget.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo); 443 msg.sendToTarget(); 444 } 445 } 446 447 public void setTeardownRequested(boolean isRequested) { 448 mTeardownRequested = isRequested; 449 } 450 451 public boolean isTeardownRequested() { 452 return mTeardownRequested; 453 } 454 455 /** 456 * Re-enable mobile data connectivity after a {@link #teardown()}. 457 * TODO - make async and always get a notification? 458 */ 459 public boolean reconnect() { 460 boolean retValue = false; //connected or expect to be? 461 setTeardownRequested(false); 462 switch (setEnableApn(mApnType, true)) { 463 case Phone.APN_ALREADY_ACTIVE: 464 // need to set self to CONNECTING so the below message is handled. 465 retValue = true; 466 break; 467 case Phone.APN_REQUEST_STARTED: 468 // no need to do anything - we're already due some status update intents 469 retValue = true; 470 break; 471 case Phone.APN_REQUEST_FAILED: 472 case Phone.APN_TYPE_NOT_AVAILABLE: 473 break; 474 default: 475 loge("Error in reconnect - unexpected response."); 476 break; 477 } 478 return retValue; 479 } 480 481 /** 482 * Turn on or off the mobile radio. No connectivity will be possible while the 483 * radio is off. The operation is a no-op if the radio is already in the desired state. 484 * @param turnOn {@code true} if the radio should be turned on, {@code false} if 485 */ 486 public boolean setRadio(boolean turnOn) { 487 getPhoneService(false); 488 /* 489 * If the phone process has crashed in the past, we'll get a 490 * RemoteException and need to re-reference the service. 491 */ 492 for (int retry = 0; retry < 2; retry++) { 493 if (mPhoneService == null) { 494 log("Ignoring mobile radio request because could not acquire PhoneService"); 495 break; 496 } 497 498 try { 499 return mPhoneService.setRadio(turnOn); 500 } catch (RemoteException e) { 501 if (retry == 0) getPhoneService(true); 502 } 503 } 504 505 log("Could not set radio power to " + (turnOn ? "on" : "off")); 506 return false; 507 } 508 509 /** 510 * Tells the phone sub-system that the caller wants to 511 * begin using the named feature. The only supported features at 512 * this time are {@code Phone.FEATURE_ENABLE_MMS}, which allows an application 513 * to specify that it wants to send and/or receive MMS data, and 514 * {@code Phone.FEATURE_ENABLE_SUPL}, which is used for Assisted GPS. 515 * @param feature the name of the feature to be used 516 * @param callingPid the process ID of the process that is issuing this request 517 * @param callingUid the user ID of the process that is issuing this request 518 * @return an integer value representing the outcome of the request. 519 * The interpretation of this value is feature-specific. 520 * specific, except that the value {@code -1} 521 * always indicates failure. For {@code Phone.FEATURE_ENABLE_MMS}, 522 * the other possible return values are 523 * <ul> 524 * <li>{@code Phone.APN_ALREADY_ACTIVE}</li> 525 * <li>{@code Phone.APN_REQUEST_STARTED}</li> 526 * <li>{@code Phone.APN_TYPE_NOT_AVAILABLE}</li> 527 * <li>{@code Phone.APN_REQUEST_FAILED}</li> 528 * </ul> 529 */ 530 public int startUsingNetworkFeature(String feature, int callingPid, int callingUid) { 531 return -1; 532 } 533 534 /** 535 * Tells the phone sub-system that the caller is finished 536 * using the named feature. The only supported feature at 537 * this time is {@code Phone.FEATURE_ENABLE_MMS}, which allows an application 538 * to specify that it wants to send and/or receive MMS data. 539 * @param feature the name of the feature that is no longer needed 540 * @param callingPid the process ID of the process that is issuing this request 541 * @param callingUid the user ID of the process that is issuing this request 542 * @return an integer value representing the outcome of the request. 543 * The interpretation of this value is feature-specific, except that 544 * the value {@code -1} always indicates failure. 545 */ 546 public int stopUsingNetworkFeature(String feature, int callingPid, int callingUid) { 547 return -1; 548 } 549 550 /** 551 * @param enabled 552 */ 553 public void setDataEnable(boolean enabled) { 554 try { 555 log("setDataEnable: E enabled=" + enabled); 556 mDataConnectionTrackerAc.sendMessage(DataConnectionTracker.CMD_SET_DATA_ENABLE, 557 enabled ? DataConnectionTracker.ENABLED : DataConnectionTracker.DISABLED); 558 log("setDataEnable: X enabled=" + enabled); 559 } catch (Exception e) { 560 log("setDataEnable: X mAc was null" + e); 561 } 562 } 563 564 @Override 565 public String toString() { 566 StringBuffer sb = new StringBuffer("Mobile data state: "); 567 568 sb.append(mMobileDataState); 569 return sb.toString(); 570 } 571 572 /** 573 * Internal method supporting the ENABLE_MMS feature. 574 * @param apnType the type of APN to be enabled or disabled (e.g., mms) 575 * @param enable {@code true} to enable the specified APN type, 576 * {@code false} to disable it. 577 * @return an integer value representing the outcome of the request. 578 */ 579 private int setEnableApn(String apnType, boolean enable) { 580 getPhoneService(false); 581 /* 582 * If the phone process has crashed in the past, we'll get a 583 * RemoteException and need to re-reference the service. 584 */ 585 for (int retry = 0; retry < 2; retry++) { 586 if (mPhoneService == null) { 587 log("Ignoring feature request because could not acquire PhoneService"); 588 break; 589 } 590 591 try { 592 if (enable) { 593 return mPhoneService.enableApnType(apnType); 594 } else { 595 return mPhoneService.disableApnType(apnType); 596 } 597 } catch (RemoteException e) { 598 if (retry == 0) getPhoneService(true); 599 } 600 } 601 602 log("Could not " + (enable ? "enable" : "disable") + " APN type \"" + apnType + "\""); 603 return Phone.APN_REQUEST_FAILED; 604 } 605 606 public static String networkTypeToApnType(int netType) { 607 switch(netType) { 608 case ConnectivityManager.TYPE_MOBILE: 609 return Phone.APN_TYPE_DEFAULT; // TODO - use just one of these 610 case ConnectivityManager.TYPE_MOBILE_MMS: 611 return Phone.APN_TYPE_MMS; 612 case ConnectivityManager.TYPE_MOBILE_SUPL: 613 return Phone.APN_TYPE_SUPL; 614 case ConnectivityManager.TYPE_MOBILE_DUN: 615 return Phone.APN_TYPE_DUN; 616 case ConnectivityManager.TYPE_MOBILE_HIPRI: 617 return Phone.APN_TYPE_HIPRI; 618 default: 619 sloge("Error mapping networkType " + netType + " to apnType."); 620 return null; 621 } 622 } 623 624 /** 625 * @see android.net.NetworkStateTracker#getLinkProperties() 626 */ 627 public LinkProperties getLinkProperties() { 628 return new LinkProperties(mLinkProperties); 629 } 630 631 /** 632 * @see android.net.NetworkStateTracker#getLinkCapabilities() 633 */ 634 public LinkCapabilities getLinkCapabilities() { 635 return new LinkCapabilities(mLinkCapabilities); 636 } 637 638 private void log(String s) { 639 Slog.d(TAG, mApnType + ": " + s); 640 } 641 642 private void loge(String s) { 643 Slog.e(TAG, mApnType + ": " + s); 644 } 645 646 static private void sloge(String s) { 647 Slog.e(TAG, s); 648 } 649} 650