blob: e5765819a371a176c1351d409c6a4c491af34dbd [file] [log] [blame]
/*
* Copyright (C) 2013 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 android.content.Context;
import android.net.INetworkPolicyListener;
import android.net.LinkAddress;
import android.net.LinkProperties.CompareResult;
import android.net.NetworkPolicyManager;
import android.net.NetworkUtils;
import android.os.AsyncResult;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.telephony.AccessNetworkConstants.TransportType;
import android.telephony.DataFailCause;
import android.telephony.PhoneStateListener;
import android.telephony.Rlog;
import android.telephony.TelephonyManager;
import android.telephony.data.DataCallResponse;
import com.android.internal.telephony.DctConstants;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.dataconnection.DataConnection.UpdateLinkPropertyResult;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* Data Connection Controller which is a package visible class and controls
* multiple data connections. For instance listening for unsolicited messages
* and then demultiplexing them to the appropriate DC.
*/
public class DcController extends StateMachine {
private static final boolean DBG = true;
private static final boolean VDBG = false;
private final Phone mPhone;
private final DcTracker mDct;
private final DataServiceManager mDataServiceManager;
private final DcTesterDeactivateAll mDcTesterDeactivateAll;
// package as its used by Testing code
// @GuardedBy("mDcListAll")
final ArrayList<DataConnection> mDcListAll = new ArrayList<>();
// @GuardedBy("mDcListAll")
private final HashMap<Integer, DataConnection> mDcListActiveByCid = new HashMap<>();
/**
* Constants for the data connection activity:
* physical link down/up
*
* TODO: Move to RILConstants.java
*/
static final int DATA_CONNECTION_ACTIVE_PH_LINK_INACTIVE = 0;
static final int DATA_CONNECTION_ACTIVE_PH_LINK_DORMANT = 1;
static final int DATA_CONNECTION_ACTIVE_PH_LINK_UP = 2;
static final int DATA_CONNECTION_ACTIVE_UNKNOWN = Integer.MAX_VALUE;
private DccDefaultState mDccDefaultState = new DccDefaultState();
final TelephonyManager mTelephonyManager;
final NetworkPolicyManager mNetworkPolicyManager;
private PhoneStateListener mPhoneStateListener;
//mExecutingCarrierChange tracks whether the phone is currently executing
//carrier network change
private volatile boolean mExecutingCarrierChange;
/**
* Constructor.
*
* @param name to be used for the Controller
* @param phone the phone associated with Dcc and Dct
* @param dct the DataConnectionTracker associated with Dcc
* @param dataServiceManager the data service manager that manages data services
* @param handler defines the thread/looper to be used with Dcc
*/
private DcController(String name, Phone phone, DcTracker dct,
DataServiceManager dataServiceManager, Handler handler) {
super(name, handler);
setLogRecSize(300);
log("E ctor");
mPhone = phone;
mDct = dct;
mDataServiceManager = dataServiceManager;
addState(mDccDefaultState);
setInitialState(mDccDefaultState);
log("X ctor");
mPhoneStateListener = new PhoneStateListener(handler.getLooper()) {
@Override
public void onCarrierNetworkChange(boolean active) {
mExecutingCarrierChange = active;
}
};
mTelephonyManager = (TelephonyManager) phone.getContext()
.getSystemService(Context.TELEPHONY_SERVICE);
mNetworkPolicyManager = (NetworkPolicyManager) phone.getContext()
.getSystemService(Context.NETWORK_POLICY_SERVICE);
mDcTesterDeactivateAll = (Build.IS_DEBUGGABLE)
? new DcTesterDeactivateAll(mPhone, DcController.this, getHandler())
: null;
if (mTelephonyManager != null) {
mTelephonyManager.listen(mPhoneStateListener,
PhoneStateListener.LISTEN_CARRIER_NETWORK_CHANGE);
}
}
public static DcController makeDcc(Phone phone, DcTracker dct,
DataServiceManager dataServiceManager, Handler handler) {
DcController dcc = new DcController("Dcc", phone, dct, dataServiceManager, handler);
return dcc;
}
void dispose() {
log("dispose: call quiteNow()");
if(mTelephonyManager != null) mTelephonyManager.listen(mPhoneStateListener, 0);
quitNow();
}
void addDc(DataConnection dc) {
synchronized (mDcListAll) {
mDcListAll.add(dc);
}
}
void removeDc(DataConnection dc) {
synchronized (mDcListAll) {
mDcListActiveByCid.remove(dc.mCid);
mDcListAll.remove(dc);
}
}
public void addActiveDcByCid(DataConnection dc) {
if (DBG && dc.mCid < 0) {
log("addActiveDcByCid dc.mCid < 0 dc=" + dc);
}
synchronized (mDcListAll) {
mDcListActiveByCid.put(dc.mCid, dc);
}
}
public DataConnection getActiveDcByCid(int cid) {
synchronized (mDcListAll) {
return mDcListActiveByCid.get(cid);
}
}
void removeActiveDcByCid(DataConnection dc) {
synchronized (mDcListAll) {
DataConnection removedDc = mDcListActiveByCid.remove(dc.mCid);
if (DBG && removedDc == null) {
log("removeActiveDcByCid removedDc=null dc=" + dc);
}
}
}
boolean isExecutingCarrierChange() {
return mExecutingCarrierChange;
}
private final INetworkPolicyListener mListener = new NetworkPolicyManager.Listener() {
@Override
public void onSubscriptionOverride(int subId, int overrideMask, int overrideValue) {
if (mPhone == null || mPhone.getSubId() != subId) return;
final HashMap<Integer, DataConnection> dcListActiveByCid;
synchronized (mDcListAll) {
dcListActiveByCid = new HashMap<>(mDcListActiveByCid);
}
for (DataConnection dc : dcListActiveByCid.values()) {
dc.onSubscriptionOverride(overrideMask, overrideValue);
}
}
};
private class DccDefaultState extends State {
@Override
public void enter() {
if (mPhone != null && mDataServiceManager.getTransportType()
== TransportType.WWAN) {
mPhone.mCi.registerForRilConnected(getHandler(),
DataConnection.EVENT_RIL_CONNECTED, null);
}
mDataServiceManager.registerForDataCallListChanged(getHandler(),
DataConnection.EVENT_DATA_STATE_CHANGED);
if (mNetworkPolicyManager != null) {
mNetworkPolicyManager.registerListener(mListener);
}
}
@Override
public void exit() {
if (mPhone != null & mDataServiceManager.getTransportType()
== TransportType.WWAN) {
mPhone.mCi.unregisterForRilConnected(getHandler());
}
mDataServiceManager.unregisterForDataCallListChanged(getHandler());
if (mDcTesterDeactivateAll != null) {
mDcTesterDeactivateAll.dispose();
}
if (mNetworkPolicyManager != null) {
mNetworkPolicyManager.unregisterListener(mListener);
}
}
@Override
public boolean processMessage(Message msg) {
AsyncResult ar;
switch (msg.what) {
case DataConnection.EVENT_RIL_CONNECTED:
ar = (AsyncResult)msg.obj;
if (ar.exception == null) {
if (DBG) {
log("DccDefaultState: msg.what=EVENT_RIL_CONNECTED mRilVersion=" +
ar.result);
}
} else {
log("DccDefaultState: Unexpected exception on EVENT_RIL_CONNECTED");
}
break;
case DataConnection.EVENT_DATA_STATE_CHANGED:
ar = (AsyncResult)msg.obj;
if (ar.exception == null) {
onDataStateChanged((ArrayList<DataCallResponse>)ar.result);
} else {
log("DccDefaultState: EVENT_DATA_STATE_CHANGED:" +
" exception; likely radio not available, ignore");
}
break;
}
return HANDLED;
}
/**
* Process the new list of "known" Data Calls
* @param dcsList as sent by RIL_UNSOL_DATA_CALL_LIST_CHANGED
*/
private void onDataStateChanged(ArrayList<DataCallResponse> dcsList) {
final ArrayList<DataConnection> dcListAll;
final HashMap<Integer, DataConnection> dcListActiveByCid;
synchronized (mDcListAll) {
dcListAll = new ArrayList<>(mDcListAll);
dcListActiveByCid = new HashMap<>(mDcListActiveByCid);
}
if (DBG) {
lr("onDataStateChanged: dcsList=" + dcsList
+ " dcListActiveByCid=" + dcListActiveByCid);
}
if (VDBG) {
log("onDataStateChanged: mDcListAll=" + dcListAll);
}
// Create hashmap of cid to DataCallResponse
HashMap<Integer, DataCallResponse> dataCallResponseListByCid =
new HashMap<Integer, DataCallResponse>();
for (DataCallResponse dcs : dcsList) {
dataCallResponseListByCid.put(dcs.getCallId(), dcs);
}
// Add a DC that is active but not in the
// dcsList to the list of DC's to retry
ArrayList<DataConnection> dcsToRetry = new ArrayList<DataConnection>();
for (DataConnection dc : dcListActiveByCid.values()) {
if (dataCallResponseListByCid.get(dc.mCid) == null) {
if (DBG) log("onDataStateChanged: add to retry dc=" + dc);
dcsToRetry.add(dc);
}
}
if (DBG) log("onDataStateChanged: dcsToRetry=" + dcsToRetry);
// Find which connections have changed state and send a notification or cleanup
// and any that are in active need to be retried.
ArrayList<ApnContext> apnsToCleanup = new ArrayList<ApnContext>();
boolean isAnyDataCallDormant = false;
boolean isAnyDataCallActive = false;
for (DataCallResponse newState : dcsList) {
DataConnection dc = dcListActiveByCid.get(newState.getCallId());
if (dc == null) {
// UNSOL_DATA_CALL_LIST_CHANGED arrived before SETUP_DATA_CALL completed.
loge("onDataStateChanged: no associated DC yet, ignore");
continue;
}
List<ApnContext> apnContexts = dc.getApnContexts();
if (apnContexts.size() == 0) {
if (DBG) loge("onDataStateChanged: no connected apns, ignore");
} else {
// Determine if the connection/apnContext should be cleaned up
// or just a notification should be sent out.
if (DBG) {
log("onDataStateChanged: Found ConnId=" + newState.getCallId()
+ " newState=" + newState.toString());
}
if (newState.getActive() == DATA_CONNECTION_ACTIVE_PH_LINK_INACTIVE) {
if (mDct.isCleanupRequired.get()) {
apnsToCleanup.addAll(apnContexts);
mDct.isCleanupRequired.set(false);
} else {
DataFailCause failCause = DataFailCause.fromInt(newState.getStatus());
if (failCause.isRadioRestartFailure(mPhone.getContext(),
mPhone.getSubId())) {
if (DBG) {
log("onDataStateChanged: X restart radio, failCause="
+ failCause);
}
mDct.sendRestartRadio();
} else if (mDct.isPermanentFailure(failCause)) {
if (DBG) {
log("onDataStateChanged: inactive, add to cleanup list. "
+ "failCause=" + failCause);
}
apnsToCleanup.addAll(apnContexts);
} else {
if (DBG) {
log("onDataStateChanged: inactive, add to retry list. "
+ "failCause=" + failCause);
}
dcsToRetry.add(dc);
}
}
} else {
// Its active so update the DataConnections link properties
UpdateLinkPropertyResult result = dc.updateLinkProperty(newState);
if (result.oldLp.equals(result.newLp)) {
if (DBG) log("onDataStateChanged: no change");
} else {
if (result.oldLp.isIdenticalInterfaceName(result.newLp)) {
if (! result.oldLp.isIdenticalDnses(result.newLp) ||
! result.oldLp.isIdenticalRoutes(result.newLp) ||
! result.oldLp.isIdenticalHttpProxy(result.newLp) ||
! result.oldLp.isIdenticalAddresses(result.newLp)) {
// If the same address type was removed and
// added we need to cleanup
CompareResult<LinkAddress> car =
result.oldLp.compareAddresses(result.newLp);
if (DBG) {
log("onDataStateChanged: oldLp=" + result.oldLp +
" newLp=" + result.newLp + " car=" + car);
}
boolean needToClean = false;
for (LinkAddress added : car.added) {
for (LinkAddress removed : car.removed) {
if (NetworkUtils.addressTypeMatches(
removed.getAddress(),
added.getAddress())) {
needToClean = true;
break;
}
}
}
if (needToClean) {
if (DBG) {
log("onDataStateChanged: addr change,"
+ " cleanup apns=" + apnContexts
+ " oldLp=" + result.oldLp
+ " newLp=" + result.newLp);
}
apnsToCleanup.addAll(apnContexts);
} else {
if (DBG) log("onDataStateChanged: simple change");
for (ApnContext apnContext : apnContexts) {
mPhone.notifyDataConnection(
PhoneConstants.REASON_LINK_PROPERTIES_CHANGED,
apnContext.getApnType());
}
}
} else {
if (DBG) {
log("onDataStateChanged: no changes");
}
}
} else {
apnsToCleanup.addAll(apnContexts);
if (DBG) {
log("onDataStateChanged: interface change, cleanup apns="
+ apnContexts);
}
}
}
}
}
if (newState.getActive() == DATA_CONNECTION_ACTIVE_PH_LINK_UP) {
isAnyDataCallActive = true;
}
if (newState.getActive() == DATA_CONNECTION_ACTIVE_PH_LINK_DORMANT) {
isAnyDataCallDormant = true;
}
}
if (isAnyDataCallDormant && !isAnyDataCallActive) {
// There is no way to indicate link activity per APN right now. So
// Link Activity will be considered dormant only when all data calls
// are dormant.
// If a single data call is in dormant state and none of the data
// calls are active broadcast overall link state as dormant.
if (DBG) {
log("onDataStateChanged: Data Activity updated to DORMANT. stopNetStatePoll");
}
mDct.sendStopNetStatPoll(DctConstants.Activity.DORMANT);
} else {
if (DBG) {
log("onDataStateChanged: Data Activity updated to NONE. " +
"isAnyDataCallActive = " + isAnyDataCallActive +
" isAnyDataCallDormant = " + isAnyDataCallDormant);
}
if (isAnyDataCallActive) {
mDct.sendStartNetStatPoll(DctConstants.Activity.NONE);
}
}
if (DBG) {
lr("onDataStateChanged: dcsToRetry=" + dcsToRetry
+ " apnsToCleanup=" + apnsToCleanup);
}
// Cleanup connections that have changed
for (ApnContext apnContext : apnsToCleanup) {
mDct.sendCleanUpConnection(true, apnContext);
}
// Retry connections that have disappeared
for (DataConnection dc : dcsToRetry) {
if (DBG) log("onDataStateChanged: send EVENT_LOST_CONNECTION dc.mTag=" + dc.mTag);
dc.sendMessage(DataConnection.EVENT_LOST_CONNECTION, dc.mTag);
}
if (VDBG) log("onDataStateChanged: X");
}
}
/**
* lr is short name for logAndAddLogRec
* @param s
*/
private void lr(String s) {
logAndAddLogRec(s);
}
@Override
protected void log(String s) {
Rlog.d(getName(), s);
}
@Override
protected void loge(String s) {
Rlog.e(getName(), s);
}
/**
* @return the string for msg.what as our info.
*/
@Override
protected String getWhatToString(int what) {
String info = null;
info = DataConnection.cmdToString(what);
return info;
}
@Override
public String toString() {
synchronized (mDcListAll) {
return "mDcListAll=" + mDcListAll + " mDcListActiveByCid=" + mDcListActiveByCid;
}
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
super.dump(fd, pw, args);
pw.println(" mPhone=" + mPhone);
synchronized (mDcListAll) {
pw.println(" mDcListAll=" + mDcListAll);
pw.println(" mDcListActiveByCid=" + mDcListActiveByCid);
}
}
}