blob: 2156e663cdd34e780aa253ebe09053ef8b0271ba [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.annotation.IntDef;
import android.hardware.radio.V1_4.DataConnActiveStatus;
import android.net.LinkAddress;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.RegistrantList;
import android.telephony.AccessNetworkConstants;
import android.telephony.DataFailCause;
import android.telephony.data.ApnSetting;
import android.telephony.data.DataCallResponse;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.DctConstants;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.dataconnection.DataConnection.UpdateLinkPropertyResult;
import com.android.internal.telephony.util.TelephonyUtils;
import com.android.net.module.util.LinkPropertiesUtils;
import com.android.net.module.util.LinkPropertiesUtils.CompareOrUpdateResult;
import com.android.net.module.util.NetUtils;
import com.android.telephony.Rlog;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
/**
* 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 Handler {
private static final boolean DBG = true;
private static final boolean VDBG = false;
/** Physical link state unknown */
public static final int PHYSICAL_LINK_UNKNOWN = 0;
/** Physical link state inactive (i.e. RRC idle) */
public static final int PHYSICAL_LINK_NOT_ACTIVE = 1;
/** Physical link state active (i.e. RRC connected) */
public static final int PHYSICAL_LINK_ACTIVE = 2;
/** @hide */
@IntDef(prefix = { "PHYSICAL_LINK_" }, value = {
PHYSICAL_LINK_UNKNOWN,
PHYSICAL_LINK_NOT_ACTIVE,
PHYSICAL_LINK_ACTIVE
})
@Retention(RetentionPolicy.SOURCE)
public @interface PhysicalLinkState{}
private final Phone mPhone;
private final DcTracker mDct;
private final String mTag;
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<>();
/**
* Aggregated physical link state from all data connections. This reflects the device's RRC
* connection state.
* If {@link CarrierConfigManager.KEY_LTE_ENDC_USING_USER_DATA_FOR_RRC_DETECTION_BOOL} is true,
* then This reflects "internet data connection" instead of RRC state.
*/
private @PhysicalLinkState int mPhysicalLinkState = PHYSICAL_LINK_UNKNOWN;
private RegistrantList mPhysicalLinkStateChangedRegistrants = new RegistrantList();
/**
* 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 looper looper for this handler
*/
private DcController(String name, Phone phone, DcTracker dct,
DataServiceManager dataServiceManager, Looper looper) {
super(looper);
mPhone = phone;
mDct = dct;
mTag = name;
mDataServiceManager = dataServiceManager;
mDcTesterDeactivateAll = (TelephonyUtils.IS_DEBUGGABLE)
? new DcTesterDeactivateAll(mPhone, DcController.this, this)
: null;
mDataServiceManager.registerForDataCallListChanged(this,
DataConnection.EVENT_DATA_STATE_CHANGED);
}
public static DcController makeDcc(Phone phone, DcTracker dct,
DataServiceManager dataServiceManager, Looper looper,
String tagSuffix) {
return new DcController("Dcc" + tagSuffix, phone, dct, dataServiceManager, looper);
}
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);
}
}
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 isDefaultDataActive() {
synchronized (mDcListAll) {
return mDcListActiveByCid.values().stream()
.anyMatch(dc -> dc.getApnContexts().stream()
.anyMatch(apn -> apn.getApnTypeBitmask() == ApnSetting.TYPE_DEFAULT));
}
}
@Override
public void handleMessage(Message msg) {
AsyncResult ar;
switch (msg.what) {
case DataConnection.EVENT_DATA_STATE_CHANGED:
ar = (AsyncResult) msg.obj;
if (ar.exception == null) {
onDataStateChanged((ArrayList<DataCallResponse>) ar.result);
} else {
log("EVENT_DATA_STATE_CHANGED: exception; likely radio not available, ignore");
}
break;
default:
loge("Unexpected event " + msg);
break;
}
}
/**
* 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 HashMap<Integer, DataConnection> dcListActiveByCid;
synchronized (mDcListAll) {
dcListActiveByCid = new HashMap<>(mDcListActiveByCid);
}
if (DBG) {
log("onDataStateChanged: dcsList=" + dcsList
+ " dcListActiveByCid=" + dcListActiveByCid);
}
// Create hashmap of cid to DataCallResponse
HashMap<Integer, DataCallResponse> dataCallResponseListByCid =
new HashMap<Integer, DataCallResponse>();
for (DataCallResponse dcs : dcsList) {
dataCallResponseListByCid.put(dcs.getId(), 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;
boolean isInternetDataCallActive = false;
for (DataCallResponse newState : dcsList) {
DataConnection dc = dcListActiveByCid.get(newState.getId());
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.getId()
+ " newState=" + newState.toString());
}
if (apnContexts.stream().anyMatch(
i -> ApnSetting.TYPE_DEFAULT_STRING.equals(i.getApnType()))
&& newState.getLinkStatus() == DataConnActiveStatus.ACTIVE) {
isInternetDataCallActive = true;
}
if (newState.getLinkStatus() == DataConnActiveStatus.INACTIVE) {
if (mDct.isCleanupRequired.get()) {
apnsToCleanup.addAll(apnContexts);
mDct.isCleanupRequired.set(false);
} else {
int failCause = DataFailCause.getFailCause(newState.getCause());
if (DataFailCause.isRadioRestartFailure(mPhone.getContext(), failCause,
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 {
// Update the pdu session id
dc.setPduSessionId(newState.getPduSessionId());
dc.updatePcscfAddr(newState);
// Its active so update the DataConnections link properties
UpdateLinkPropertyResult result = dc.updateLinkProperty(newState);
dc.updateResponseFields(newState);
if (result.oldLp.equals(result.newLp)) {
if (DBG) log("onDataStateChanged: no change");
} else {
if (LinkPropertiesUtils.isIdenticalInterfaceName(
result.oldLp, result.newLp)) {
if (!LinkPropertiesUtils.isIdenticalDnses(
result.oldLp, result.newLp)
|| !LinkPropertiesUtils.isIdenticalRoutes(
result.oldLp, result.newLp)
|| !LinkPropertiesUtils.isIdenticalHttpProxy(
result.oldLp, result.newLp)
|| !LinkPropertiesUtils.isIdenticalAddresses(
result.oldLp, result.newLp)) {
// If the same address type was removed and
// added we need to cleanup
CompareOrUpdateResult<Integer, LinkAddress> car =
new CompareOrUpdateResult(
result.oldLp != null
? result.oldLp.getLinkAddresses() : null,
result.newLp != null
? result.newLp.getLinkAddresses() : null,
(la) -> Objects.hash(((LinkAddress) la)
.getAddress(),
((LinkAddress) la).getPrefixLength(),
((LinkAddress) la).getScope()));
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 (NetUtils.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: no changes");
}
}
} else {
apnsToCleanup.addAll(apnContexts);
if (DBG) {
log("onDataStateChanged: interface change, cleanup apns="
+ apnContexts);
}
}
}
}
}
if (newState.getLinkStatus() == DataConnActiveStatus.ACTIVE) {
isAnyDataCallActive = true;
}
if (newState.getLinkStatus() == DataConnActiveStatus.DORMANT) {
isAnyDataCallDormant = true;
}
}
if (mDataServiceManager.getTransportType()
== AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
boolean isPhysicalLinkStateFocusingOnInternetData =
mDct.getLteEndcUsingUserDataForIdleDetection();
int physicalLinkState =
(isPhysicalLinkStateFocusingOnInternetData
? isInternetDataCallActive : isAnyDataCallActive)
? PHYSICAL_LINK_ACTIVE : PHYSICAL_LINK_NOT_ACTIVE;
if (mPhysicalLinkState != physicalLinkState) {
mPhysicalLinkState = physicalLinkState;
mPhysicalLinkStateChangedRegistrants.notifyResult(mPhysicalLinkState);
}
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 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) {
log("onDataStateChanged: dcsToRetry=" + dcsToRetry
+ " apnsToCleanup=" + apnsToCleanup);
}
// Cleanup connections that have changed
for (ApnContext apnContext : apnsToCleanup) {
mDct.cleanUpConnection(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");
}
/**
* Register for physical link state (i.e. RRC state) changed event.
* if {@link CarrierConfigManager.KEY_LTE_ENDC_USING_USER_DATA_FOR_RRC_DETECTION_BOOL} is true,
* then physical link state is focusing on "internet data connection" instead of RRC state.
* @param h The handler
* @param what The event
*/
@VisibleForTesting
public void registerForPhysicalLinkStateChanged(Handler h, int what) {
mPhysicalLinkStateChangedRegistrants.addUnique(h, what, null);
}
/**
* Unregister from physical link state (i.e. RRC state) changed event.
*
* @param h The previously registered handler
*/
void unregisterForPhysicalLinkStateChanged(Handler h) {
mPhysicalLinkStateChangedRegistrants.remove(h);
}
private void log(String s) {
Rlog.d(mTag, s);
}
private void loge(String s) {
Rlog.e(mTag, s);
}
@Override
public String toString() {
synchronized (mDcListAll) {
return "mDcListAll=" + mDcListAll + " mDcListActiveByCid=" + mDcListActiveByCid;
}
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println(" mPhone=" + mPhone);
synchronized (mDcListAll) {
pw.println(" mDcListAll=" + mDcListAll);
pw.println(" mDcListActiveByCid=" + mDcListActiveByCid);
}
}
}