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