blob: 0de4a5367ded95d01c8c0ace0fa5ff8795111559 [file] [log] [blame]
/*
* Copyright (C) 2006 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 com.android.internal.telephony.dataconnection;
import static android.net.NetworkPolicyManager.OVERRIDE_CONGESTED;
import static android.net.NetworkPolicyManager.OVERRIDE_UNMETERED;
import android.app.PendingIntent;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.PacketKeepalive;
import android.net.KeepalivePacketData;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkAgent;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkMisc;
import android.net.NetworkUtils;
import android.net.ProxyInfo;
import android.net.RouteInfo;
import android.net.StringNetworkSpecifier;
import android.os.AsyncResult;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.telephony.AccessNetworkConstants.TransportType;
import android.telephony.DataFailCause;
import android.telephony.Rlog;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
import android.telephony.data.ApnSetting;
import android.telephony.data.DataCallResponse;
import android.telephony.data.DataProfile;
import android.telephony.data.DataService;
import android.telephony.data.DataServiceCallback;
import android.text.TextUtils;
import android.util.LocalLog;
import android.util.Pair;
import android.util.SparseArray;
import android.util.StatsLog;
import android.util.TimeUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.CallTracker;
import com.android.internal.telephony.CarrierSignalAgent;
import com.android.internal.telephony.DctConstants;
import com.android.internal.telephony.LinkCapacityEstimate;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.RILConstants;
import com.android.internal.telephony.RetryManager;
import com.android.internal.telephony.ServiceStateTracker;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.telephony.metrics.TelephonyMetrics;
import com.android.internal.util.AsyncChannel;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Protocol;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* {@hide}
*
* DataConnection StateMachine.
*
* This a class for representing a single data connection, with instances of this
* class representing a connection via the cellular network. There may be multiple
* data connections and all of them are managed by the <code>DataConnectionTracker</code>.
*
* NOTE: All DataConnection objects must be running on the same looper, which is the default
* as the coordinator has members which are used without synchronization.
*/
public class DataConnection extends StateMachine {
private static final boolean DBG = true;
private static final boolean VDBG = true;
private static final String NETWORK_TYPE = "MOBILE";
// The data connection controller
private DcController mDcController;
// The Tester for failing all bringup's
private DcTesterFailBringUpAll mDcTesterFailBringUpAll;
private static AtomicInteger mInstanceNumber = new AtomicInteger(0);
private AsyncChannel mAc;
// The DCT that's talking to us, we only support one!
private DcTracker mDct = null;
private String[] mPcscfAddr;
/**
* Used internally for saving connecting parameters.
*/
public static class ConnectionParams {
int mTag;
ApnContext mApnContext;
int mProfileId;
int mRilRat;
Message mOnCompletedMsg;
final int mConnectionGeneration;
ConnectionParams(ApnContext apnContext, int profileId, int rilRadioTechnology,
Message onCompletedMsg, int connectionGeneration) {
mApnContext = apnContext;
mProfileId = profileId;
mRilRat = rilRadioTechnology;
mOnCompletedMsg = onCompletedMsg;
mConnectionGeneration = connectionGeneration;
}
@Override
public String toString() {
return "{mTag=" + mTag + " mApnContext=" + mApnContext
+ " mProfileId=" + mProfileId
+ " mRat=" + mRilRat
+ " mOnCompletedMsg=" + msgToString(mOnCompletedMsg) + "}";
}
}
/**
* Used internally for saving disconnecting parameters.
*/
public static class DisconnectParams {
int mTag;
public ApnContext mApnContext;
String mReason;
Message mOnCompletedMsg;
DisconnectParams(ApnContext apnContext, String reason, Message onCompletedMsg) {
mApnContext = apnContext;
mReason = reason;
mOnCompletedMsg = onCompletedMsg;
}
@Override
public String toString() {
return "{mTag=" + mTag + " mApnContext=" + mApnContext
+ " mReason=" + mReason
+ " mOnCompletedMsg=" + msgToString(mOnCompletedMsg) + "}";
}
}
private ApnSetting mApnSetting;
private ConnectionParams mConnectionParams;
private DisconnectParams mDisconnectParams;
private DataFailCause mDcFailCause;
private Phone mPhone;
private DataServiceManager mDataServiceManager;
private LinkProperties mLinkProperties = new LinkProperties();
private long mCreateTime;
private long mLastFailTime;
private DataFailCause mLastFailCause;
private static final String NULL_IP = "0.0.0.0";
private Object mUserData;
private int mSubscriptionOverride;
private int mRilRat = Integer.MAX_VALUE;
private int mDataRegState = Integer.MAX_VALUE;
private NetworkInfo mNetworkInfo;
private DcNetworkAgent mNetworkAgent;
private LocalLog mNetCapsLocalLog = new LocalLog(50);
int mTag;
public int mCid;
private final Map<ApnContext, ConnectionParams> mApnContexts = new ConcurrentHashMap<>();
PendingIntent mReconnectIntent = null;
// ***** Event codes for driving the state machine, package visible for Dcc
static final int BASE = Protocol.BASE_DATA_CONNECTION;
static final int EVENT_CONNECT = BASE + 0;
static final int EVENT_SETUP_DATA_CONNECTION_DONE = BASE + 1;
static final int EVENT_DEACTIVATE_DONE = BASE + 3;
static final int EVENT_DISCONNECT = BASE + 4;
static final int EVENT_RIL_CONNECTED = BASE + 5;
static final int EVENT_DISCONNECT_ALL = BASE + 6;
static final int EVENT_DATA_STATE_CHANGED = BASE + 7;
static final int EVENT_TEAR_DOWN_NOW = BASE + 8;
static final int EVENT_LOST_CONNECTION = BASE + 9;
static final int EVENT_DATA_CONNECTION_DRS_OR_RAT_CHANGED = BASE + 11;
static final int EVENT_DATA_CONNECTION_ROAM_ON = BASE + 12;
static final int EVENT_DATA_CONNECTION_ROAM_OFF = BASE + 13;
static final int EVENT_BW_REFRESH_RESPONSE = BASE + 14;
static final int EVENT_DATA_CONNECTION_VOICE_CALL_STARTED = BASE + 15;
static final int EVENT_DATA_CONNECTION_VOICE_CALL_ENDED = BASE + 16;
static final int EVENT_DATA_CONNECTION_OVERRIDE_CHANGED = BASE + 17;
static final int EVENT_KEEPALIVE_STATUS = BASE + 18;
static final int EVENT_KEEPALIVE_STARTED = BASE + 19;
static final int EVENT_KEEPALIVE_STOPPED = BASE + 20;
static final int EVENT_KEEPALIVE_START_REQUEST = BASE + 21;
static final int EVENT_KEEPALIVE_STOP_REQUEST = BASE + 22;
static final int EVENT_LINK_CAPACITY_CHANGED = BASE + 23;
static final int EVENT_RESET = BASE + 24;
static final int EVENT_REEVALUATE_RESTRICTED_STATE = BASE + 25;
private static final int CMD_TO_STRING_COUNT = EVENT_REEVALUATE_RESTRICTED_STATE - BASE + 1;
private static String[] sCmdToString = new String[CMD_TO_STRING_COUNT];
static {
sCmdToString[EVENT_CONNECT - BASE] = "EVENT_CONNECT";
sCmdToString[EVENT_SETUP_DATA_CONNECTION_DONE - BASE] =
"EVENT_SETUP_DATA_CONNECTION_DONE";
sCmdToString[EVENT_DEACTIVATE_DONE - BASE] = "EVENT_DEACTIVATE_DONE";
sCmdToString[EVENT_DISCONNECT - BASE] = "EVENT_DISCONNECT";
sCmdToString[EVENT_RIL_CONNECTED - BASE] = "EVENT_RIL_CONNECTED";
sCmdToString[EVENT_DISCONNECT_ALL - BASE] = "EVENT_DISCONNECT_ALL";
sCmdToString[EVENT_DATA_STATE_CHANGED - BASE] = "EVENT_DATA_STATE_CHANGED";
sCmdToString[EVENT_TEAR_DOWN_NOW - BASE] = "EVENT_TEAR_DOWN_NOW";
sCmdToString[EVENT_LOST_CONNECTION - BASE] = "EVENT_LOST_CONNECTION";
sCmdToString[EVENT_DATA_CONNECTION_DRS_OR_RAT_CHANGED - BASE] =
"EVENT_DATA_CONNECTION_DRS_OR_RAT_CHANGED";
sCmdToString[EVENT_DATA_CONNECTION_ROAM_ON - BASE] = "EVENT_DATA_CONNECTION_ROAM_ON";
sCmdToString[EVENT_DATA_CONNECTION_ROAM_OFF - BASE] = "EVENT_DATA_CONNECTION_ROAM_OFF";
sCmdToString[EVENT_BW_REFRESH_RESPONSE - BASE] = "EVENT_BW_REFRESH_RESPONSE";
sCmdToString[EVENT_DATA_CONNECTION_VOICE_CALL_STARTED - BASE] =
"EVENT_DATA_CONNECTION_VOICE_CALL_STARTED";
sCmdToString[EVENT_DATA_CONNECTION_VOICE_CALL_ENDED - BASE] =
"EVENT_DATA_CONNECTION_VOICE_CALL_ENDED";
sCmdToString[EVENT_DATA_CONNECTION_OVERRIDE_CHANGED - BASE] =
"EVENT_DATA_CONNECTION_OVERRIDE_CHANGED";
sCmdToString[EVENT_KEEPALIVE_STATUS - BASE] = "EVENT_KEEPALIVE_STATUS";
sCmdToString[EVENT_KEEPALIVE_STARTED - BASE] = "EVENT_KEEPALIVE_STARTED";
sCmdToString[EVENT_KEEPALIVE_STOPPED - BASE] = "EVENT_KEEPALIVE_STOPPED";
sCmdToString[EVENT_KEEPALIVE_START_REQUEST - BASE] = "EVENT_KEEPALIVE_START_REQUEST";
sCmdToString[EVENT_KEEPALIVE_STOP_REQUEST - BASE] = "EVENT_KEEPALIVE_STOP_REQUEST";
sCmdToString[EVENT_LINK_CAPACITY_CHANGED - BASE] = "EVENT_LINK_CAPACITY_CHANGED";
sCmdToString[EVENT_RESET - BASE] = "EVENT_RESET";
sCmdToString[EVENT_REEVALUATE_RESTRICTED_STATE - BASE] =
"EVENT_REEVALUATE_RESTRICTED_STATE";
}
// Convert cmd to string or null if unknown
static String cmdToString(int cmd) {
String value = null;
cmd -= BASE;
if ((cmd >= 0) && (cmd < sCmdToString.length)) {
value = sCmdToString[cmd];
}
if (value == null) {
value = "0x" + Integer.toHexString(cmd + BASE);
}
return value;
}
/**
* Create the connection object
*
* @param phone the Phone
* @param id the connection id
* @return DataConnection that was created.
*/
public static DataConnection makeDataConnection(Phone phone, int id, DcTracker dct,
DataServiceManager dataServiceManager,
DcTesterFailBringUpAll failBringUpAll,
DcController dcc) {
DataConnection dc = new DataConnection(phone,
"DC-" + mInstanceNumber.incrementAndGet(), id, dct, dataServiceManager,
failBringUpAll, dcc);
dc.start();
if (DBG) dc.log("Made " + dc.getName());
return dc;
}
void dispose() {
log("dispose: call quiteNow()");
quitNow();
}
/* Getter functions */
LinkProperties getLinkProperties() {
return new LinkProperties(mLinkProperties);
}
boolean isInactive() {
return getCurrentState() == mInactiveState;
}
boolean isDisconnecting() {
return getCurrentState() == mDisconnectingState;
}
boolean isActive() {
return getCurrentState() == mActiveState;
}
boolean isActivating() {
return getCurrentState() == mActivatingState;
}
int getCid() {
return mCid;
}
ApnSetting getApnSetting() {
return mApnSetting;
}
void setLinkPropertiesHttpProxy(ProxyInfo proxy) {
mLinkProperties.setHttpProxy(proxy);
}
public static class UpdateLinkPropertyResult {
public SetupResult setupResult = SetupResult.SUCCESS;
public LinkProperties oldLp;
public LinkProperties newLp;
public UpdateLinkPropertyResult(LinkProperties curLp) {
oldLp = curLp;
newLp = curLp;
}
}
/**
* Class returned by onSetupConnectionCompleted.
*/
public enum SetupResult {
SUCCESS,
ERROR_RADIO_NOT_AVAILABLE,
ERROR_INVALID_ARG,
ERROR_STALE,
ERROR_DATA_SERVICE_SPECIFIC_ERROR;
public DataFailCause mFailCause;
SetupResult() {
mFailCause = DataFailCause.fromInt(0);
}
@Override
public String toString() {
return name() + " SetupResult.mFailCause=" + mFailCause;
}
}
public boolean isIpv4Connected() {
boolean ret = false;
Collection <InetAddress> addresses = mLinkProperties.getAddresses();
for (InetAddress addr: addresses) {
if (addr instanceof java.net.Inet4Address) {
java.net.Inet4Address i4addr = (java.net.Inet4Address) addr;
if (!i4addr.isAnyLocalAddress() && !i4addr.isLinkLocalAddress() &&
!i4addr.isLoopbackAddress() && !i4addr.isMulticastAddress()) {
ret = true;
break;
}
}
}
return ret;
}
public boolean isIpv6Connected() {
boolean ret = false;
Collection <InetAddress> addresses = mLinkProperties.getAddresses();
for (InetAddress addr: addresses) {
if (addr instanceof java.net.Inet6Address) {
java.net.Inet6Address i6addr = (java.net.Inet6Address) addr;
if (!i6addr.isAnyLocalAddress() && !i6addr.isLinkLocalAddress() &&
!i6addr.isLoopbackAddress() && !i6addr.isMulticastAddress()) {
ret = true;
break;
}
}
}
return ret;
}
@VisibleForTesting
public UpdateLinkPropertyResult updateLinkProperty(DataCallResponse newState) {
UpdateLinkPropertyResult result = new UpdateLinkPropertyResult(mLinkProperties);
if (newState == null) return result;
result.newLp = new LinkProperties();
// set link properties based on data call response
result.setupResult = setLinkProperties(newState, result.newLp);
if (result.setupResult != SetupResult.SUCCESS) {
if (DBG) log("updateLinkProperty failed : " + result.setupResult);
return result;
}
// copy HTTP proxy as it is not part DataCallResponse.
result.newLp.setHttpProxy(mLinkProperties.getHttpProxy());
checkSetMtu(mApnSetting, result.newLp);
mLinkProperties = result.newLp;
updateTcpBufferSizes(mRilRat);
if (DBG && (! result.oldLp.equals(result.newLp))) {
log("updateLinkProperty old LP=" + result.oldLp);
log("updateLinkProperty new LP=" + result.newLp);
}
if (result.newLp.equals(result.oldLp) == false &&
mNetworkAgent != null) {
mNetworkAgent.sendLinkProperties(mLinkProperties);
}
return result;
}
/**
* Read the MTU value from link properties where it can be set from network. In case
* not set by the network, set it again using the mtu szie value defined in the APN
* database for the connected APN
*/
private void checkSetMtu(ApnSetting apn, LinkProperties lp) {
if (lp == null) return;
if (apn == null || lp == null) return;
if (lp.getMtu() != PhoneConstants.UNSET_MTU) {
if (DBG) log("MTU set by call response to: " + lp.getMtu());
return;
}
if (apn != null && apn.getMtu() != PhoneConstants.UNSET_MTU) {
lp.setMtu(apn.getMtu());
if (DBG) log("MTU set by APN to: " + apn.getMtu());
return;
}
int mtu = mPhone.getContext().getResources().getInteger(
com.android.internal.R.integer.config_mobile_mtu);
if (mtu != PhoneConstants.UNSET_MTU) {
lp.setMtu(mtu);
if (DBG) log("MTU set by config resource to: " + mtu);
}
}
//***** Constructor (NOTE: uses dcc.getHandler() as its Handler)
private DataConnection(Phone phone, String name, int id,
DcTracker dct, DataServiceManager dataServiceManager,
DcTesterFailBringUpAll failBringUpAll, DcController dcc) {
super(name, dcc.getHandler());
setLogRecSize(300);
setLogOnlyTransitions(true);
if (DBG) log("DataConnection created");
mPhone = phone;
mDct = dct;
mDataServiceManager = dataServiceManager;
mDcTesterFailBringUpAll = failBringUpAll;
mDcController = dcc;
mId = id;
mCid = -1;
ServiceState ss = mPhone.getServiceState();
mRilRat = ss.getRilDataRadioTechnology();
mDataRegState = mPhone.getServiceState().getDataRegState();
int networkType = ss.getDataNetworkType();
mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_MOBILE,
networkType, NETWORK_TYPE, TelephonyManager.getNetworkTypeName(networkType));
mNetworkInfo.setRoaming(ss.getDataRoaming());
mNetworkInfo.setIsAvailable(true);
addState(mDefaultState);
addState(mInactiveState, mDefaultState);
addState(mActivatingState, mDefaultState);
addState(mActiveState, mDefaultState);
addState(mDisconnectingState, mDefaultState);
addState(mDisconnectingErrorCreatingConnection, mDefaultState);
setInitialState(mInactiveState);
}
/**
* Begin setting up a data connection, calls setupDataCall
* and the ConnectionParams will be returned with the
* EVENT_SETUP_DATA_CONNECTION_DONE
*
* @param cp is the connection parameters
*/
private void onConnect(ConnectionParams cp) {
if (DBG) {
log("onConnect: carrier='" + mApnSetting.getEntryName()
+ "' APN='" + mApnSetting.getApnName()
+ "' proxy='" + mApnSetting.getProxyAddressAsString()
+ "' port='" + mApnSetting.getProxyPort() + "'");
}
if (cp.mApnContext != null) cp.mApnContext.requestLog("DataConnection.onConnect");
// Check if we should fake an error.
if (mDcTesterFailBringUpAll.getDcFailBringUp().mCounter > 0) {
DataCallResponse response = new DataCallResponse(
mDcTesterFailBringUpAll.getDcFailBringUp().mFailCause.getErrorCode(),
mDcTesterFailBringUpAll.getDcFailBringUp().mSuggestedRetryTime, 0, 0, "", "",
null, null, null, null, PhoneConstants.UNSET_MTU);
Message msg = obtainMessage(EVENT_SETUP_DATA_CONNECTION_DONE, cp);
AsyncResult.forMessage(msg, response, null);
sendMessage(msg);
if (DBG) {
log("onConnect: FailBringUpAll=" + mDcTesterFailBringUpAll.getDcFailBringUp()
+ " send error response=" + response);
}
mDcTesterFailBringUpAll.getDcFailBringUp().mCounter -= 1;
return;
}
mCreateTime = -1;
mLastFailTime = -1;
mLastFailCause = DataFailCause.NONE;
Message msg = obtainMessage(EVENT_SETUP_DATA_CONNECTION_DONE, cp);
msg.obj = cp;
DataProfile dp = DcTracker.createDataProfile(mApnSetting, cp.mProfileId,
mApnSetting.equals(mDct.getPreferredApn()));
// We need to use the actual modem roaming state instead of the framework roaming state
// here. This flag is only passed down to ril_service for picking the correct protocol (for
// old modem backward compatibility).
boolean isModemRoaming = mPhone.getServiceState().getDataRoamingFromRegistration();
// Set this flag to true if the user turns on data roaming. Or if we override the roaming
// state in framework, we should set this flag to true as well so the modem will not reject
// the data call setup (because the modem actually thinks the device is roaming).
boolean allowRoaming = mPhone.getDataRoamingEnabled()
|| (isModemRoaming && !mPhone.getServiceState().getDataRoaming());
mDataServiceManager.setupDataCall(
ServiceState.rilRadioTechnologyToAccessNetworkType(cp.mRilRat), dp, isModemRoaming,
allowRoaming, DataService.REQUEST_REASON_NORMAL, null, msg);
TelephonyMetrics.getInstance().writeSetupDataCall(mPhone.getPhoneId(), cp.mRilRat,
dp.getProfileId(), dp.getApn(), dp.getProtocol());
}
public void onSubscriptionOverride(int overrideMask, int overrideValue) {
mSubscriptionOverride = (mSubscriptionOverride & ~overrideMask)
| (overrideValue & overrideMask);
sendMessage(obtainMessage(EVENT_DATA_CONNECTION_OVERRIDE_CHANGED));
}
/**
* TearDown the data connection when the deactivation is complete a Message with
* msg.what == EVENT_DEACTIVATE_DONE
*
* @param o is the object returned in the AsyncResult.obj.
*/
private void tearDownData(Object o) {
int discReason = DataService.REQUEST_REASON_NORMAL;
ApnContext apnContext = null;
if ((o != null) && (o instanceof DisconnectParams)) {
DisconnectParams dp = (DisconnectParams) o;
apnContext = dp.mApnContext;
if (TextUtils.equals(dp.mReason, Phone.REASON_RADIO_TURNED_OFF)
|| TextUtils.equals(dp.mReason, Phone.REASON_PDP_RESET)) {
discReason = DataService.REQUEST_REASON_SHUTDOWN;
}
}
String str = "tearDownData. mCid=" + mCid + ", reason=" + discReason;
if (DBG) log(str);
if (apnContext != null) apnContext.requestLog(str);
mDataServiceManager.deactivateDataCall(mCid, discReason,
obtainMessage(EVENT_DEACTIVATE_DONE, mTag, 0, o));
}
private void notifyAllWithEvent(ApnContext alreadySent, int event, String reason) {
mNetworkInfo.setDetailedState(mNetworkInfo.getDetailedState(), reason,
mNetworkInfo.getExtraInfo());
for (ConnectionParams cp : mApnContexts.values()) {
ApnContext apnContext = cp.mApnContext;
if (apnContext == alreadySent) continue;
if (reason != null) apnContext.setReason(reason);
Pair<ApnContext, Integer> pair =
new Pair<ApnContext, Integer>(apnContext, cp.mConnectionGeneration);
Message msg = mDct.obtainMessage(event, pair);
AsyncResult.forMessage(msg);
msg.sendToTarget();
}
}
private void notifyAllOfConnected(String reason) {
notifyAllWithEvent(null, DctConstants.EVENT_DATA_SETUP_COMPLETE, reason);
}
private void notifyAllOfDisconnectDcRetrying(String reason) {
notifyAllWithEvent(null, DctConstants.EVENT_DISCONNECT_DC_RETRYING, reason);
}
private void notifyAllDisconnectCompleted(DataFailCause cause) {
notifyAllWithEvent(null, DctConstants.EVENT_DISCONNECT_DONE, cause.toString());
}
/**
* Send the connectionCompletedMsg.
*
* @param cp is the ConnectionParams
* @param cause and if no error the cause is DataFailCause.NONE
* @param sendAll is true if all contexts are to be notified
*/
private void notifyConnectCompleted(ConnectionParams cp, DataFailCause cause, boolean sendAll) {
ApnContext alreadySent = null;
if (cp != null && cp.mOnCompletedMsg != null) {
// Get the completed message but only use it once
Message connectionCompletedMsg = cp.mOnCompletedMsg;
cp.mOnCompletedMsg = null;
alreadySent = cp.mApnContext;
long timeStamp = System.currentTimeMillis();
connectionCompletedMsg.arg1 = mCid;
if (cause == DataFailCause.NONE) {
mCreateTime = timeStamp;
AsyncResult.forMessage(connectionCompletedMsg);
} else {
mLastFailCause = cause;
mLastFailTime = timeStamp;
// Return message with a Throwable exception to signify an error.
if (cause == null) cause = DataFailCause.UNKNOWN;
AsyncResult.forMessage(connectionCompletedMsg, cause,
new Throwable(cause.toString()));
}
if (DBG) {
log("notifyConnectCompleted at " + timeStamp + " cause=" + cause
+ " connectionCompletedMsg=" + msgToString(connectionCompletedMsg));
}
connectionCompletedMsg.sendToTarget();
}
if (sendAll) {
log("Send to all. " + alreadySent + " " + cause.toString());
notifyAllWithEvent(alreadySent, DctConstants.EVENT_DATA_SETUP_COMPLETE_ERROR,
cause.toString());
}
}
/**
* Send ar.userObj if its a message, which is should be back to originator.
*
* @param dp is the DisconnectParams.
*/
private void notifyDisconnectCompleted(DisconnectParams dp, boolean sendAll) {
if (VDBG) log("NotifyDisconnectCompleted");
ApnContext alreadySent = null;
String reason = null;
if (dp != null && dp.mOnCompletedMsg != null) {
// Get the completed message but only use it once
Message msg = dp.mOnCompletedMsg;
dp.mOnCompletedMsg = null;
if (msg.obj instanceof ApnContext) {
alreadySent = (ApnContext)msg.obj;
}
reason = dp.mReason;
if (VDBG) {
log(String.format("msg=%s msg.obj=%s", msg.toString(),
((msg.obj instanceof String) ? (String) msg.obj : "<no-reason>")));
}
AsyncResult.forMessage(msg);
msg.sendToTarget();
}
if (sendAll) {
if (reason == null) {
reason = DataFailCause.UNKNOWN.toString();
}
notifyAllWithEvent(alreadySent, DctConstants.EVENT_DISCONNECT_DONE, reason);
}
if (DBG) log("NotifyDisconnectCompleted DisconnectParams=" + dp);
}
/*
* **************************************************************************
* Begin Members and methods owned by DataConnectionTracker but stored
* in a DataConnection because there is one per connection.
* **************************************************************************
*/
/*
* The id is owned by DataConnectionTracker.
*/
private int mId;
/**
* Get the DataConnection ID
*/
public int getDataConnectionId() {
return mId;
}
/*
* **************************************************************************
* End members owned by DataConnectionTracker
* **************************************************************************
*/
/**
* Clear all settings called when entering mInactiveState.
*/
private void clearSettings() {
if (DBG) log("clearSettings");
mCreateTime = -1;
mLastFailTime = -1;
mLastFailCause = DataFailCause.NONE;
mCid = -1;
mPcscfAddr = new String[5];
mLinkProperties = new LinkProperties();
mApnContexts.clear();
mApnSetting = null;
mUnmeteredUseOnly = false;
mRestrictedNetworkOverride = false;
mDcFailCause = null;
}
/**
* Process setup data completion result from data service
*
* @param resultCode The result code returned by data service
* @param response Data call setup response from data service
* @param cp The original connection params used for data call setup
* @return Setup result
*/
private SetupResult onSetupConnectionCompleted(@DataServiceCallback.ResultCode int resultCode,
DataCallResponse response,
ConnectionParams cp) {
SetupResult result;
if (cp.mTag != mTag) {
if (DBG) {
log("onSetupConnectionCompleted stale cp.tag=" + cp.mTag + ", mtag=" + mTag);
}
result = SetupResult.ERROR_STALE;
} else if (resultCode == DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE) {
result = SetupResult.ERROR_RADIO_NOT_AVAILABLE;
result.mFailCause = DataFailCause.RADIO_NOT_AVAILABLE;
} else if (response.getStatus() != 0) {
if (response.getStatus() == DataFailCause.RADIO_NOT_AVAILABLE.getErrorCode()) {
result = SetupResult.ERROR_RADIO_NOT_AVAILABLE;
result.mFailCause = DataFailCause.RADIO_NOT_AVAILABLE;
} else {
result = SetupResult.ERROR_DATA_SERVICE_SPECIFIC_ERROR;
result.mFailCause = DataFailCause.fromInt(response.getStatus());
}
} else {
if (DBG) log("onSetupConnectionCompleted received successful DataCallResponse");
mCid = response.getCallId();
mPcscfAddr = response.getPcscfs().toArray(new String[response.getPcscfs().size()]);
result = updateLinkProperty(response).setupResult;
}
return result;
}
private boolean isDnsOk(String[] domainNameServers) {
if (NULL_IP.equals(domainNameServers[0]) && NULL_IP.equals(domainNameServers[1])
&& !mPhone.isDnsCheckDisabled()) {
// Work around a race condition where QMI does not fill in DNS:
// Deactivate PDP and let DataConnectionTracker retry.
// Do not apply the race condition workaround for MMS APN
// if Proxy is an IP-address.
// Otherwise, the default APN will not be restored anymore.
if (!isIpAddress(mApnSetting.getMmsProxyAddressAsString())) {
log(String.format(
"isDnsOk: return false apn.types=%d APN_TYPE_MMS=%s isIpAddress(%s)=%s",
mApnSetting.getApnTypeBitmask(), PhoneConstants.APN_TYPE_MMS,
mApnSetting.getMmsProxyAddressAsString(),
isIpAddress(mApnSetting.getMmsProxyAddressAsString())));
return false;
}
}
return true;
}
private static final String TCP_BUFFER_SIZES_GPRS = "4092,8760,48000,4096,8760,48000";
private static final String TCP_BUFFER_SIZES_EDGE = "4093,26280,70800,4096,16384,70800";
private static final String TCP_BUFFER_SIZES_UMTS = "58254,349525,1048576,58254,349525,1048576";
private static final String TCP_BUFFER_SIZES_1XRTT= "16384,32768,131072,4096,16384,102400";
private static final String TCP_BUFFER_SIZES_EVDO = "4094,87380,262144,4096,16384,262144";
private static final String TCP_BUFFER_SIZES_EHRPD= "131072,262144,1048576,4096,16384,524288";
private static final String TCP_BUFFER_SIZES_HSDPA= "61167,367002,1101005,8738,52429,262114";
private static final String TCP_BUFFER_SIZES_HSPA = "40778,244668,734003,16777,100663,301990";
private static final String TCP_BUFFER_SIZES_LTE =
"524288,1048576,2097152,262144,524288,1048576";
private static final String TCP_BUFFER_SIZES_HSPAP= "122334,734003,2202010,32040,192239,576717";
private void updateTcpBufferSizes(int rilRat) {
String sizes = null;
if (rilRat == ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA) {
// for now treat CA as LTE. Plan to surface the extra bandwith in a more
// precise manner which should affect buffer sizes
rilRat = ServiceState.RIL_RADIO_TECHNOLOGY_LTE;
}
String ratName = ServiceState.rilRadioTechnologyToString(rilRat).toLowerCase(Locale.ROOT);
// ServiceState gives slightly different names for EVDO tech ("evdo-rev.0" for ex)
// - patch it up:
if (rilRat == ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_0 ||
rilRat == ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_A ||
rilRat == ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_B) {
ratName = "evdo";
}
// in the form: "ratname:rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max"
String[] configOverride = mPhone.getContext().getResources().getStringArray(
com.android.internal.R.array.config_mobile_tcp_buffers);
for (int i = 0; i < configOverride.length; i++) {
String[] split = configOverride[i].split(":");
if (ratName.equals(split[0]) && split.length == 2) {
sizes = split[1];
break;
}
}
if (sizes == null) {
// no override - use telephony defaults
// doing it this way allows device or carrier to just override the types they
// care about and inherit the defaults for the others.
switch (rilRat) {
case ServiceState.RIL_RADIO_TECHNOLOGY_GPRS:
sizes = TCP_BUFFER_SIZES_GPRS;
break;
case ServiceState.RIL_RADIO_TECHNOLOGY_EDGE:
sizes = TCP_BUFFER_SIZES_EDGE;
break;
case ServiceState.RIL_RADIO_TECHNOLOGY_UMTS:
sizes = TCP_BUFFER_SIZES_UMTS;
break;
case ServiceState.RIL_RADIO_TECHNOLOGY_1xRTT:
sizes = TCP_BUFFER_SIZES_1XRTT;
break;
case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_0:
case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_A:
case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_B:
sizes = TCP_BUFFER_SIZES_EVDO;
break;
case ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD:
sizes = TCP_BUFFER_SIZES_EHRPD;
break;
case ServiceState.RIL_RADIO_TECHNOLOGY_HSDPA:
sizes = TCP_BUFFER_SIZES_HSDPA;
break;
case ServiceState.RIL_RADIO_TECHNOLOGY_HSPA:
case ServiceState.RIL_RADIO_TECHNOLOGY_HSUPA:
sizes = TCP_BUFFER_SIZES_HSPA;
break;
case ServiceState.RIL_RADIO_TECHNOLOGY_LTE:
case ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA:
sizes = TCP_BUFFER_SIZES_LTE;
break;
case ServiceState.RIL_RADIO_TECHNOLOGY_HSPAP:
sizes = TCP_BUFFER_SIZES_HSPAP;
break;
default:
// Leave empty - this will let ConnectivityService use the system default.
break;
}
}
mLinkProperties.setTcpBufferSizes(sizes);
}
/**
* Indicates if this data connection was established for unmetered use only. Note that this
* flag should be populated when data becomes active. And if it is set to true, it can be set to
* false later when we are reevaluating the data connection. But if it is set to false, it
* can never become true later because setting it to true will cause this data connection
* losing some immutable network capabilities, which can cause issues in connectivity service.
*/
private boolean mUnmeteredUseOnly = false;
/**
* Indicates if when this connection was established we had a restricted/privileged
* NetworkRequest and needed it to overcome data-enabled limitations.
*
* This flag overrides the APN-based restriction capability, restricting the network
* based on both having a NetworkRequest with restricted AND needing a restricted
* bit to overcome user-disabled status. This allows us to handle the common case
* of having both restricted requests and unrestricted requests for the same apn:
* if conditions require a restricted network to overcome user-disabled then it must
* be restricted, otherwise it is unrestricted (or restricted based on APN type).
*
* This supports a privileged app bringing up a network without general apps having access
* to it when the network is otherwise unavailable (hipri). The first use case is
* pre-paid SIM reprovisioning over internet, where the carrier insists on no traffic
* other than from the privileged carrier-app.
*
* Note that the data connection cannot go from unrestricted to restricted because the
* connectivity service does not support dynamically closing TCP connections at this point.
*/
private boolean mRestrictedNetworkOverride = false;
/**
* Check if this data connection should be restricted. We should call this when data connection
* becomes active, or when we want to re-evaluate the conditions to decide if we need to
* unstrict the data connection.
*
* @return True if this data connection needs to be restricted.
*/
private boolean shouldRestrictNetwork() {
// first, check if there is any network request that containing restricted capability
// (i.e. Do not have NET_CAPABILITY_NOT_RESTRICTED in the request)
boolean isAnyRestrictedRequest = false;
for (ApnContext apnContext : mApnContexts.keySet()) {
if (apnContext.hasRestrictedRequests(true /* exclude DUN */)) {
isAnyRestrictedRequest = true;
break;
}
}
// If all of the network requests are non-restricted, then we don't need to restrict
// the network.
if (!isAnyRestrictedRequest) {
return false;
}
// If the network is unmetered, then we don't need to restrict the network because users
// won't be charged anyway.
if (!ApnSettingUtils.isMetered(mApnSetting, mPhone)) {
return false;
}
// If the data is disabled, then we need to restrict the network so only privileged apps can
// use the restricted network while data is disabled.
if (!mDct.isDataEnabled()) {
return true;
}
// If the device is roaming, and the user does not turn on data roaming, then we need to
// restrict the network so only privileged apps can use it.
if (!mDct.getDataRoamingEnabled() && mPhone.getServiceState().getDataRoaming()) {
return true;
}
// Otherwise we should not restrict the network so anyone who requests can use it.
return false;
}
/**
* @return True if this data connection should only be used for unmetered purposes.
*/
private boolean isUnmeteredUseOnly() {
// The data connection can only be unmetered used only if all requests' reasons are
// unmetered.
for (ApnContext apnContext : mApnContexts.keySet()) {
DataConnectionReasons dataConnectionReasons = new DataConnectionReasons();
boolean isDataAllowed = mDct.isDataAllowed(apnContext, dataConnectionReasons);
if (!isDataAllowed || !dataConnectionReasons.contains(
DataConnectionReasons.DataAllowedReasonType.UNMETERED_APN)) {
return false;
}
}
return true;
}
NetworkCapabilities getNetworkCapabilities() {
NetworkCapabilities result = new NetworkCapabilities();
result.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
if (mApnSetting != null) {
final String[] types = ApnSetting.getApnTypesStringFromBitmask(
mApnSetting.getApnTypeBitmask()).split(",");
for (String type : types) {
if (!mRestrictedNetworkOverride && mUnmeteredUseOnly
&& ApnSettingUtils.isMeteredApnType(type, mPhone)) {
log("Dropped the metered " + type + " for the unmetered data call.");
continue;
}
switch (type) {
case PhoneConstants.APN_TYPE_ALL: {
result.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
result.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
result.addCapability(NetworkCapabilities.NET_CAPABILITY_SUPL);
result.addCapability(NetworkCapabilities.NET_CAPABILITY_FOTA);
result.addCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
result.addCapability(NetworkCapabilities.NET_CAPABILITY_CBS);
result.addCapability(NetworkCapabilities.NET_CAPABILITY_IA);
result.addCapability(NetworkCapabilities.NET_CAPABILITY_DUN);
break;
}
case PhoneConstants.APN_TYPE_DEFAULT: {
result.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
break;
}
case PhoneConstants.APN_TYPE_MMS: {
result.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
break;
}
case PhoneConstants.APN_TYPE_SUPL: {
result.addCapability(NetworkCapabilities.NET_CAPABILITY_SUPL);
break;
}
case PhoneConstants.APN_TYPE_DUN: {
result.addCapability(NetworkCapabilities.NET_CAPABILITY_DUN);
break;
}
case PhoneConstants.APN_TYPE_FOTA: {
result.addCapability(NetworkCapabilities.NET_CAPABILITY_FOTA);
break;
}
case PhoneConstants.APN_TYPE_IMS: {
result.addCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
break;
}
case PhoneConstants.APN_TYPE_CBS: {
result.addCapability(NetworkCapabilities.NET_CAPABILITY_CBS);
break;
}
case PhoneConstants.APN_TYPE_IA: {
result.addCapability(NetworkCapabilities.NET_CAPABILITY_IA);
break;
}
case PhoneConstants.APN_TYPE_EMERGENCY: {
result.addCapability(NetworkCapabilities.NET_CAPABILITY_EIMS);
break;
}
default:
}
}
// Mark NOT_METERED in the following cases,
// 1. All APNs in APN settings are unmetered.
// 2. The non-restricted data and is intended for unmetered use only.
if ((mUnmeteredUseOnly && !mRestrictedNetworkOverride)
|| !ApnSettingUtils.isMetered(mApnSetting, mPhone)) {
result.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
} else {
result.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
}
result.maybeMarkCapabilitiesRestricted();
}
if (mRestrictedNetworkOverride) {
result.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
// don't use dun on restriction-overriden networks.
result.removeCapability(NetworkCapabilities.NET_CAPABILITY_DUN);
}
int up = 14;
int down = 14;
switch (mRilRat) {
case ServiceState.RIL_RADIO_TECHNOLOGY_GPRS: up = 80; down = 80; break;
case ServiceState.RIL_RADIO_TECHNOLOGY_EDGE: up = 59; down = 236; break;
case ServiceState.RIL_RADIO_TECHNOLOGY_UMTS: up = 384; down = 384; break;
case ServiceState.RIL_RADIO_TECHNOLOGY_IS95A: // fall through
case ServiceState.RIL_RADIO_TECHNOLOGY_IS95B: up = 14; down = 14; break;
case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_0: up = 153; down = 2457; break;
case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_A: up = 1843; down = 3174; break;
case ServiceState.RIL_RADIO_TECHNOLOGY_1xRTT: up = 100; down = 100; break;
case ServiceState.RIL_RADIO_TECHNOLOGY_HSDPA: up = 2048; down = 14336; break;
case ServiceState.RIL_RADIO_TECHNOLOGY_HSUPA: up = 5898; down = 14336; break;
case ServiceState.RIL_RADIO_TECHNOLOGY_HSPA: up = 5898; down = 14336; break;
case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_B: up = 1843; down = 5017; break;
case ServiceState.RIL_RADIO_TECHNOLOGY_LTE: up = 51200; down = 102400; break;
case ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA: up = 51200; down = 102400; break;
case ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD: up = 153; down = 2516; break;
case ServiceState.RIL_RADIO_TECHNOLOGY_HSPAP: up = 11264; down = 43008; break;
default:
}
result.setLinkUpstreamBandwidthKbps(up);
result.setLinkDownstreamBandwidthKbps(down);
result.setNetworkSpecifier(new StringNetworkSpecifier(Integer.toString(mPhone.getSubId())));
result.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING,
!mPhone.getServiceState().getDataRoaming());
result.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED);
// Override values set above when requested by policy
if ((mSubscriptionOverride & OVERRIDE_UNMETERED) != 0) {
result.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
}
if ((mSubscriptionOverride & OVERRIDE_CONGESTED) != 0) {
result.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED);
}
return result;
}
/**
* @return {@code} true iff. {@code address} is a literal IPv4 or IPv6 address.
*/
@VisibleForTesting
public static boolean isIpAddress(String address) {
if (address == null) return false;
return InetAddress.isNumeric(address);
}
private SetupResult setLinkProperties(DataCallResponse response,
LinkProperties linkProperties) {
// Check if system property dns usable
String propertyPrefix = "net." + response.getIfname() + ".";
String dnsServers[] = new String[2];
dnsServers[0] = SystemProperties.get(propertyPrefix + "dns1");
dnsServers[1] = SystemProperties.get(propertyPrefix + "dns2");
boolean okToUseSystemPropertyDns = isDnsOk(dnsServers);
SetupResult result;
// Start with clean network properties and if we have
// a failure we'll clear again at the bottom of this code.
linkProperties.clear();
if (response.getStatus() == DataFailCause.NONE.getErrorCode()) {
try {
// set interface name
linkProperties.setInterfaceName(response.getIfname());
// set link addresses
if (response.getAddresses().size() > 0) {
for (LinkAddress la : response.getAddresses()) {
if (!la.getAddress().isAnyLocalAddress()) {
if (DBG) {
log("addr/pl=" + la.getAddress() + "/"
+ la.getNetworkPrefixLength());
}
linkProperties.addLinkAddress(la);
}
}
} else {
throw new UnknownHostException("no address for ifname=" + response.getIfname());
}
// set dns servers
if (response.getDnses().size() > 0) {
for (InetAddress dns : response.getDnses()) {
if (!dns.isAnyLocalAddress()) {
linkProperties.addDnsServer(dns);
}
}
} else if (okToUseSystemPropertyDns) {
for (String dnsAddr : dnsServers) {
dnsAddr = dnsAddr.trim();
if (dnsAddr.isEmpty()) continue;
InetAddress ia;
try {
ia = NetworkUtils.numericToInetAddress(dnsAddr);
} catch (IllegalArgumentException e) {
throw new UnknownHostException("Non-numeric dns addr=" + dnsAddr);
}
if (!ia.isAnyLocalAddress()) {
linkProperties.addDnsServer(ia);
}
}
} else {
throw new UnknownHostException("Empty dns response and no system default dns");
}
for (InetAddress gateway : response.getGateways()) {
// Allow 0.0.0.0 or :: as a gateway;
// this indicates a point-to-point interface.
linkProperties.addRoute(new RouteInfo(gateway));
}
// set interface MTU
// this may clobber the setting read from the APN db, but that's ok
linkProperties.setMtu(response.getMtu());
result = SetupResult.SUCCESS;
} catch (UnknownHostException e) {
log("setLinkProperties: UnknownHostException " + e);
result = SetupResult.ERROR_INVALID_ARG;
}
} else {
result = SetupResult.ERROR_DATA_SERVICE_SPECIFIC_ERROR;
}
// An error occurred so clear properties
if (result != SetupResult.SUCCESS) {
if (DBG) {
log("setLinkProperties: error clearing LinkProperties status="
+ response.getStatus() + " result=" + result);
}
linkProperties.clear();
}
return result;
}
/**
* Initialize connection, this will fail if the
* apnSettings are not compatible.
*
* @param cp the Connection parameters
* @return true if initialization was successful.
*/
private boolean initConnection(ConnectionParams cp) {
ApnContext apnContext = cp.mApnContext;
if (mApnSetting == null) {
// Only change apn setting if it isn't set, it will
// only NOT be set only if we're in DcInactiveState.
mApnSetting = apnContext.getApnSetting();
}
if (mApnSetting == null || !mApnSetting.canHandleType(apnContext.getApnTypeBitmask())) {
if (DBG) {
log("initConnection: incompatible apnSetting in ConnectionParams cp=" + cp
+ " dc=" + DataConnection.this);
}
return false;
}
mTag += 1;
mConnectionParams = cp;
mConnectionParams.mTag = mTag;
// always update the ConnectionParams with the latest or the
// connectionGeneration gets stale
mApnContexts.put(apnContext, cp);
if (DBG) {
log("initConnection: "
+ " RefCount=" + mApnContexts.size()
+ " mApnList=" + mApnContexts
+ " mConnectionParams=" + mConnectionParams);
}
return true;
}
/**
* The parent state for all other states.
*/
private class DcDefaultState extends State {
@Override
public void enter() {
if (DBG) log("DcDefaultState: enter");
// Register for DRS or RAT change
mPhone.getServiceStateTracker().registerForDataRegStateOrRatChanged(getHandler(),
DataConnection.EVENT_DATA_CONNECTION_DRS_OR_RAT_CHANGED, null);
mPhone.getServiceStateTracker().registerForDataRoamingOn(getHandler(),
DataConnection.EVENT_DATA_CONNECTION_ROAM_ON, null);
mPhone.getServiceStateTracker().registerForDataRoamingOff(getHandler(),
DataConnection.EVENT_DATA_CONNECTION_ROAM_OFF, null, true);
// Add ourselves to the list of data connections
mDcController.addDc(DataConnection.this);
}
@Override
public void exit() {
if (DBG) log("DcDefaultState: exit");
// Unregister for DRS or RAT change.
mPhone.getServiceStateTracker().unregisterForDataRegStateOrRatChanged(getHandler());
mPhone.getServiceStateTracker().unregisterForDataRoamingOn(getHandler());
mPhone.getServiceStateTracker().unregisterForDataRoamingOff(getHandler());
// Remove ourselves from the DC lists
mDcController.removeDc(DataConnection.this);
if (mAc != null) {
mAc.disconnected();
mAc = null;
}
mApnContexts.clear();
mReconnectIntent = null;
mDct = null;
mApnSetting = null;
mPhone = null;
mDataServiceManager = null;
mLinkProperties = null;
mLastFailCause = null;
mUserData = null;
mDcController = null;
mDcTesterFailBringUpAll = null;
}
@Override
public boolean processMessage(Message msg) {
boolean retVal = HANDLED;
if (VDBG) {
log("DcDefault msg=" + getWhatToString(msg.what)
+ " RefCount=" + mApnContexts.size());
}
switch (msg.what) {
case EVENT_RESET:
if (VDBG) log("DcDefaultState: msg.what=REQ_RESET");
transitionTo(mInactiveState);
break;
case EVENT_CONNECT:
if (DBG) log("DcDefaultState: msg.what=EVENT_CONNECT, fail not expected");
ConnectionParams cp = (ConnectionParams) msg.obj;
notifyConnectCompleted(cp, DataFailCause.UNKNOWN, false);
break;
case EVENT_DISCONNECT:
case EVENT_DISCONNECT_ALL:
case EVENT_REEVALUATE_RESTRICTED_STATE:
if (DBG) {
log("DcDefaultState deferring msg.what=" + getWhatToString(msg.what)
+ " RefCount=" + mApnContexts.size());
}
deferMessage(msg);
break;
case EVENT_TEAR_DOWN_NOW:
if (DBG) log("DcDefaultState EVENT_TEAR_DOWN_NOW");
mDataServiceManager.deactivateDataCall(mCid, DataService.REQUEST_REASON_NORMAL,
null);
break;
case EVENT_LOST_CONNECTION:
if (DBG) {
String s = "DcDefaultState ignore EVENT_LOST_CONNECTION"
+ " tag=" + msg.arg1 + ":mTag=" + mTag;
logAndAddLogRec(s);
}
break;
case EVENT_DATA_CONNECTION_DRS_OR_RAT_CHANGED:
AsyncResult ar = (AsyncResult)msg.obj;
Pair<Integer, Integer> drsRatPair = (Pair<Integer, Integer>)ar.result;
mDataRegState = drsRatPair.first;
if (mRilRat != drsRatPair.second) {
updateTcpBufferSizes(drsRatPair.second);
}
mRilRat = drsRatPair.second;
if (DBG) {
log("DcDefaultState: EVENT_DATA_CONNECTION_DRS_OR_RAT_CHANGED"
+ " drs=" + mDataRegState
+ " mRilRat=" + mRilRat);
}
updateNetworkInfo();
updateNetworkInfoSuspendState();
if (mNetworkAgent != null) {
mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities());
mNetworkAgent.sendNetworkInfo(mNetworkInfo);
mNetworkAgent.sendLinkProperties(mLinkProperties);
}
break;
case EVENT_DATA_CONNECTION_ROAM_ON:
case EVENT_DATA_CONNECTION_ROAM_OFF:
case EVENT_DATA_CONNECTION_OVERRIDE_CHANGED:
updateNetworkInfo();
if (mNetworkAgent != null) {
mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities());
mNetworkAgent.sendNetworkInfo(mNetworkInfo);
}
break;
case EVENT_KEEPALIVE_START_REQUEST:
case EVENT_KEEPALIVE_STOP_REQUEST:
if (mNetworkAgent != null) {
mNetworkAgent.onPacketKeepaliveEvent(
msg.arg1,
ConnectivityManager.PacketKeepalive.ERROR_INVALID_NETWORK);
}
break;
default:
if (DBG) {
log("DcDefaultState: shouldn't happen but ignore msg.what="
+ getWhatToString(msg.what));
}
break;
}
return retVal;
}
}
private void updateNetworkInfo() {
final ServiceState state = mPhone.getServiceState();
final int subtype = state.getDataNetworkType();
mNetworkInfo.setSubtype(subtype, TelephonyManager.getNetworkTypeName(subtype));
mNetworkInfo.setRoaming(state.getDataRoaming());
}
private void updateNetworkInfoSuspendState() {
// this is only called when we are either connected or suspended. Decide which.
if (mNetworkAgent == null) {
Rlog.e(getName(), "Setting suspend state without a NetworkAgent");
}
// if we are not in-service change to SUSPENDED
final ServiceStateTracker sst = mPhone.getServiceStateTracker();
if (sst.getCurrentDataConnectionState() != ServiceState.STATE_IN_SERVICE) {
mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.SUSPENDED, null,
mNetworkInfo.getExtraInfo());
} else {
// check for voice call and concurrency issues
if (sst.isConcurrentVoiceAndDataAllowed() == false) {
final CallTracker ct = mPhone.getCallTracker();
if (ct.getState() != PhoneConstants.State.IDLE) {
mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.SUSPENDED, null,
mNetworkInfo.getExtraInfo());
return;
}
}
mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null,
mNetworkInfo.getExtraInfo());
}
}
private DcDefaultState mDefaultState = new DcDefaultState();
/**
* The state machine is inactive and expects a EVENT_CONNECT.
*/
private class DcInactiveState extends State {
// Inform all contexts we've failed connecting
public void setEnterNotificationParams(ConnectionParams cp, DataFailCause cause) {
if (VDBG) log("DcInactiveState: setEnterNotificationParams cp,cause");
mConnectionParams = cp;
mDisconnectParams = null;
mDcFailCause = cause;
}
// Inform all contexts we've failed disconnected
public void setEnterNotificationParams(DisconnectParams dp) {
if (VDBG) log("DcInactiveState: setEnterNotificationParams dp");
mConnectionParams = null;
mDisconnectParams = dp;
mDcFailCause = DataFailCause.NONE;
}
// Inform all contexts of the failure cause
public void setEnterNotificationParams(DataFailCause cause) {
mConnectionParams = null;
mDisconnectParams = null;
mDcFailCause = cause;
}
@Override
public void enter() {
mTag += 1;
if (DBG) log("DcInactiveState: enter() mTag=" + mTag);
StatsLog.write(StatsLog.MOBILE_CONNECTION_STATE_CHANGED,
StatsLog.MOBILE_CONNECTION_STATE_CHANGED__STATE__INACTIVE,
mPhone.getPhoneId(), mId,
mApnSetting != null ? (long) mApnSetting.getApnTypeBitmask() : 0L,
mApnSetting != null
? mApnSetting.canHandleType(ApnSetting.TYPE_DEFAULT) : false);
if (mConnectionParams != null) {
if (DBG) {
log("DcInactiveState: enter notifyConnectCompleted +ALL failCause="
+ mDcFailCause);
}
notifyConnectCompleted(mConnectionParams, mDcFailCause, true);
}
if (mDisconnectParams != null) {
if (DBG) {
log("DcInactiveState: enter notifyDisconnectCompleted +ALL failCause="
+ mDcFailCause);
}
notifyDisconnectCompleted(mDisconnectParams, true);
}
if (mDisconnectParams == null && mConnectionParams == null && mDcFailCause != null) {
if (DBG) {
log("DcInactiveState: enter notifyAllDisconnectCompleted failCause="
+ mDcFailCause);
}
notifyAllDisconnectCompleted(mDcFailCause);
}
// Remove ourselves from cid mapping, before clearSettings
mDcController.removeActiveDcByCid(DataConnection.this);
clearSettings();
}
@Override
public void exit() {
}
@Override
public boolean processMessage(Message msg) {
boolean retVal;
switch (msg.what) {
case EVENT_RESET:
case EVENT_REEVALUATE_RESTRICTED_STATE:
if (DBG) {
log("DcInactiveState: msg.what=" + getWhatToString(msg.what)
+ ", ignore we're already done");
}
retVal = HANDLED;
break;
case EVENT_CONNECT:
if (DBG) log("DcInactiveState: mag.what=EVENT_CONNECT");
ConnectionParams cp = (ConnectionParams) msg.obj;
if (initConnection(cp)) {
onConnect(mConnectionParams);
transitionTo(mActivatingState);
} else {
if (DBG) {
log("DcInactiveState: msg.what=EVENT_CONNECT initConnection failed");
}
notifyConnectCompleted(cp, DataFailCause.UNACCEPTABLE_NETWORK_PARAMETER,
false);
}
retVal = HANDLED;
break;
case EVENT_DISCONNECT:
if (DBG) log("DcInactiveState: msg.what=EVENT_DISCONNECT");
notifyDisconnectCompleted((DisconnectParams)msg.obj, false);
retVal = HANDLED;
break;
case EVENT_DISCONNECT_ALL:
if (DBG) log("DcInactiveState: msg.what=EVENT_DISCONNECT_ALL");
notifyDisconnectCompleted((DisconnectParams)msg.obj, false);
retVal = HANDLED;
break;
default:
if (VDBG) {
log("DcInactiveState nothandled msg.what=" + getWhatToString(msg.what));
}
retVal = NOT_HANDLED;
break;
}
return retVal;
}
}
private DcInactiveState mInactiveState = new DcInactiveState();
/**
* The state machine is activating a connection.
*/
private class DcActivatingState extends State {
@Override
public void enter() {
StatsLog.write(StatsLog.MOBILE_CONNECTION_STATE_CHANGED,
StatsLog.MOBILE_CONNECTION_STATE_CHANGED__STATE__ACTIVATING,
mPhone.getPhoneId(), mId,
mApnSetting != null ? (long) mApnSetting.getApnTypeBitmask() : 0L,
mApnSetting != null
? mApnSetting.canHandleType(ApnSetting.TYPE_DEFAULT) : false);
}
@Override
public boolean processMessage(Message msg) {
boolean retVal;
AsyncResult ar;
ConnectionParams cp;
if (DBG) log("DcActivatingState: msg=" + msgToString(msg));
switch (msg.what) {
case EVENT_DATA_CONNECTION_DRS_OR_RAT_CHANGED:
case EVENT_CONNECT:
// Activating can't process until we're done.
deferMessage(msg);
retVal = HANDLED;
break;
case EVENT_SETUP_DATA_CONNECTION_DONE:
cp = (ConnectionParams) msg.obj;
DataCallResponse dataCallResponse =
msg.getData().getParcelable(DataServiceManager.DATA_CALL_RESPONSE);
SetupResult result = onSetupConnectionCompleted(msg.arg1, dataCallResponse, cp);
if (result != SetupResult.ERROR_STALE) {
if (mConnectionParams != cp) {
loge("DcActivatingState: WEIRD mConnectionsParams:"+ mConnectionParams
+ " != cp:" + cp);
}
}
if (DBG) {
log("DcActivatingState onSetupConnectionCompleted result=" + result
+ " dc=" + DataConnection.this);
}
if (cp.mApnContext != null) {
cp.mApnContext.requestLog("onSetupConnectionCompleted result=" + result);
}
switch (result) {
case SUCCESS:
// All is well
mDcFailCause = DataFailCause.NONE;
transitionTo(mActiveState);
break;
case ERROR_RADIO_NOT_AVAILABLE:
// Vendor ril rejected the command and didn't connect.
// Transition to inactive but send notifications after
// we've entered the mInactive state.
mInactiveState.setEnterNotificationParams(cp, result.mFailCause);
transitionTo(mInactiveState);
break;
case ERROR_INVALID_ARG:
// The addresses given from the RIL are bad
tearDownData(cp);
transitionTo(mDisconnectingErrorCreatingConnection);
break;
case ERROR_DATA_SERVICE_SPECIFIC_ERROR:
// Retrieve the suggested retry delay from the modem and save it.
// If the modem want us to retry the current APN again, it will
// suggest a positive delay value (in milliseconds). Otherwise we'll get
// NO_SUGGESTED_RETRY_DELAY here.
long delay = getSuggestedRetryDelay(dataCallResponse);
cp.mApnContext.setModemSuggestedDelay(delay);
String str = "DcActivatingState: ERROR_DATA_SERVICE_SPECIFIC_ERROR "
+ " delay=" + delay
+ " result=" + result
+ " result.isRadioRestartFailure="
+ result.mFailCause.isRadioRestartFailure(mPhone.getContext(),
mPhone.getSubId())
+ " isPermanentFailure=" +
mDct.isPermanentFailure(result.mFailCause);
if (DBG) log(str);
if (cp.mApnContext != null) cp.mApnContext.requestLog(str);
// Save the cause. DcTracker.onDataSetupComplete will check this
// failure cause and determine if we need to retry this APN later
// or not.
mInactiveState.setEnterNotificationParams(cp, result.mFailCause);
transitionTo(mInactiveState);
break;
case ERROR_STALE:
loge("DcActivatingState: stale EVENT_SETUP_DATA_CONNECTION_DONE"
+ " tag:" + cp.mTag + " != mTag:" + mTag);
break;
default:
throw new RuntimeException("Unknown SetupResult, should not happen");
}
retVal = HANDLED;
break;
default:
if (VDBG) {
log("DcActivatingState not handled msg.what=" +
getWhatToString(msg.what) + " RefCount=" + mApnContexts.size());
}
retVal = NOT_HANDLED;
break;
}
return retVal;
}
}
private DcActivatingState mActivatingState = new DcActivatingState();
/**
* The state machine is connected, expecting an EVENT_DISCONNECT.
*/
private class DcActiveState extends State {
@Override public void enter() {
if (DBG) log("DcActiveState: enter dc=" + DataConnection.this);
StatsLog.write(StatsLog.MOBILE_CONNECTION_STATE_CHANGED,
StatsLog.MOBILE_CONNECTION_STATE_CHANGED__STATE__ACTIVE,
mPhone.getPhoneId(), mId,
mApnSetting != null ? (long) mApnSetting.getApnTypeBitmask() : 0L,
mApnSetting != null
? mApnSetting.canHandleType(ApnSetting.TYPE_DEFAULT) : false);
updateNetworkInfo();
// If we were retrying there maybe more than one, otherwise they'll only be one.
notifyAllOfConnected(Phone.REASON_CONNECTED);
mPhone.getCallTracker().registerForVoiceCallStarted(getHandler(),
DataConnection.EVENT_DATA_CONNECTION_VOICE_CALL_STARTED, null);
mPhone.getCallTracker().registerForVoiceCallEnded(getHandler(),
DataConnection.EVENT_DATA_CONNECTION_VOICE_CALL_ENDED, null);
// If the EVENT_CONNECT set the current max retry restore it here
// if it didn't then this is effectively a NOP.
mDcController.addActiveDcByCid(DataConnection.this);
mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED,
mNetworkInfo.getReason(), null);
mNetworkInfo.setExtraInfo(mApnSetting.getApnName());
updateTcpBufferSizes(mRilRat);
final NetworkMisc misc = new NetworkMisc();
final CarrierSignalAgent carrierSignalAgent = mPhone.getCarrierSignalAgent();
if (carrierSignalAgent.hasRegisteredReceivers(TelephonyIntents
.ACTION_CARRIER_SIGNAL_REDIRECTED)) {
// carrierSignal Receivers will place the carrier-specific provisioning notification
misc.provisioningNotificationDisabled = true;
}
misc.subscriberId = mPhone.getSubscriberId();
mRestrictedNetworkOverride = shouldRestrictNetwork();
mUnmeteredUseOnly = isUnmeteredUseOnly();
if (DBG) {
log("mRestrictedNetworkOverride = " + mRestrictedNetworkOverride
+ ", mUnmeteredUseOnly = " + mUnmeteredUseOnly);
}
mNetworkAgent = new DcNetworkAgent(getHandler().getLooper(), mPhone.getContext(),
"DcNetworkAgent", mNetworkInfo, getNetworkCapabilities(), mLinkProperties,
50, misc);
if (mDataServiceManager.getTransportType() == TransportType.WWAN) {
mPhone.mCi.registerForNattKeepaliveStatus(
getHandler(), DataConnection.EVENT_KEEPALIVE_STATUS, null);
mPhone.mCi.registerForLceInfo(
getHandler(), DataConnection.EVENT_LINK_CAPACITY_CHANGED, null);
}
}
@Override
public void exit() {
if (DBG) log("DcActiveState: exit dc=" + this);
String reason = mNetworkInfo.getReason();
if(mDcController.isExecutingCarrierChange()) {
reason = Phone.REASON_CARRIER_CHANGE;
} else if (mDisconnectParams != null && mDisconnectParams.mReason != null) {
reason = mDisconnectParams.mReason;
} else if (mDcFailCause != null) {
reason = mDcFailCause.toString();
}
mPhone.getCallTracker().unregisterForVoiceCallStarted(getHandler());
mPhone.getCallTracker().unregisterForVoiceCallEnded(getHandler());
mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED,
reason, mNetworkInfo.getExtraInfo());
if (mDataServiceManager.getTransportType() == TransportType.WWAN) {
mPhone.mCi.unregisterForNattKeepaliveStatus(getHandler());
mPhone.mCi.unregisterForLceInfo(getHandler());
}
if (mNetworkAgent != null) {
mNetworkAgent.sendNetworkInfo(mNetworkInfo);
mNetworkAgent = null;
}
}
@Override
public boolean processMessage(Message msg) {
boolean retVal;
switch (msg.what) {
case EVENT_CONNECT: {
ConnectionParams cp = (ConnectionParams) msg.obj;
// either add this new apn context to our set or
// update the existing cp with the latest connection generation number
mApnContexts.put(cp.mApnContext, cp);
if (DBG) {
log("DcActiveState: EVENT_CONNECT cp=" + cp + " dc=" + DataConnection.this);
}
notifyConnectCompleted(cp, DataFailCause.NONE, false);
retVal = HANDLED;
break;
}
case EVENT_DISCONNECT: {
DisconnectParams dp = (DisconnectParams) msg.obj;
if (DBG) {
log("DcActiveState: EVENT_DISCONNECT dp=" + dp
+ " dc=" + DataConnection.this);
}
if (mApnContexts.containsKey(dp.mApnContext)) {
if (DBG) {
log("DcActiveState msg.what=EVENT_DISCONNECT RefCount="
+ mApnContexts.size());
}
if (mApnContexts.size() == 1) {
mApnContexts.clear();
mDisconnectParams = dp;
mConnectionParams = null;
dp.mTag = mTag;
tearDownData(dp);
transitionTo(mDisconnectingState);
} else {
mApnContexts.remove(dp.mApnContext);
notifyDisconnectCompleted(dp, false);
}
} else {
log("DcActiveState ERROR no such apnContext=" + dp.mApnContext
+ " in this dc=" + DataConnection.this);
notifyDisconnectCompleted(dp, false);
}
retVal = HANDLED;
break;
}
case EVENT_DISCONNECT_ALL: {
if (DBG) {
log("DcActiveState EVENT_DISCONNECT clearing apn contexts,"
+ " dc=" + DataConnection.this);
}
DisconnectParams dp = (DisconnectParams) msg.obj;
mDisconnectParams = dp;
mConnectionParams = null;
dp.mTag = mTag;
tearDownData(dp);
transitionTo(mDisconnectingState);
retVal = HANDLED;
break;
}
case EVENT_LOST_CONNECTION: {
if (DBG) {
log("DcActiveState EVENT_LOST_CONNECTION dc=" + DataConnection.this);
}
mInactiveState.setEnterNotificationParams(DataFailCause.LOST_CONNECTION);
transitionTo(mInactiveState);
retVal = HANDLED;
break;
}
case EVENT_DATA_CONNECTION_ROAM_ON:
case EVENT_DATA_CONNECTION_ROAM_OFF:
case EVENT_DATA_CONNECTION_OVERRIDE_CHANGED: {
updateNetworkInfo();
if (mNetworkAgent != null) {
mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities());
mNetworkAgent.sendNetworkInfo(mNetworkInfo);
}
retVal = HANDLED;
break;
}
case EVENT_BW_REFRESH_RESPONSE: {
AsyncResult ar = (AsyncResult)msg.obj;
if (ar.exception != null) {
log("EVENT_BW_REFRESH_RESPONSE: error ignoring, e=" + ar.exception);
} else {
final LinkCapacityEstimate lce = (LinkCapacityEstimate) ar.result;
NetworkCapabilities nc = getNetworkCapabilities();
if (mPhone.getLceStatus() == RILConstants.LCE_ACTIVE) {
nc.setLinkDownstreamBandwidthKbps(lce.downlinkCapacityKbps);
if (mNetworkAgent != null) {
mNetworkAgent.sendNetworkCapabilities(nc);
}
}
}
retVal = HANDLED;
break;
}
case EVENT_DATA_CONNECTION_VOICE_CALL_STARTED:
case EVENT_DATA_CONNECTION_VOICE_CALL_ENDED: {
updateNetworkInfo();
updateNetworkInfoSuspendState();
if (mNetworkAgent != null) {
mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities());
mNetworkAgent.sendNetworkInfo(mNetworkInfo);
}
retVal = HANDLED;
break;
}
case EVENT_KEEPALIVE_START_REQUEST: {
KeepalivePacketData pkt = (KeepalivePacketData) msg.obj;
int slotId = msg.arg1;
int intervalMillis = msg.arg2 * 1000;
if (mDataServiceManager.getTransportType() == TransportType.WWAN) {
mPhone.mCi.startNattKeepalive(
DataConnection.this.mCid, pkt, intervalMillis,
DataConnection.this.obtainMessage(
EVENT_KEEPALIVE_STARTED, slotId, 0, null));
} else {
// We currently do not support NATT Keepalive requests using the
// DataService API, so unless the request is WWAN (always bound via
// the CommandsInterface), the request cannot be honored.
//
// TODO: b/72331356 to add support for Keepalive to the DataService
// so that keepalive requests can be handled (if supported) by the
// underlying transport.
if (mNetworkAgent != null) {
mNetworkAgent.onPacketKeepaliveEvent(
msg.arg1,
ConnectivityManager.PacketKeepalive.ERROR_INVALID_NETWORK);
}
}
retVal = HANDLED;
break;
}
case EVENT_KEEPALIVE_STOP_REQUEST: {
int slotId = msg.arg1;
int handle = mNetworkAgent.keepaliveTracker.getHandleForSlot(slotId);
if (handle < 0) {
loge("No slot found for stopPacketKeepalive! " + slotId);
retVal = HANDLED;
break;
} else {
logd("Stopping keepalive with handle: " + handle);
}
mPhone.mCi.stopNattKeepalive(
handle, DataConnection.this.obtainMessage(
EVENT_KEEPALIVE_STOPPED, handle, slotId, null));
retVal = HANDLED;
break;
}
case EVENT_KEEPALIVE_STARTED: {
AsyncResult ar = (AsyncResult) msg.obj;
final int slot = msg.arg1;
if (ar.exception != null || ar.result == null) {
loge("EVENT_KEEPALIVE_STARTED: error starting keepalive, e="
+ ar.exception);
mNetworkAgent.onPacketKeepaliveEvent(
slot, ConnectivityManager.PacketKeepalive.ERROR_HARDWARE_ERROR);
} else {
KeepaliveStatus ks = (KeepaliveStatus) ar.result;
if (ks == null) {
loge("Null KeepaliveStatus received!");
} else {
mNetworkAgent.keepaliveTracker.handleKeepaliveStarted(slot, ks);
}
}
retVal = HANDLED;
break;
}
case EVENT_KEEPALIVE_STATUS: {
AsyncResult ar = (AsyncResult) msg.obj;
if (ar.exception != null) {
loge("EVENT_KEEPALIVE_STATUS: error in keepalive, e=" + ar.exception);
// We have no way to notify connectivity in this case.
}
if (ar.result != null) {
KeepaliveStatus ks = (KeepaliveStatus) ar.result;
mNetworkAgent.keepaliveTracker.handleKeepaliveStatus(ks);
}
retVal = HANDLED;
break;
}
case EVENT_KEEPALIVE_STOPPED: {
AsyncResult ar = (AsyncResult) msg.obj;
final int handle = msg.arg1;
final int slotId = msg.arg2;
if (ar.exception != null) {
loge("EVENT_KEEPALIVE_STOPPED: error stopping keepalive for handle="
+ handle + " e=" + ar.exception);
mNetworkAgent.keepaliveTracker.handleKeepaliveStatus(
new KeepaliveStatus(KeepaliveStatus.ERROR_UNKNOWN));
} else {
log("Keepalive Stop Requested for handle=" + handle);
mNetworkAgent.keepaliveTracker.handleKeepaliveStatus(
new KeepaliveStatus(handle, KeepaliveStatus.STATUS_INACTIVE));
}
retVal = HANDLED;
break;
}
case EVENT_LINK_CAPACITY_CHANGED: {
AsyncResult ar = (AsyncResult) msg.obj;
if (ar.exception != null) {
loge("EVENT_LINK_CAPACITY_CHANGED e=" + ar.exception);
} else {
LinkCapacityEstimate lce = (LinkCapacityEstimate) ar.result;
NetworkCapabilities nc = getNetworkCapabilities();
if (lce.downlinkCapacityKbps != LinkCapacityEstimate.INVALID) {
nc.setLinkDownstreamBandwidthKbps(lce.downlinkCapacityKbps);
}
if (lce.uplinkCapacityKbps != LinkCapacityEstimate.INVALID) {
nc.setLinkUpstreamBandwidthKbps(lce.uplinkCapacityKbps);
}
if (mNetworkAgent != null) {
mNetworkAgent.sendNetworkCapabilities(nc);
}
}
retVal = HANDLED;
break;
}
case EVENT_REEVALUATE_RESTRICTED_STATE: {
// If the network was restricted, and now it does not need to be restricted
// anymore, we should add the NET_CAPABILITY_NOT_RESTRICTED capability.
if (mRestrictedNetworkOverride && !shouldRestrictNetwork()) {
if (DBG) {
log("Data connection becomes not-restricted. dc=" + this);
}
// Note we only do this when network becomes non-restricted. When a
// non-restricted becomes restricted (e.g. users disable data, or turn off
// data roaming), DCT will explicitly tear down the networks (because
// connectivity service does not support force-close TCP connections today).
// Also note that NET_CAPABILITY_NOT_RESTRICTED is an immutable capability
// (see {@link NetworkCapabilities}) once we add it to the network, we can't
// remove it through the entire life cycle of the connection.
mRestrictedNetworkOverride = false;
mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities());
}
// If the data does need to be unmetered use only (e.g. users turn on data, or
// device is not roaming anymore assuming data roaming is off), then we can
// dynamically add those metered APN type capabilities back. (But not the
// other way around because most of the APN-type capabilities are immutable
// capabilities.)
if (mUnmeteredUseOnly && !isUnmeteredUseOnly()) {
mUnmeteredUseOnly = false;
mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities());
}
retVal = HANDLED;
break;
}
default:
if (VDBG) {
log("DcActiveState not handled msg.what=" + getWhatToString(msg.what));
}
retVal = NOT_HANDLED;
break;
}
return retVal;
}
}
private DcActiveState mActiveState = new DcActiveState();
/**
* The state machine is disconnecting.
*/
private class DcDisconnectingState extends State {
@Override
public void enter() {
StatsLog.write(StatsLog.MOBILE_CONNECTION_STATE_CHANGED,
StatsLog.MOBILE_CONNECTION_STATE_CHANGED__STATE__DISCONNECTING,
mPhone.getPhoneId(), mId,
mApnSetting != null ? (long) mApnSetting.getApnTypeBitmask() : 0L,
mApnSetting != null
? mApnSetting.canHandleType(ApnSetting.TYPE_DEFAULT) : false);
}
@Override
public boolean processMessage(Message msg) {
boolean retVal;
switch (msg.what) {
case EVENT_CONNECT:
if (DBG) log("DcDisconnectingState msg.what=EVENT_CONNECT. Defer. RefCount = "
+ mApnContexts.size());
deferMessage(msg);
retVal = HANDLED;
break;
case EVENT_DEACTIVATE_DONE:
DisconnectParams dp = (DisconnectParams) msg.obj;
String str = "DcDisconnectingState msg.what=EVENT_DEACTIVATE_DONE RefCount="
+ mApnContexts.size();
if (DBG) log(str);
if (dp.mApnContext != null) dp.mApnContext.requestLog(str);
if (dp.mTag == mTag) {
// Transition to inactive but send notifications after
// we've entered the mInactive state.
mInactiveState.setEnterNotificationParams(dp);
transitionTo(mInactiveState);
} else {
if (DBG) log("DcDisconnectState stale EVENT_DEACTIVATE_DONE"
+ " dp.tag=" + dp.mTag + " mTag=" + mTag);
}
retVal = HANDLED;
break;
default:
if (VDBG) {
log("DcDisconnectingState not handled msg.what="
+ getWhatToString(msg.what));
}
retVal = NOT_HANDLED;
break;
}
return retVal;
}
}
private DcDisconnectingState mDisconnectingState = new DcDisconnectingState();
/**
* The state machine is disconnecting after an creating a connection.
*/
private class DcDisconnectionErrorCreatingConnection extends State {
@Override
public void enter() {
StatsLog.write(StatsLog.MOBILE_CONNECTION_STATE_CHANGED,
StatsLog.MOBILE_CONNECTION_STATE_CHANGED__STATE__DISCONNECTION_ERROR_CREATING_CONNECTION,
mPhone.getPhoneId(), mId,
mApnSetting != null ? (long) mApnSetting.getApnTypeBitmask() : 0L,
mApnSetting != null
? mApnSetting.canHandleType(ApnSetting.TYPE_DEFAULT) : false);
}
@Override
public boolean processMessage(Message msg) {
boolean retVal;
switch (msg.what) {
case EVENT_DEACTIVATE_DONE:
ConnectionParams cp = (ConnectionParams) msg.obj;
if (cp.mTag == mTag) {
String str = "DcDisconnectionErrorCreatingConnection" +
" msg.what=EVENT_DEACTIVATE_DONE";
if (DBG) log(str);
if (cp.mApnContext != null) cp.mApnContext.requestLog(str);
// Transition to inactive but send notifications after
// we've entered the mInactive state.
mInactiveState.setEnterNotificationParams(cp,
DataFailCause.UNACCEPTABLE_NETWORK_PARAMETER);
transitionTo(mInactiveState);
} else {
if (DBG) {
log("DcDisconnectionErrorCreatingConnection stale EVENT_DEACTIVATE_DONE"
+ " dp.tag=" + cp.mTag + ", mTag=" + mTag);
}
}
retVal = HANDLED;
break;
default:
if (VDBG) {
log("DcDisconnectionErrorCreatingConnection not handled msg.what="
+ getWhatToString(msg.what));
}
retVal = NOT_HANDLED;
break;
}
return retVal;
}
}
private DcDisconnectionErrorCreatingConnection mDisconnectingErrorCreatingConnection =
new DcDisconnectionErrorCreatingConnection();
private class DcNetworkAgent extends NetworkAgent {
private NetworkCapabilities mNetworkCapabilities;
public final DcKeepaliveTracker keepaliveTracker = new DcKeepaliveTracker();
public DcNetworkAgent(Looper l, Context c, String TAG, NetworkInfo ni,
NetworkCapabilities nc, LinkProperties lp, int score, NetworkMisc misc) {
super(l, c, TAG, ni, nc, lp, score, misc);
mNetCapsLocalLog.log("New network agent created. capabilities=" + nc);
mNetworkCapabilities = nc;
}
@Override
protected void unwanted() {
if (mNetworkAgent != this) {
log("DcNetworkAgent: unwanted found mNetworkAgent=" + mNetworkAgent +
", which isn't me. Aborting unwanted");
return;
}
// this can only happen if our exit has been called - we're already disconnected
if (mApnContexts == null) return;
for (ConnectionParams cp : mApnContexts.values()) {
final ApnContext apnContext = cp.mApnContext;
final Pair<ApnContext, Integer> pair =
new Pair<ApnContext, Integer>(apnContext, cp.mConnectionGeneration);
log("DcNetworkAgent: [unwanted]: disconnect apnContext=" + apnContext);
Message msg = mDct.obtainMessage(DctConstants.EVENT_DISCONNECT_DONE, pair);
DisconnectParams dp = new DisconnectParams(apnContext, apnContext.getReason(), msg);
DataConnection.this.sendMessage(DataConnection.this.
obtainMessage(EVENT_DISCONNECT, dp));
}
}
@Override
protected void pollLceData() {
if (mPhone.getLceStatus() == RILConstants.LCE_ACTIVE // active LCE service
&& mDataServiceManager.getTransportType() == TransportType.WWAN) {
mPhone.mCi.pullLceData(
DataConnection.this.obtainMessage(EVENT_BW_REFRESH_RESPONSE));
}
}
@Override
protected void networkStatus(int status, String redirectUrl) {
if(!TextUtils.isEmpty(redirectUrl)) {
log("validation status: " + status + " with redirection URL: " + redirectUrl);
/* its possible that we have multiple DataConnection with INTERNET_CAPABILITY
all fail the validation with the same redirection url, send CMD back to DCTracker
and let DcTracker to make the decision */
Message msg = mDct.obtainMessage(DctConstants.EVENT_REDIRECTION_DETECTED,
redirectUrl);
msg.sendToTarget();
}
}
@Override
public void sendNetworkCapabilities(NetworkCapabilities networkCapabilities) {
if (!networkCapabilities.equals(mNetworkCapabilities)) {
String logStr = "Changed from " + mNetworkCapabilities + " to "
+ networkCapabilities + ", Data RAT="
+ mPhone.getServiceState().getRilDataRadioTechnology()
+ ", mApnSetting=" + mApnSetting;
mNetCapsLocalLog.log(logStr);
log(logStr);
mNetworkCapabilities = networkCapabilities;
}
super.sendNetworkCapabilities(networkCapabilities);
}
@Override
protected void startPacketKeepalive(Message msg) {
DataConnection.this.obtainMessage(EVENT_KEEPALIVE_START_REQUEST,
msg.arg1, msg.arg2, msg.obj).sendToTarget();
}
@Override
protected void stopPacketKeepalive(Message msg) {
DataConnection.this.obtainMessage(EVENT_KEEPALIVE_STOP_REQUEST,
msg.arg1, msg.arg2, msg.obj).sendToTarget();
}
private class DcKeepaliveTracker {
private class KeepaliveRecord {
public int slotId;
public int currentStatus;
KeepaliveRecord(int slotId, int status) {
this.slotId = slotId;
this.currentStatus = status;
}
};
private final SparseArray<KeepaliveRecord> mKeepalives = new SparseArray();
int getHandleForSlot(int slotId) {
for (int i = 0; i < mKeepalives.size(); i++) {
KeepaliveRecord kr = mKeepalives.valueAt(i);
if (kr.slotId == slotId) return mKeepalives.keyAt(i);
}
return -1;
}
int keepaliveStatusErrorToPacketKeepaliveError(int error) {
switch(error) {
case KeepaliveStatus.ERROR_NONE:
return PacketKeepalive.SUCCESS;
case KeepaliveStatus.ERROR_UNSUPPORTED:
return PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED;
case KeepaliveStatus.ERROR_NO_RESOURCES:
case KeepaliveStatus.ERROR_UNKNOWN:
default:
return PacketKeepalive.ERROR_HARDWARE_ERROR;
}
}
void handleKeepaliveStarted(final int slot, KeepaliveStatus ks) {
switch (ks.statusCode) {
case KeepaliveStatus.STATUS_INACTIVE:
DcNetworkAgent.this.onPacketKeepaliveEvent(slot,
keepaliveStatusErrorToPacketKeepaliveError(ks.errorCode));
break;
case KeepaliveStatus.STATUS_ACTIVE:
DcNetworkAgent.this.onPacketKeepaliveEvent(
slot, PacketKeepalive.SUCCESS);
// fall through to add record
case KeepaliveStatus.STATUS_PENDING:
log("Adding keepalive handle="
+ ks.sessionHandle + " slot = " + slot);
mKeepalives.put(ks.sessionHandle,
new KeepaliveRecord(
slot, ks.statusCode));
break;
default:
loge("Invalid KeepaliveStatus Code: " + ks.statusCode);
break;
}
}
void handleKeepaliveStatus(KeepaliveStatus ks) {
final KeepaliveRecord kr;
kr = mKeepalives.get(ks.sessionHandle);
if (kr == null) {
// If there is no slot for the session handle, we received an event
// for a different data connection. This is not an error because the
// keepalive session events are broadcast to all listeners.
log("Discarding keepalive event for different data connection:" + ks);
return;
}
// Switch on the current state, to see what we do with the status update
switch (kr.currentStatus) {
case KeepaliveStatus.STATUS_INACTIVE:
loge("Inactive Keepalive received status!");
DcNetworkAgent.this.onPacketKeepaliveEvent(
kr.slotId, PacketKeepalive.ERROR_HARDWARE_ERROR);
break;
case KeepaliveStatus.STATUS_PENDING:
switch (ks.statusCode) {
case KeepaliveStatus.STATUS_INACTIVE:
DcNetworkAgent.this.onPacketKeepaliveEvent(kr.slotId,
keepaliveStatusErrorToPacketKeepaliveError(ks.errorCode));
kr.currentStatus = KeepaliveStatus.STATUS_INACTIVE;
mKeepalives.remove(ks.sessionHandle);
break;
case KeepaliveStatus.STATUS_ACTIVE:
log("Pending Keepalive received active status!");
kr.currentStatus = KeepaliveStatus.STATUS_ACTIVE;
DcNetworkAgent.this.onPacketKeepaliveEvent(
kr.slotId, PacketKeepalive.SUCCESS);
break;
case KeepaliveStatus.STATUS_PENDING:
loge("Invalid unsolicied Keepalive Pending Status!");
break;
default:
loge("Invalid Keepalive Status received, " + ks.statusCode);
}
break;
case KeepaliveStatus.STATUS_ACTIVE:
switch (ks.statusCode) {
case KeepaliveStatus.STATUS_INACTIVE:
loge("Keepalive received stopped status!");
DcNetworkAgent.this.onPacketKeepaliveEvent(
kr.slotId, PacketKeepalive.SUCCESS);
kr.currentStatus = KeepaliveStatus.STATUS_INACTIVE;
mKeepalives.remove(ks.sessionHandle);
break;
case KeepaliveStatus.STATUS_PENDING:
case KeepaliveStatus.STATUS_ACTIVE:
loge("Active Keepalive received invalid status!");
break;
default:
loge("Invalid Keepalive Status received, " + ks.statusCode);
}
break;
default:
loge("Invalid Keepalive Status received, " + kr.currentStatus);
}
}
};
}
/**
* Bring up a connection to the apn and return an AsyncResult in onCompletedMsg.
* Used for cellular networks that use Access Point Names (APN) such
* as GSM networks.
*
* @param apnContext is the Access Point Name to bring up a connection to
* @param profileId for the connection
* @param rilRadioTechnology Radio technology for the data connection
* @param onCompletedMsg is sent with its msg.obj as an AsyncResult object.
* With AsyncResult.userObj set to the original msg.obj,
* AsyncResult.result = FailCause and AsyncResult.exception = Exception().
* @param connectionGeneration used to track a single connection request so disconnects can get
* ignored if obsolete.
*/
public void bringUp(ApnContext apnContext, int profileId, int rilRadioTechnology,
Message onCompletedMsg, int connectionGeneration) {
if (DBG) {
log("bringUp: apnContext=" + apnContext + " onCompletedMsg=" + onCompletedMsg);
}
sendMessage(DataConnection.EVENT_CONNECT,
new ConnectionParams(apnContext, profileId, rilRadioTechnology, onCompletedMsg,
connectionGeneration));
}
/**
* Tear down the connection through the apn on the network.
*
* @param onCompletedMsg is sent with its msg.obj as an AsyncResult object.
* With AsyncResult.userObj set to the original msg.obj.
*/
public void tearDown(ApnContext apnContext, String reason, Message onCompletedMsg) {
if (DBG) {
log("tearDown: apnContext=" + apnContext
+ " reason=" + reason + " onCompletedMsg=" + onCompletedMsg);
}
sendMessage(DataConnection.EVENT_DISCONNECT,
new DisconnectParams(apnContext, reason, onCompletedMsg));
}
// ******* "public" interface
/**
* Used for testing purposes.
*/
void tearDownNow() {
if (DBG) log("tearDownNow()");
sendMessage(obtainMessage(EVENT_TEAR_DOWN_NOW));
}
/**
* Tear down the connection through the apn on the network. Ignores reference count and
* and always tears down.
*
* @param onCompletedMsg is sent with its msg.obj as an AsyncResult object.
* With AsyncResult.userObj set to the original msg.obj.
*/
public void tearDownAll(String reason, Message onCompletedMsg) {
if (DBG) log("tearDownAll: reason=" + reason + " onCompletedMsg=" + onCompletedMsg);
sendMessage(DataConnection.EVENT_DISCONNECT_ALL,
new DisconnectParams(null, reason, onCompletedMsg));
}
/**
* Reset the data connection to inactive state.
*/
public void reset() {
sendMessage(EVENT_RESET);
if (DBG) log("reset");
}
/**
* Re-evaluate the restricted state. If the restricted data connection does not need to be
* restricted anymore, we need to dynamically change the network's capability.
*/
void reevaluateRestrictedState() {
sendMessage(EVENT_REEVALUATE_RESTRICTED_STATE);
if (DBG) log("reevaluate restricted state");
}
/**
* @return The parameters used for initiating a data connection.
*/
public ConnectionParams getConnectionParams() {
return mConnectionParams;
}
/**
* @return The list of PCSCF addresses
*/
public String[] getPcscfAddresses() {
return mPcscfAddr;
}
/**
* Using the result of the SETUP_DATA_CALL determine the retry delay.
*
* @param response The response from setup data call
* @return NO_SUGGESTED_RETRY_DELAY if no retry is needed otherwise the delay to the
* next SETUP_DATA_CALL
*/
private long getSuggestedRetryDelay(DataCallResponse response) {
/** According to ril.h
* The value < 0 means no value is suggested
* The value 0 means retry should be done ASAP.
* The value of Integer.MAX_VALUE(0x7fffffff) means no retry.
*/
// The value < 0 means no value is suggested
if (response.getSuggestedRetryTime() < 0) {
if (DBG) log("No suggested retry delay.");
return RetryManager.NO_SUGGESTED_RETRY_DELAY;
}
// The value of Integer.MAX_VALUE(0x7fffffff) means no retry.
else if (response.getSuggestedRetryTime() == Integer.MAX_VALUE) {
if (DBG) log("Modem suggested not retrying.");
return RetryManager.NO_RETRY;
}
// We need to cast it to long because the value returned from RIL is a 32-bit integer,
// but the time values used in AlarmManager are all 64-bit long.
return (long) response.getSuggestedRetryTime();
}
public List<ApnContext> getApnContexts() {
return new ArrayList<>(mApnContexts.keySet());
}
/**
* @return the string for msg.what as our info.
*/
@Override
protected String getWhatToString(int what) {
return cmdToString(what);
}
private static String msgToString(Message msg) {
String retVal;
if (msg == null) {
retVal = "null";
} else {
StringBuilder b = new StringBuilder();
b.append("{what=");
b.append(cmdToString(msg.what));
b.append(" when=");
TimeUtils.formatDuration(msg.getWhen() - SystemClock.uptimeMillis(), b);
if (msg.arg1 != 0) {
b.append(" arg1=");
b.append(msg.arg1);
}
if (msg.arg2 != 0) {
b.append(" arg2=");
b.append(msg.arg2);
}
if (msg.obj != null) {
b.append(" obj=");
b.append(msg.obj);
}
b.append(" target=");
b.append(msg.getTarget());
b.append(" replyTo=");
b.append(msg.replyTo);
b.append("}");
retVal = b.toString();
}
return retVal;
}
static void slog(String s) {
Rlog.d("DC", s);
}
/**
* Log with debug
*
* @param s is string log
*/
@Override
protected void log(String s) {
Rlog.d(getName(), s);
}
/**
* Log with debug attribute
*
* @param s is string log
*/
@Override
protected void logd(String s) {
Rlog.d(getName(), s);
}
/**
* Log with verbose attribute
*
* @param s is string log
*/
@Override
protected void logv(String s) {
Rlog.v(getName(), s);
}
/**
* Log with info attribute
*
* @param s is string log
*/
@Override
protected void logi(String s) {
Rlog.i(getName(), s);
}
/**
* Log with warning attribute
*
* @param s is string log
*/
@Override
protected void logw(String s) {
Rlog.w(getName(), s);
}
/**
* Log with error attribute
*
* @param s is string log
*/
@Override
protected void loge(String s) {
Rlog.e(getName(), s);
}
/**
* Log with error attribute
*
* @param s is string log
* @param e is a Throwable which logs additional information.
*/
@Override
protected void loge(String s, Throwable e) {
Rlog.e(getName(), s, e);
}
/** Doesn't print mApnList of ApnContext's which would be recursive */
public String toStringSimple() {
return getName() + ": State=" + getCurrentState().getName()
+ " mApnSetting=" + mApnSetting + " RefCount=" + mApnContexts.size()
+ " mCid=" + mCid + " mCreateTime=" + mCreateTime
+ " mLastastFailTime=" + mLastFailTime
+ " mLastFailCause=" + mLastFailCause
+ " mTag=" + mTag
+ " mLinkProperties=" + mLinkProperties
+ " linkCapabilities=" + getNetworkCapabilities()
+ " mRestrictedNetworkOverride=" + mRestrictedNetworkOverride;
}
@Override
public String toString() {
return "{" + toStringSimple() + " mApnContexts=" + mApnContexts + "}";
}
private void dumpToLog() {
dump(null, new PrintWriter(new StringWriter(0)) {
@Override
public void println(String s) {
DataConnection.this.logd(s);
}
@Override
public void flush() {
}
}, null);
}
/**
* Dump the current state.
*
* @param fd
* @param pw
* @param args
*/
@Override
public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
pw.print("DataConnection ");
super.dump(fd, pw, args);
pw.flush();
pw.increaseIndent();
pw.println("mApnContexts.size=" + mApnContexts.size());
pw.println("mApnContexts=" + mApnContexts);
pw.println("mDataConnectionTracker=" + mDct);
pw.println("mApnSetting=" + mApnSetting);
pw.println("mTag=" + mTag);
pw.println("mCid=" + mCid);
pw.println("mConnectionParams=" + mConnectionParams);
pw.println("mDisconnectParams=" + mDisconnectParams);
pw.println("mDcFailCause=" + mDcFailCause);
pw.println("mPhone=" + mPhone);
pw.println("mLinkProperties=" + mLinkProperties);
pw.flush();
pw.println("mDataRegState=" + mDataRegState);
pw.println("mRilRat=" + mRilRat);
pw.println("mNetworkCapabilities=" + getNetworkCapabilities());
pw.println("mCreateTime=" + TimeUtils.logTimeOfDay(mCreateTime));
pw.println("mLastFailTime=" + TimeUtils.logTimeOfDay(mLastFailTime));
pw.println("mLastFailCause=" + mLastFailCause);
pw.println("mUserData=" + mUserData);
pw.println("mSubscriptionOverride=" + Integer.toHexString(mSubscriptionOverride));
pw.println("mRestrictedNetworkOverride=" + mRestrictedNetworkOverride);
pw.println("mUnmeteredUseOnly=" + mUnmeteredUseOnly);
pw.println("mInstanceNumber=" + mInstanceNumber);
pw.println("mAc=" + mAc);
pw.println("Network capabilities changed history:");
pw.increaseIndent();
mNetCapsLocalLog.dump(fd, pw, args);
pw.decreaseIndent();
pw.decreaseIndent();
pw.println();
pw.flush();
}
}