blob: adad8decca8979e482815f2e5f385c676ff546d4 [file] [log] [blame]
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.NetworkInfo.DetailedState;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Slog;
import com.android.internal.telephony.DctConstants;
import com.android.internal.telephony.ITelephony;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.util.AsyncChannel;
import java.io.CharArrayWriter;
import java.io.PrintWriter;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Track the state of mobile data connectivity. This is done by
* receiving broadcast intents from the Phone process whenever
* the state of data connectivity changes.
*
* {@hide}
*/
public class MobileDataStateTracker implements NetworkStateTracker {
private static final String TAG = "MobileDataStateTracker";
private static final boolean DBG = false;
private static final boolean VDBG = false;
private PhoneConstants.DataState mMobileDataState;
private ITelephony mPhoneService;
private String mApnType;
private NetworkInfo mNetworkInfo;
private boolean mTeardownRequested = false;
private Handler mTarget;
private Context mContext;
private LinkProperties mLinkProperties;
private LinkCapabilities mLinkCapabilities;
private boolean mPrivateDnsRouteSet = false;
private boolean mDefaultRouteSet = false;
// NOTE: these are only kept for debugging output; actual values are
// maintained in DataConnectionTracker.
protected boolean mUserDataEnabled = true;
protected boolean mPolicyDataEnabled = true;
private Handler mHandler;
private AsyncChannel mDataConnectionTrackerAc;
private AtomicBoolean mIsCaptivePortal = new AtomicBoolean(false);
/**
* Create a new MobileDataStateTracker
* @param netType the ConnectivityManager network type
* @param tag the name of this network
*/
public MobileDataStateTracker(int netType, String tag) {
mNetworkInfo = new NetworkInfo(netType,
TelephonyManager.getDefault().getNetworkType(), tag,
TelephonyManager.getDefault().getNetworkTypeName());
mApnType = networkTypeToApnType(netType);
}
/**
* Begin monitoring data connectivity.
*
* @param context is the current Android context
* @param target is the Hander to which to return the events.
*/
public void startMonitoring(Context context, Handler target) {
mTarget = target;
mContext = context;
mHandler = new MdstHandler(target.getLooper(), this);
IntentFilter filter = new IntentFilter();
filter.addAction(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED);
mContext.registerReceiver(new MobileDataStateReceiver(), filter);
mMobileDataState = PhoneConstants.DataState.DISCONNECTED;
}
static class MdstHandler extends Handler {
private MobileDataStateTracker mMdst;
MdstHandler(Looper looper, MobileDataStateTracker mdst) {
super(looper);
mMdst = mdst;
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
if (VDBG) {
mMdst.log("MdstHandler connected");
}
mMdst.mDataConnectionTrackerAc = (AsyncChannel) msg.obj;
} else {
if (VDBG) {
mMdst.log("MdstHandler %s NOT connected error=" + msg.arg1);
}
}
break;
case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
if (VDBG) mMdst.log("Disconnected from DataStateTracker");
mMdst.mDataConnectionTrackerAc = null;
break;
default: {
if (VDBG) mMdst.log("Ignorning unknown message=" + msg);
break;
}
}
}
}
public boolean isPrivateDnsRouteSet() {
return mPrivateDnsRouteSet;
}
public void privateDnsRouteSet(boolean enabled) {
mPrivateDnsRouteSet = enabled;
}
public NetworkInfo getNetworkInfo() {
return mNetworkInfo;
}
public boolean isDefaultRouteSet() {
return mDefaultRouteSet;
}
public void defaultRouteSet(boolean enabled) {
mDefaultRouteSet = enabled;
}
/**
* This is not implemented.
*/
public void releaseWakeLock() {
}
private class MobileDataStateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(TelephonyIntents.
ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) {
String apnType = intent.getStringExtra(PhoneConstants.DATA_APN_TYPE_KEY);
if (VDBG) {
log(String.format("Broadcast received: ACTION_ANY_DATA_CONNECTION_STATE_CHANGED"
+ "mApnType=%s %s received apnType=%s", mApnType,
TextUtils.equals(apnType, mApnType) ? "==" : "!=", apnType));
}
if (!TextUtils.equals(apnType, mApnType)) {
return;
}
int oldSubtype = mNetworkInfo.getSubtype();
int newSubType = TelephonyManager.getDefault().getNetworkType();
String subTypeName = TelephonyManager.getDefault().getNetworkTypeName();
mNetworkInfo.setSubtype(newSubType, subTypeName);
if (newSubType != oldSubtype && mNetworkInfo.isConnected()) {
Message msg = mTarget.obtainMessage(EVENT_NETWORK_SUBTYPE_CHANGED,
oldSubtype, 0, mNetworkInfo);
msg.sendToTarget();
}
PhoneConstants.DataState state = Enum.valueOf(PhoneConstants.DataState.class,
intent.getStringExtra(PhoneConstants.STATE_KEY));
String reason = intent.getStringExtra(PhoneConstants.STATE_CHANGE_REASON_KEY);
String apnName = intent.getStringExtra(PhoneConstants.DATA_APN_KEY);
mNetworkInfo.setRoaming(intent.getBooleanExtra(
PhoneConstants.DATA_NETWORK_ROAMING_KEY, false));
if (VDBG) {
log(mApnType + " setting isAvailable to " +
intent.getBooleanExtra(PhoneConstants.NETWORK_UNAVAILABLE_KEY,false));
}
mNetworkInfo.setIsAvailable(!intent.getBooleanExtra(
PhoneConstants.NETWORK_UNAVAILABLE_KEY, false));
if (DBG) {
log("Received state=" + state + ", old=" + mMobileDataState +
", reason=" + (reason == null ? "(unspecified)" : reason));
}
if (mMobileDataState != state) {
mMobileDataState = state;
switch (state) {
case DISCONNECTED:
if(isTeardownRequested()) {
setTeardownRequested(false);
}
setDetailedState(DetailedState.DISCONNECTED, reason, apnName);
// can't do this here - ConnectivityService needs it to clear stuff
// it's ok though - just leave it to be refreshed next time
// we connect.
//if (DBG) log("clearing mInterfaceName for "+ mApnType +
// " as it DISCONNECTED");
//mInterfaceName = null;
break;
case CONNECTING:
setDetailedState(DetailedState.CONNECTING, reason, apnName);
break;
case SUSPENDED:
setDetailedState(DetailedState.SUSPENDED, reason, apnName);
break;
case CONNECTED:
mLinkProperties = intent.getParcelableExtra(
PhoneConstants.DATA_LINK_PROPERTIES_KEY);
if (mLinkProperties == null) {
loge("CONNECTED event did not supply link properties.");
mLinkProperties = new LinkProperties();
}
mLinkCapabilities = intent.getParcelableExtra(
PhoneConstants.DATA_LINK_CAPABILITIES_KEY);
if (mLinkCapabilities == null) {
loge("CONNECTED event did not supply link capabilities.");
mLinkCapabilities = new LinkCapabilities();
}
setDetailedState(DetailedState.CONNECTED, reason, apnName);
break;
}
} else {
// There was no state change. Check if LinkProperties has been updated.
if (TextUtils.equals(reason, PhoneConstants.REASON_LINK_PROPERTIES_CHANGED)) {
mLinkProperties = intent.getParcelableExtra(
PhoneConstants.DATA_LINK_PROPERTIES_KEY);
if (mLinkProperties == null) {
loge("No link property in LINK_PROPERTIES change event.");
mLinkProperties = new LinkProperties();
}
// Just update reason field in this NetworkInfo
mNetworkInfo.setDetailedState(mNetworkInfo.getDetailedState(), reason,
mNetworkInfo.getExtraInfo());
Message msg = mTarget.obtainMessage(EVENT_CONFIGURATION_CHANGED,
mNetworkInfo);
msg.sendToTarget();
}
}
} else if (intent.getAction().
equals(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED)) {
String apnType = intent.getStringExtra(PhoneConstants.DATA_APN_TYPE_KEY);
if (!TextUtils.equals(apnType, mApnType)) {
if (DBG) {
log(String.format(
"Broadcast received: ACTION_ANY_DATA_CONNECTION_FAILED ignore, " +
"mApnType=%s != received apnType=%s", mApnType, apnType));
}
return;
}
String reason = intent.getStringExtra(PhoneConstants.FAILURE_REASON_KEY);
String apnName = intent.getStringExtra(PhoneConstants.DATA_APN_KEY);
if (DBG) {
log("Received " + intent.getAction() +
" broadcast" + reason == null ? "" : "(" + reason + ")");
}
setDetailedState(DetailedState.FAILED, reason, apnName);
} else {
if (DBG) log("Broadcast received: ignore " + intent.getAction());
}
}
}
private void getPhoneService(boolean forceRefresh) {
if ((mPhoneService == null) || forceRefresh) {
mPhoneService = ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
}
}
/**
* Report whether data connectivity is possible.
*/
public boolean isAvailable() {
return mNetworkInfo.isAvailable();
}
/**
* Return the system properties name associated with the tcp buffer sizes
* for this network.
*/
public String getTcpBufferSizesPropName() {
String networkTypeStr = "unknown";
TelephonyManager tm = new TelephonyManager(mContext);
//TODO We have to edit the parameter for getNetworkType regarding CDMA
switch(tm.getNetworkType()) {
case TelephonyManager.NETWORK_TYPE_GPRS:
networkTypeStr = "gprs";
break;
case TelephonyManager.NETWORK_TYPE_EDGE:
networkTypeStr = "edge";
break;
case TelephonyManager.NETWORK_TYPE_UMTS:
networkTypeStr = "umts";
break;
case TelephonyManager.NETWORK_TYPE_HSDPA:
networkTypeStr = "hsdpa";
break;
case TelephonyManager.NETWORK_TYPE_HSUPA:
networkTypeStr = "hsupa";
break;
case TelephonyManager.NETWORK_TYPE_HSPA:
networkTypeStr = "hspa";
break;
case TelephonyManager.NETWORK_TYPE_HSPAP:
networkTypeStr = "hspap";
break;
case TelephonyManager.NETWORK_TYPE_CDMA:
networkTypeStr = "cdma";
break;
case TelephonyManager.NETWORK_TYPE_1xRTT:
networkTypeStr = "1xrtt";
break;
case TelephonyManager.NETWORK_TYPE_EVDO_0:
networkTypeStr = "evdo";
break;
case TelephonyManager.NETWORK_TYPE_EVDO_A:
networkTypeStr = "evdo";
break;
case TelephonyManager.NETWORK_TYPE_EVDO_B:
networkTypeStr = "evdo";
break;
case TelephonyManager.NETWORK_TYPE_IDEN:
networkTypeStr = "iden";
break;
case TelephonyManager.NETWORK_TYPE_LTE:
networkTypeStr = "lte";
break;
case TelephonyManager.NETWORK_TYPE_EHRPD:
networkTypeStr = "ehrpd";
break;
default:
loge("unknown network type: " + tm.getNetworkType());
}
return "net.tcp.buffersize." + networkTypeStr;
}
/**
* Tear down mobile data connectivity, i.e., disable the ability to create
* mobile data connections.
* TODO - make async and return nothing?
*/
public boolean teardown() {
setTeardownRequested(true);
return (setEnableApn(mApnType, false) != PhoneConstants.APN_REQUEST_FAILED);
}
@Override
public void captivePortalCheckComplete() {
// not implemented
}
@Override
public void captivePortalCheckCompleted(boolean isCaptivePortal) {
if (mIsCaptivePortal.getAndSet(isCaptivePortal) != isCaptivePortal) {
// Captive portal change enable/disable failing fast
setEnableFailFastMobileData(
isCaptivePortal ? DctConstants.ENABLED : DctConstants.DISABLED);
}
}
/**
* Record the detailed state of a network, and if it is a
* change from the previous state, send a notification to
* any listeners.
* @param state the new {@code DetailedState}
* @param reason a {@code String} indicating a reason for the state change,
* if one was supplied. May be {@code null}.
* @param extraInfo optional {@code String} providing extra information about the state change
*/
private void setDetailedState(NetworkInfo.DetailedState state, String reason,
String extraInfo) {
if (DBG) log("setDetailed state, old ="
+ mNetworkInfo.getDetailedState() + " and new state=" + state);
if (state != mNetworkInfo.getDetailedState()) {
boolean wasConnecting = (mNetworkInfo.getState() == NetworkInfo.State.CONNECTING);
String lastReason = mNetworkInfo.getReason();
/*
* If a reason was supplied when the CONNECTING state was entered, and no
* reason was supplied for entering the CONNECTED state, then retain the
* reason that was supplied when going to CONNECTING.
*/
if (wasConnecting && state == NetworkInfo.DetailedState.CONNECTED && reason == null
&& lastReason != null)
reason = lastReason;
mNetworkInfo.setDetailedState(state, reason, extraInfo);
Message msg = mTarget.obtainMessage(EVENT_STATE_CHANGED, new NetworkInfo(mNetworkInfo));
msg.sendToTarget();
}
}
public void setTeardownRequested(boolean isRequested) {
mTeardownRequested = isRequested;
}
public boolean isTeardownRequested() {
return mTeardownRequested;
}
/**
* Re-enable mobile data connectivity after a {@link #teardown()}.
* TODO - make async and always get a notification?
*/
public boolean reconnect() {
boolean retValue = false; //connected or expect to be?
setTeardownRequested(false);
switch (setEnableApn(mApnType, true)) {
case PhoneConstants.APN_ALREADY_ACTIVE:
// need to set self to CONNECTING so the below message is handled.
retValue = true;
break;
case PhoneConstants.APN_REQUEST_STARTED:
// set IDLE here , avoid the following second FAILED not sent out
mNetworkInfo.setDetailedState(DetailedState.IDLE, null, null);
retValue = true;
break;
case PhoneConstants.APN_REQUEST_FAILED:
case PhoneConstants.APN_TYPE_NOT_AVAILABLE:
break;
default:
loge("Error in reconnect - unexpected response.");
break;
}
return retValue;
}
/**
* Turn on or off the mobile radio. No connectivity will be possible while the
* radio is off. The operation is a no-op if the radio is already in the desired state.
* @param turnOn {@code true} if the radio should be turned on, {@code false} if
*/
public boolean setRadio(boolean turnOn) {
getPhoneService(false);
/*
* If the phone process has crashed in the past, we'll get a
* RemoteException and need to re-reference the service.
*/
for (int retry = 0; retry < 2; retry++) {
if (mPhoneService == null) {
loge("Ignoring mobile radio request because could not acquire PhoneService");
break;
}
try {
return mPhoneService.setRadio(turnOn);
} catch (RemoteException e) {
if (retry == 0) getPhoneService(true);
}
}
loge("Could not set radio power to " + (turnOn ? "on" : "off"));
return false;
}
@Override
public void setUserDataEnable(boolean enabled) {
if (DBG) log("setUserDataEnable: E enabled=" + enabled);
final AsyncChannel channel = mDataConnectionTrackerAc;
if (channel != null) {
channel.sendMessage(DctConstants.CMD_SET_USER_DATA_ENABLE,
enabled ? DctConstants.ENABLED : DctConstants.DISABLED);
mUserDataEnabled = enabled;
}
if (VDBG) log("setUserDataEnable: X enabled=" + enabled);
}
@Override
public void setPolicyDataEnable(boolean enabled) {
if (DBG) log("setPolicyDataEnable(enabled=" + enabled + ")");
final AsyncChannel channel = mDataConnectionTrackerAc;
if (channel != null) {
channel.sendMessage(DctConstants.CMD_SET_POLICY_DATA_ENABLE,
enabled ? DctConstants.ENABLED : DctConstants.DISABLED);
mPolicyDataEnabled = enabled;
}
}
/**
* Eanble/disable FailFast
*
* @param enabled is DctConstants.ENABLED/DISABLED
*/
public void setEnableFailFastMobileData(int enabled) {
if (DBG) log("setEnableFailFastMobileData(enabled=" + enabled + ")");
final AsyncChannel channel = mDataConnectionTrackerAc;
if (channel != null) {
channel.sendMessage(DctConstants.CMD_SET_ENABLE_FAIL_FAST_MOBILE_DATA, enabled);
}
}
/**
* carrier dependency is met/unmet
* @param met
*/
public void setDependencyMet(boolean met) {
Bundle bundle = Bundle.forPair(DctConstants.APN_TYPE_KEY, mApnType);
try {
if (DBG) log("setDependencyMet: E met=" + met);
Message msg = Message.obtain();
msg.what = DctConstants.CMD_SET_DEPENDENCY_MET;
msg.arg1 = (met ? DctConstants.ENABLED : DctConstants.DISABLED);
msg.setData(bundle);
mDataConnectionTrackerAc.sendMessage(msg);
if (VDBG) log("setDependencyMet: X met=" + met);
} catch (NullPointerException e) {
loge("setDependencyMet: X mAc was null" + e);
}
}
@Override
public void addStackedLink(LinkProperties link) {
mLinkProperties.addStackedLink(link);
}
@Override
public void removeStackedLink(LinkProperties link) {
mLinkProperties.removeStackedLink(link);
}
@Override
public String toString() {
final CharArrayWriter writer = new CharArrayWriter();
final PrintWriter pw = new PrintWriter(writer);
pw.print("Mobile data state: "); pw.println(mMobileDataState);
pw.print("Data enabled: user="); pw.print(mUserDataEnabled);
pw.print(", policy="); pw.println(mPolicyDataEnabled);
return writer.toString();
}
/**
* Internal method supporting the ENABLE_MMS feature.
* @param apnType the type of APN to be enabled or disabled (e.g., mms)
* @param enable {@code true} to enable the specified APN type,
* {@code false} to disable it.
* @return an integer value representing the outcome of the request.
*/
private int setEnableApn(String apnType, boolean enable) {
getPhoneService(false);
/*
* If the phone process has crashed in the past, we'll get a
* RemoteException and need to re-reference the service.
*/
for (int retry = 0; retry < 2; retry++) {
if (mPhoneService == null) {
loge("Ignoring feature request because could not acquire PhoneService");
break;
}
try {
if (enable) {
return mPhoneService.enableApnType(apnType);
} else {
return mPhoneService.disableApnType(apnType);
}
} catch (RemoteException e) {
if (retry == 0) getPhoneService(true);
}
}
loge("Could not " + (enable ? "enable" : "disable") + " APN type \"" + apnType + "\"");
return PhoneConstants.APN_REQUEST_FAILED;
}
public static String networkTypeToApnType(int netType) {
switch(netType) {
case ConnectivityManager.TYPE_MOBILE:
return PhoneConstants.APN_TYPE_DEFAULT; // TODO - use just one of these
case ConnectivityManager.TYPE_MOBILE_MMS:
return PhoneConstants.APN_TYPE_MMS;
case ConnectivityManager.TYPE_MOBILE_SUPL:
return PhoneConstants.APN_TYPE_SUPL;
case ConnectivityManager.TYPE_MOBILE_DUN:
return PhoneConstants.APN_TYPE_DUN;
case ConnectivityManager.TYPE_MOBILE_HIPRI:
return PhoneConstants.APN_TYPE_HIPRI;
case ConnectivityManager.TYPE_MOBILE_FOTA:
return PhoneConstants.APN_TYPE_FOTA;
case ConnectivityManager.TYPE_MOBILE_IMS:
return PhoneConstants.APN_TYPE_IMS;
case ConnectivityManager.TYPE_MOBILE_CBS:
return PhoneConstants.APN_TYPE_CBS;
default:
sloge("Error mapping networkType " + netType + " to apnType.");
return null;
}
}
/**
* @see android.net.NetworkStateTracker#getLinkProperties()
*/
public LinkProperties getLinkProperties() {
return new LinkProperties(mLinkProperties);
}
/**
* @see android.net.NetworkStateTracker#getLinkCapabilities()
*/
public LinkCapabilities getLinkCapabilities() {
return new LinkCapabilities(mLinkCapabilities);
}
public void supplyMessenger(Messenger messenger) {
if (VDBG) log(mApnType + " got supplyMessenger");
AsyncChannel ac = new AsyncChannel();
ac.connect(mContext, MobileDataStateTracker.this.mHandler, messenger);
}
private void log(String s) {
Slog.d(TAG, mApnType + ": " + s);
}
private void loge(String s) {
Slog.e(TAG, mApnType + ": " + s);
}
static private void sloge(String s) {
Slog.e(TAG, s);
}
}