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