blob: dabb9f3cd7c80a39e01e85124cfbbefbbd43a234 [file] [log] [blame]
/*
* Copyright 2021 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.data;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.ConnectivityManager;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkAgent;
import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkFactory;
import android.net.NetworkProvider;
import android.net.NetworkScore;
import android.net.ProxyInfo;
import android.net.RouteInfo;
import android.net.TelephonyNetworkSpecifier;
import android.net.Uri;
import android.os.AsyncResult;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.telephony.AccessNetworkConstants;
import android.telephony.AccessNetworkConstants.AccessNetworkType;
import android.telephony.AccessNetworkConstants.TransportType;
import android.telephony.Annotation.DataFailureCause;
import android.telephony.Annotation.DataState;
import android.telephony.Annotation.NetworkType;
import android.telephony.Annotation.ValidationStatus;
import android.telephony.DataFailCause;
import android.telephony.LinkCapacityEstimate;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.PreciseDataConnectionState;
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.telephony.data.QosBearerSession;
import android.telephony.data.TrafficDescriptor;
import android.text.TextUtils;
import android.util.IndentingPrintWriter;
import android.util.LocalLog;
import android.util.Pair;
import android.util.SparseArray;
import android.util.SparseIntArray;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.PhoneFactory;
import com.android.internal.telephony.data.DataNetworkController.NetworkRequestList;
import com.android.internal.telephony.data.TelephonyNetworkAgent.TelephonyNetworkAgentCallback;
import com.android.internal.telephony.dataconnection.AccessNetworksManager;
import com.android.internal.telephony.metrics.TelephonyMetrics;
import com.android.internal.util.IState;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import com.android.net.module.util.NetworkCapabilitiesUtils;
import com.android.telephony.Rlog;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
* DataNetwork class represents a single PDN (Packet Data Network).
*
* The life cycle of a data network starts from {@link ConnectingState}. If setup data request
* succeeds, then it enters {@link ConnectedState}, otherwise it enters
* {@link DisconnectedState}.
*
* When data network is in {@link ConnectingState}, it can enter {@link HandoverState} if handover
* between IWLAN and cellular occurs. After handover completes or fails, it return back to
* {@link ConnectedState}. When the data network is about to be disconnected, it first enters
* {@link DisconnectingState} when performing graceful tear down or when sending the data
* deactivation request. At the end, it enters {@link DisconnectedState} when {@link DataService}
* notifies data disconnected. Note that a unsolicited disconnected event from {@link DataService}
* can immediately move data network transited from {@link ConnectedState} to
* {@link DisconnectedState}. {@link DisconnectedState} is the final state of a data network.
*
* State machine diagram:
*
* ┌─────────────────────────────────────────┐
* │ │
* │ ┌─────────┐ │
* │ │Handover │ │
* │ └─▲────┬──┘ │
* │ │ │ │
* ┌─────┴─────┐ ┌─┴────▼──┐ ┌───────▼──────┐
* │Connecting ├────────►Connected├────────►Disconnecting │
* └─────┬─────┘ └────┬────┘ └───────┬──────┘
* │ │ │
* │ ┌─────▼──────┐ │
* └─────────────►Disconnected◄──────────────┘
* └────────────┘
*
*/
public class DataNetwork extends StateMachine {
private static final boolean VDBG = false;
/** Event for data config updated. */
private static final int EVENT_DATA_CONFIG_UPDATED = 1;
/** Event for attaching a network request. */
private static final int EVENT_ATTACH_NETWORK_REQUEST = 2;
/** Event for detaching a network request. */
private static final int EVENT_DETACH_NETWORK_REQUEST = 3;
/** Event for allocating PDU session id response. */
private static final int EVENT_ALLOCATE_PDU_SESSION_ID_RESPONSE = 5;
/** Event for setup data network response. */
private static final int EVENT_SETUP_DATA_CALL_RESPONSE = 6;
/** Event for tearing down data network. */
private static final int EVENT_TEAR_DOWN_NETWORK = 7;
/** Event triggered by {@link DataServiceCallback#onDataCallListChanged(List)}. */
private static final int EVENT_DATA_STATE_CHANGED = 8;
/** Data network service state changed event. */
private static final int EVENT_SERVICE_STATE_CHANGED = 9;
/** Event for detaching all network requests. */
private static final int EVENT_DETACH_ALL_NETWORK_REQUESTS = 10;
/** Event for bandwidth estimation from the modem changed. */
private static final int EVENT_BANDWIDTH_ESTIMATE_FROM_MODEM_CHANGED = 11;
/** Event for bandwidth estimation from the bandwidth estimator changed. */
private static final int EVENT_BANDWIDTH_ESTIMATE_FROM_BANDWIDTH_ESTIMATOR_CHANGED = 12;
/** Event for display info changed. This is for getting 5G NSA or mmwave information. */
private static final int EVENT_DISPLAY_INFO_CHANGED = 13;
/** The default MTU for IPv4 network */
private static final int DEFAULT_MTU_V4 = 1280;
/** The default MTU for IPv6 network */
private static final int DEFAULT_MTU_V6 = 1280;
/** Invalid context id. */
private static final int INVALID_CID = -1;
/**
* The data network providing default internet will have a higher score of 50. Other network
* will have a slightly lower score of 45. The intention is other connections will not cause
* connectivity service to tear down default internet connection. For example, to validate
* internet connection on non-default data SIM, we'll set up a temporary internet network on
* that data SIM. In this case, score of 45 is assigned so connectivity service will not replace
* the default internet network with it.
*/
private static final int DEFAULT_INTERNET_NETWORK_SCORE = 50;
private static final int OTHER_NETWORK_SCORE = 45;
@IntDef(prefix = {"DEACTIVATION_REASON_"},
value = {
TEAR_DOWN_REASON_CONNECTIVITY_SERVICE_UNWANTED,
TEAR_DOWN_REASON_SIM_REMOVAL,
TEAR_DOWN_REASON_AIRPLANE_MODE_ON,
TEAR_DOWN_REASON_DATA_DISABLED,
TEAR_DOWN_REASON_NO_LIVE_REQUEST,
TEAR_DOWN_REASON_RAT_NOT_ALLOWED,
TEAR_DOWN_REASON_ROAMING_DISABLED,
TEAR_DOWN_REASON_CONCURRENT_VOICE_DATA_NOT_ALLOWED,
TEAR_DOWN_REASON_DATA_RESTRICTED_BY_NETWORK,
TEAR_DOWN_REASON_DATA_SERVICE_NOT_READY,
TEAR_DOWN_REASON_POWER_OFF_BY_CARRIER,
})
public @interface TearDownReason {}
/** Data network tear down requested by connectivity service. */
public static final int TEAR_DOWN_REASON_CONNECTIVITY_SERVICE_UNWANTED = 1;
/** Data network tear down due to SIM removal. */
public static final int TEAR_DOWN_REASON_SIM_REMOVAL = 2;
/** Data network tear down due to airplane mode turned on. */
public static final int TEAR_DOWN_REASON_AIRPLANE_MODE_ON = 3;
/** Data network tear down due to data disabled (by user, policy, carrier, etc...). */
public static final int TEAR_DOWN_REASON_DATA_DISABLED = 4;
/** Data network tear down due to no live network request. */
public static final int TEAR_DOWN_REASON_NO_LIVE_REQUEST = 5;
/** Data network tear down due to current RAT is not allowed by the data profile. */
public static final int TEAR_DOWN_REASON_RAT_NOT_ALLOWED = 6;
/** Data network tear down due to data roaming not enabled. */
public static final int TEAR_DOWN_REASON_ROAMING_DISABLED = 7;
/** Data network tear down due to concurrent voice/data not allowed. */
public static final int TEAR_DOWN_REASON_CONCURRENT_VOICE_DATA_NOT_ALLOWED = 8;
/** Data network tear down due to network restricted. */
public static final int TEAR_DOWN_REASON_DATA_RESTRICTED_BY_NETWORK = 9;
/** Data network tear down due to data service unbound. */
public static final int TEAR_DOWN_REASON_DATA_SERVICE_NOT_READY = 10;
/** Data network tear down due to radio turned off by the carrier. */
public static final int TEAR_DOWN_REASON_POWER_OFF_BY_CARRIER = 11;
@IntDef(prefix = {"BANDWIDTH_SOURCE_"},
value = {
BANDWIDTH_SOURCE_UNKNOWN,
BANDWIDTH_SOURCE_MODEM,
BANDWIDTH_SOURCE_CARRIER_CONFIG,
BANDWIDTH_SOURCE_BANDWIDTH_ESTIMATOR,
})
public @interface BandwidthEstimationSource {}
/** Indicates the bandwidth estimation source is unknown. This must be a configuration error. */
public static final int BANDWIDTH_SOURCE_UNKNOWN = 0;
/** Indicates the bandwidth estimation source is from the modem. */
public static final int BANDWIDTH_SOURCE_MODEM = 1;
/** Indicates the bandwidth estimation source is from the static carrier config. */
public static final int BANDWIDTH_SOURCE_CARRIER_CONFIG = 2;
/** Indicates the bandwidth estimation source is from {@link LinkBandwidthEstimator}. */
public static final int BANDWIDTH_SOURCE_BANDWIDTH_ESTIMATOR = 3;
/** The parent state. Any messages not handled by the child state fallback to this. */
private final DefaultState mDefaultState = new DefaultState();
/**
* The connecting state. This is the initial state of a data network.
*
* @see DataNetwork for the state machine diagram.
*/
private final ConnectingState mConnectingState = new ConnectingState();
/**
* The connected state. This is the state when data network becomes usable.
*
* @see DataNetwork for the state machine diagram.
*/
private final ConnectedState mConnectedState = new ConnectedState();
/**
* The handover state. This is the state when data network handover between IWLAN and cellular.
*
* @see DataNetwork for the state machine diagram.
*/
private final HandoverState mHandoverState = new HandoverState();
/**
* The disconnecting state. This is the state when data network is about to be disconnected.
* The network is still usable in this state, but the clients should be prepared to lose the
* network in any moment.
*
* @see DataNetwork for the state machine diagram.
*/
private final DisconnectingState mDisconnectingState = new DisconnectingState();
/**
* The disconnected state. This is the final state of a data network.
*
* @see DataNetwork for the state machine diagram.
*/
private final DisconnectedState mDisconnectedState = new DisconnectedState();
/** The phone instance. */
private final @NonNull Phone mPhone;
/**
* The subscription id. This is assigned when the network is created, and not supposed to
* change afterwards.
*/
private final int mSubId;
/**
* Indicates that
* {@link DataService.DataServiceProvider#deactivateDataCall(int, int, DataServiceCallback)}
* has been called. This flag can be only changed from {@code false} to {@code true}.
*/
private boolean mInvokedDataDeactivation = false;
/** RIL interface. */
private final @NonNull CommandsInterface mRil;
/** Local log. */
private final LocalLog mLocalLog = new LocalLog(128);
/** The callback to receives data network state update. */
private final @NonNull DataNetworkCallback mDataNetworkCallback;
/** The log tag. */
private String mLogTag;
/**
* The unique context id assigned by the data service in {@link DataCallResponse#getId()}. One
* for {@link AccessNetworkConstants#TRANSPORT_TYPE_WWAN} and one for
* {@link AccessNetworkConstants#TRANSPORT_TYPE_WLAN}. The reason for storing both is that
* during handover, both cid will be used.
*/
private SparseIntArray mCid = new SparseIntArray(2);
/** PDU session id. */
private int mPduSessionId = DataCallResponse.PDU_SESSION_ID_NOT_SET;
/**
* Data service managers for accessing {@link AccessNetworkConstants#TRANSPORT_TYPE_WWAN} and
* {@link AccessNetworkConstants#TRANSPORT_TYPE_WLAN} data services.
*/
private final @NonNull SparseArray<DataServiceManager> mDataServiceManagers;
/** Access networks manager. */
private final @NonNull AccessNetworksManager mAccessNetworksManager;
/** Data network controller. */
private final @NonNull DataNetworkController mDataNetworkController;
/** Data config manager. */
private final @NonNull DataConfigManager mDataConfigManager;
/** The network agent associated with this data network. */
private @NonNull TelephonyNetworkAgent mNetworkAgent;
/** QOS callback tracker. This is only created after network connected on WWAN. */
private @Nullable QosCallbackTracker mQosCallbackTracker;
/** The data profile used to establish this data network. */
private final @NonNull DataProfile mDataProfile;
/** The network capabilities of this data network. */
private @NonNull NetworkCapabilities mNetworkCapabilities;
/** The link properties of this data network. */
private @NonNull LinkProperties mLinkProperties;
/** The network bandwidth. */
private @NonNull NetworkBandwidth mNetworkBandwidth = new NetworkBandwidth(14, 14);
/** The network requests associated with this data network */
private @NonNull NetworkRequestList mAttachedNetworkRequestList = new NetworkRequestList();
/**
* The latest data call response received from either
* {@link DataServiceCallback#onSetupDataCallComplete(int, DataCallResponse)} or
* {@link DataServiceCallback#onDataCallListChanged(List)}. The very first update must be
* from {@link DataServiceCallback#onSetupDataCallComplete(int, DataCallResponse)}.
*/
private @Nullable DataCallResponse mDataCallResponse = null;
/**
* The fail cause from either setup data failure or unsolicited disconnect reported by data
* service.
*/
private @DataFailureCause int mFailCause = DataFailCause.NONE;
/**
* Indicates if data network is suspended. Note this is slightly different from the
* {@link TelephonyManager#DATA_SUSPENDED}, which is only possible when data network is in
* connected state. This flag reflects to the
* {@link NetworkCapabilities#NET_CAPABILITY_NOT_SUSPENDED} which can happen when data network
* is in connected or disconnecting state.
*/
private boolean mSuspended = false;
/**
* The current transport of the data network. For handover, the current transport will be set
* after handover completes.
*/
private @TransportType int mTransport = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
/**
* The preferred transport of the data network. If the preferred transport is different from
* the current transport, then handover will happen.
*/
private @TransportType int mPreferredTransport = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
/** The QOS bearer sessions. */
private @NonNull List<QosBearerSession> mQosBearerSessions = new ArrayList<>();
/**
* The network bandwidth.
*/
public static class NetworkBandwidth {
/** The downlink bandwidth in Kbps. */
public final int downlinkBandwidthKbps;
/** The uplink Bandwidth in Kbps. */
public final int uplinkBandwidthKbps;
/**
* Constructor.
*
* @param downlinkBandwidthKbps The downlink bandwidth in Kbps.
* @param uplinkBandwidthKbps The uplink Bandwidth in Kbps.
*/
public NetworkBandwidth(int downlinkBandwidthKbps, int uplinkBandwidthKbps) {
this.downlinkBandwidthKbps = downlinkBandwidthKbps;
this.uplinkBandwidthKbps = uplinkBandwidthKbps;
}
@Override
public String toString() {
return String.format("NetworkBandwidth=[downlink=%d, uplink=%d]",
downlinkBandwidthKbps, uplinkBandwidthKbps);
}
}
/**
* Data network callback. Should only be used by {@link DataNetworkController}.
*/
public abstract static class DataNetworkCallback extends DataCallback {
/**
* Constructor
*
* @param executor The executor of the callback.
*/
public DataNetworkCallback(@NonNull @CallbackExecutor Executor executor) {
super(executor);
}
/**
* Called when data setup failed.
*
* @param dataNetwork The data network.
* @param requestList The network requests attached to this data network.
* @param cause The fail cause of setup data network.
* @param retryDurationMillis The network suggested data retry duration in milliseconds as
* specified in 3GPP TS 24.302 section 8.2.9.1. The {@link DataProfile} associated to this
* data network will be throttled for the specified duration unless
* {@link DataServiceCallback#onApnUnthrottled} is called. {@link Long#MAX_VALUE} indicates
* data retry should not occur. {@link DataCallResponse#RETRY_DURATION_UNDEFINED} indicates
* network did not suggest any retry duration.
*/
public abstract void onSetupDataFailed(@NonNull DataNetwork dataNetwork,
@NonNull NetworkRequestList requestList, @DataFailureCause int cause,
long retryDurationMillis);
/**
* Called when data network enters {@link ConnectedState}.
*
* @param dataNetwork The data network.
*/
public abstract void onConnected(@NonNull DataNetwork dataNetwork);
/**
* Called when data network validation status changed.
*
* @param dataNetwork The data network.
* @param status one of {@link NetworkAgent#VALIDATION_STATUS_VALID} or
* {@link NetworkAgent#VALIDATION_STATUS_NOT_VALID}.
* @param redirectUri If internet connectivity is being redirected (e.g., on a captive
* portal), this is the destination the probes are being redirected to, otherwise
* {@code null}.
*/
public abstract void onValidationStatusChanged(@NonNull DataNetwork dataNetwork,
@ValidationStatus int status, @Nullable Uri redirectUri);
/**
* Called when data network suspended state changed.
*
* @param dataNetwork The data network.
* @param suspended {@code true} if data is suspended.
*/
public abstract void onSuspendedStateChanged(@NonNull DataNetwork dataNetwork,
boolean suspended);
/**
* Called when network requests were failed to attach to the data network.
*
* @param dataNetwork The data network.
* @param requestList The requests failed to attach.
*/
public abstract void onAttachFailed(@NonNull DataNetwork dataNetwork,
@NonNull NetworkRequestList requestList);
/**
* Called when data network enters {@link DisconnectedState}. Note this is only called
* when the data network was previously connected. For setup data network failed,
* {@link #onSetupDataFailed(DataNetwork, NetworkRequestList, int, long)} is called.
*
* @param dataNetwork The data network.
* @param cause The disconnect cause.
*/
public abstract void onDisconnected(@NonNull DataNetwork dataNetwork,
@DataFailureCause int cause);
}
/**
* Constructor
*
* @param phone The phone instance.
* @param looper The looper to be used by the state machine. Currently the handler thread is the
* phone process's main thread.
* @param dataServiceManagers Data service managers.
* @param dataProfile The data profile for establishing the data network.
* @param networkRequestList The initial network requests attached to this data network.
* @param callback The callback to receives data network state update.
*/
public DataNetwork(@NonNull Phone phone, @NonNull Looper looper,
@NonNull SparseArray<DataServiceManager> dataServiceManagers,
@NonNull DataProfile dataProfile,
@NonNull NetworkRequestList networkRequestList,
@NonNull DataNetworkCallback callback) {
super("DataNetwork", looper);
mPhone = phone;
mSubId = phone.getSubId();
mRil = mPhone.mCi;
mLinkProperties = new LinkProperties();
mDataServiceManagers = dataServiceManagers;
mAccessNetworksManager = phone.getAccessNetworksManager();
mDataNetworkController = phone.getDataNetworkController();
mDataConfigManager = mDataNetworkController.getDataConfigManager();
mDataNetworkCallback = callback;
mDataProfile = dataProfile;
dataProfile.setLastSetupTimestamp(SystemClock.elapsedRealtime());
mAttachedNetworkRequestList.addAll(networkRequestList);
mCid.put(AccessNetworkConstants.TRANSPORT_TYPE_WWAN, INVALID_CID);
mCid.put(AccessNetworkConstants.TRANSPORT_TYPE_WLAN, INVALID_CID);
for (TelephonyNetworkRequest networkRequest : networkRequestList) {
networkRequest.setAttachedNetwork(DataNetwork.this);
networkRequest.setState(
TelephonyNetworkRequest.REQUEST_STATE_SATISFIED);
}
addState(mDefaultState);
addState(mConnectingState, mDefaultState);
addState(mConnectedState, mDefaultState);
addState(mHandoverState, mDefaultState);
addState(mDisconnectingState, mDefaultState);
addState(mDisconnectedState, mDefaultState);
setInitialState(mConnectingState);
/**
* This will trigger {@link DefaultState#enter()}, and then {@link ConnectingState#enter()}.
* Check {@link StateMachine} class to see how Android state machine works.
*/
start();
// Do not add more stuffs here.
}
/**
* Create the telephony network agent.
*
* @return The telephony network agent.
*/
private @NonNull TelephonyNetworkAgent createNetworkAgent() {
final NetworkAgentConfig.Builder configBuilder = new NetworkAgentConfig.Builder();
configBuilder.setLegacyType(ConnectivityManager.TYPE_MOBILE);
configBuilder.setLegacyTypeName("MOBILE");
int networkType = getDataNetworkType();
configBuilder.setLegacySubType(networkType);
configBuilder.setLegacySubTypeName(TelephonyManager.getNetworkTypeName(networkType));
if (mDataProfile.getApnSetting() != null) {
configBuilder.setLegacyExtraInfo(mDataProfile.getApnSetting().getApnName());
}
final NetworkFactory factory = PhoneFactory.getNetworkFactory(
mPhone.getPhoneId());
final NetworkProvider provider = (null == factory) ? null : factory.getProvider();
return new TelephonyNetworkAgent(mPhone, getHandler().getLooper(), this,
getNetworkScore(), configBuilder.build(), provider,
new TelephonyNetworkAgentCallback(getHandler()::post) {
@Override
public void onValidationStatus(@ValidationStatus int status,
@Nullable Uri redirectUri) {
mDataNetworkCallback.invokeFromExecutor(
() -> mDataNetworkCallback.onValidationStatusChanged(
DataNetwork.this, status, redirectUri));
}
});
}
/**
* The default state. Any events that were not handled by the child states fallback to this
* state.
*
* @see DataNetwork for the state machine diagram.
*/
private final class DefaultState extends State {
@Override
public void enter() {
logv("Registering all events.");
mDataConfigManager.registerForConfigUpdate(getHandler(), EVENT_DATA_CONFIG_UPDATED);
mDataServiceManagers.get(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
.registerForDataCallListChanged(getHandler(), EVENT_DATA_STATE_CHANGED);
if (!mAccessNetworksManager.isInLegacyMode()) {
mDataServiceManagers.get(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
.registerForDataCallListChanged(getHandler(), EVENT_DATA_STATE_CHANGED);
mPhone.getServiceStateTracker().registerForDataRegStateOrRatChanged(
AccessNetworkConstants.TRANSPORT_TYPE_WLAN, getHandler(),
EVENT_SERVICE_STATE_CHANGED,
AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
}
mPhone.getServiceStateTracker().registerForDataRegStateOrRatChanged(
AccessNetworkConstants.TRANSPORT_TYPE_WWAN, getHandler(),
EVENT_SERVICE_STATE_CHANGED,
AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
// Only add symmetric code here, for example, registering and unregistering.
// DefaultState.enter() is the starting point in the life cycle of the DataNetwork,
// and DefaultState.exit() is the end. For non-symmetric initializing works, put them
// in ConnectingState.enter().
}
@Override
public void exit() {
logv("Unregistering all events.");
mPhone.getServiceStateTracker().unregisterForDataRegStateOrRatChanged(
AccessNetworkConstants.TRANSPORT_TYPE_WWAN, getHandler());
if (!mAccessNetworksManager.isInLegacyMode()) {
mDataServiceManagers.get(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
.unregisterForDataCallListChanged(getHandler());
mPhone.getServiceStateTracker().unregisterForDataRegStateOrRatChanged(
AccessNetworkConstants.TRANSPORT_TYPE_WLAN, getHandler());
}
mDataServiceManagers.get(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
.unregisterForDataCallListChanged(getHandler());
mDataConfigManager.unregisterForConfigUpdate(getHandler());
}
@Override
public boolean processMessage(Message msg) {
switch (msg.what) {
case EVENT_DATA_CONFIG_UPDATED:
onDataConfigUpdated();
break;
case EVENT_SERVICE_STATE_CHANGED: {
// TODO: Should update suspend state when CSS indicator changes.
// TODO: Should update suspend state when call started/ended.
updateSuspendState();
// TODO: Update TCP buffer size
// TODO: Update Bandwidth
break;
}
case EVENT_ATTACH_NETWORK_REQUEST: {
NetworkRequestList requestList = (NetworkRequestList) msg.obj;
NetworkRequestList failedList = new NetworkRequestList();
for (TelephonyNetworkRequest networkRequest : requestList) {
if (networkRequest.canBeSatisfiedBy(getNetworkCapabilities())) {
mAttachedNetworkRequestList.add(networkRequest);
networkRequest.setAttachedNetwork(DataNetwork.this);
networkRequest.setState(
TelephonyNetworkRequest.REQUEST_STATE_SATISFIED);
log("Successfully attached network request " + networkRequest);
} else {
failedList.add(networkRequest);
log("Attached failed. Cannot satisfy the network request "
+ networkRequest);
}
if (failedList.size() > 0) {
mDataNetworkCallback.invokeFromExecutor(() -> mDataNetworkCallback
.onAttachFailed(DataNetwork.this, failedList));
}
}
break;
}
case EVENT_DETACH_NETWORK_REQUEST: {
TelephonyNetworkRequest networkRequest = (TelephonyNetworkRequest) msg.obj;
mAttachedNetworkRequestList.remove(networkRequest);
networkRequest.setState(TelephonyNetworkRequest.REQUEST_STATE_UNSATISFIED);
networkRequest.setAttachedNetwork(null);
break;
}
case EVENT_DETACH_ALL_NETWORK_REQUESTS: {
for (TelephonyNetworkRequest networkRequest : mAttachedNetworkRequestList) {
networkRequest.setState(TelephonyNetworkRequest.REQUEST_STATE_UNSATISFIED);
networkRequest.setAttachedNetwork(null);
}
mAttachedNetworkRequestList.clear();
break;
}
case EVENT_DATA_STATE_CHANGED: {
AsyncResult ar = (AsyncResult) msg.obj;
int transport = (int) ar.userObj;
onDataStateChanged(transport, (ArrayList<DataCallResponse>) ar.result);
break;
}
case EVENT_BANDWIDTH_ESTIMATE_FROM_MODEM_CHANGED:
case EVENT_BANDWIDTH_ESTIMATE_FROM_BANDWIDTH_ESTIMATOR_CHANGED:
case EVENT_TEAR_DOWN_NETWORK:
// Ignore the events when not in the correct state.
break;
default:
loge("Unhandled event " + eventToString(msg.what));
break;
}
return HANDLED;
}
}
/**
* The connecting state. This is the initial state of a data network.
*
* @see DataNetwork for the state machine diagram.
*/
private final class ConnectingState extends State {
@Override
public void enter() {
// Need to update the transport so we have the correct log tag.
updatePreferredTransports();
// Need to calculate the initial capabilities before creating the network agent.
updateNetworkCapabilities();
mNetworkAgent = createNetworkAgent();
mLogTag = "DN-" + mNetworkAgent.getId() + "-"
+ ((mTransport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) ? "C" : "I");
notifyPreciseDataConnectionState();
if (mTransport == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
allocatePduSessionId();
return;
}
setupData();
}
@Override
public void exit() {
}
@Override
public boolean processMessage(Message msg) {
logv("event=" + eventToString(msg.what));
switch (msg.what) {
case EVENT_ALLOCATE_PDU_SESSION_ID_RESPONSE:
AsyncResult ar = (AsyncResult) msg.obj;
if (ar.exception == null) {
mPduSessionId = (int) ar.result;
log("Set PDU session id to " + mPduSessionId);
} else {
loge("Failed to allocate PDU session id. e=" + ar.exception);
}
setupData();
break;
case EVENT_SETUP_DATA_CALL_RESPONSE:
int resultCode = msg.arg1;
DataCallResponse dataCallResponse =
msg.getData().getParcelable(DataServiceManager.DATA_CALL_RESPONSE);
onSetupResponse(resultCode, dataCallResponse);
break;
case EVENT_TEAR_DOWN_NETWORK:
// Defer the tear down request until connected or disconnected.
deferMessage(msg);
break;
case EVENT_DATA_STATE_CHANGED:
// Ignore any data call list changed event before connected.
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
}
/**
* The connected state. This is the state when data network becomes usable.
*
* @see DataNetwork for the state machine diagram.
*/
private final class ConnectedState extends State {
@Override
public void enter() {
log("network connected.");
notifyPreciseDataConnectionState();
mNetworkAgent.markConnected();
mDataNetworkCallback.invokeFromExecutor(
() -> mDataNetworkCallback.onConnected(DataNetwork.this));
mQosCallbackTracker = new QosCallbackTracker(mNetworkAgent, mPhone);
mQosCallbackTracker.updateSessions(mQosBearerSessions);
updateSuspendState();
mPhone.getDisplayInfoController().registerForTelephonyDisplayInfoChanged(
getHandler(), EVENT_DISPLAY_INFO_CHANGED, null);
// TODO: Register for the following events after handover from IWLAN to cellular.
if (mTransport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
int bandwidthEstimateSource = mDataConfigManager.getBandwidthEstimateSource();
if (bandwidthEstimateSource == BANDWIDTH_SOURCE_MODEM) {
mPhone.mCi.registerForLceInfo(getHandler(),
EVENT_BANDWIDTH_ESTIMATE_FROM_MODEM_CHANGED, null);
} else if (bandwidthEstimateSource == BANDWIDTH_SOURCE_BANDWIDTH_ESTIMATOR) {
mPhone.getLinkBandwidthEstimator().registerForBandwidthChanged(getHandler(),
EVENT_BANDWIDTH_ESTIMATE_FROM_BANDWIDTH_ESTIMATOR_CHANGED, null);
} else {
loge("Invalid bandwidth source configuration: " + bandwidthEstimateSource);
}
}
}
@Override
public void exit() {
// TODO: Unregister for the following events after handover from cellular to IWLAN.
if (mTransport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
int bandwidthEstimateSource = mDataConfigManager.getBandwidthEstimateSource();
if (bandwidthEstimateSource == BANDWIDTH_SOURCE_MODEM) {
mPhone.mCi.unregisterForLceInfo(getHandler());
} else if (bandwidthEstimateSource == BANDWIDTH_SOURCE_BANDWIDTH_ESTIMATOR) {
mPhone.getLinkBandwidthEstimator().unregisterForBandwidthChanged(getHandler());
} else {
loge("Invalid bandwidth source configuration: " + bandwidthEstimateSource);
}
}
mPhone.getDisplayInfoController().unregisterForTelephonyDisplayInfoChanged(
getHandler());
}
@Override
public boolean processMessage(Message msg) {
logv("event=" + eventToString(msg.what));
switch (msg.what) {
case EVENT_TEAR_DOWN_NETWORK:
if (mInvokedDataDeactivation) {
log("Ignore tear down request because network is being torn down.");
break;
}
removeMessages(EVENT_TEAR_DOWN_NETWORK);
removeDeferredMessages(EVENT_TEAR_DOWN_NETWORK);
transitionTo(mDisconnectingState);
onTearDown(msg.arg1);
break;
case EVENT_BANDWIDTH_ESTIMATE_FROM_MODEM_CHANGED:
AsyncResult ar = (AsyncResult) msg.obj;
if (ar.exception != null) {
log("EVENT_BANDWIDTH_ESTIMATE_FROM_MODEM_CHANGED: error ignoring, e="
+ ar.exception);
break;
}
onBandwidthUpdatedFromModem((List<LinkCapacityEstimate>) ar.result);
break;
case EVENT_BANDWIDTH_ESTIMATE_FROM_BANDWIDTH_ESTIMATOR_CHANGED:
ar = (AsyncResult) msg.obj;
Pair<Integer, Integer> pair = (Pair<Integer, Integer>) ar.result;
onBandwidthUpdated(pair.first, pair.second);
break;
case EVENT_DISPLAY_INFO_CHANGED:
onDisplayInfoChanged();
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
}
/**
* The handover state. This is the state when data network handover between IWLAN and cellular.
*
* @see DataNetwork for the state machine diagram.
*/
private final class HandoverState extends State {
@Override
public void enter() {
notifyPreciseDataConnectionState();
}
@Override
public void exit() {
}
@Override
public boolean processMessage(Message msg) {
logv("event=" + eventToString(msg.what));
switch (msg.what) {
case EVENT_TEAR_DOWN_NETWORK:
// Defer the deactivate request until handover succeeds or fails.
deferMessage(msg);
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
}
/**
* The disconnecting state. This is the state when data network is about to be disconnected.
* The network is still usable in this state, but the clients should be prepared to lose the
* network in any moment.
*
* @see DataNetwork for the state machine diagram.
*/
private final class DisconnectingState extends State {
@Override
public void enter() {
notifyPreciseDataConnectionState();
}
@Override
public void exit() {
}
@Override
public boolean processMessage(Message msg) {
logv("event=" + eventToString(msg.what));
return NOT_HANDLED;
}
}
/**
* The disconnected state. This is the final state of a data network.
*
* @see DataNetwork for the state machine diagram.
*/
private final class DisconnectedState extends State {
@Override
public void enter() {
logl("Data network disconnected.");
// The detach all network requests must be tge last message to handle.
sendMessage(EVENT_DETACH_ALL_NETWORK_REQUESTS);
// Gracefully handle all the un-processed events then quit the state machine.
// quit() throws a QUIT event to the end of message queue. All the events before quit()
// will be processed. Events after quit() will not be processed.
quit();
notifyPreciseDataConnectionState();
mNetworkAgent.unregister();
if (mTransport == AccessNetworkConstants.TRANSPORT_TYPE_WLAN
&& mPduSessionId != DataCallResponse.PDU_SESSION_ID_NOT_SET) {
mRil.releasePduSessionId(null, mPduSessionId);
}
}
@Override
public boolean processMessage(Message msg) {
logv("event=" + eventToString(msg.what));
return NOT_HANDLED;
}
}
@Override
protected void unhandledMessage(Message msg) {
IState state = getCurrentState();
loge("Unhandled message " + msg.what + " in state "
+ (state == null ? "null" : state.getName()));
}
/**
* Attempt to attach the network request list to this data network. Whether the network can
* satisfy the request or not will be checked when EVENT_ATTACH_NETWORK_REQUEST is processed.
* If the request can't be attached, {@link DataNetworkCallback#onAttachFailed(
* DataNetwork, NetworkRequestList)} will be called, and retry should be scheduled.
*
* @param requestList Network request list to attach.
* @return {@code false} if the network is already disconnected. {@code true} means the request
* has been scheduled to attach to the network. If attach succeeds, the network request's state
* will be set to {@link TelephonyNetworkRequest#REQUEST_STATE_SATISFIED}. If failed, the
* callback {@link DataNetworkCallback#onAttachFailed(DataNetwork, NetworkRequestList)} will
* be called, and retry should be scheduled.
*/
public boolean attachNetworkRequests(@NonNull NetworkRequestList requestList) {
// If the network is already ended, we still attach the network request to the data network,
// so it can be retried later by data network controller.
if (getCurrentState() == null || isDisconnected()) {
// The state machine has already stopped. This is due to data network is disconnected.
return false;
}
sendMessage(obtainMessage(EVENT_ATTACH_NETWORK_REQUEST, requestList));
return true;
}
/**
* Detach the network request from this data network. Note that this will not tear down the
* network.
*
* @param networkRequest Network request to detach.
*/
public void detachNetworkRequest(@NonNull TelephonyNetworkRequest networkRequest) {
if (getCurrentState() == null || isDisconnected()) {
mAttachedNetworkRequestList.remove(networkRequest);
return;
}
sendMessage(obtainMessage(EVENT_DETACH_NETWORK_REQUEST, networkRequest));
}
/**
* Update the network capabilities.
*/
private void updateNetworkCapabilities() {
final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
builder.setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
.setSubscriptionId(mSubId).build());
builder.setSubscriptionIds(Collections.singleton(mSubId));
ApnSetting apnSetting = mDataProfile.getApnSetting();
if (apnSetting != null) {
for (int apnType : apnSetting.getApnTypes()) {
int cap = DataUtils.apnTypeToNetworkCapability(apnType);
if (cap >= 0) {
builder.addCapability(cap);
}
}
}
// TODO: Support NET_CAPABILITY_NOT_METERED
// TODO: Support NET_CAPABILITY_NOT_RESTRICTED
// TODO: Support NET_CAPABILITY_NOT_CONGESTED correctly
builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED);
// TODO: Support NET_CAPABILITY_TEMPORARILY_NOT_METERED
// TODO: Support NET_CAPABILITY_NOT_VCN_MANAGED correctly
builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
if (!mPhone.getServiceState().getDataRoaming()) {
builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
}
if (!mSuspended) {
builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
}
if (NetworkCapabilitiesUtils.inferRestrictedCapability(builder.build())) {
builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
}
// Set the bandwidth information.
builder.setLinkDownstreamBandwidthKbps(mNetworkBandwidth.downlinkBandwidthKbps);
builder.setLinkUpstreamBandwidthKbps(mNetworkBandwidth.uplinkBandwidthKbps);
NetworkCapabilities nc = builder.build();
if (!nc.equals(mNetworkCapabilities)) {
mNetworkCapabilities = nc;
if (mNetworkAgent != null) {
log("sendNetworkCapabilities: " + mNetworkCapabilities);
mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
}
}
}
/**
* @return The network capabilities of this data network.
*/
public @NonNull NetworkCapabilities getNetworkCapabilities() {
return mNetworkCapabilities;
}
/**
* @return The link properties of this data network.
*/
public @NonNull LinkProperties getLinkProperties() {
return mLinkProperties;
}
/**
* @return The data profile of this data network.
*/
public @NonNull DataProfile getDataProfile() {
return mDataProfile;
}
/**
* Update the preferred transport based on the attached network request.
*/
private void updatePreferredTransports() {
if (mAttachedNetworkRequestList.size() == 0) return;
// Get the highest priority network request.
TelephonyNetworkRequest networkRequest = mAttachedNetworkRequestList.get(0);
mPreferredTransport = mAccessNetworksManager.getPreferredTransportByNetworkCapability(
networkRequest.getHighestPriorityNetworkCapability());
if (mTransport == AccessNetworkConstants.TRANSPORT_TYPE_INVALID) {
mTransport = mPreferredTransport;
}
// TODO: Compare preferred transport and current transport. If they are different, initiate
// handover.
}
/**
* Update data suspended state.
*/
private void updateSuspendState() {
if (isConnecting() || isDisconnected()) {
// Return if not in the right state.
return;
}
boolean newSuspendedState = false;
// Get the uncombined service state directly.
NetworkRegistrationInfo nri = mPhone.getServiceStateTracker().getServiceState()
.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS, mTransport);
if (nri == null) {
loge("Can't get network registration info for "
+ AccessNetworkConstants.transportTypeToString(mTransport));
return;
}
// Never set suspended for emergency apn. Emergency data connection
// can work while device is not in service.
if (mNetworkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_EIMS)) {
newSuspendedState = false;
// If we are not in service, change to suspended.
} else if (nri.getRegistrationState()
!= NetworkRegistrationInfo.REGISTRATION_STATE_HOME
&& nri.getRegistrationState()
!= NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING) {
newSuspendedState = true;
// Check voice/data concurrency.
} else if (!mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()
&& mTransport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
newSuspendedState = mPhone.getCallTracker().getState() != PhoneConstants.State.IDLE;
}
// Only notify when there is a change.
if (mSuspended != newSuspendedState) {
mSuspended = newSuspendedState;
logl("Network becomes " + (mSuspended ? "suspended" : "unsuspended"));
// To update NOT_SUSPENDED capability.
updateNetworkCapabilities();
notifyPreciseDataConnectionState();
mDataNetworkCallback.invokeFromExecutor(() -> mDataNetworkCallback
.onSuspendedStateChanged(DataNetwork.this, mSuspended));
}
}
/**
* Allocate PDU session ID from the modem. This is only needed when the data network is
* initiated on IWLAN.
*/
private void allocatePduSessionId() {
mRil.allocatePduSessionId(obtainMessage(EVENT_ALLOCATE_PDU_SESSION_ID_RESPONSE));
}
/**
* Setup a data network.
*/
private void setupData() {
int dataNetworkType = getDataNetworkType();
// 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()
/*|| isUnmeteredUseOnly()*/));
TrafficDescriptor trafficDescriptor = mDataProfile.getTrafficDescriptor();
final boolean matchAllRuleAllowed = trafficDescriptor == null
|| trafficDescriptor.getOsAppId() == null;
int accessNetwork = DataUtils.networkTypeToAccessNetworkType(dataNetworkType);
mDataServiceManagers.get(mTransport)
.setupDataCall(accessNetwork, mDataProfile, isModemRoaming, allowRoaming,
DataService.REQUEST_REASON_NORMAL, null, mPduSessionId, null,
trafficDescriptor, matchAllRuleAllowed,
obtainMessage(EVENT_SETUP_DATA_CALL_RESPONSE));
logl("setupData: accessNetwork="
+ AccessNetworkType.toString(accessNetwork) + ", " + mDataProfile
+ ", isModemRoaming=" + isModemRoaming + ", allowRoaming=" + allowRoaming
+ ", PDU session id=" + mPduSessionId + ", matchAllRuleAllowed="
+ matchAllRuleAllowed);
TelephonyMetrics.getInstance().writeSetupDataCall(mPhone.getPhoneId(),
ServiceState.networkTypeToRilRadioTechnology(dataNetworkType),
mDataProfile.getProfileId(), mDataProfile.getApn(), mDataProfile.getProtocolType());
}
/**
* Get fail cause from {@link DataCallResponse} and the result code.
*
* @param resultCode The result code returned from
* {@link DataServiceCallback#onSetupDataCallComplete(int, DataCallResponse)}.
* @param response The data call response returned from
* {@link DataServiceCallback#onSetupDataCallComplete(int, DataCallResponse)}.
*
* @return The fail cause. {@link DataFailCause#NONE} if succeeds.
*/
private @DataFailureCause int getFailCauseFromDataCallResponse(
@DataServiceCallback.ResultCode int resultCode, @Nullable DataCallResponse response) {
int failCause = DataFailCause.NONE;
switch (resultCode) {
case DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE:
failCause = DataFailCause.RADIO_NOT_AVAILABLE;
break;
case DataServiceCallback.RESULT_ERROR_BUSY:
case DataServiceCallback.RESULT_ERROR_TEMPORARILY_UNAVAILABLE:
failCause = DataFailCause.SERVICE_TEMPORARILY_UNAVAILABLE;
break;
case DataServiceCallback.RESULT_ERROR_INVALID_ARG:
failCause = DataFailCause.UNACCEPTABLE_NETWORK_PARAMETER;
break;
case DataServiceCallback.RESULT_ERROR_UNSUPPORTED:
failCause = DataFailCause.REQUEST_NOT_SUPPORTED;
break;
case DataServiceCallback.RESULT_SUCCESS:
if (response != null) {
failCause = DataFailCause.getFailCause(response.getCause());
}
break;
}
return failCause;
}
/**
* Update data network based on the latest {@link DataCallResponse}.
*
* @param response The data call response from data service.
*/
private void updateDataNetwork(@NonNull DataCallResponse response) {
mCid.put(mTransport, response.getId());
LinkProperties linkProperties = new LinkProperties();
// Set interface name
linkProperties.setInterfaceName(response.getInterfaceName());
// Set PDU session id
if (mPduSessionId != response.getPduSessionId()) {
log("PDU session id updated to " + mPduSessionId);
mPduSessionId = response.getPduSessionId();
}
// Set link addresses
if (response.getAddresses().size() > 0) {
for (LinkAddress la : response.getAddresses()) {
if (!la.getAddress().isAnyLocalAddress()) {
logv("addr/pl=" + la.getAddress() + "/" + la.getPrefixLength());
linkProperties.addLinkAddress(la);
}
}
} else {
loge("no address for ifname=" + response.getInterfaceName());
}
// Set DNS servers
if (response.getDnsAddresses().size() > 0) {
for (InetAddress dns : response.getDnsAddresses()) {
if (!dns.isAnyLocalAddress()) {
linkProperties.addDnsServer(dns);
}
}
} else {
loge("Empty dns response");
}
// Set PCSCF
if (response.getPcscfAddresses().size() > 0) {
for (InetAddress pcscf : response.getPcscfAddresses()) {
linkProperties.addPcscfServer(pcscf);
}
}
// For backwards compatibility, use getMtu() if getMtuV4() is not available.
int mtuV4 = response.getMtuV4() > 0 ? response.getMtuV4() : response.getMtu();
if (mtuV4 <= 0) {
// Use back up value from data profile.
if (mDataProfile.getApnSetting() != null) {
mtuV4 = mDataProfile.getApnSetting().getMtuV4();
}
if (mtuV4 <= 0) {
mtuV4 = DEFAULT_MTU_V4;
}
}
// For backwards compatibility, use getMtu() if getMtuV6() is not available.
int mtuV6 = response.getMtuV6() > 0 ? response.getMtuV6() : response.getMtu();
if (mtuV6 <= 0) {
// Use back up value from data profile.
if (mDataProfile.getApnSetting() != null) {
mtuV6 = mDataProfile.getApnSetting().getMtuV6();
}
if (mtuV6 <= 0) {
mtuV6 = DEFAULT_MTU_V6;
}
}
// Set MTU for each route.
for (InetAddress gateway : response.getGatewayAddresses()) {
int mtu = gateway instanceof java.net.Inet6Address ? mtuV6 : mtuV4;
linkProperties.addRoute(new RouteInfo(null, gateway, null,
RouteInfo.RTN_UNICAST, mtu));
}
// LinkProperties.setMtu should be deprecated. The mtu for each route has been already
// provided in addRoute() above. For backwards compatibility, we still need to provide
// a value for the legacy MTU. Use the lower value of v4 and v6 value here.
linkProperties.setMtu(Math.min(mtuV4, mtuV6));
if (mDataProfile.getApnSetting() != null
&& !TextUtils.isEmpty(mDataProfile.getApnSetting().getProxyAddressAsString())) {
int port = mDataProfile.getApnSetting().getProxyPort();
if (port == -1) {
port = 8080;
}
ProxyInfo proxy = ProxyInfo.buildDirectProxy(
mDataProfile.getApnSetting().getProxyAddressAsString(), port);
linkProperties.setHttpProxy(proxy);
}
// updateTcpBufferSizes
linkProperties.setTcpBufferSizes(getTcpConfig());
mQosBearerSessions = response.getQosBearerSessions();
if (mQosCallbackTracker != null) {
mQosCallbackTracker.updateSessions(mQosBearerSessions);
}
if (!linkProperties.equals(mLinkProperties)) {
mLinkProperties = linkProperties;
log("sendLinkProperties " + mLinkProperties);
mNetworkAgent.sendLinkProperties(mLinkProperties);
}
// TODO: Support default QOS update
// TODO: Support QOS bearer sessions
}
/**
* @return {@code true} if the device is connected to NR cell in 5G NSA mode, and the current
* data network is using the NR cell.
*/
private boolean isNrConnected() {
return mPhone.getServiceState().getNrState() == NetworkRegistrationInfo.NR_STATE_CONNECTED
&& mPhone.getServiceStateTracker().getNrContextIds().contains(
mCid.get(AccessNetworkConstants.TRANSPORT_TYPE_WWAN));
}
/**
* Get the TCP config string.
*
* @return The TCP config string used in {@link LinkProperties#setTcpBufferSizes(String)}.
*/
private @Nullable String getTcpConfig() {
ServiceState ss = mPhone.getServiceState();
NetworkRegistrationInfo nrs = ss.getNetworkRegistrationInfo(
NetworkRegistrationInfo.DOMAIN_PS, mTransport);
int networkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
if (nrs != null) {
networkType = nrs.getAccessNetworkTechnology();
if (networkType == TelephonyManager.NETWORK_TYPE_LTE
&& nrs.isUsingCarrierAggregation()) {
// Although LTE_CA is not a real RAT, but since LTE CA generally has higher speed
// we use LTE_CA to get a different TCP config for LTE CA.
networkType = TelephonyManager.NETWORK_TYPE_LTE_CA;
}
}
return mDataConfigManager.getTcpConfigString(networkType, ss);
}
/**
* Called when receiving setup data network response from the data service.
*
* @param resultCode The result code.
* @param response The response.
*/
private void onSetupResponse(@DataServiceCallback.ResultCode int resultCode,
@Nullable DataCallResponse response) {
logl("onSetupResponse: resultCode=" + DataServiceCallback.resultCodeToString(resultCode)
+ ", response=" + response);
int failCause = getFailCauseFromDataCallResponse(resultCode, response);
if (failCause == DataFailCause.NONE) {
updateDataNetwork(response);
// TODO: Check if the cid already exists. If yes, should notify DNC and let it force
// attach the network requests to that existing data network.
// TODO: Check if there is still network request attached, if not, silently deactivate
// the network.
// TODO: Evaluate all network requests and see if each request still can be satisfied.
// For requests that can't be satisfied anymore, we need to put them back to the
// unsatisfied pool. If none of network requests can be satisfied, then there is no
// need to mark network agent connected. Just silently deactivate the data network.
if (mAttachedNetworkRequestList.size() != 0) {
// Setup data succeeded.
transitionTo(mConnectedState);
} else {
log("Tear down the network since there is no live network request.");
// Directly call onTearDown hear. We should not enter disconnecting state for silent
// tear down. Once the tear down is complete, the data call list changed event will
// move the state into disconnected there.
onTearDown(TEAR_DOWN_REASON_NO_LIVE_REQUEST);
}
} else {
// Setup data failed.
long retry = response != null ? response.getRetryDurationMillis()
: DataCallResponse.RETRY_DURATION_UNDEFINED;
NetworkRequestList requestList = new NetworkRequestList(mAttachedNetworkRequestList);
mDataNetworkCallback.invokeFromExecutor(() -> mDataNetworkCallback.onSetupDataFailed(
DataNetwork.this, requestList, failCause, retry));
transitionTo(mDisconnectedState);
}
}
/**
* Check if should perform graceful teardown. Currently we only support IMS/RCS PDN tear down.
* Frameworks wait for IMS de-registration before tearing down the IMS PDN.
*
* @return {@code true} if graceful tear down should be performed.
*/
private boolean shouldPerformGracefulTearDown() {
// TODO: The following APIs could be used
// ImsMmTelManager.registerImsRegistrationCallback() for IMS registration
// ImsRcsManager.registerImsRegistrationCallback() for RCS registration
// Then check if the network requests are from those packages.
// ImsResolver.getInstance().getConfiguredImsServicePackageName(ImsFeature.FEATURE_MMTEL);
// ImsResolver.getInstance().getConfiguredImsServicePackageName(ImsFeature.FEATURE_RCS)
return false;
}
/**
* Tear down the data network
*
* @param reason The reason of tearing down the network.
*/
public void tearDown(@TearDownReason int reason) {
if (getCurrentState() == null || isDisconnected()) {
return;
}
sendMessage(obtainMessage(EVENT_TEAR_DOWN_NETWORK, reason));
}
private void onTearDown(@TearDownReason int reason) {
logl("onTearDown: reason=" + tearDownReasonToString(reason));
// TODO: Need to support DataService.REQUEST_REASON_SHUTDOWN
mDataServiceManagers.get(mTransport).deactivateDataCall(mCid.get(mTransport),
DataService.REQUEST_REASON_NORMAL, null);
mInvokedDataDeactivation = true;
}
/**
* Tear down the data network when condition is met or timed out. Data network will enter
* {@link DisconnectingState} immediately and waiting for condition met. When condition is met,
* {@link DataNetworkController} should invoke {@link Consumer#accept(Object)} so the actual
* tear down work can be performed.
*
* This is primarily used for IMS graceful tear down. {@link DataNetworkController} inform
* {@link DataNetwork} to enter {@link DisconnectingState}. IMS service can observe this
* through {@link PreciseDataConnectionState#getState()} and then perform IMS de-registration
* work. After IMS de-registered, {@link DataNetworkController} informs {@link DataNetwork}
* that it's okay to tear down the network.
*
* @param reason The tear down reason.
*
* @param timeoutMillis Timeout in milliseconds. Within the time window, clients will have to
* call {@link Consumer#accept(Object)}, otherwise, data network will be torn down when
* timed out.
*
* @return The runnable for client to execute when condition is met. When executed, tear down
* will be performed. {@code null} if the data network is already disconnected or being
* disconnected.
*/
public @Nullable Runnable tearDownWithCondition(@TearDownReason int reason,
long timeoutMillis) {
if (getCurrentState() == null || isDisconnected() || isDisconnecting()) {
loge("tearDownGracefully: Not in the right state. State=" + getCurrentState());
return null;
}
logl("tearDownWithCondition: reason=" + tearDownReasonToString(reason) + ", timeout="
+ timeoutMillis + "ms.");
sendMessageDelayed(EVENT_TEAR_DOWN_NETWORK, timeoutMillis);
return () -> this.tearDown(reason);
}
/**
* Called when receiving {@link DataServiceCallback#onDataCallListChanged(List)} from the data
* service.
*
* @param transport The transport where this event from.
* @param responseList The data call response list.
*/
private void onDataStateChanged(@TransportType int transport,
@NonNull List<DataCallResponse> responseList) {
// Ignore the update if it's not from the data service on the right transport.
// Also if never received data call response from setup call response, which updates the
// cid, ignore the update here.
logv("onDataStateChanged: " + responseList);
if (transport != mTransport || mCid.get(mTransport) == INVALID_CID || isConnecting()
|| isDisconnected()) {
return;
}
DataCallResponse response = responseList.stream()
.filter(r -> mCid.get(mTransport) == r.getId())
.findFirst()
.orElse(null);
if (response != null) {
if (!response.equals(mDataCallResponse)) {
log("onDataStateChanged: " + response);
mDataCallResponse = response;
if (response.getLinkStatus() != DataCallResponse.LINK_STATUS_INACTIVE) {
updateDataNetwork(response);
} else {
log("onDataStateChanged: PDN inactive reported by "
+ AccessNetworkConstants.transportTypeToString(mTransport)
+ " data service.");
mDataNetworkCallback.invokeFromExecutor(() -> mDataNetworkCallback
.onDisconnected(DataNetwork.this, response.getCause()));
transitionTo(mDisconnectedState);
}
}
} else {
// The data call response is missing from the list. This means the PDN is gone. This
// is the PDN lost reported by the modem. We don't send another DEACTIVATE_DATA request
// for that
log("onDataStateChanged: PDN disconnected reported by "
+ AccessNetworkConstants.transportTypeToString(mTransport) + " data service.");
mDataNetworkCallback.invokeFromExecutor(() -> mDataNetworkCallback
.onDisconnected(DataNetwork.this, DataFailCause.LOST_CONNECTION));
transitionTo(mDisconnectedState);
}
}
/**
* Called when data config updated.
*/
private void onDataConfigUpdated() {
log("onDataConfigUpdated");
updateBandwidthFromDataConfig();
}
/**
* Called when receiving bandwidth update from the modem.
*
* @param linkCapacityEstimates The link capacity estimate list from the modem.
*/
private void onBandwidthUpdatedFromModem(
@NonNull List<LinkCapacityEstimate> linkCapacityEstimates) {
Objects.requireNonNull(linkCapacityEstimates);
if (linkCapacityEstimates.isEmpty()) return;
int uplinkBandwidthKbps = 0, downlinkBandwidthKbps = 0;
for (LinkCapacityEstimate linkCapacityEstimate : linkCapacityEstimates) {
if (linkCapacityEstimate.getType() == LinkCapacityEstimate.LCE_TYPE_COMBINED) {
uplinkBandwidthKbps = linkCapacityEstimate.getUplinkCapacityKbps();
downlinkBandwidthKbps = linkCapacityEstimate.getDownlinkCapacityKbps();
break;
} else if (linkCapacityEstimate.getType() == LinkCapacityEstimate.LCE_TYPE_PRIMARY
|| linkCapacityEstimate.getType() == LinkCapacityEstimate.LCE_TYPE_SECONDARY) {
uplinkBandwidthKbps += linkCapacityEstimate.getUplinkCapacityKbps();
downlinkBandwidthKbps += linkCapacityEstimate.getDownlinkCapacityKbps();
} else {
loge("Invalid LinkCapacityEstimate type " + linkCapacityEstimate.getType());
}
}
onBandwidthUpdated(uplinkBandwidthKbps, downlinkBandwidthKbps);
}
/**
* Called when bandwidth estimation updated from either modem or the bandwidth estimator.
*
* @param uplinkBandwidthKbps Uplink bandwidth estimate in Kbps.
* @param downlinkBandwidthKbps Downlink bandwidth estimate in Kbps.
*/
private void onBandwidthUpdated(int uplinkBandwidthKbps, int downlinkBandwidthKbps) {
log("onBandwidthUpdated: downlinkBandwidthKbps=" + downlinkBandwidthKbps
+ ", uplinkBandwidthKbps=" + uplinkBandwidthKbps);
NetworkBandwidth bandwidthFromConfig = mDataConfigManager.getBandwidthForNetworkType(
getDataNetworkType(), mPhone.getServiceState());
if (downlinkBandwidthKbps == LinkCapacityEstimate.INVALID && bandwidthFromConfig != null) {
// Fallback to carrier config.
downlinkBandwidthKbps = bandwidthFromConfig.downlinkBandwidthKbps;
}
if (uplinkBandwidthKbps == LinkCapacityEstimate.INVALID && bandwidthFromConfig != null) {
// Fallback to carrier config.
uplinkBandwidthKbps = bandwidthFromConfig.uplinkBandwidthKbps;
}
// Make sure uplink is not greater than downlink.
uplinkBandwidthKbps = Math.min(uplinkBandwidthKbps, downlinkBandwidthKbps);
mNetworkBandwidth = new NetworkBandwidth(downlinkBandwidthKbps, uplinkBandwidthKbps);
updateNetworkCapabilities();
}
/**
* Called when display info changed. This can happen when network types changed or override
* types (5G NSA, 5G MMWAVE) changes.
*/
private void onDisplayInfoChanged() {
updateBandwidthFromDataConfig();
// TODO: Update meteredness flags.
}
/**
* Update the bandwidth from carrier config. Note this is no-op if the bandwidth source is not
* carrier config.
*/
private void updateBandwidthFromDataConfig() {
if (mDataConfigManager.getBandwidthEstimateSource() != BANDWIDTH_SOURCE_CARRIER_CONFIG) {
return;
}
log("updateBandwidthFromDataConfig");
mNetworkBandwidth = mDataConfigManager.getBandwidthForNetworkType(
getDataNetworkType(), mPhone.getServiceState());
updateNetworkCapabilities();
}
/**
* @return The unique context id assigned by the data service in
* {@link DataCallResponse#getId()}.
*/
public int getId() {
return mCid.get(mTransport);
}
/**
* @return The current network type reported by the network service.
*/
private @NetworkType int getDataNetworkType() {
ServiceState ss = mPhone.getServiceState();
NetworkRegistrationInfo nrs = ss.getNetworkRegistrationInfo(
NetworkRegistrationInfo.DOMAIN_PS, mTransport);
if (nrs != null) {
return nrs.getAccessNetworkTechnology();
}
return TelephonyManager.NETWORK_TYPE_UNKNOWN;
}
/**
* @return The network score. The higher score of the network has higher chance to be
* selected by the connectivity service as active network.
*/
private @NonNull NetworkScore getNetworkScore() {
// If it's serving a network request that asks NET_CAPABILITY_INTERNET and doesn't have
// specify a sub id, this data network is considered to be default internet data
// connection. In this case we assign a slightly higher score of 50. The intention is
// it will not be replaced by other data networks accidentally in DSDS use case.
int score = OTHER_NETWORK_SCORE;
// TODO: Should update the score when attached list changed.
for (TelephonyNetworkRequest networkRequest : mAttachedNetworkRequestList) {
if (networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
&& networkRequest.getNetworkSpecifier() == null) {
score = DEFAULT_INTERNET_NETWORK_SCORE;
}
}
return new NetworkScore.Builder().setLegacyInt(score).build();
}
/**
* @return The attached network request list.
*/
public @NonNull NetworkRequestList getAttachedNetworkRequestList() {
return mAttachedNetworkRequestList;
}
/**
* @return {@code true} if in connecting state.
*/
public boolean isConnecting() {
return getCurrentState() == mConnectingState;
}
/**
* @return {@code true} if in connected state.
*/
public boolean isConnected() {
return getCurrentState() == mConnectedState;
}
/**
* @return {@code true} if in disconnecting state.
*/
public boolean isDisconnecting() {
return getCurrentState() == mDisconnectingState;
}
/**
* @return {@code true} if in disconnected state.
*/
public boolean isDisconnected() {
return getCurrentState() == mDisconnectedState;
}
/**
* @return {@code true} if in handover state.
*/
public boolean isUnderHandover() {
return getCurrentState() == mHandoverState;
}
/**
* @return {@code true} if the data network is suspended.
*/
public boolean isSuspended() {
return getState() == TelephonyManager.DATA_SUSPENDED;
}
/**
* @return The current transport of the data network.
*/
public @TransportType int getTransport() {
return mTransport;
}
private @DataState int getState() {
IState state = getCurrentState();
if (state == null || isDisconnected()) {
return TelephonyManager.DATA_DISCONNECTED;
} else if (isConnecting()) {
return TelephonyManager.DATA_CONNECTING;
} else if (isConnected()) {
// The data connection can only be suspended when it's in active state.
if (mSuspended) {
return TelephonyManager.DATA_SUSPENDED;
}
return TelephonyManager.DATA_CONNECTED;
} else if (isDisconnecting()) {
return TelephonyManager.DATA_DISCONNECTING;
}
// TODO: Support handover
/* else if (state == mHandoverState) {
return TelephonyManager.DATA_UNDER_HANDOVER;
}*/
return TelephonyManager.DATA_UNKNOWN;
}
/**
* @return {@code true} if this data network supports internet.
*/
public boolean isInternetSupported() {
return mNetworkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
&& mNetworkCapabilities.hasCapability(
NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
&& mNetworkCapabilities.hasCapability(
NetworkCapabilities.NET_CAPABILITY_TRUSTED)
&& mNetworkCapabilities.hasCapability(
NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
&& mNetworkCapabilities.hasCapability(
NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
}
/**
* Get precise data connection state
*
* @return The {@link PreciseDataConnectionState}
*/
private PreciseDataConnectionState getPreciseDataConnectionState() {
return new PreciseDataConnectionState.Builder()
.setTransportType(mTransport)
.setId(mCid.get(mTransport))
.setState(getState())
.setApnSetting(mDataProfile.getApnSetting())
.setLinkProperties(mLinkProperties)
.setNetworkType(getDataNetworkType())
.setFailCause(mFailCause)
.build();
}
/**
* Send the precise data connection state to the listener of
* {@link android.telephony.TelephonyCallback.PreciseDataConnectionStateListener}.
*/
private void notifyPreciseDataConnectionState() {
PreciseDataConnectionState pdcs = getPreciseDataConnectionState();
logv("notifyPreciseDataConnectionState=" + pdcs);
mPhone.notifyDataConnection(pdcs);
}
/**
* Convert the data tear down reason to string.
*
* @param reason Data deactivation reason.
* @return The deactivation reason in string format.
*/
public static @NonNull String tearDownReasonToString(@TearDownReason int reason) {
switch (reason) {
case TEAR_DOWN_REASON_CONNECTIVITY_SERVICE_UNWANTED:
return "CONNECTIVITY_SERVICE_UNWANTED";
case TEAR_DOWN_REASON_SIM_REMOVAL:
return "SIM_REMOVAL";
case TEAR_DOWN_REASON_AIRPLANE_MODE_ON:
return "AIRPLANE_MODE_ON";
case TEAR_DOWN_REASON_DATA_DISABLED:
return "DATA_DISABLED";
case TEAR_DOWN_REASON_NO_LIVE_REQUEST:
return "TEAR_DOWN_REASON_NO_LIVE_REQUEST";
case TEAR_DOWN_REASON_RAT_NOT_ALLOWED:
return "TEAR_DOWN_REASON_RAT_NOT_ALLOWED";
case TEAR_DOWN_REASON_ROAMING_DISABLED:
return "TEAR_DOWN_REASON_ROAMING_DISABLED";
case TEAR_DOWN_REASON_CONCURRENT_VOICE_DATA_NOT_ALLOWED:
return "TEAR_DOWN_REASON_CONCURRENT_VOICE_DATA_NOT_ALLOWED";
case TEAR_DOWN_REASON_DATA_RESTRICTED_BY_NETWORK:
return "TEAR_DOWN_REASON_DATA_RESTRICTED_BY_NETWORK";
case TEAR_DOWN_REASON_DATA_SERVICE_NOT_READY:
return "TEAR_DOWN_REASON_DATA_SERVICE_NOT_READY";
case TEAR_DOWN_REASON_POWER_OFF_BY_CARRIER:
return "TEAR_DOWN_REASON_POWER_OFF_BY_CARRIER";
default:
return "UNKNOWN(" + reason + ")";
}
}
/**
* Convert event to string
*
* @param event The event
* @return The event in string format.
*/
private static @NonNull String eventToString(int event) {
switch (event) {
case EVENT_DATA_CONFIG_UPDATED:
return "EVENT_DATA_CONFIG_UPDATED";
case EVENT_ATTACH_NETWORK_REQUEST:
return "EVENT_ATTACH_NETWORK_REQUEST";
case EVENT_DETACH_NETWORK_REQUEST:
return "EVENT_DETACH_NETWORK_REQUEST";
case EVENT_ALLOCATE_PDU_SESSION_ID_RESPONSE:
return "EVENT_ALLOCATE_PDU_SESSION_ID_RESPONSE";
case EVENT_SETUP_DATA_CALL_RESPONSE:
return "EVENT_SETUP_DATA_NETWORK_RESPONSE";
case EVENT_TEAR_DOWN_NETWORK:
return "EVENT_TEAR_DOWN_NETWORK";
case EVENT_DATA_STATE_CHANGED:
return "EVENT_DATA_STATE_CHANGED";
case EVENT_SERVICE_STATE_CHANGED:
return "EVENT_DATA_NETWORK_TYPE_REG_STATE_CHANGED";
default:
return "Unknown(" + event + ")";
}
}
@Override
public String toString() {
return "[DataNetwork: " + mLogTag + ", " + (mDataProfile.getApnSetting() != null
? mDataProfile.getApnSetting().getApnName() : null) + ", state="
+ (getCurrentState() != null ? getCurrentState().getName() : null) + "]";
}
/**
* @return The short name of the data network (e.g. DN-C-1)
*/
public @NonNull String name() {
return mLogTag;
}
/**
* Log debug messages.
* @param s debug messages
*/
@Override
protected void log(@NonNull String s) {
Rlog.d(mLogTag, (getCurrentState() != null
? (getCurrentState().getName() + ": ") : "") + s);
}
/**
* Log error messages.
* @param s error messages
*/
@Override
protected void loge(@NonNull String s) {
Rlog.e(mLogTag, (getCurrentState() != null
? (getCurrentState().getName() + ": ") : "") + s);
}
/**
* Log verbose messages.
* @param s error messages
*/
@Override
protected void logv(@NonNull String s) {
if (VDBG) {
Rlog.v(mLogTag, (getCurrentState() != null
? (getCurrentState().getName() + ": ") : "") + s);
}
}
/**
* Log debug messages and also log into the local log.
* @param s debug messages
*/
private void logl(@NonNull String s) {
log(s);
mLocalLog.log(s);
}
/**
* Dump the state of DataNetwork
*
* @param fd File descriptor
* @param printWriter Print writer
* @param args Arguments
*/
public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
super.dump(fd, pw, args);
pw.println("Tag: " + name());
pw.increaseIndent();
pw.println("WWAN cid=" + mCid.get(AccessNetworkConstants.TRANSPORT_TYPE_WWAN));
pw.println("WLAN cid=" + mCid.get(AccessNetworkConstants.TRANSPORT_TYPE_WLAN));
mNetworkAgent.dump(fd, pw, args);
pw.println("Attached network requests:");
pw.increaseIndent();
for (TelephonyNetworkRequest request : mAttachedNetworkRequestList) {
pw.println(request);
}
pw.decreaseIndent();
pw.println("mQosBearerSessions=" + mQosBearerSessions);
pw.println("Local logs:");
pw.increaseIndent();
mLocalLog.dump(fd, pw, args);
pw.decreaseIndent();
pw.decreaseIndent();
pw.println("---------------");
}
}