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;
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.
51 public class MobileDataStateTracker implements NetworkStateTracker {
53 private static final String TAG = "MobileDataStateTracker";
54 private static final boolean DBG = false;
55 private static final boolean VDBG = false;
57 private PhoneConstants.DataState mMobileDataState;
58 private ITelephony mPhoneService;
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;
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;
75 private Handler mHandler;
76 private AsyncChannel mDataConnectionTrackerAc;
79 * Create a new MobileDataStateTracker
80 * @param netType the ConnectivityManager network type
81 * @param tag the name of this network
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);
91 * Begin monitoring data connectivity.
93 * @param context is the current Android context
94 * @param target is the Hander to which to return the events.
96 public void startMonitoring(Context context, Handler target) {
100 mHandler = new MdstHandler(target.getLooper(), this);
102 IntentFilter filter = new IntentFilter();
103 filter.addAction(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
104 filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED);
106 mContext.registerReceiver(new MobileDataStateReceiver(), filter);
107 mMobileDataState = PhoneConstants.DataState.DISCONNECTED;
110 static class MdstHandler extends Handler {
111 private MobileDataStateTracker mMdst;
113 MdstHandler(Looper looper, MobileDataStateTracker mdst) {
119 public void handleMessage(Message msg) {
121 case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
122 if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
124 mMdst.log("MdstHandler connected");
126 mMdst.mDataConnectionTrackerAc = (AsyncChannel) msg.obj;
129 mMdst.log("MdstHandler %s NOT connected error=" + msg.arg1);
133 case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
134 if (VDBG) mMdst.log("Disconnected from DataStateTracker");
135 mMdst.mDataConnectionTrackerAc = null;
138 if (VDBG) mMdst.log("Ignorning unknown message=" + msg);
145 public boolean isPrivateDnsRouteSet() {
146 return mPrivateDnsRouteSet;
149 public void privateDnsRouteSet(boolean enabled) {
150 mPrivateDnsRouteSet = enabled;
153 public NetworkInfo getNetworkInfo() {
157 public boolean isDefaultRouteSet() {
158 return mDefaultRouteSet;
161 public void defaultRouteSet(boolean enabled) {
162 mDefaultRouteSet = enabled;
166 * This is not implemented.
168 public void releaseWakeLock() {
171 private class MobileDataStateReceiver extends BroadcastReceiver {
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);
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));
182 if (!TextUtils.equals(apnType, mApnType)) {
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);
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));
203 log(mApnType + " setting isAvailable to " +
204 intent.getBooleanExtra(PhoneConstants.NETWORK_UNAVAILABLE_KEY,false));
206 mNetworkInfo.setIsAvailable(!intent.getBooleanExtra(
207 PhoneConstants.NETWORK_UNAVAILABLE_KEY, false));
210 log("Received state=" + state + ", old=" + mMobileDataState +
211 ", reason=" + (reason == null ? "(unspecified)" : reason));
213 if (mMobileDataState != state) {
214 mMobileDataState = state;
217 if(isTeardownRequested()) {
218 setTeardownRequested(false);
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
225 //if (DBG) log("clearing mInterfaceName for "+ mApnType +
226 // " as it DISCONNECTED");
227 //mInterfaceName = null;
230 setDetailedState(DetailedState.CONNECTING, reason, apnName);
233 setDetailedState(DetailedState.SUSPENDED, reason, apnName);
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();
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();
248 setDetailedState(DetailedState.CONNECTED, reason, apnName);
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();
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,
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)) {
274 "Broadcast received: ACTION_ANY_DATA_CONNECTION_FAILED ignore, " +
275 "mApnType=%s != received apnType=%s", mApnType, apnType));
279 String reason = intent.getStringExtra(PhoneConstants.FAILURE_REASON_KEY);
280 String apnName = intent.getStringExtra(PhoneConstants.DATA_APN_KEY);
282 log("Received " + intent.getAction() +
283 " broadcast" + reason == null ? "" : "(" + reason + ")");
285 setDetailedState(DetailedState.FAILED, reason, apnName);
287 if (DBG) log("Broadcast received: ignore " + intent.getAction());
292 private void getPhoneService(boolean forceRefresh) {
293 if ((mPhoneService == null) || forceRefresh) {
294 mPhoneService = ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
299 * Report whether data connectivity is possible.
301 public boolean isAvailable() {
302 return mNetworkInfo.isAvailable();
306 * Return the system properties name associated with the tcp buffer sizes
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";
317 case TelephonyManager.NETWORK_TYPE_EDGE:
318 networkTypeStr = "edge";
320 case TelephonyManager.NETWORK_TYPE_UMTS:
321 networkTypeStr = "umts";
323 case TelephonyManager.NETWORK_TYPE_HSDPA:
324 networkTypeStr = "hsdpa";
326 case TelephonyManager.NETWORK_TYPE_HSUPA:
327 networkTypeStr = "hsupa";
329 case TelephonyManager.NETWORK_TYPE_HSPA:
330 networkTypeStr = "hspa";
332 case TelephonyManager.NETWORK_TYPE_HSPAP:
333 networkTypeStr = "hspap";
335 case TelephonyManager.NETWORK_TYPE_CDMA:
336 networkTypeStr = "cdma";
338 case TelephonyManager.NETWORK_TYPE_1xRTT:
339 networkTypeStr = "1xrtt";
341 case TelephonyManager.NETWORK_TYPE_EVDO_0:
342 networkTypeStr = "evdo";
344 case TelephonyManager.NETWORK_TYPE_EVDO_A:
345 networkTypeStr = "evdo";
347 case TelephonyManager.NETWORK_TYPE_EVDO_B:
348 networkTypeStr = "evdo";
350 case TelephonyManager.NETWORK_TYPE_IDEN:
351 networkTypeStr = "iden";
353 case TelephonyManager.NETWORK_TYPE_LTE:
354 networkTypeStr = "lte";
356 case TelephonyManager.NETWORK_TYPE_EHRPD:
357 networkTypeStr = "ehrpd";
360 loge("unknown network type: " + tm.getNetworkType());
362 return "net.tcp.buffersize." + networkTypeStr;
366 * Tear down mobile data connectivity, i.e., disable the ability to create
367 * mobile data connections.
368 * TODO - make async and return nothing?
370 public boolean teardown() {
371 setTeardownRequested(true);
372 return (setEnableApn(mApnType, false) != PhoneConstants.APN_REQUEST_FAILED);
376 public void captivePortalCheckComplete() {
381 * Record the detailed state of a network, and if it is a
382 * change from the previous state, send a notification to
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
389 private void setDetailedState(NetworkInfo.DetailedState state, String reason,
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();
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.
401 if (wasConnecting && state == NetworkInfo.DetailedState.CONNECTED && reason == null
402 && lastReason != null)
404 mNetworkInfo.setDetailedState(state, reason, extraInfo);
405 Message msg = mTarget.obtainMessage(EVENT_STATE_CHANGED, new NetworkInfo(mNetworkInfo));
410 public void setTeardownRequested(boolean isRequested) {
411 mTeardownRequested = isRequested;
414 public boolean isTeardownRequested() {
415 return mTeardownRequested;
419 * Re-enable mobile data connectivity after a {@link #teardown()}.
420 * TODO - make async and always get a notification?
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.
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);
435 case PhoneConstants.APN_REQUEST_FAILED:
436 case PhoneConstants.APN_TYPE_NOT_AVAILABLE:
439 loge("Error in reconnect - unexpected response.");
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
450 public boolean setRadio(boolean turnOn) {
451 getPhoneService(false);
453 * If the phone process has crashed in the past, we'll get a
454 * RemoteException and need to re-reference the service.
456 for (int retry = 0; retry < 2; retry++) {
457 if (mPhoneService == null) {
458 loge("Ignoring mobile radio request because could not acquire PhoneService");
463 return mPhoneService.setRadio(turnOn);
464 } catch (RemoteException e) {
465 if (retry == 0) getPhoneService(true);
469 loge("Could not set radio power to " + (turnOn ? "on" : "off"));
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;
482 if (VDBG) log("setUserDataEnable: X enabled=" + enabled);
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;
497 * Eanble/disable FailFast
499 * @param enabled is DctConstants.ENABLED/DISABLED
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);
510 * carrier dependency is met/unmet
513 public void setDependencyMet(boolean met) {
514 Bundle bundle = Bundle.forPair(DctConstants.APN_TYPE_KEY, mApnType);
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);
521 mDataConnectionTrackerAc.sendMessage(msg);
522 if (VDBG) log("setDependencyMet: X met=" + met);
523 } catch (NullPointerException e) {
524 loge("setDependencyMet: X mAc was null" + e);
529 public void addStackedLink(LinkProperties link) {
530 mLinkProperties.addStackedLink(link);
534 public void removeStackedLink(LinkProperties link) {
535 mLinkProperties.removeStackedLink(link);
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();
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.
555 private int setEnableApn(String apnType, boolean enable) {
556 getPhoneService(false);
558 * If the phone process has crashed in the past, we'll get a
559 * RemoteException and need to re-reference the service.
561 for (int retry = 0; retry < 2; retry++) {
562 if (mPhoneService == null) {
563 loge("Ignoring feature request because could not acquire PhoneService");
569 return mPhoneService.enableApnType(apnType);
571 return mPhoneService.disableApnType(apnType);
573 } catch (RemoteException e) {
574 if (retry == 0) getPhoneService(true);
578 loge("Could not " + (enable ? "enable" : "disable") + " APN type \"" + apnType + "\"");
579 return PhoneConstants.APN_REQUEST_FAILED;
582 public static String networkTypeToApnType(int 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;
601 sloge("Error mapping networkType " + netType + " to apnType.");
607 * @see android.net.NetworkStateTracker#getLinkProperties()
609 public LinkProperties getLinkProperties() {
610 return new LinkProperties(mLinkProperties);
614 * @see android.net.NetworkStateTracker#getLinkCapabilities()
616 public LinkCapabilities getLinkCapabilities() {
617 return new LinkCapabilities(mLinkCapabilities);
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);
626 private void log(String s) {
627 Slog.d(TAG, mApnType + ": " + s);
630 private void loge(String s) {
631 Slog.e(TAG, mApnType + ": " + s);
634 static private void sloge(String s) {