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.os.Bundle;
24 import android.os.HandlerThread;
25 import android.os.Looper;
26 import android.os.Messenger;
27 import android.os.RemoteException;
28 import android.os.Handler;
29 import android.os.IBinder;
30 import android.os.Message;
31 import android.os.ServiceManager;
33 import com.android.internal.telephony.DataConnectionTracker;
34 import com.android.internal.telephony.ITelephony;
35 import com.android.internal.telephony.Phone;
36 import com.android.internal.telephony.TelephonyIntents;
37 import com.android.internal.util.AsyncChannel;
39 import android.net.NetworkInfo.DetailedState;
40 import android.net.NetworkInfo;
41 import android.net.LinkProperties;
42 import android.telephony.TelephonyManager;
43 import android.util.Slog;
44 import android.text.TextUtils;
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.
53 public class MobileDataStateTracker implements NetworkStateTracker {
55 private static final String TAG = "MobileDataStateTracker";
56 private static final boolean DBG = true;
57 private static final boolean VDBG = false;
59 private Phone.DataState mMobileDataState;
60 private ITelephony mPhoneService;
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;
72 // DEFAULT and HIPRI are the same connection. If we're one of these we need to check if
73 // the other is also disconnected before we reset sockets
74 private boolean mIsDefaultOrHipri = false;
76 private Handler mHandler;
77 private AsyncChannel mDataConnectionTrackerAc;
78 private Messenger mMessenger;
81 * Create a new MobileDataStateTracker
82 * @param netType the ConnectivityManager network type
83 * @param tag the name of this network
85 public MobileDataStateTracker(int netType, String tag) {
86 mNetworkInfo = new NetworkInfo(netType,
87 TelephonyManager.getDefault().getNetworkType(), tag,
88 TelephonyManager.getDefault().getNetworkTypeName());
89 mApnType = networkTypeToApnType(netType);
90 if (netType == ConnectivityManager.TYPE_MOBILE ||
91 netType == ConnectivityManager.TYPE_MOBILE_HIPRI) {
92 mIsDefaultOrHipri = true;
99 * Begin monitoring data connectivity.
101 * @param context is the current Android context
102 * @param target is the Hander to which to return the events.
104 public void startMonitoring(Context context, Handler target) {
108 HandlerThread handlerThread = new HandlerThread("ConnectivityServiceThread");
109 handlerThread.start();
110 mHandler = new MdstHandler(handlerThread.getLooper(), this);
112 IntentFilter filter = new IntentFilter();
113 filter.addAction(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
114 filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED);
115 filter.addAction(DataConnectionTracker.ACTION_DATA_CONNECTION_TRACKER_MESSENGER);
117 mContext.registerReceiver(new MobileDataStateReceiver(), filter);
118 mMobileDataState = Phone.DataState.DISCONNECTED;
121 static class MdstHandler extends Handler {
122 private MobileDataStateTracker mMdst;
124 MdstHandler(Looper looper, MobileDataStateTracker mdst) {
130 public void handleMessage(Message msg) {
132 case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
133 if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
135 mMdst.log("MdstHandler connected");
137 mMdst.mDataConnectionTrackerAc = (AsyncChannel) msg.obj;
140 mMdst.log("MdstHandler %s NOT connected error=" + msg.arg1);
144 case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
145 mMdst.log("Disconnected from DataStateTracker");
146 mMdst.mDataConnectionTrackerAc = null;
149 mMdst.log("Ignorning unknown message=" + msg);
156 public boolean isPrivateDnsRouteSet() {
157 return mPrivateDnsRouteSet;
160 public void privateDnsRouteSet(boolean enabled) {
161 mPrivateDnsRouteSet = enabled;
164 public NetworkInfo getNetworkInfo() {
168 public boolean isDefaultRouteSet() {
169 return mDefaultRouteSet;
172 public void defaultRouteSet(boolean enabled) {
173 mDefaultRouteSet = enabled;
177 * This is not implemented.
179 public void releaseWakeLock() {
182 private class MobileDataStateReceiver extends BroadcastReceiver {
183 IConnectivityManager mConnectivityManager;
186 public void onReceive(Context context, Intent intent) {
187 if (intent.getAction().equals(TelephonyIntents.
188 ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) {
189 String apnType = intent.getStringExtra(Phone.DATA_APN_TYPE_KEY);
191 log(String.format("Broadcast received: ACTION_ANY_DATA_CONNECTION_STATE_CHANGED"
192 + "mApnType=%s %s received apnType=%s", mApnType,
193 TextUtils.equals(apnType, mApnType) ? "==" : "!=", apnType));
195 if (!TextUtils.equals(apnType, mApnType)) {
198 mNetworkInfo.setSubtype(TelephonyManager.getDefault().getNetworkType(),
199 TelephonyManager.getDefault().getNetworkTypeName());
200 Phone.DataState state = Enum.valueOf(Phone.DataState.class,
201 intent.getStringExtra(Phone.STATE_KEY));
202 String reason = intent.getStringExtra(Phone.STATE_CHANGE_REASON_KEY);
203 String apnName = intent.getStringExtra(Phone.DATA_APN_KEY);
205 mNetworkInfo.setIsAvailable(!intent.getBooleanExtra(Phone.NETWORK_UNAVAILABLE_KEY,
209 log("Received state=" + state + ", old=" + mMobileDataState +
210 ", reason=" + (reason == null ? "(unspecified)" : reason));
212 if (mMobileDataState != state) {
213 mMobileDataState = state;
216 if(isTeardownRequested()) {
217 setTeardownRequested(false);
220 setDetailedState(DetailedState.DISCONNECTED, reason, apnName);
221 boolean doReset = true;
222 if (mIsDefaultOrHipri == true) {
223 // both default and hipri must go down before we reset
224 int typeToCheck = (Phone.APN_TYPE_DEFAULT.equals(mApnType) ?
225 ConnectivityManager.TYPE_MOBILE_HIPRI :
226 ConnectivityManager.TYPE_MOBILE);
227 if (mConnectivityManager == null) {
228 IBinder b = ServiceManager.getService(
229 Context.CONNECTIVITY_SERVICE);
230 mConnectivityManager = IConnectivityManager.Stub.asInterface(b);
233 if (mConnectivityManager != null) {
234 NetworkInfo info = mConnectivityManager.getNetworkInfo(
236 if (info.isConnected() == true) {
240 } catch (RemoteException e) {
241 // just go ahead with the reset
242 loge("Exception trying to contact ConnService: " + e);
245 if (doReset && mLinkProperties != null) {
246 String iface = mLinkProperties.getInterfaceName();
247 if (iface != null) NetworkUtils.resetConnections(iface);
250 // can't do this here - ConnectivityService needs it to clear stuff
251 // it's ok though - just leave it to be refreshed next time
253 //if (DBG) log("clearing mInterfaceName for "+ mApnType +
254 // " as it DISCONNECTED");
255 //mInterfaceName = null;
258 setDetailedState(DetailedState.CONNECTING, reason, apnName);
261 setDetailedState(DetailedState.SUSPENDED, reason, apnName);
264 mLinkProperties = intent.getParcelableExtra(
265 Phone.DATA_LINK_PROPERTIES_KEY);
266 if (mLinkProperties == null) {
267 log("CONNECTED event did not supply link properties.");
268 mLinkProperties = new LinkProperties();
270 mLinkCapabilities = intent.getParcelableExtra(
271 Phone.DATA_LINK_CAPABILITIES_KEY);
272 if (mLinkCapabilities == null) {
273 log("CONNECTED event did not supply link capabilities.");
274 mLinkCapabilities = new LinkCapabilities();
276 setDetailedState(DetailedState.CONNECTED, reason, apnName);
280 // There was no state change. Check if LinkProperties has been updated.
281 if (TextUtils.equals(reason, Phone.REASON_LINK_PROPERTIES_CHANGED)) {
282 mLinkProperties = intent.getParcelableExtra(Phone.DATA_LINK_PROPERTIES_KEY);
283 if (mLinkProperties == null) {
284 log("No link property in LINK_PROPERTIES change event.");
285 mLinkProperties = new LinkProperties();
287 // Just update reason field in this NetworkInfo
288 mNetworkInfo.setDetailedState(mNetworkInfo.getDetailedState(), reason,
289 mNetworkInfo.getExtraInfo());
290 Message msg = mTarget.obtainMessage(EVENT_CONFIGURATION_CHANGED,
295 } else if (intent.getAction().
296 equals(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED)) {
297 String apnType = intent.getStringExtra(Phone.DATA_APN_TYPE_KEY);
298 if (!TextUtils.equals(apnType, mApnType)) {
301 "Broadcast received: ACTION_ANY_DATA_CONNECTION_FAILED ignore, " +
302 "mApnType=%s != received apnType=%s", mApnType, apnType));
306 String reason = intent.getStringExtra(Phone.FAILURE_REASON_KEY);
307 String apnName = intent.getStringExtra(Phone.DATA_APN_KEY);
309 log("Received " + intent.getAction() +
310 " broadcast" + reason == null ? "" : "(" + reason + ")");
312 setDetailedState(DetailedState.FAILED, reason, apnName);
313 } else if (intent.getAction().
314 equals(DataConnectionTracker.ACTION_DATA_CONNECTION_TRACKER_MESSENGER)) {
315 if (DBG) log(mApnType + " got ACTION_DATA_CONNECTION_TRACKER_MESSENGER");
316 mMessenger = intent.getParcelableExtra(DataConnectionTracker.EXTRA_MESSENGER);
317 AsyncChannel ac = new AsyncChannel();
318 ac.connect(mContext, MobileDataStateTracker.this.mHandler, mMessenger);
320 if (DBG) log("Broadcast received: ignore " + intent.getAction());
325 private void getPhoneService(boolean forceRefresh) {
326 if ((mPhoneService == null) || forceRefresh) {
327 mPhoneService = ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
332 * Report whether data connectivity is possible.
334 public boolean isAvailable() {
335 return mNetworkInfo.isAvailable();
339 * Return the system properties name associated with the tcp buffer sizes
342 public String getTcpBufferSizesPropName() {
343 String networkTypeStr = "unknown";
344 TelephonyManager tm = new TelephonyManager(mContext);
345 //TODO We have to edit the parameter for getNetworkType regarding CDMA
346 switch(tm.getNetworkType()) {
347 case TelephonyManager.NETWORK_TYPE_GPRS:
348 networkTypeStr = "gprs";
350 case TelephonyManager.NETWORK_TYPE_EDGE:
351 networkTypeStr = "edge";
353 case TelephonyManager.NETWORK_TYPE_UMTS:
354 networkTypeStr = "umts";
356 case TelephonyManager.NETWORK_TYPE_HSDPA:
357 networkTypeStr = "hsdpa";
359 case TelephonyManager.NETWORK_TYPE_HSUPA:
360 networkTypeStr = "hsupa";
362 case TelephonyManager.NETWORK_TYPE_HSPA:
363 networkTypeStr = "hspa";
365 case TelephonyManager.NETWORK_TYPE_CDMA:
366 networkTypeStr = "cdma";
368 case TelephonyManager.NETWORK_TYPE_1xRTT:
369 networkTypeStr = "1xrtt";
371 case TelephonyManager.NETWORK_TYPE_EVDO_0:
372 networkTypeStr = "evdo";
374 case TelephonyManager.NETWORK_TYPE_EVDO_A:
375 networkTypeStr = "evdo";
377 case TelephonyManager.NETWORK_TYPE_EVDO_B:
378 networkTypeStr = "evdo";
380 case TelephonyManager.NETWORK_TYPE_IDEN:
381 networkTypeStr = "iden";
383 case TelephonyManager.NETWORK_TYPE_LTE:
384 networkTypeStr = "lte";
386 case TelephonyManager.NETWORK_TYPE_EHRPD:
387 networkTypeStr = "ehrpd";
390 loge("unknown network type: " + tm.getNetworkType());
392 return "net.tcp.buffersize." + networkTypeStr;
396 * Tear down mobile data connectivity, i.e., disable the ability to create
397 * mobile data connections.
398 * TODO - make async and return nothing?
400 public boolean teardown() {
401 setTeardownRequested(true);
402 return (setEnableApn(mApnType, false) != Phone.APN_REQUEST_FAILED);
406 * Record the detailed state of a network, and if it is a
407 * change from the previous state, send a notification to
409 * @param state the new @{code DetailedState}
410 * @param reason a {@code String} indicating a reason for the state change,
411 * if one was supplied. May be {@code null}.
412 * @param extraInfo optional {@code String} providing extra information about the state change
414 private void setDetailedState(NetworkInfo.DetailedState state, String reason,
416 if (DBG) log("setDetailed state, old ="
417 + mNetworkInfo.getDetailedState() + " and new state=" + state);
418 if (state != mNetworkInfo.getDetailedState()) {
419 boolean wasConnecting = (mNetworkInfo.getState() == NetworkInfo.State.CONNECTING);
420 String lastReason = mNetworkInfo.getReason();
422 * If a reason was supplied when the CONNECTING state was entered, and no
423 * reason was supplied for entering the CONNECTED state, then retain the
424 * reason that was supplied when going to CONNECTING.
426 if (wasConnecting && state == NetworkInfo.DetailedState.CONNECTED && reason == null
427 && lastReason != null)
429 mNetworkInfo.setDetailedState(state, reason, extraInfo);
430 Message msg = mTarget.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo);
435 public void setTeardownRequested(boolean isRequested) {
436 mTeardownRequested = isRequested;
439 public boolean isTeardownRequested() {
440 return mTeardownRequested;
444 * Re-enable mobile data connectivity after a {@link #teardown()}.
445 * TODO - make async and always get a notification?
447 public boolean reconnect() {
448 boolean retValue = false; //connected or expect to be?
449 setTeardownRequested(false);
450 switch (setEnableApn(mApnType, true)) {
451 case Phone.APN_ALREADY_ACTIVE:
452 // need to set self to CONNECTING so the below message is handled.
455 case Phone.APN_REQUEST_STARTED:
456 // set IDLE here , avoid the following second FAILED not sent out
457 mNetworkInfo.setDetailedState(DetailedState.IDLE, null, null);
460 case Phone.APN_REQUEST_FAILED:
461 case Phone.APN_TYPE_NOT_AVAILABLE:
464 loge("Error in reconnect - unexpected response.");
471 * Turn on or off the mobile radio. No connectivity will be possible while the
472 * radio is off. The operation is a no-op if the radio is already in the desired state.
473 * @param turnOn {@code true} if the radio should be turned on, {@code false} if
475 public boolean setRadio(boolean turnOn) {
476 getPhoneService(false);
478 * If the phone process has crashed in the past, we'll get a
479 * RemoteException and need to re-reference the service.
481 for (int retry = 0; retry < 2; retry++) {
482 if (mPhoneService == null) {
483 log("Ignoring mobile radio request because could not acquire PhoneService");
488 return mPhoneService.setRadio(turnOn);
489 } catch (RemoteException e) {
490 if (retry == 0) getPhoneService(true);
494 log("Could not set radio power to " + (turnOn ? "on" : "off"));
501 public void setDataEnable(boolean enabled) {
503 log("setDataEnable: E enabled=" + enabled);
504 mDataConnectionTrackerAc.sendMessage(DataConnectionTracker.CMD_SET_DATA_ENABLE,
505 enabled ? DataConnectionTracker.ENABLED : DataConnectionTracker.DISABLED);
506 log("setDataEnable: X enabled=" + enabled);
507 } catch (Exception e) {
508 log("setDataEnable: X mAc was null" + e);
513 * carrier dependency is met/unmet
516 public void setDependencyMet(boolean met) {
517 Bundle bundle = Bundle.forPair(DataConnectionTracker.APN_TYPE_KEY, mApnType);
519 log("setDependencyMet: E met=" + met);
520 Message msg = Message.obtain();
521 msg.what = DataConnectionTracker.CMD_SET_DEPENDENCY_MET;
522 msg.arg1 = (met ? DataConnectionTracker.ENABLED : DataConnectionTracker.DISABLED);
524 mDataConnectionTrackerAc.sendMessage(msg);
525 log("setDependencyMet: X met=" + met);
526 } catch (NullPointerException e) {
527 log("setDependencyMet: X mAc was null" + e);
532 public String toString() {
533 StringBuffer sb = new StringBuffer("Mobile data state: ");
535 sb.append(mMobileDataState);
536 return sb.toString();
540 * Internal method supporting the ENABLE_MMS feature.
541 * @param apnType the type of APN to be enabled or disabled (e.g., mms)
542 * @param enable {@code true} to enable the specified APN type,
543 * {@code false} to disable it.
544 * @return an integer value representing the outcome of the request.
546 private int setEnableApn(String apnType, boolean enable) {
547 getPhoneService(false);
549 * If the phone process has crashed in the past, we'll get a
550 * RemoteException and need to re-reference the service.
552 for (int retry = 0; retry < 2; retry++) {
553 if (mPhoneService == null) {
554 log("Ignoring feature request because could not acquire PhoneService");
560 return mPhoneService.enableApnType(apnType);
562 return mPhoneService.disableApnType(apnType);
564 } catch (RemoteException e) {
565 if (retry == 0) getPhoneService(true);
569 log("Could not " + (enable ? "enable" : "disable") + " APN type \"" + apnType + "\"");
570 return Phone.APN_REQUEST_FAILED;
573 public static String networkTypeToApnType(int netType) {
575 case ConnectivityManager.TYPE_MOBILE:
576 return Phone.APN_TYPE_DEFAULT; // TODO - use just one of these
577 case ConnectivityManager.TYPE_MOBILE_MMS:
578 return Phone.APN_TYPE_MMS;
579 case ConnectivityManager.TYPE_MOBILE_SUPL:
580 return Phone.APN_TYPE_SUPL;
581 case ConnectivityManager.TYPE_MOBILE_DUN:
582 return Phone.APN_TYPE_DUN;
583 case ConnectivityManager.TYPE_MOBILE_HIPRI:
584 return Phone.APN_TYPE_HIPRI;
585 case ConnectivityManager.TYPE_MOBILE_FOTA:
586 return Phone.APN_TYPE_FOTA;
587 case ConnectivityManager.TYPE_MOBILE_IMS:
588 return Phone.APN_TYPE_IMS;
589 case ConnectivityManager.TYPE_MOBILE_CBS:
590 return Phone.APN_TYPE_CBS;
592 sloge("Error mapping networkType " + netType + " to apnType.");
598 * @see android.net.NetworkStateTracker#getLinkProperties()
600 public LinkProperties getLinkProperties() {
601 return new LinkProperties(mLinkProperties);
605 * @see android.net.NetworkStateTracker#getLinkCapabilities()
607 public LinkCapabilities getLinkCapabilities() {
608 return new LinkCapabilities(mLinkCapabilities);
611 private void log(String s) {
612 Slog.d(TAG, mApnType + ": " + s);
615 private void loge(String s) {
616 Slog.e(TAG, mApnType + ": " + s);
619 static private void sloge(String s) {