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