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