2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 import android.content.BroadcastReceiver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.IntentFilter;
23 import android.net.NetworkInfo.DetailedState;
24 import android.os.Bundle;
25 import android.os.Handler;
26 import android.os.Looper;
27 import android.os.Message;
28 import android.os.Messenger;
29 import android.os.RemoteException;
30 import android.os.ServiceManager;
31 import android.telephony.TelephonyManager;
32 import android.text.TextUtils;
33 import android.util.Slog;
35 import com.android.internal.telephony.DctConstants;
36 import com.android.internal.telephony.ITelephony;
37 import com.android.internal.telephony.PhoneConstants;
38 import com.android.internal.telephony.TelephonyIntents;
39 import com.android.internal.util.AsyncChannel;
41 import java.io.CharArrayWriter;
42 import java.io.PrintWriter;
43 import java.util.concurrent.atomic.AtomicBoolean;
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.
52 public class MobileDataStateTracker implements NetworkStateTracker {
54 private static final String TAG = "MobileDataStateTracker";
55 private static final boolean DBG = true;
56 private static final boolean VDBG = false;
58 private PhoneConstants.DataState mMobileDataState;
59 private ITelephony mPhoneService;
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;
71 // NOTE: these are only kept for debugging output; actual values are
72 // maintained in DataConnectionTracker.
73 protected boolean mUserDataEnabled = true;
74 protected boolean mPolicyDataEnabled = true;
76 private Handler mHandler;
77 private AsyncChannel mDataConnectionTrackerAc;
79 private AtomicBoolean mIsCaptivePortal = new AtomicBoolean(false);
82 * Create a new MobileDataStateTracker
83 * @param netType the ConnectivityManager network type
84 * @param tag the name of this network
86 public MobileDataStateTracker(int netType, String tag) {
87 mNetworkInfo = new NetworkInfo(netType,
88 TelephonyManager.getDefault().getNetworkType(), tag,
89 TelephonyManager.getDefault().getNetworkTypeName());
90 mApnType = networkTypeToApnType(netType);
94 * Begin monitoring data connectivity.
96 * @param context is the current Android context
97 * @param target is the Hander to which to return the events.
99 public void startMonitoring(Context context, Handler target) {
103 mHandler = new MdstHandler(target.getLooper(), this);
105 IntentFilter filter = new IntentFilter();
106 filter.addAction(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
107 filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_CONNECTED_TO_PROVISIONING_APN);
108 filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED);
110 mContext.registerReceiver(new MobileDataStateReceiver(), filter);
111 mMobileDataState = PhoneConstants.DataState.DISCONNECTED;
114 static class MdstHandler extends Handler {
115 private MobileDataStateTracker mMdst;
117 MdstHandler(Looper looper, MobileDataStateTracker mdst) {
123 public void handleMessage(Message msg) {
125 case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
126 if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
128 mMdst.log("MdstHandler connected");
130 mMdst.mDataConnectionTrackerAc = (AsyncChannel) msg.obj;
133 mMdst.log("MdstHandler %s NOT connected error=" + msg.arg1);
137 case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
138 if (VDBG) mMdst.log("Disconnected from DataStateTracker");
139 mMdst.mDataConnectionTrackerAc = null;
142 if (VDBG) mMdst.log("Ignorning unknown message=" + msg);
149 public boolean isPrivateDnsRouteSet() {
150 return mPrivateDnsRouteSet;
153 public void privateDnsRouteSet(boolean enabled) {
154 mPrivateDnsRouteSet = enabled;
157 public NetworkInfo getNetworkInfo() {
161 public boolean isDefaultRouteSet() {
162 return mDefaultRouteSet;
165 public void defaultRouteSet(boolean enabled) {
166 mDefaultRouteSet = enabled;
170 * This is not implemented.
172 public void releaseWakeLock() {
175 private void updateLinkProperitesAndCapatilities(Intent intent) {
176 mLinkProperties = intent.getParcelableExtra(
177 PhoneConstants.DATA_LINK_PROPERTIES_KEY);
178 if (mLinkProperties == null) {
179 loge("CONNECTED event did not supply link properties.");
180 mLinkProperties = new LinkProperties();
182 mLinkCapabilities = intent.getParcelableExtra(
183 PhoneConstants.DATA_LINK_CAPABILITIES_KEY);
184 if (mLinkCapabilities == null) {
185 loge("CONNECTED event did not supply link capabilities.");
186 mLinkCapabilities = new LinkCapabilities();
190 private class MobileDataStateReceiver extends BroadcastReceiver {
192 public void onReceive(Context context, Intent intent) {
193 if (intent.getAction().equals(TelephonyIntents.
194 ACTION_DATA_CONNECTION_CONNECTED_TO_PROVISIONING_APN)) {
195 String apnName = intent.getStringExtra(PhoneConstants.DATA_APN_KEY);
196 String apnType = intent.getStringExtra(PhoneConstants.DATA_APN_TYPE_KEY);
197 if (!TextUtils.equals(mApnType, apnType)) {
201 log("Broadcast received: " + intent.getAction() + " apnType=" + apnType
202 + " apnName=" + apnName);
205 // Make us in the connecting state until we make a new TYPE_MOBILE_PROVISIONING
206 mMobileDataState = PhoneConstants.DataState.CONNECTING;
207 updateLinkProperitesAndCapatilities(intent);
208 setDetailedState(DetailedState.CONNECTED_TO_PROVISIONING_NETWORK, "", apnName);
209 } else if (intent.getAction().equals(TelephonyIntents.
210 ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) {
211 String apnType = intent.getStringExtra(PhoneConstants.DATA_APN_TYPE_KEY);
212 if (!TextUtils.equals(apnType, mApnType)) {
216 log("Broadcast received: " + intent.getAction() + " apnType=" + apnType);
219 int oldSubtype = mNetworkInfo.getSubtype();
220 int newSubType = TelephonyManager.getDefault().getNetworkType();
221 String subTypeName = TelephonyManager.getDefault().getNetworkTypeName();
222 mNetworkInfo.setSubtype(newSubType, subTypeName);
223 if (newSubType != oldSubtype && mNetworkInfo.isConnected()) {
224 Message msg = mTarget.obtainMessage(EVENT_NETWORK_SUBTYPE_CHANGED,
225 oldSubtype, 0, mNetworkInfo);
229 PhoneConstants.DataState state = Enum.valueOf(PhoneConstants.DataState.class,
230 intent.getStringExtra(PhoneConstants.STATE_KEY));
231 String reason = intent.getStringExtra(PhoneConstants.STATE_CHANGE_REASON_KEY);
232 String apnName = intent.getStringExtra(PhoneConstants.DATA_APN_KEY);
233 mNetworkInfo.setRoaming(intent.getBooleanExtra(
234 PhoneConstants.DATA_NETWORK_ROAMING_KEY, false));
236 log(mApnType + " setting isAvailable to " +
237 intent.getBooleanExtra(PhoneConstants.NETWORK_UNAVAILABLE_KEY,false));
239 mNetworkInfo.setIsAvailable(!intent.getBooleanExtra(
240 PhoneConstants.NETWORK_UNAVAILABLE_KEY, false));
243 log("Received state=" + state + ", old=" + mMobileDataState +
244 ", reason=" + (reason == null ? "(unspecified)" : reason));
246 if (mMobileDataState != state) {
247 mMobileDataState = state;
250 if(isTeardownRequested()) {
251 setTeardownRequested(false);
254 setDetailedState(DetailedState.DISCONNECTED, reason, apnName);
255 // can't do this here - ConnectivityService needs it to clear stuff
256 // it's ok though - just leave it to be refreshed next time
258 //if (DBG) log("clearing mInterfaceName for "+ mApnType +
259 // " as it DISCONNECTED");
260 //mInterfaceName = null;
263 setDetailedState(DetailedState.CONNECTING, reason, apnName);
266 setDetailedState(DetailedState.SUSPENDED, reason, apnName);
269 updateLinkProperitesAndCapatilities(intent);
270 setDetailedState(DetailedState.CONNECTED, reason, apnName);
274 // There was no state change. Check if LinkProperties has been updated.
275 if (TextUtils.equals(reason, PhoneConstants.REASON_LINK_PROPERTIES_CHANGED)) {
276 mLinkProperties = intent.getParcelableExtra(
277 PhoneConstants.DATA_LINK_PROPERTIES_KEY);
278 if (mLinkProperties == null) {
279 loge("No link property in LINK_PROPERTIES change event.");
280 mLinkProperties = new LinkProperties();
282 // Just update reason field in this NetworkInfo
283 mNetworkInfo.setDetailedState(mNetworkInfo.getDetailedState(), reason,
284 mNetworkInfo.getExtraInfo());
285 Message msg = mTarget.obtainMessage(EVENT_CONFIGURATION_CHANGED,
290 } else if (intent.getAction().
291 equals(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED)) {
292 String apnType = intent.getStringExtra(PhoneConstants.DATA_APN_TYPE_KEY);
293 if (!TextUtils.equals(apnType, mApnType)) {
296 String reason = intent.getStringExtra(PhoneConstants.FAILURE_REASON_KEY);
297 String apnName = intent.getStringExtra(PhoneConstants.DATA_APN_KEY);
299 log("Broadcast received: " + intent.getAction() +
300 " reason=" + reason == null ? "null" : reason);
302 setDetailedState(DetailedState.FAILED, reason, apnName);
304 if (DBG) log("Broadcast received: ignore " + intent.getAction());
309 private void getPhoneService(boolean forceRefresh) {
310 if ((mPhoneService == null) || forceRefresh) {
311 mPhoneService = ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
316 * Report whether data connectivity is possible.
318 public boolean isAvailable() {
319 return mNetworkInfo.isAvailable();
323 * Return the system properties name associated with the tcp buffer sizes
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";
334 case TelephonyManager.NETWORK_TYPE_EDGE:
335 networkTypeStr = "edge";
337 case TelephonyManager.NETWORK_TYPE_UMTS:
338 networkTypeStr = "umts";
340 case TelephonyManager.NETWORK_TYPE_HSDPA:
341 networkTypeStr = "hsdpa";
343 case TelephonyManager.NETWORK_TYPE_HSUPA:
344 networkTypeStr = "hsupa";
346 case TelephonyManager.NETWORK_TYPE_HSPA:
347 networkTypeStr = "hspa";
349 case TelephonyManager.NETWORK_TYPE_HSPAP:
350 networkTypeStr = "hspap";
352 case TelephonyManager.NETWORK_TYPE_CDMA:
353 networkTypeStr = "cdma";
355 case TelephonyManager.NETWORK_TYPE_1xRTT:
356 networkTypeStr = "1xrtt";
358 case TelephonyManager.NETWORK_TYPE_EVDO_0:
359 networkTypeStr = "evdo";
361 case TelephonyManager.NETWORK_TYPE_EVDO_A:
362 networkTypeStr = "evdo";
364 case TelephonyManager.NETWORK_TYPE_EVDO_B:
365 networkTypeStr = "evdo";
367 case TelephonyManager.NETWORK_TYPE_IDEN:
368 networkTypeStr = "iden";
370 case TelephonyManager.NETWORK_TYPE_LTE:
371 networkTypeStr = "lte";
373 case TelephonyManager.NETWORK_TYPE_EHRPD:
374 networkTypeStr = "ehrpd";
377 loge("unknown network type: " + tm.getNetworkType());
379 return "net.tcp.buffersize." + networkTypeStr;
383 * Tear down mobile data connectivity, i.e., disable the ability to create
384 * mobile data connections.
385 * TODO - make async and return nothing?
387 public boolean teardown() {
388 setTeardownRequested(true);
389 return (setEnableApn(mApnType, false) != PhoneConstants.APN_REQUEST_FAILED);
393 * @return true if this is ready to operate
395 public boolean isReady() {
396 return mDataConnectionTrackerAc != null;
400 public void captivePortalCheckComplete() {
405 public void captivePortalCheckCompleted(boolean isCaptivePortal) {
406 if (mIsCaptivePortal.getAndSet(isCaptivePortal) != isCaptivePortal) {
407 // Captive portal change enable/disable failing fast
408 setEnableFailFastMobileData(
409 isCaptivePortal ? DctConstants.ENABLED : DctConstants.DISABLED);
414 * Record the detailed state of a network, and if it is a
415 * change from the previous state, send a notification to
417 * @param state the new {@code DetailedState}
418 * @param reason a {@code String} indicating a reason for the state change,
419 * if one was supplied. May be {@code null}.
420 * @param extraInfo optional {@code String} providing extra information about the state change
422 private void setDetailedState(NetworkInfo.DetailedState state, String reason,
424 if (DBG) log("setDetailed state, old ="
425 + mNetworkInfo.getDetailedState() + " and new state=" + state);
426 if (state != mNetworkInfo.getDetailedState()) {
427 boolean wasConnecting = (mNetworkInfo.getState() == NetworkInfo.State.CONNECTING);
428 String lastReason = mNetworkInfo.getReason();
430 * If a reason was supplied when the CONNECTING state was entered, and no
431 * reason was supplied for entering the CONNECTED state, then retain the
432 * reason that was supplied when going to CONNECTING.
434 if (wasConnecting && state == NetworkInfo.DetailedState.CONNECTED && reason == null
435 && lastReason != null)
437 mNetworkInfo.setDetailedState(state, reason, extraInfo);
438 Message msg = mTarget.obtainMessage(EVENT_STATE_CHANGED, new NetworkInfo(mNetworkInfo));
443 public void setTeardownRequested(boolean isRequested) {
444 mTeardownRequested = isRequested;
447 public boolean isTeardownRequested() {
448 return mTeardownRequested;
452 * Re-enable mobile data connectivity after a {@link #teardown()}.
453 * TODO - make async and always get a notification?
455 public boolean reconnect() {
456 boolean retValue = false; //connected or expect to be?
457 setTeardownRequested(false);
458 switch (setEnableApn(mApnType, true)) {
459 case PhoneConstants.APN_ALREADY_ACTIVE:
460 // need to set self to CONNECTING so the below message is handled.
463 case PhoneConstants.APN_REQUEST_STARTED:
464 // set IDLE here , avoid the following second FAILED not sent out
465 mNetworkInfo.setDetailedState(DetailedState.IDLE, null, null);
468 case PhoneConstants.APN_REQUEST_FAILED:
469 case PhoneConstants.APN_TYPE_NOT_AVAILABLE:
472 loge("Error in reconnect - unexpected response.");
479 * Turn on or off the mobile radio. No connectivity will be possible while the
480 * radio is off. The operation is a no-op if the radio is already in the desired state.
481 * @param turnOn {@code true} if the radio should be turned on, {@code false} if
483 public boolean setRadio(boolean turnOn) {
484 getPhoneService(false);
486 * If the phone process has crashed in the past, we'll get a
487 * RemoteException and need to re-reference the service.
489 for (int retry = 0; retry < 2; retry++) {
490 if (mPhoneService == null) {
491 loge("Ignoring mobile radio request because could not acquire PhoneService");
496 return mPhoneService.setRadio(turnOn);
497 } catch (RemoteException e) {
498 if (retry == 0) getPhoneService(true);
502 loge("Could not set radio power to " + (turnOn ? "on" : "off"));
507 public void setUserDataEnable(boolean enabled) {
508 if (DBG) log("setUserDataEnable: E enabled=" + enabled);
509 final AsyncChannel channel = mDataConnectionTrackerAc;
510 if (channel != null) {
511 channel.sendMessage(DctConstants.CMD_SET_USER_DATA_ENABLE,
512 enabled ? DctConstants.ENABLED : DctConstants.DISABLED);
513 mUserDataEnabled = enabled;
515 if (VDBG) log("setUserDataEnable: X enabled=" + enabled);
519 public void setPolicyDataEnable(boolean enabled) {
520 if (DBG) log("setPolicyDataEnable(enabled=" + enabled + ")");
521 final AsyncChannel channel = mDataConnectionTrackerAc;
522 if (channel != null) {
523 channel.sendMessage(DctConstants.CMD_SET_POLICY_DATA_ENABLE,
524 enabled ? DctConstants.ENABLED : DctConstants.DISABLED);
525 mPolicyDataEnabled = enabled;
530 * Eanble/disable FailFast
532 * @param enabled is DctConstants.ENABLED/DISABLED
534 public void setEnableFailFastMobileData(int enabled) {
535 if (DBG) log("setEnableFailFastMobileData(enabled=" + enabled + ")");
536 final AsyncChannel channel = mDataConnectionTrackerAc;
537 if (channel != null) {
538 channel.sendMessage(DctConstants.CMD_SET_ENABLE_FAIL_FAST_MOBILE_DATA, enabled);
543 * carrier dependency is met/unmet
546 public void setDependencyMet(boolean met) {
547 Bundle bundle = Bundle.forPair(DctConstants.APN_TYPE_KEY, mApnType);
549 if (DBG) log("setDependencyMet: E met=" + met);
550 Message msg = Message.obtain();
551 msg.what = DctConstants.CMD_SET_DEPENDENCY_MET;
552 msg.arg1 = (met ? DctConstants.ENABLED : DctConstants.DISABLED);
554 mDataConnectionTrackerAc.sendMessage(msg);
555 if (VDBG) log("setDependencyMet: X met=" + met);
556 } catch (NullPointerException e) {
557 loge("setDependencyMet: X mAc was null" + e);
562 * Inform DCT mobile provisioning has started, it ends when provisioning completes.
564 public void enableMobileProvisioning(String url) {
565 if (DBG) log("enableMobileProvisioning(url=" + url + ")");
566 final AsyncChannel channel = mDataConnectionTrackerAc;
567 if (channel != null) {
568 Message msg = Message.obtain();
569 msg.what = DctConstants.CMD_ENABLE_MOBILE_PROVISIONING;
570 msg.setData(Bundle.forPair(DctConstants.PROVISIONING_URL_KEY, url));
571 channel.sendMessage(msg);
576 * Return if this network is the provisioning network. Valid only if connected.
579 public boolean isProvisioningNetwork() {
582 Message msg = Message.obtain();
583 msg.what = DctConstants.CMD_IS_PROVISIONING_APN;
584 msg.setData(Bundle.forPair(DctConstants.APN_TYPE_KEY, mApnType));
585 Message result = mDataConnectionTrackerAc.sendMessageSynchronously(msg);
586 retVal = result.arg1 == DctConstants.ENABLED;
587 } catch (NullPointerException e) {
588 loge("isProvisioningNetwork: X " + e);
591 if (DBG) log("isProvisioningNetwork: retVal=" + retVal);
596 public void addStackedLink(LinkProperties link) {
597 mLinkProperties.addStackedLink(link);
601 public void removeStackedLink(LinkProperties link) {
602 mLinkProperties.removeStackedLink(link);
606 public String toString() {
607 final CharArrayWriter writer = new CharArrayWriter();
608 final PrintWriter pw = new PrintWriter(writer);
609 pw.print("Mobile data state: "); pw.println(mMobileDataState);
610 pw.print("Data enabled: user="); pw.print(mUserDataEnabled);
611 pw.print(", policy="); pw.println(mPolicyDataEnabled);
612 return writer.toString();
616 * Internal method supporting the ENABLE_MMS feature.
617 * @param apnType the type of APN to be enabled or disabled (e.g., mms)
618 * @param enable {@code true} to enable the specified APN type,
619 * {@code false} to disable it.
620 * @return an integer value representing the outcome of the request.
622 private int setEnableApn(String apnType, boolean enable) {
623 getPhoneService(false);
625 * If the phone process has crashed in the past, we'll get a
626 * RemoteException and need to re-reference the service.
628 for (int retry = 0; retry < 2; retry++) {
629 if (mPhoneService == null) {
630 loge("Ignoring feature request because could not acquire PhoneService");
636 return mPhoneService.enableApnType(apnType);
638 return mPhoneService.disableApnType(apnType);
640 } catch (RemoteException e) {
641 if (retry == 0) getPhoneService(true);
645 loge("Could not " + (enable ? "enable" : "disable") + " APN type \"" + apnType + "\"");
646 return PhoneConstants.APN_REQUEST_FAILED;
649 public static String networkTypeToApnType(int netType) {
651 case ConnectivityManager.TYPE_MOBILE:
652 return PhoneConstants.APN_TYPE_DEFAULT; // TODO - use just one of these
653 case ConnectivityManager.TYPE_MOBILE_MMS:
654 return PhoneConstants.APN_TYPE_MMS;
655 case ConnectivityManager.TYPE_MOBILE_SUPL:
656 return PhoneConstants.APN_TYPE_SUPL;
657 case ConnectivityManager.TYPE_MOBILE_DUN:
658 return PhoneConstants.APN_TYPE_DUN;
659 case ConnectivityManager.TYPE_MOBILE_HIPRI:
660 return PhoneConstants.APN_TYPE_HIPRI;
661 case ConnectivityManager.TYPE_MOBILE_FOTA:
662 return PhoneConstants.APN_TYPE_FOTA;
663 case ConnectivityManager.TYPE_MOBILE_IMS:
664 return PhoneConstants.APN_TYPE_IMS;
665 case ConnectivityManager.TYPE_MOBILE_CBS:
666 return PhoneConstants.APN_TYPE_CBS;
668 sloge("Error mapping networkType " + netType + " to apnType.");
674 * @see android.net.NetworkStateTracker#getLinkProperties()
676 public LinkProperties getLinkProperties() {
677 return new LinkProperties(mLinkProperties);
681 * @see android.net.NetworkStateTracker#getLinkCapabilities()
683 public LinkCapabilities getLinkCapabilities() {
684 return new LinkCapabilities(mLinkCapabilities);
687 public void supplyMessenger(Messenger messenger) {
688 if (VDBG) log(mApnType + " got supplyMessenger");
689 AsyncChannel ac = new AsyncChannel();
690 ac.connect(mContext, MobileDataStateTracker.this.mHandler, messenger);
693 private void log(String s) {
694 Slog.d(TAG, mApnType + ": " + s);
697 private void loge(String s) {
698 Slog.e(TAG, mApnType + ": " + s);
701 static private void sloge(String s) {