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