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 private Handler mHandler;
73 private AsyncChannel mDataConnectionTrackerAc;
74 private Messenger mMessenger;
77 * Create a new MobileDataStateTracker
78 * @param netType the ConnectivityManager network type
79 * @param tag the name of this network
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);
89 * Begin monitoring data connectivity.
91 * @param context is the current Android context
92 * @param target is the Hander to which to return the events.
94 public void startMonitoring(Context context, Handler target) {
98 HandlerThread handlerThread = new HandlerThread("ConnectivityServiceThread");
99 handlerThread.start();
100 mHandler = new MdstHandler(handlerThread.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);
105 filter.addAction(DataConnectionTracker.ACTION_DATA_CONNECTION_TRACKER_MESSENGER);
107 mContext.registerReceiver(new MobileDataStateReceiver(), filter);
108 mMobileDataState = Phone.DataState.DISCONNECTED;
111 static class MdstHandler extends Handler {
112 private MobileDataStateTracker mMdst;
114 MdstHandler(Looper looper, MobileDataStateTracker mdst) {
120 public void handleMessage(Message msg) {
122 case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
123 if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
125 mMdst.log("MdstHandler connected");
127 mMdst.mDataConnectionTrackerAc = (AsyncChannel) msg.obj;
130 mMdst.log("MdstHandler %s NOT connected error=" + msg.arg1);
134 case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
135 mMdst.log("Disconnected from DataStateTracker");
136 mMdst.mDataConnectionTrackerAc = null;
139 mMdst.log("Ignorning unknown message=" + msg);
146 public boolean isPrivateDnsRouteSet() {
147 return mPrivateDnsRouteSet;
150 public void privateDnsRouteSet(boolean enabled) {
151 mPrivateDnsRouteSet = enabled;
154 public NetworkInfo getNetworkInfo() {
158 public boolean isDefaultRouteSet() {
159 return mDefaultRouteSet;
162 public void defaultRouteSet(boolean enabled) {
163 mDefaultRouteSet = enabled;
167 * This is not implemented.
169 public void releaseWakeLock() {
172 private class MobileDataStateReceiver extends BroadcastReceiver {
174 public void onReceive(Context context, Intent intent) {
175 if (intent.getAction().equals(TelephonyIntents.
176 ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) {
177 String apnType = intent.getStringExtra(Phone.DATA_APN_TYPE_KEY);
179 log(String.format("Broadcast received: ACTION_ANY_DATA_CONNECTION_STATE_CHANGED"
180 + "mApnType=%s %s received apnType=%s", mApnType,
181 TextUtils.equals(apnType, mApnType) ? "==" : "!=", apnType));
183 if (!TextUtils.equals(apnType, mApnType)) {
186 mNetworkInfo.setSubtype(TelephonyManager.getDefault().getNetworkType(),
187 TelephonyManager.getDefault().getNetworkTypeName());
188 Phone.DataState state = Enum.valueOf(Phone.DataState.class,
189 intent.getStringExtra(Phone.STATE_KEY));
190 String reason = intent.getStringExtra(Phone.STATE_CHANGE_REASON_KEY);
191 String apnName = intent.getStringExtra(Phone.DATA_APN_KEY);
193 mNetworkInfo.setIsAvailable(!intent.getBooleanExtra(Phone.NETWORK_UNAVAILABLE_KEY,
197 log("Received state=" + state + ", old=" + mMobileDataState +
198 ", reason=" + (reason == null ? "(unspecified)" : reason));
200 if (mMobileDataState != state) {
201 mMobileDataState = state;
204 if(isTeardownRequested()) {
205 setTeardownRequested(false);
208 setDetailedState(DetailedState.DISCONNECTED, reason, apnName);
209 // can't do this here - ConnectivityService needs it to clear stuff
210 // it's ok though - just leave it to be refreshed next time
212 //if (DBG) log("clearing mInterfaceName for "+ mApnType +
213 // " as it DISCONNECTED");
214 //mInterfaceName = null;
217 setDetailedState(DetailedState.CONNECTING, reason, apnName);
220 setDetailedState(DetailedState.SUSPENDED, reason, apnName);
223 mLinkProperties = intent.getParcelableExtra(
224 Phone.DATA_LINK_PROPERTIES_KEY);
225 if (mLinkProperties == null) {
226 log("CONNECTED event did not supply link properties.");
227 mLinkProperties = new LinkProperties();
229 mLinkCapabilities = intent.getParcelableExtra(
230 Phone.DATA_LINK_CAPABILITIES_KEY);
231 if (mLinkCapabilities == null) {
232 log("CONNECTED event did not supply link capabilities.");
233 mLinkCapabilities = new LinkCapabilities();
235 setDetailedState(DetailedState.CONNECTED, reason, apnName);
239 // There was no state change. Check if LinkProperties has been updated.
240 if (TextUtils.equals(reason, Phone.REASON_LINK_PROPERTIES_CHANGED)) {
241 mLinkProperties = intent.getParcelableExtra(Phone.DATA_LINK_PROPERTIES_KEY);
242 if (mLinkProperties == null) {
243 log("No link property in LINK_PROPERTIES change event.");
244 mLinkProperties = new LinkProperties();
246 // Just update reason field in this NetworkInfo
247 mNetworkInfo.setDetailedState(mNetworkInfo.getDetailedState(), reason,
248 mNetworkInfo.getExtraInfo());
249 Message msg = mTarget.obtainMessage(EVENT_CONFIGURATION_CHANGED,
254 } else if (intent.getAction().
255 equals(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED)) {
256 String apnType = intent.getStringExtra(Phone.DATA_APN_TYPE_KEY);
257 if (!TextUtils.equals(apnType, mApnType)) {
260 "Broadcast received: ACTION_ANY_DATA_CONNECTION_FAILED ignore, " +
261 "mApnType=%s != received apnType=%s", mApnType, apnType));
265 String reason = intent.getStringExtra(Phone.FAILURE_REASON_KEY);
266 String apnName = intent.getStringExtra(Phone.DATA_APN_KEY);
268 log("Received " + intent.getAction() +
269 " broadcast" + reason == null ? "" : "(" + reason + ")");
271 setDetailedState(DetailedState.FAILED, reason, apnName);
272 } else if (intent.getAction().
273 equals(DataConnectionTracker.ACTION_DATA_CONNECTION_TRACKER_MESSENGER)) {
274 if (DBG) log(mApnType + " got ACTION_DATA_CONNECTION_TRACKER_MESSENGER");
275 mMessenger = intent.getParcelableExtra(DataConnectionTracker.EXTRA_MESSENGER);
276 AsyncChannel ac = new AsyncChannel();
277 ac.connect(mContext, MobileDataStateTracker.this.mHandler, mMessenger);
279 if (DBG) log("Broadcast received: ignore " + intent.getAction());
284 private void getPhoneService(boolean forceRefresh) {
285 if ((mPhoneService == null) || forceRefresh) {
286 mPhoneService = ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
291 * Report whether data connectivity is possible.
293 public boolean isAvailable() {
294 return mNetworkInfo.isAvailable();
298 * Return the system properties name associated with the tcp buffer sizes
301 public String getTcpBufferSizesPropName() {
302 String networkTypeStr = "unknown";
303 TelephonyManager tm = new TelephonyManager(mContext);
304 //TODO We have to edit the parameter for getNetworkType regarding CDMA
305 switch(tm.getNetworkType()) {
306 case TelephonyManager.NETWORK_TYPE_GPRS:
307 networkTypeStr = "gprs";
309 case TelephonyManager.NETWORK_TYPE_EDGE:
310 networkTypeStr = "edge";
312 case TelephonyManager.NETWORK_TYPE_UMTS:
313 networkTypeStr = "umts";
315 case TelephonyManager.NETWORK_TYPE_HSDPA:
316 networkTypeStr = "hsdpa";
318 case TelephonyManager.NETWORK_TYPE_HSUPA:
319 networkTypeStr = "hsupa";
321 case TelephonyManager.NETWORK_TYPE_HSPA:
322 networkTypeStr = "hspa";
324 case TelephonyManager.NETWORK_TYPE_CDMA:
325 networkTypeStr = "cdma";
327 case TelephonyManager.NETWORK_TYPE_1xRTT:
328 networkTypeStr = "1xrtt";
330 case TelephonyManager.NETWORK_TYPE_EVDO_0:
331 networkTypeStr = "evdo";
333 case TelephonyManager.NETWORK_TYPE_EVDO_A:
334 networkTypeStr = "evdo";
336 case TelephonyManager.NETWORK_TYPE_EVDO_B:
337 networkTypeStr = "evdo";
339 case TelephonyManager.NETWORK_TYPE_IDEN:
340 networkTypeStr = "iden";
342 case TelephonyManager.NETWORK_TYPE_LTE:
343 networkTypeStr = "lte";
345 case TelephonyManager.NETWORK_TYPE_EHRPD:
346 networkTypeStr = "ehrpd";
349 loge("unknown network type: " + tm.getNetworkType());
351 return "net.tcp.buffersize." + networkTypeStr;
355 * Tear down mobile data connectivity, i.e., disable the ability to create
356 * mobile data connections.
357 * TODO - make async and return nothing?
359 public boolean teardown() {
360 setTeardownRequested(true);
361 return (setEnableApn(mApnType, false) != Phone.APN_REQUEST_FAILED);
365 * Record the detailed state of a network, and if it is a
366 * change from the previous state, send a notification to
368 * @param state the new @{code DetailedState}
369 * @param reason a {@code String} indicating a reason for the state change,
370 * if one was supplied. May be {@code null}.
371 * @param extraInfo optional {@code String} providing extra information about the state change
373 private void setDetailedState(NetworkInfo.DetailedState state, String reason,
375 if (DBG) log("setDetailed state, old ="
376 + mNetworkInfo.getDetailedState() + " and new state=" + state);
377 if (state != mNetworkInfo.getDetailedState()) {
378 boolean wasConnecting = (mNetworkInfo.getState() == NetworkInfo.State.CONNECTING);
379 String lastReason = mNetworkInfo.getReason();
381 * If a reason was supplied when the CONNECTING state was entered, and no
382 * reason was supplied for entering the CONNECTED state, then retain the
383 * reason that was supplied when going to CONNECTING.
385 if (wasConnecting && state == NetworkInfo.DetailedState.CONNECTED && reason == null
386 && lastReason != null)
388 mNetworkInfo.setDetailedState(state, reason, extraInfo);
389 Message msg = mTarget.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo);
394 public void setTeardownRequested(boolean isRequested) {
395 mTeardownRequested = isRequested;
398 public boolean isTeardownRequested() {
399 return mTeardownRequested;
403 * Re-enable mobile data connectivity after a {@link #teardown()}.
404 * TODO - make async and always get a notification?
406 public boolean reconnect() {
407 boolean retValue = false; //connected or expect to be?
408 setTeardownRequested(false);
409 switch (setEnableApn(mApnType, true)) {
410 case Phone.APN_ALREADY_ACTIVE:
411 // need to set self to CONNECTING so the below message is handled.
414 case Phone.APN_REQUEST_STARTED:
415 // set IDLE here , avoid the following second FAILED not sent out
416 mNetworkInfo.setDetailedState(DetailedState.IDLE, null, null);
419 case Phone.APN_REQUEST_FAILED:
420 case Phone.APN_TYPE_NOT_AVAILABLE:
423 loge("Error in reconnect - unexpected response.");
430 * Turn on or off the mobile radio. No connectivity will be possible while the
431 * radio is off. The operation is a no-op if the radio is already in the desired state.
432 * @param turnOn {@code true} if the radio should be turned on, {@code false} if
434 public boolean setRadio(boolean turnOn) {
435 getPhoneService(false);
437 * If the phone process has crashed in the past, we'll get a
438 * RemoteException and need to re-reference the service.
440 for (int retry = 0; retry < 2; retry++) {
441 if (mPhoneService == null) {
442 log("Ignoring mobile radio request because could not acquire PhoneService");
447 return mPhoneService.setRadio(turnOn);
448 } catch (RemoteException e) {
449 if (retry == 0) getPhoneService(true);
453 log("Could not set radio power to " + (turnOn ? "on" : "off"));
460 public void setDataEnable(boolean enabled) {
462 log("setDataEnable: E enabled=" + enabled);
463 mDataConnectionTrackerAc.sendMessage(DataConnectionTracker.CMD_SET_DATA_ENABLE,
464 enabled ? DataConnectionTracker.ENABLED : DataConnectionTracker.DISABLED);
465 log("setDataEnable: X enabled=" + enabled);
466 } catch (Exception e) {
467 log("setDataEnable: X mAc was null" + e);
472 * carrier dependency is met/unmet
475 public void setDependencyMet(boolean met) {
476 Bundle bundle = Bundle.forPair(DataConnectionTracker.APN_TYPE_KEY, mApnType);
478 log("setDependencyMet: E met=" + met);
479 Message msg = Message.obtain();
480 msg.what = DataConnectionTracker.CMD_SET_DEPENDENCY_MET;
481 msg.arg1 = (met ? DataConnectionTracker.ENABLED : DataConnectionTracker.DISABLED);
483 mDataConnectionTrackerAc.sendMessage(msg);
484 log("setDependencyMet: X met=" + met);
485 } catch (NullPointerException e) {
486 log("setDependencyMet: X mAc was null" + e);
491 public String toString() {
492 StringBuffer sb = new StringBuffer("Mobile data state: ");
494 sb.append(mMobileDataState);
495 return sb.toString();
499 * Internal method supporting the ENABLE_MMS feature.
500 * @param apnType the type of APN to be enabled or disabled (e.g., mms)
501 * @param enable {@code true} to enable the specified APN type,
502 * {@code false} to disable it.
503 * @return an integer value representing the outcome of the request.
505 private int setEnableApn(String apnType, boolean enable) {
506 getPhoneService(false);
508 * If the phone process has crashed in the past, we'll get a
509 * RemoteException and need to re-reference the service.
511 for (int retry = 0; retry < 2; retry++) {
512 if (mPhoneService == null) {
513 log("Ignoring feature request because could not acquire PhoneService");
519 return mPhoneService.enableApnType(apnType);
521 return mPhoneService.disableApnType(apnType);
523 } catch (RemoteException e) {
524 if (retry == 0) getPhoneService(true);
528 log("Could not " + (enable ? "enable" : "disable") + " APN type \"" + apnType + "\"");
529 return Phone.APN_REQUEST_FAILED;
532 public static String networkTypeToApnType(int netType) {
534 case ConnectivityManager.TYPE_MOBILE:
535 return Phone.APN_TYPE_DEFAULT; // TODO - use just one of these
536 case ConnectivityManager.TYPE_MOBILE_MMS:
537 return Phone.APN_TYPE_MMS;
538 case ConnectivityManager.TYPE_MOBILE_SUPL:
539 return Phone.APN_TYPE_SUPL;
540 case ConnectivityManager.TYPE_MOBILE_DUN:
541 return Phone.APN_TYPE_DUN;
542 case ConnectivityManager.TYPE_MOBILE_HIPRI:
543 return Phone.APN_TYPE_HIPRI;
544 case ConnectivityManager.TYPE_MOBILE_FOTA:
545 return Phone.APN_TYPE_FOTA;
546 case ConnectivityManager.TYPE_MOBILE_IMS:
547 return Phone.APN_TYPE_IMS;
548 case ConnectivityManager.TYPE_MOBILE_CBS:
549 return Phone.APN_TYPE_CBS;
551 sloge("Error mapping networkType " + netType + " to apnType.");
557 * @see android.net.NetworkStateTracker#getLinkProperties()
559 public LinkProperties getLinkProperties() {
560 return new LinkProperties(mLinkProperties);
564 * @see android.net.NetworkStateTracker#getLinkCapabilities()
566 public LinkCapabilities getLinkCapabilities() {
567 return new LinkCapabilities(mLinkCapabilities);
570 private void log(String s) {
571 Slog.d(TAG, mApnType + ": " + s);
574 private void loge(String s) {
575 Slog.e(TAG, mApnType + ": " + s);
578 static private void sloge(String s) {