blob: 609e98bd55dc28bbeb92879c6a38ef5f76574f45 [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.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.NetworkAgent;
import android.net.NetworkCapabilities;
import android.net.NetworkPolicyManager;
import android.net.NetworkPolicyManager.SubscriptionCallback;
import android.net.NetworkRequest;
import android.net.Uri;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.telephony.AccessNetworkConstants;
import android.telephony.AccessNetworkConstants.AccessNetworkType;
import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
import android.telephony.AccessNetworkConstants.TransportType;
import android.telephony.Annotation.DataActivityType;
import android.telephony.Annotation.DataFailureCause;
import android.telephony.Annotation.NetCapability;
import android.telephony.Annotation.NetworkType;
import android.telephony.Annotation.ValidationStatus;
import android.telephony.AnomalyReporter;
import android.telephony.CarrierConfigManager;
import android.telephony.CellSignalStrength;
import android.telephony.DataFailCause;
import android.telephony.DataSpecificRegistrationInfo;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.NetworkRegistrationInfo.RegistrationState;
import android.telephony.PcoData;
import android.telephony.ServiceState;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import android.telephony.SubscriptionPlan;
import android.telephony.TelephonyManager;
import android.telephony.TelephonyManager.DataState;
import android.telephony.TelephonyManager.SimState;
import android.telephony.TelephonyRegistryManager;
import android.telephony.data.DataCallResponse;
import android.telephony.data.DataCallResponse.HandoverFailureMode;
import android.telephony.data.DataCallResponse.LinkStatus;
import android.telephony.data.DataProfile;
import android.telephony.ims.ImsException;
import android.telephony.ims.ImsManager;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.ImsRegistrationAttributes;
import android.telephony.ims.ImsStateCallback;
import android.telephony.ims.RegistrationManager;
import android.telephony.ims.feature.ImsFeature;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.LocalLog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.SlidingWindowEventCounter;
import com.android.internal.telephony.TelephonyComponentFactory;
import com.android.internal.telephony.data.AccessNetworksManager.AccessNetworksManagerCallback;
import com.android.internal.telephony.data.DataConfigManager.DataConfigManagerCallback;
import com.android.internal.telephony.data.DataEvaluation.DataAllowedReason;
import com.android.internal.telephony.data.DataEvaluation.DataDisallowedReason;
import com.android.internal.telephony.data.DataEvaluation.DataEvaluationReason;
import com.android.internal.telephony.data.DataNetwork.DataNetworkCallback;
import com.android.internal.telephony.data.DataNetwork.TearDownReason;
import com.android.internal.telephony.data.DataProfileManager.DataProfileManagerCallback;
import com.android.internal.telephony.data.DataRetryManager.DataHandoverRetryEntry;
import com.android.internal.telephony.data.DataRetryManager.DataRetryEntry;
import com.android.internal.telephony.data.DataRetryManager.DataRetryManagerCallback;
import com.android.internal.telephony.data.DataRetryManager.DataSetupRetryEntry;
import com.android.internal.telephony.data.DataSettingsManager.DataSettingsManagerCallback;
import com.android.internal.telephony.data.DataStallRecoveryManager.DataStallRecoveryManagerCallback;
import com.android.internal.telephony.data.LinkBandwidthEstimator.LinkBandwidthEstimatorCallback;
import com.android.internal.telephony.ims.ImsResolver;
import com.android.internal.telephony.util.TelephonyUtils;
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.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* DataNetworkController in the central module of the telephony data stack. It is responsible to
* create and manage all the mobile data networks. It is per-SIM basis which means for DSDS devices,
* there will be two DataNetworkController instances. Unlike the Android 12 DcTracker, which is
* designed to be per-transport (i.e. cellular, IWLAN), DataNetworkController is designed to handle
* data networks on both cellular and IWLAN.
*/
public class DataNetworkController extends Handler {
private static final boolean VDBG = false;
/** Event for adding a network request. */
private static final int EVENT_ADD_NETWORK_REQUEST = 2;
/** Event for removing a network request. */
private static final int EVENT_REMOVE_NETWORK_REQUEST = 3;
/** Re-evaluate all unsatisfied network requests. */
private static final int EVENT_REEVALUATE_UNSATISFIED_NETWORK_REQUESTS = 5;
/** Event for packet switch restricted enabled by network. */
private static final int EVENT_PS_RESTRICT_ENABLED = 6;
/** Event for packet switch restricted disabled by network. */
private static final int EVENT_PS_RESTRICT_DISABLED = 7;
/** Event for data service binding changed. */
private static final int EVENT_DATA_SERVICE_BINDING_CHANGED = 8;
/** Event for SIM state changed. */
private static final int EVENT_SIM_STATE_CHANGED = 9;
/** Event for tearing down all data networks. */
private static final int EVENT_TEAR_DOWN_ALL_DATA_NETWORKS = 12;
/** Event for registering data network controller callback. */
private static final int EVENT_REGISTER_DATA_NETWORK_CONTROLLER_CALLBACK = 13;
/** Event for unregistering data network controller callback. */
private static final int EVENT_UNREGISTER_DATA_NETWORK_CONTROLLER_CALLBACK = 14;
/** Event for subscription info changed. */
private static final int EVENT_SUBSCRIPTION_CHANGED = 15;
/** Event for re-evaluating existing data networks. */
private static final int EVENT_REEVALUATE_EXISTING_DATA_NETWORKS = 16;
/** Event for data RAT or registration state changed. */
private static final int EVENT_SERVICE_STATE_CHANGED = 17;
/** Event for voice call ended. */
private static final int EVENT_VOICE_CALL_ENDED = 18;
/** Event for registering all events. */
private static final int EVENT_REGISTER_ALL_EVENTS = 19;
/** Event for emergency call started or ended. */
private static final int EVENT_EMERGENCY_CALL_CHANGED = 20;
/** Event for evaluating preferred transport. */
private static final int EVENT_EVALUATE_PREFERRED_TRANSPORT = 21;
/** Event for subscription plans changed. */
private static final int EVENT_SUBSCRIPTION_PLANS_CHANGED = 22;
/** Event for unmetered or congested subscription override. */
private static final int EVENT_SUBSCRIPTION_OVERRIDE = 23;
/** Event for slice config changed. */
private static final int EVENT_SLICE_CONFIG_CHANGED = 24;
/** Event for tracking area code changed. */
private static final int EVENT_TAC_CHANGED = 25;
/** The supported IMS features. This is for IMS graceful tear down support. */
private static final Collection<Integer> SUPPORTED_IMS_FEATURES =
List.of(ImsFeature.FEATURE_MMTEL, ImsFeature.FEATURE_RCS);
/** The maximum number of previously connected data networks for debugging purposes. */
private static final int MAX_HISTORICAL_CONNECTED_DATA_NETWORKS = 10;
/**
* The delay in milliseconds to re-evaluate preferred transport when handover failed and
* fallback to source.
*/
private static final long REEVALUATE_PREFERRED_TRANSPORT_DELAY_MILLIS =
TimeUnit.SECONDS.toMillis(3);
/** The delay in milliseconds to re-evaluate unsatisfied network requests after call end. */
private static final long REEVALUATE_UNSATISFIED_NETWORK_REQUESTS_AFTER_CALL_END_DELAY_MILLIS =
TimeUnit.MILLISECONDS.toMillis(500);
/** The delay in milliseconds to re-evaluate unsatisfied network requests after TAC changes. */
private static final long REEVALUATE_UNSATISFIED_NETWORK_REQUESTS_TAC_CHANGED_DELAY_MILLIS =
TimeUnit.MILLISECONDS.toMillis(100);
private final Phone mPhone;
private final String mLogTag;
private final LocalLog mLocalLog = new LocalLog(128);
private final @NonNull DataConfigManager mDataConfigManager;
private final @NonNull DataSettingsManager mDataSettingsManager;
private final @NonNull DataProfileManager mDataProfileManager;
private final @NonNull DataStallRecoveryManager mDataStallRecoveryManager;
private final @NonNull AccessNetworksManager mAccessNetworksManager;
private final @NonNull DataRetryManager mDataRetryManager;
private final @NonNull ImsManager mImsManager;
private final @NonNull NetworkPolicyManager mNetworkPolicyManager;
private final @NonNull SparseArray<DataServiceManager> mDataServiceManagers =
new SparseArray<>();
/** The subscription index associated with this data network controller. */
private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
/** The current service state of the device. */
// Note that keeping a copy here instead of directly using ServiceStateTracker.getServiceState()
// is intended for detecting the delta.
private @NonNull ServiceState mServiceState;
/** The list of SubscriptionPlans, updated when initialized and when plans are changed. */
private final @NonNull List<SubscriptionPlan> mSubscriptionPlans = new ArrayList<>();
/**
* The set of network types an unmetered override applies to, set by onSubscriptionOverride
* and cleared when the device is rebooted or the override expires.
*/
private final @NonNull @NetworkType Set<Integer> mUnmeteredOverrideNetworkTypes =
new ArraySet<>();
/**
* The set of network types a congested override applies to, set by onSubscriptionOverride
* and cleared when the device is rebooted or the override expires.
*/
private final @NonNull @NetworkType Set<Integer> mCongestedOverrideNetworkTypes =
new ArraySet<>();
/**
* The list of all network requests.
*/
private final @NonNull NetworkRequestList mAllNetworkRequestList = new NetworkRequestList();
/**
* The current data network list, including the ones that are connected, connecting, or
* disconnecting.
*/
private final @NonNull List<DataNetwork> mDataNetworkList = new ArrayList<>();
/** {@code true} indicating at least one data network exists. */
private boolean mAnyDataNetworkExisting;
/**
* Contain the last 10 data networks that were connected. This is for debugging purposes only.
*/
private final @NonNull List<DataNetwork> mPreviousConnectedDataNetworkList = new ArrayList<>();
/**
* The internet data network state. Note that this is the best effort if more than one
* data network supports internet.
*/
private @DataState int mInternetDataNetworkState = TelephonyManager.DATA_DISCONNECTED;
/**
* The IMS data network state. For now this is just for debugging purposes.
*/
private @DataState int mImsDataNetworkState = TelephonyManager.DATA_DISCONNECTED;
/** Overall aggregated link status from internet data networks. */
private @LinkStatus int mInternetLinkStatus = DataCallResponse.LINK_STATUS_UNKNOWN;
/** Data network controller callbacks. */
private final @NonNull Set<DataNetworkControllerCallback> mDataNetworkControllerCallbacks =
new ArraySet<>();
/** Indicates if packet switch data is restricted by the cellular network. */
private boolean mPsRestricted = false;
/** Indicates if NR advanced is allowed by PCO. */
private boolean mNrAdvancedCapableByPco = false;
/**
* Indicates if the data services are bound. Key if the transport type, and value is the boolean
* indicating service is bound or not.
*/
private final @NonNull SparseBooleanArray mDataServiceBound = new SparseBooleanArray();
/** SIM state. */
private @SimState int mSimState = TelephonyManager.SIM_STATE_UNKNOWN;
/** Data activity. */
private @DataActivityType int mDataActivity = TelephonyManager.DATA_ACTIVITY_NONE;
/**
* IMS state callbacks. Key is the IMS feature, value is the callback.
*/
private final @NonNull SparseArray<ImsStateCallback> mImsStateCallbacks = new SparseArray<>();
/** Registered IMS features. Unregistered IMS features are removed from the set. */
private final @NonNull Set<Integer> mRegisteredImsFeatures = new ArraySet<>();
/** IMS feature package names. Key is the IMS feature, value is the package name. */
private final @NonNull SparseArray<String> mImsFeaturePackageName = new SparseArray<>();
/**
* Networks that are pending IMS de-registration. Key is the data network, value is the function
* to tear down the network.
*/
private final @NonNull Map<DataNetwork, Runnable> mPendingImsDeregDataNetworks =
new ArrayMap<>();
/**
* IMS feature registration callback. The key is the IMS feature, the value is the registration
* callback. When new SIM inserted, the old callbacks associated with the old subscription index
* will be unregistered.
*/
private final @NonNull SparseArray<RegistrationManager.RegistrationCallback>
mImsFeatureRegistrationCallbacks = new SparseArray<>();
/** The counter to detect back to back release/request IMS network. */
private @NonNull SlidingWindowEventCounter mImsThrottleCounter;
/** Event counter for unwanted network within time window, is used to trigger anomaly report. */
private @NonNull SlidingWindowEventCounter mNetworkUnwantedCounter;
/** Event counter for WLAN setup data failure within time window to trigger anomaly report. */
private @NonNull SlidingWindowEventCounter mSetupDataCallWlanFailureCounter;
/** Event counter for WWAN setup data failure within time window to trigger anomaly report. */
private @NonNull SlidingWindowEventCounter mSetupDataCallWwanFailureCounter;
/**
* {@code true} if {@link #tearDownAllDataNetworks(int)} was invoked and waiting for all
* networks torn down.
*/
private boolean mPendingTearDownAllNetworks = false;
/**
* The capabilities of the latest released IMS request. To detect back to back release/request
* IMS network.
*/
private int[] mLastReleasedImsRequestCapabilities;
/** True after try to release an IMS network; False after try to request an IMS network. */
private boolean mLastImsOperationIsRelease;
/** The broadcast receiver. */
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
switch(intent.getAction()) {
case TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED:
case TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED:
if (mPhone.getPhoneId() == intent.getIntExtra(
SubscriptionManager.EXTRA_SLOT_INDEX,
SubscriptionManager.INVALID_SIM_SLOT_INDEX)) {
int simState = intent.getIntExtra(TelephonyManager.EXTRA_SIM_STATE,
TelephonyManager.SIM_STATE_UNKNOWN);
sendMessage(obtainMessage(EVENT_SIM_STATE_CHANGED, simState, 0));
}
}
}
};
/**
* The sorted network request list by priority. The highest priority network request stays at
* the head of the list. The highest priority is 100, the lowest is 0.
*
* Note this list is not thread-safe. Do not access the list from different threads.
*/
@VisibleForTesting
public static class NetworkRequestList extends LinkedList<TelephonyNetworkRequest> {
/**
* Constructor
*/
public NetworkRequestList() {
}
/**
* Copy constructor
*
* @param requestList The network request list.
*/
public NetworkRequestList(@NonNull NetworkRequestList requestList) {
addAll(requestList);
}
/**
* Constructor
*
* @param requestList The network request list.
*/
public NetworkRequestList(@NonNull List<TelephonyNetworkRequest> requestList) {
addAll(requestList);
}
/**
* Constructor
*
* @param newRequest The initial request of the list.
*/
public NetworkRequestList(@NonNull TelephonyNetworkRequest newRequest) {
this();
add(newRequest);
}
/**
* Add the network request to the list. Note that the item will be inserted to the position
* based on the priority.
*
* @param newRequest The network request to be added.
* @return {@code true} if added successfully. {@code false} if the request already exists.
*/
@Override
public boolean add(@NonNull TelephonyNetworkRequest newRequest) {
int index = 0;
while (index < size()) {
TelephonyNetworkRequest networkRequest = get(index);
if (networkRequest.equals(newRequest)) {
return false; // Do not allow duplicate
}
if (newRequest.getPriority() > networkRequest.getPriority()) {
break;
}
index++;
}
super.add(index, newRequest);
return true;
}
@Override
public void add(int index, @NonNull TelephonyNetworkRequest newRequest) {
throw new UnsupportedOperationException("Insertion to certain position is illegal.");
}
@Override
public boolean addAll(Collection<? extends TelephonyNetworkRequest> requests) {
for (TelephonyNetworkRequest networkRequest : requests) {
add(networkRequest);
}
return true;
}
/**
* Get the first network request that contains all the provided network capabilities.
*
* @param netCaps The network capabilities.
* @return The first network request in the list that contains all the provided
* capabilities.
*/
public @Nullable TelephonyNetworkRequest get(@NonNull @NetCapability int[] netCaps) {
int index = 0;
while (index < size()) {
TelephonyNetworkRequest networkRequest = get(index);
// Check if any network requests contains all the provided capabilities.
if (Arrays.stream(networkRequest.getCapabilities())
.boxed()
.collect(Collectors.toSet())
.containsAll(Arrays.stream(netCaps).boxed()
.collect(Collectors.toList()))) {
return networkRequest;
}
index++;
}
return null;
}
/**
* Check if any network request is requested by the specified package.
*
* @param packageName The package name.
* @return {@code true} if any request is originated from the specified package.
*/
public boolean hasNetworkRequestsFromPackage(@NonNull String packageName) {
for (TelephonyNetworkRequest networkRequest : this) {
if (packageName.equals(
networkRequest.getNativeNetworkRequest().getRequestorPackageName())) {
return true;
}
}
return false;
}
@Override
public String toString() {
return "[NetworkRequestList: size=" + size() + (size() > 0 ? ", leading by "
+ get(0) : "") + "]";
}
/**
* Dump the network request list.
*
* @param pw print writer.
*/
public void dump(IndentingPrintWriter pw) {
pw.increaseIndent();
for (TelephonyNetworkRequest networkRequest : this) {
pw.println(networkRequest);
}
pw.decreaseIndent();
}
}
/**
* The data network controller callback. Note this is only used for passing information
* internally in the data stack, should not be used externally.
*/
public static class DataNetworkControllerCallback extends DataCallback {
/**
* Constructor
*
* @param executor The executor of the callback.
*/
public DataNetworkControllerCallback(@NonNull @CallbackExecutor Executor executor) {
super(executor);
}
/**
* Called when internet data network validation status changed.
*
* @param validationStatus The validation status.
*/
public void onInternetDataNetworkValidationStatusChanged(
@ValidationStatus int validationStatus) {}
/**
* Called when internet data network is connected.
*
* @param dataProfiles The data profiles of the connected internet data network. It should
* be only one in most of the cases.
*/
public void onInternetDataNetworkConnected(@NonNull List<DataProfile> dataProfiles) {}
/**
* Called when data network is connected.
*
* @param transport Transport for the connected network.
* @param dataProfile The data profile of the connected data network.
*/
public void onDataNetworkConnected(@TransportType int transport,
@NonNull DataProfile dataProfile) {}
/** Called when internet data network is disconnected. */
public void onInternetDataNetworkDisconnected() {}
/**
* Called when any data network existing status changed.
*
* @param anyDataExisting {@code true} indicating there is at least one data network
* existing regardless of its state. {@code false} indicating all data networks are
* disconnected.
*/
public void onAnyDataNetworkExistingChanged(boolean anyDataExisting) {}
/**
* Called when {@link SubscriptionPlan}s change or an unmetered or congested subscription
* override is set.
*/
public void onSubscriptionPlanOverride() {}
/**
* Called when the physical link status changed.
*
* @param status The latest link status.
*/
public void onPhysicalLinkStatusChanged(@LinkStatus int status) {}
/**
* Called when NR advanced capable by PCO changed.
*
* @param nrAdvancedCapable {@code true} if at least one of the data network is NR advanced
* capable.
*/
public void onNrAdvancedCapableByPcoChanged(boolean nrAdvancedCapable) {}
/**
* Called when data service is bound.
*
* @param transport The transport of the data service.
*/
public void onDataServiceBound(@TransportType int transport) {}
/**
* Called when SIM load state changed.
*
* @param simState The current SIM state
*/
public void onSimStateChanged(@SimState int simState) {}
}
/**
* This class represent a rule allowing or disallowing handover between IWLAN and cellular
* networks.
*
* @see CarrierConfigManager#KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY
*/
public static class HandoverRule {
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"RULE_TYPE_"},
value = {
RULE_TYPE_ALLOWED,
RULE_TYPE_DISALLOWED,
})
public @interface HandoverRuleType {}
/** Indicating this rule is for allowing handover. */
public static final int RULE_TYPE_ALLOWED = 1;
/** Indicating this rule is for disallowing handover. */
public static final int RULE_TYPE_DISALLOWED = 2;
private static final String RULE_TAG_SOURCE_ACCESS_NETWORKS = "source";
private static final String RULE_TAG_TARGET_ACCESS_NETWORKS = "target";
private static final String RULE_TAG_TYPE = "type";
private static final String RULE_TAG_CAPABILITIES = "capabilities";
private static final String RULE_TAG_ROAMING = "roaming";
/** Handover rule type. */
public final @HandoverRuleType int type;
/** The applicable source access networks for handover. */
public final @NonNull @RadioAccessNetworkType Set<Integer> sourceAccessNetworks;
/** The applicable target access networks for handover. */
public final @NonNull @RadioAccessNetworkType Set<Integer> targetAccessNetworks;
/**
* The network capabilities to any of which this handover rule applies.
* If is empty, then capability is ignored as a rule matcher.
*/
public final @NonNull @NetCapability Set<Integer> networkCapabilities;
/** {@code true} indicates this policy is only applicable when the device is roaming. */
public final boolean isOnlyForRoaming;
/**
* Constructor
*
* @param ruleString The rule in string format.
*
* @see CarrierConfigManager#KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY
*/
public HandoverRule(@NonNull String ruleString) {
if (TextUtils.isEmpty(ruleString)) {
throw new IllegalArgumentException("illegal rule " + ruleString);
}
Set<Integer> source = null, target = null, capabilities = Collections.emptySet();
int type = 0;
boolean roaming = false;
ruleString = ruleString.trim().toLowerCase(Locale.ROOT);
String[] expressions = ruleString.split("\\s*,\\s*");
for (String expression : expressions) {
String[] tokens = expression.trim().split("\\s*=\\s*");
if (tokens.length != 2) {
throw new IllegalArgumentException("illegal rule " + ruleString + ", tokens="
+ Arrays.toString(tokens));
}
String key = tokens[0].trim();
String value = tokens[1].trim();
try {
switch (key) {
case RULE_TAG_SOURCE_ACCESS_NETWORKS:
source = Arrays.stream(value.split("\\s*\\|\\s*"))
.map(String::trim)
.map(AccessNetworkType::fromString)
.collect(Collectors.toSet());
break;
case RULE_TAG_TARGET_ACCESS_NETWORKS:
target = Arrays.stream(value.split("\\s*\\|\\s*"))
.map(String::trim)
.map(AccessNetworkType::fromString)
.collect(Collectors.toSet());
break;
case RULE_TAG_TYPE:
if (value.toLowerCase(Locale.ROOT).equals("allowed")) {
type = RULE_TYPE_ALLOWED;
} else if (value.toLowerCase(Locale.ROOT).equals("disallowed")) {
type = RULE_TYPE_DISALLOWED;
} else {
throw new IllegalArgumentException("unexpected rule type " + value);
}
break;
case RULE_TAG_CAPABILITIES:
capabilities = DataUtils.getNetworkCapabilitiesFromString(value);
break;
case RULE_TAG_ROAMING:
roaming = Boolean.parseBoolean(value);
break;
default:
throw new IllegalArgumentException("unexpected key " + key);
}
} catch (Exception e) {
e.printStackTrace();
throw new IllegalArgumentException("illegal rule \"" + ruleString + "\", e="
+ e);
}
}
if (source == null || target == null || source.isEmpty() || target.isEmpty()) {
throw new IllegalArgumentException("Need to specify both source and target. "
+ "\"" + ruleString + "\"");
}
if (source.contains(AccessNetworkType.UNKNOWN) && type != RULE_TYPE_DISALLOWED) {
throw new IllegalArgumentException("Unknown access network can be only specified in"
+ " the disallowed rule. \"" + ruleString + "\"");
}
if (target.contains(AccessNetworkType.UNKNOWN)) {
throw new IllegalArgumentException("Target access networks contains unknown. "
+ "\"" + ruleString + "\"");
}
if (type == 0) {
throw new IllegalArgumentException("Rule type is not specified correctly. "
+ "\"" + ruleString + "\"");
}
if (capabilities != null && capabilities.contains(-1)) {
throw new IllegalArgumentException("Network capabilities contains unknown. "
+ "\"" + ruleString + "\"");
}
if (!source.contains(AccessNetworkType.IWLAN)
&& !target.contains(AccessNetworkType.IWLAN)) {
throw new IllegalArgumentException("IWLAN must be specified in either source or "
+ "target access networks.\"" + ruleString + "\"");
}
sourceAccessNetworks = source;
targetAccessNetworks = target;
this.type = type;
networkCapabilities = capabilities;
isOnlyForRoaming = roaming;
}
@Override
public String toString() {
return "[HandoverRule: type=" + (type == RULE_TYPE_ALLOWED ? "allowed"
: "disallowed") + ", source=" + sourceAccessNetworks.stream()
.map(AccessNetworkType::toString).collect(Collectors.joining("|"))
+ ", target=" + targetAccessNetworks.stream().map(AccessNetworkType::toString)
.collect(Collectors.joining("|")) + ", isRoaming=" + isOnlyForRoaming
+ ", capabilities=" + DataUtils.networkCapabilitiesToString(networkCapabilities)
+ "]";
}
}
/**
* Constructor
*
* @param phone The phone instance.
* @param looper The looper to be used by the handler. Currently the handler thread is the
* phone process's main thread.
*/
public DataNetworkController(@NonNull Phone phone, @NonNull Looper looper) {
super(looper);
mPhone = phone;
mLogTag = "DNC-" + mPhone.getPhoneId();
log("DataNetworkController created.");
mAccessNetworksManager = phone.getAccessNetworksManager();
for (int transport : mAccessNetworksManager.getAvailableTransports()) {
mDataServiceManagers.put(transport, new DataServiceManager(mPhone, looper, transport));
}
mDataConfigManager = new DataConfigManager(mPhone, looper);
// ========== Anomaly counters ==========
mImsThrottleCounter = new SlidingWindowEventCounter(
mDataConfigManager.getAnomalyImsReleaseRequestThreshold().timeWindow,
mDataConfigManager.getAnomalyImsReleaseRequestThreshold().eventNumOccurrence);
mNetworkUnwantedCounter = new SlidingWindowEventCounter(
mDataConfigManager.getAnomalyNetworkUnwantedThreshold().timeWindow,
mDataConfigManager.getAnomalyNetworkUnwantedThreshold().eventNumOccurrence);
mSetupDataCallWlanFailureCounter = new SlidingWindowEventCounter(
mDataConfigManager.getAnomalySetupDataCallThreshold().timeWindow,
mDataConfigManager.getAnomalySetupDataCallThreshold().eventNumOccurrence);
mSetupDataCallWwanFailureCounter = new SlidingWindowEventCounter(
mDataConfigManager.getAnomalySetupDataCallThreshold().timeWindow,
mDataConfigManager.getAnomalySetupDataCallThreshold().eventNumOccurrence);
// ========================================
mDataSettingsManager = TelephonyComponentFactory.getInstance().inject(
DataSettingsManager.class.getName())
.makeDataSettingsManager(mPhone, this, looper,
new DataSettingsManagerCallback(this::post) {
@Override
public void onDataEnabledChanged(boolean enabled,
@TelephonyManager.DataEnabledChangedReason int reason,
@NonNull String callingPackage) {
// If mobile data is enabled by the user, evaluate the unsatisfied
// network requests and then attempt to setup data networks to
// satisfy them. If mobile data is disabled, evaluate the existing
// data networks and see if they need to be torn down.
logl("onDataEnabledChanged: enabled=" + enabled);
sendMessage(obtainMessage(enabled
? EVENT_REEVALUATE_UNSATISFIED_NETWORK_REQUESTS
: EVENT_REEVALUATE_EXISTING_DATA_NETWORKS,
DataEvaluationReason.DATA_ENABLED_CHANGED));
}
@Override
public void onDataEnabledOverrideChanged(boolean enabled,
@TelephonyManager.MobileDataPolicy int policy) {
// If data enabled override is enabled by the user, evaluate the
// unsatisfied network requests and then attempt to setup data
// networks to satisfy them. If data enabled override is disabled,
// evaluate the existing data networks and see if they need to be
// torn down.
logl("onDataEnabledOverrideChanged: enabled=" + enabled);
sendMessage(obtainMessage(enabled
? EVENT_REEVALUATE_UNSATISFIED_NETWORK_REQUESTS
: EVENT_REEVALUATE_EXISTING_DATA_NETWORKS,
DataEvaluationReason.DATA_ENABLED_OVERRIDE_CHANGED));
}
@Override
public void onDataRoamingEnabledChanged(boolean enabled) {
// If data roaming is enabled by the user, evaluate the unsatisfied
// network requests and then attempt to setup data networks to
// satisfy them. If data roaming is disabled, evaluate the existing
// data networks and see if they need to be torn down.
logl("onDataRoamingEnabledChanged: enabled=" + enabled);
sendMessage(obtainMessage(enabled
? EVENT_REEVALUATE_UNSATISFIED_NETWORK_REQUESTS
: EVENT_REEVALUATE_EXISTING_DATA_NETWORKS,
DataEvaluationReason.ROAMING_ENABLED_CHANGED));
}
});
mDataProfileManager = TelephonyComponentFactory.getInstance().inject(
DataProfileManager.class.getName())
.makeDataProfileManager(mPhone, this, mDataServiceManagers
.get(AccessNetworkConstants.TRANSPORT_TYPE_WWAN), looper,
new DataProfileManagerCallback(this::post) {
@Override
public void onDataProfilesChanged() {
sendMessage(
obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS,
DataEvaluationReason.DATA_PROFILES_CHANGED));
sendMessage(
obtainMessage(EVENT_REEVALUATE_UNSATISFIED_NETWORK_REQUESTS,
DataEvaluationReason.DATA_PROFILES_CHANGED));
}
});
mDataStallRecoveryManager = new DataStallRecoveryManager(mPhone, this, mDataServiceManagers
.get(AccessNetworkConstants.TRANSPORT_TYPE_WWAN), looper,
new DataStallRecoveryManagerCallback(this::post) {
@Override
public void onDataStallReestablishInternet() {
DataNetworkController.this.onDataStallReestablishInternet();
}
});
mDataRetryManager = new DataRetryManager(mPhone, this,
mDataServiceManagers, looper,
new DataRetryManagerCallback(this::post) {
@Override
public void onDataNetworkSetupRetry(
@NonNull DataSetupRetryEntry dataSetupRetryEntry) {
Objects.requireNonNull(dataSetupRetryEntry);
DataNetworkController.this.onDataNetworkSetupRetry(dataSetupRetryEntry);
}
@Override
public void onDataNetworkHandoverRetry(
@NonNull DataHandoverRetryEntry dataHandoverRetryEntry) {
Objects.requireNonNull(dataHandoverRetryEntry);
DataNetworkController.this
.onDataNetworkHandoverRetry(dataHandoverRetryEntry);
}
@Override
public void onDataNetworkHandoverRetryStopped(
@NonNull DataNetwork dataNetwork) {
Objects.requireNonNull(dataNetwork);
int preferredTransport = mAccessNetworksManager
.getPreferredTransportByNetworkCapability(
dataNetwork.getApnTypeNetworkCapability());
if (dataNetwork.getTransport() == preferredTransport) {
log("onDataNetworkHandoverRetryStopped: " + dataNetwork + " is already "
+ "on the preferred transport "
+ AccessNetworkConstants.transportTypeToString(
preferredTransport));
return;
}
if (dataNetwork.shouldDelayImsTearDown()) {
log("onDataNetworkHandoverRetryStopped: Delay IMS tear down until call "
+ "ends. " + dataNetwork);
return;
}
tearDownGracefully(dataNetwork,
DataNetwork.TEAR_DOWN_REASON_HANDOVER_FAILED);
}
});
mImsManager = mPhone.getContext().getSystemService(ImsManager.class);
mNetworkPolicyManager = mPhone.getContext().getSystemService(NetworkPolicyManager.class);
// Use the raw one from ServiceStateTracker instead of the combined one from
// mPhone.getServiceState().
mServiceState = mPhone.getServiceStateTracker().getServiceState();
// Instead of calling onRegisterAllEvents directly from the constructor, send the event.
// The reason is that getImsPhone is null when we are still in the constructor here.
sendEmptyMessage(EVENT_REGISTER_ALL_EVENTS);
}
/**
* Called when needed to register for all events that data network controller is interested.
*/
private void onRegisterAllEvents() {
IntentFilter filter = new IntentFilter();
filter.addAction(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED);
filter.addAction(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED);
mPhone.getContext().registerReceiver(mIntentReceiver, filter, null, mPhone);
mAccessNetworksManager.registerCallback(new AccessNetworksManagerCallback(this::post) {
@Override
public void onPreferredTransportChanged(@NetCapability int capability) {
int preferredTransport = mAccessNetworksManager
.getPreferredTransportByNetworkCapability(capability);
logl("onPreferredTransportChanged: "
+ DataUtils.networkCapabilityToString(capability) + " preferred on "
+ AccessNetworkConstants.transportTypeToString(preferredTransport));
DataNetworkController.this.onEvaluatePreferredTransport(capability);
if (!hasMessages(EVENT_REEVALUATE_UNSATISFIED_NETWORK_REQUESTS)) {
sendMessage(obtainMessage(EVENT_REEVALUATE_UNSATISFIED_NETWORK_REQUESTS,
DataEvaluationReason.PREFERRED_TRANSPORT_CHANGED));
} else {
log("onPreferredTransportChanged: Skipped evaluating unsatisfied network "
+ "requests because another evaluation was already scheduled.");
}
}
});
mNetworkPolicyManager.registerSubscriptionCallback(new SubscriptionCallback() {
@Override
public void onSubscriptionPlansChanged(int subId, SubscriptionPlan[] plans) {
if (mSubId != subId) return;
obtainMessage(EVENT_SUBSCRIPTION_PLANS_CHANGED, plans).sendToTarget();
}
@Override
public void onSubscriptionOverride(int subId, int overrideMask, int overrideValue,
int[] networkTypes) {
if (mSubId != subId) return;
obtainMessage(EVENT_SUBSCRIPTION_OVERRIDE, overrideMask, overrideValue,
networkTypes).sendToTarget();
}
});
mPhone.getServiceStateTracker().registerForServiceStateChanged(this,
EVENT_SERVICE_STATE_CHANGED, null);
mDataConfigManager.registerCallback(new DataConfigManagerCallback(this::post) {
@Override
public void onCarrierConfigChanged() {
DataNetworkController.this.onCarrierConfigUpdated();
}
@Override
public void onDeviceConfigChanged() {
DataNetworkController.this.onDeviceConfigUpdated();
}
});
mPhone.getServiceStateTracker().registerForPsRestrictedEnabled(this,
EVENT_PS_RESTRICT_ENABLED, null);
mPhone.getServiceStateTracker().registerForPsRestrictedDisabled(this,
EVENT_PS_RESTRICT_DISABLED, null);
mPhone.getServiceStateTracker().registerForAreaCodeChanged(this, EVENT_TAC_CHANGED, null);
mPhone.registerForEmergencyCallToggle(this, EVENT_EMERGENCY_CALL_CHANGED, null);
mDataServiceManagers.get(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
.registerForServiceBindingChanged(this, EVENT_DATA_SERVICE_BINDING_CHANGED);
mPhone.getServiceStateTracker().registerForServiceStateChanged(this,
EVENT_SERVICE_STATE_CHANGED, null);
mDataServiceManagers.get(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
.registerForServiceBindingChanged(this, EVENT_DATA_SERVICE_BINDING_CHANGED);
mPhone.getContext().getSystemService(TelephonyRegistryManager.class)
.addOnSubscriptionsChangedListener(new OnSubscriptionsChangedListener() {
@Override
public void onSubscriptionsChanged() {
sendEmptyMessage(EVENT_SUBSCRIPTION_CHANGED);
}
}, this::post);
// Register for call ended event for voice/data concurrent not supported case. It is
// intended to only listen for events from the same phone as most of the telephony modules
// are designed as per-SIM basis. For DSDS call ended on non-DDS sub, the frameworks relies
// on service state on DDS sub change from out-of-service to in-service to trigger data
// retry.
mPhone.getCallTracker().registerForVoiceCallEnded(this, EVENT_VOICE_CALL_ENDED, null);
// Check null for devices not supporting FEATURE_TELEPHONY_IMS.
if (mPhone.getImsPhone() != null) {
mPhone.getImsPhone().getCallTracker().registerForVoiceCallEnded(
this, EVENT_VOICE_CALL_ENDED, null);
}
mPhone.mCi.registerForSlicingConfigChanged(this, EVENT_SLICE_CONFIG_CHANGED, null);
mPhone.getLinkBandwidthEstimator().registerCallback(
new LinkBandwidthEstimatorCallback(this::post) {
@Override
public void onDataActivityChanged(@DataActivityType int dataActivity) {
DataNetworkController.this.updateDataActivity();
}
}
);
}
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case EVENT_REGISTER_ALL_EVENTS:
onRegisterAllEvents();
break;
case EVENT_ADD_NETWORK_REQUEST:
onAddNetworkRequest((TelephonyNetworkRequest) msg.obj);
break;
case EVENT_REEVALUATE_UNSATISFIED_NETWORK_REQUESTS:
DataEvaluationReason reason = (DataEvaluationReason) msg.obj;
onReevaluateUnsatisfiedNetworkRequests(reason);
break;
case EVENT_REEVALUATE_EXISTING_DATA_NETWORKS:
reason = (DataEvaluationReason) msg.obj;
onReevaluateExistingDataNetworks(reason);
break;
case EVENT_REMOVE_NETWORK_REQUEST:
onRemoveNetworkRequest((TelephonyNetworkRequest) msg.obj);
break;
case EVENT_VOICE_CALL_ENDED:
// In some cases we need to tear down network after call ends. For example, when
// delay IMS tear down until call ends is turned on.
sendMessage(obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS,
DataEvaluationReason.VOICE_CALL_ENDED));
// Delay evaluating unsatisfied network requests. In temporary DDS switch case, it
// takes some time to switch DDS after call end. We do not want to bring up network
// before switch completes.
sendMessageDelayed(obtainMessage(EVENT_REEVALUATE_UNSATISFIED_NETWORK_REQUESTS,
DataEvaluationReason.VOICE_CALL_ENDED),
REEVALUATE_UNSATISFIED_NETWORK_REQUESTS_AFTER_CALL_END_DELAY_MILLIS);
break;
case EVENT_SLICE_CONFIG_CHANGED:
sendMessage(obtainMessage(EVENT_REEVALUATE_UNSATISFIED_NETWORK_REQUESTS,
DataEvaluationReason.SLICE_CONFIG_CHANGED));
break;
case EVENT_PS_RESTRICT_ENABLED:
mPsRestricted = true;
break;
case EVENT_PS_RESTRICT_DISABLED:
mPsRestricted = false;
sendMessage(obtainMessage(EVENT_REEVALUATE_UNSATISFIED_NETWORK_REQUESTS,
DataEvaluationReason.DATA_RESTRICTED_CHANGED));
break;
case EVENT_TAC_CHANGED:
// Re-evaluate unsatisfied network requests with some delays to let DataRetryManager
// clears the throttling record.
sendMessageDelayed(obtainMessage(EVENT_REEVALUATE_UNSATISFIED_NETWORK_REQUESTS,
DataEvaluationReason.TAC_CHANGED),
REEVALUATE_UNSATISFIED_NETWORK_REQUESTS_TAC_CHANGED_DELAY_MILLIS);
break;
case EVENT_DATA_SERVICE_BINDING_CHANGED:
AsyncResult ar = (AsyncResult) msg.obj;
int transport = (int) ar.userObj;
boolean bound = (boolean) ar.result;
onDataServiceBindingChanged(transport, bound);
break;
case EVENT_SIM_STATE_CHANGED:
int simState = msg.arg1;
onSimStateChanged(simState);
break;
case EVENT_TEAR_DOWN_ALL_DATA_NETWORKS:
onTearDownAllDataNetworks(msg.arg1);
break;
case EVENT_REGISTER_DATA_NETWORK_CONTROLLER_CALLBACK:
DataNetworkControllerCallback callback = (DataNetworkControllerCallback) msg.obj;
mDataNetworkControllerCallbacks.add(callback);
// Notify upon registering if no data networks currently exist.
if (mDataNetworkList.isEmpty()) {
callback.invokeFromExecutor(
() -> callback.onAnyDataNetworkExistingChanged(false));
}
break;
case EVENT_UNREGISTER_DATA_NETWORK_CONTROLLER_CALLBACK:
mDataNetworkControllerCallbacks.remove((DataNetworkControllerCallback) msg.obj);
break;
case EVENT_SUBSCRIPTION_CHANGED:
onSubscriptionChanged();
break;
case EVENT_SERVICE_STATE_CHANGED:
onServiceStateChanged();
break;
case EVENT_EMERGENCY_CALL_CHANGED:
if (mPhone.isInEcm()) {
sendMessage(obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS,
DataEvaluationReason.EMERGENCY_CALL_CHANGED));
} else {
sendMessage(obtainMessage(EVENT_REEVALUATE_UNSATISFIED_NETWORK_REQUESTS,
DataEvaluationReason.EMERGENCY_CALL_CHANGED));
}
break;
case EVENT_EVALUATE_PREFERRED_TRANSPORT:
onEvaluatePreferredTransport(msg.arg1);
break;
case EVENT_SUBSCRIPTION_PLANS_CHANGED:
SubscriptionPlan[] plans = (SubscriptionPlan[]) msg.obj;
log("Subscription plans changed: " + Arrays.toString(plans));
mSubscriptionPlans.clear();
mSubscriptionPlans.addAll(Arrays.asList(plans));
mDataNetworkControllerCallbacks.forEach(cb -> cb.invokeFromExecutor(
() -> cb.onSubscriptionPlanOverride()));
break;
case EVENT_SUBSCRIPTION_OVERRIDE:
int overrideMask = msg.arg1;
boolean override = msg.arg2 != 0;
int[] networkTypes = (int[]) msg.obj;
if (overrideMask == NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_UNMETERED) {
log("Unmetered subscription override: override=" + override
+ ", networkTypes=" + Arrays.stream(networkTypes)
.mapToObj(TelephonyManager::getNetworkTypeName)
.collect(Collectors.joining(",")));
for (int networkType : networkTypes) {
if (override) {
mUnmeteredOverrideNetworkTypes.add(networkType);
} else {
mUnmeteredOverrideNetworkTypes.remove(networkType);
}
}
mDataNetworkControllerCallbacks.forEach(cb -> cb.invokeFromExecutor(
() -> cb.onSubscriptionPlanOverride()));
} else if (overrideMask == NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_CONGESTED) {
log("Congested subscription override: override=" + override
+ ", networkTypes=" + Arrays.stream(networkTypes)
.mapToObj(TelephonyManager::getNetworkTypeName)
.collect(Collectors.joining(",")));
for (int networkType : networkTypes) {
if (override) {
mCongestedOverrideNetworkTypes.add(networkType);
} else {
mCongestedOverrideNetworkTypes.remove(networkType);
}
}
mDataNetworkControllerCallbacks.forEach(cb -> cb.invokeFromExecutor(
() -> cb.onSubscriptionPlanOverride()));
} else {
loge("Unknown override mask: " + overrideMask);
}
break;
default:
loge("Unexpected event " + msg.what);
}
}
/**
* Add a network request, which is originated from the apps. Note that add a network request
* is not necessarily setting up a {@link DataNetwork}.
*
* @param networkRequest Network request
*
*/
public void addNetworkRequest(@NonNull TelephonyNetworkRequest networkRequest) {
sendMessage(obtainMessage(EVENT_ADD_NETWORK_REQUEST, networkRequest));
}
/**
* Called when a network request arrives data network controller.
*
* @param networkRequest The network request.
*/
private void onAddNetworkRequest(@NonNull TelephonyNetworkRequest networkRequest) {
// To detect IMS back-to-back release-request anomaly event
if (mLastImsOperationIsRelease) {
mLastImsOperationIsRelease = false;
if (Arrays.equals(
mLastReleasedImsRequestCapabilities, networkRequest.getCapabilities())
&& mImsThrottleCounter.addOccurrence()) {
reportAnomaly(networkRequest.getNativeNetworkRequest().getRequestorPackageName()
+ " requested with same capabilities "
+ mImsThrottleCounter.getFrequencyString(),
"ead6f8db-d2f2-4ed3-8da5-1d8560fe7daf");
}
}
if (!mAllNetworkRequestList.add(networkRequest)) {
loge("onAddNetworkRequest: Duplicate network request. " + networkRequest);
return;
}
log("onAddNetworkRequest: added " + networkRequest);
onSatisfyNetworkRequest(networkRequest);
}
/**
* Called when attempting to satisfy a network request. If after evaluation, the network
* request is determined that can be satisfied, the data network controller will establish
* the data network. If the network request can't be satisfied, it will remain in the
* unsatisfied pool until the environment changes.
*
* @param networkRequest The network request to be satisfied.
*/
private void onSatisfyNetworkRequest(@NonNull TelephonyNetworkRequest networkRequest) {
if (networkRequest.getState() == TelephonyNetworkRequest.REQUEST_STATE_SATISFIED) {
logv("Already satisfied. " + networkRequest);
return;
}
// Check if there is any existing data network that can satisfy the network request, and
// attempt to attach if possible.
if (findCompatibleDataNetworkAndAttach(networkRequest)) {
return;
}
// If no data network can satisfy the requests, then start the evaluation process. Since
// all the requests in the list have the same capabilities, we can only evaluate one
// of them.
DataEvaluation evaluation = evaluateNetworkRequest(networkRequest,
DataEvaluationReason.NEW_REQUEST);
if (!evaluation.containsDisallowedReasons()) {
DataProfile dataProfile = evaluation.getCandidateDataProfile();
if (dataProfile != null) {
setupDataNetwork(dataProfile, null,
evaluation.getDataAllowedReason());
}
} else if (evaluation.contains(DataDisallowedReason.ONLY_ALLOWED_SINGLE_NETWORK)) {
// Re-evaluate the existing data networks. If this request's priority is higher than
// the existing data network, the data network will be torn down so this request will
// get a chance to be satisfied.
sendMessage(obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS,
DataEvaluationReason.SINGLE_DATA_NETWORK_ARBITRATION));
}
}
/**
* Attempt to attach a network request to an existing data network that can satisfy the
* network request.
*
* @param networkRequest The network request to attach.
*
* @return {@code false} if can't find the data network to to satisfy the network request.
* {@code true} if the network request has been scheduled to attach to the data network.
* If attach succeeds, the network request's state will be set to
* {@link TelephonyNetworkRequest#REQUEST_STATE_SATISFIED}. If failed,
* {@link #onAttachNetworkRequestsFailed(DataNetwork, NetworkRequestList)} will be invoked.
*/
private boolean findCompatibleDataNetworkAndAttach(
@NonNull TelephonyNetworkRequest networkRequest) {
return findCompatibleDataNetworkAndAttach(new NetworkRequestList(networkRequest));
}
/**
* Attempt to attach a network request list to an existing data network that can satisfy all the
* network requests. Note this method does not support partial attach (i.e. Only attach some
* of the satisfiable requests to the network). All requests must be satisfied so they can be
* attached.
*
* @param requestList The network request list to attach. It is expected that every network
* request in this list has the same network capabilities.
*
* @return {@code false} if can't find the data network to to satisfy the network requests, even
* if only one of network request can't be satisfied. {@code true} if the network request
* has been scheduled to attach to the data network. If attach succeeds, the network request's
* state will be set to
* {@link TelephonyNetworkRequest#REQUEST_STATE_SATISFIED}. If failed,
* {@link #onAttachNetworkRequestsFailed(DataNetwork, NetworkRequestList)} will be invoked.
*/
private boolean findCompatibleDataNetworkAndAttach(@NonNull NetworkRequestList requestList) {
// Try to find a data network that can satisfy all the network requests.
for (DataNetwork dataNetwork : mDataNetworkList) {
TelephonyNetworkRequest networkRequest = requestList.stream()
.filter(request -> !request.canBeSatisfiedBy(
dataNetwork.getNetworkCapabilities()))
.findAny()
.orElse(null);
// If found any request that can't be satisfied by this data network, continue to try
// next data network. We must find a data network that can satisfy all the provided
// network requests.
if (networkRequest != null) {
continue;
}
// When reaching here, it means this data network can satisfy all the network requests.
logv("Found a compatible data network " + dataNetwork + ". Attaching "
+ requestList);
return dataNetwork.attachNetworkRequests(requestList);
}
return false;
}
/**
* @param ss The service state to be checked
* @param transport The transport is used to determine the data registration state
*
* @return {@code true} if data is in service or if voice is in service on legacy CS
* connections (2G/3G) on the non-DDS. In those cases we attempt to attach PS. We don't try for
* newer RAT because for those PS attach already occurred.
*/
private boolean serviceStateAllowsPSAttach(@NonNull ServiceState ss,
@TransportType int transport) {
// Use the data registration state from the modem instead of the current data registration
// state, which can be overridden.
int nriRegState = getDataRegistrationState(ss, transport);
if (nriRegState == NetworkRegistrationInfo.REGISTRATION_STATE_HOME
|| nriRegState == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING) return true;
// If data is OOS as this device slot is not modem preferred(i.e. not active for internet),
// attempt to attach PS on 2G/3G if CS connection is available.
return ss.getVoiceRegState() == ServiceState.STATE_IN_SERVICE
&& mPhone.getPhoneId() != PhoneSwitcher.getInstance().getPreferredDataPhoneId()
&& isLegacyCs(ss.getVoiceNetworkType());
}
/**
* @param voiceNetworkType The voice network type to be checked.
* @return {@code true} if the network type is on legacy CS connection.
*/
private boolean isLegacyCs(@NetworkType int voiceNetworkType) {
int voiceAccessNetworkType = DataUtils.networkTypeToAccessNetworkType(voiceNetworkType);
return voiceAccessNetworkType == AccessNetworkType.GERAN
|| voiceAccessNetworkType == AccessNetworkType.UTRAN
|| voiceAccessNetworkType == AccessNetworkType.CDMA2000;
}
/**
* @return {@code true} if the network only allows single data network at one time.
*/
private boolean isOnlySingleDataNetworkAllowed(@TransportType int transport) {
if (transport == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) return false;
return mDataConfigManager.getNetworkTypesOnlySupportSingleDataNetwork()
.contains(getDataNetworkType(transport));
}
/**
* @param capabilities The Network Capabilities to be checked.
* @return {@code true} if the capabilities contain any capability that's exempt from the single
* PDN rule.
*/
private boolean hasCapabilityExemptsFromSinglePdnRule(@NetCapability int[] capabilities) {
Set<Integer> exemptCapabilities =
mDataConfigManager.getCapabilitiesExemptFromSingleDataNetwork();
return Arrays.stream(capabilities).anyMatch(exemptCapabilities::contains);
}
/**
* Evaluate if telephony frameworks would allow data setup for internet in current environment.
*
* @return {@code true} if the environment is allowed for internet data. {@code false} if not
* allowed. For example, if SIM is absent, or airplane mode is on, then data is NOT allowed.
* This API does not reflect the currently internet data network status. It's possible there is
* no internet data due to weak cellular signal or network side issue, but internet data is
* still allowed in this case.
*/
public boolean isInternetDataAllowed() {
TelephonyNetworkRequest internetRequest = new TelephonyNetworkRequest(
new NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.build(), mPhone);
// If one of the existing networks can satisfy the internet request, then internet is
// allowed.
if (mDataNetworkList.stream().anyMatch(dataNetwork -> internetRequest.canBeSatisfiedBy(
dataNetwork.getNetworkCapabilities()))) {
return true;
}
// If no existing network can satisfy the request, then check if we can possibly setup
// the internet network.
DataEvaluation evaluation = evaluateNetworkRequest(internetRequest,
DataEvaluationReason.EXTERNAL_QUERY);
if (evaluation.containsOnly(DataDisallowedReason.ONLY_ALLOWED_SINGLE_NETWORK)) {
// If the only failed reason is only single network allowed, then check if the request
// can trump the current network.
return internetRequest.getPriority() > mDataNetworkList.stream()
.map(DataNetwork::getPriority)
.max(Comparator.comparing(Integer::valueOf))
.orElse(0);
}
return !evaluation.containsDisallowedReasons();
}
/**
* @return {@code true} if internet is unmetered.
*/
public boolean isInternetUnmetered() {
return mDataNetworkList.stream()
.filter(dataNetwork -> !dataNetwork.isConnecting() && !dataNetwork.isDisconnected())
.filter(DataNetwork::isInternetSupported)
.allMatch(dataNetwork -> dataNetwork.getNetworkCapabilities()
.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
|| dataNetwork.getNetworkCapabilities()
.hasCapability(NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED));
}
/**
* @return {@code true} if all data networks are disconnected.
*/
public boolean areAllDataDisconnected() {
if (!mDataNetworkList.isEmpty()) {
log("areAllDataDisconnected false due to: " + mDataNetworkList.stream()
.map(DataNetwork::name).collect(Collectors.joining(", ")));
}
return mDataNetworkList.isEmpty();
}
/**
* @return List of the reasons why internet data is not allowed. An empty list if internet
* is allowed.
*/
public @NonNull List<DataDisallowedReason> getInternetDataDisallowedReasons() {
TelephonyNetworkRequest internetRequest = new TelephonyNetworkRequest(
new NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.build(), mPhone);
DataEvaluation evaluation = evaluateNetworkRequest(internetRequest,
DataEvaluationReason.EXTERNAL_QUERY);
return evaluation.getDataDisallowedReasons();
}
/**
* Evaluate a network request. The goal is to find a suitable {@link DataProfile} that can be
* used to setup the data network.
*
* @param networkRequest The network request to evaluate.
* @param reason The reason for evaluation.
* @return The data evaluation result.
*/
private @NonNull DataEvaluation evaluateNetworkRequest(
@NonNull TelephonyNetworkRequest networkRequest, DataEvaluationReason reason) {
DataEvaluation evaluation = new DataEvaluation(reason);
int transport = mAccessNetworksManager.getPreferredTransportByNetworkCapability(
networkRequest.getApnTypeNetworkCapability());
// Bypass all checks for emergency network request.
if (networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_EIMS)) {
evaluation.addDataAllowedReason(DataAllowedReason.EMERGENCY_REQUEST);
evaluation.setCandidateDataProfile(mDataProfileManager.getDataProfileForNetworkRequest(
networkRequest, getDataNetworkType(transport), true));
networkRequest.setEvaluation(evaluation);
log(evaluation.toString());
return evaluation;
}
if (!serviceStateAllowsPSAttach(mServiceState, transport)) {
evaluation.addDataDisallowedReason(DataDisallowedReason.NOT_IN_SERVICE);
}
// Check SIM state
if (mSimState != TelephonyManager.SIM_STATE_LOADED) {
evaluation.addDataDisallowedReason(DataDisallowedReason.SIM_NOT_READY);
}
// Check if carrier specific config is loaded or not.
if (!mDataConfigManager.isConfigCarrierSpecific()) {
evaluation.addDataDisallowedReason(DataDisallowedReason.DATA_CONFIG_NOT_READY);
}
// Check CS call state and see if concurrent voice/data is allowed.
if (mPhone.getCallTracker().getState() != PhoneConstants.State.IDLE
&& !mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()) {
evaluation.addDataDisallowedReason(
DataDisallowedReason.CONCURRENT_VOICE_DATA_NOT_ALLOWED);
}
// Check VoPS support
if (transport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN
&& networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMTEL)) {
NetworkRegistrationInfo nri = mServiceState.getNetworkRegistrationInfo(
NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
if (nri != null) {
DataSpecificRegistrationInfo dsri = nri.getDataSpecificInfo();
if (dsri != null && dsri.getVopsSupportInfo() != null
&& !dsri.getVopsSupportInfo().isVopsSupported()) {
evaluation.addDataDisallowedReason(DataDisallowedReason.VOPS_NOT_SUPPORTED);
}
}
}
// Check if default data is selected.
if (!SubscriptionManager.isValidSubscriptionId(
SubscriptionManager.getDefaultDataSubscriptionId())) {
evaluation.addDataDisallowedReason(DataDisallowedReason.DEFAULT_DATA_UNSELECTED);
}
// Check if data roaming is disabled.
if (mServiceState.getDataRoaming() && !mDataSettingsManager.isDataRoamingEnabled()) {
evaluation.addDataDisallowedReason(DataDisallowedReason.ROAMING_DISABLED);
}
// Check if data is restricted by the cellular network.
if (mPsRestricted && transport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
evaluation.addDataDisallowedReason(DataDisallowedReason.DATA_RESTRICTED_BY_NETWORK);
}
// Check if there are pending tear down all networks request.
if (mPendingTearDownAllNetworks) {
evaluation.addDataDisallowedReason(DataDisallowedReason.PENDING_TEAR_DOWN_ALL);
}
// Check if the request is preferred on cellular and radio is/will be turned off.
// We are using getDesiredPowerState() instead of isRadioOn() because we also don't want
// to setup data network when radio power is about to be turned off.
if (transport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN
&& (!mPhone.getServiceStateTracker().getDesiredPowerState()
|| mPhone.mCi.getRadioState() != TelephonyManager.RADIO_POWER_ON)) {
evaluation.addDataDisallowedReason(DataDisallowedReason.RADIO_POWER_OFF);
}
// Check if radio is/will be turned off by carrier.
if (!mPhone.getServiceStateTracker().getPowerStateFromCarrier()) {
evaluation.addDataDisallowedReason(DataDisallowedReason.RADIO_DISABLED_BY_CARRIER);
}
// Check if the underlying data service is bound.
if (!mDataServiceBound.get(transport)) {
evaluation.addDataDisallowedReason(DataDisallowedReason.DATA_SERVICE_NOT_READY);
}
// Check if device is in CDMA ECBM
if (mPhone.isInCdmaEcm()) {
evaluation.addDataDisallowedReason(DataDisallowedReason.CDMA_EMERGENCY_CALLBACK_MODE);
}
// Check if only one data network is allowed.
if (isOnlySingleDataNetworkAllowed(transport)
&& !hasCapabilityExemptsFromSinglePdnRule(networkRequest.getCapabilities())) {
// if exists not-exempt network.
if (mDataNetworkList.stream()
.anyMatch(dataNetwork -> !hasCapabilityExemptsFromSinglePdnRule(
dataNetwork.getNetworkCapabilities().getCapabilities()))) {
evaluation.addDataDisallowedReason(
DataDisallowedReason.ONLY_ALLOWED_SINGLE_NETWORK);
}
}
if (mDataSettingsManager.isDataInitialized()) {
if (!mDataSettingsManager.isDataEnabled(DataUtils.networkCapabilityToApnType(
networkRequest.getApnTypeNetworkCapability()))) {
evaluation.addDataDisallowedReason(DataDisallowedReason.DATA_DISABLED);
}
} else {
evaluation.addDataDisallowedReason(DataDisallowedReason.DATA_SETTINGS_NOT_READY);
}
// Check whether to allow data in certain situations if data is disallowed for soft reasons
if (!evaluation.containsDisallowedReasons()) {
evaluation.addDataAllowedReason(DataAllowedReason.NORMAL);
if (!mDataSettingsManager.isDataEnabled()
&& networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
&& mDataSettingsManager.isMobileDataPolicyEnabled(TelephonyManager
.MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED)) {
// We reach here when data is disabled, but MMS always-allowed is enabled.
// (Note that isDataEnabled(ApnSetting.TYPE_MMS) returns true in this case, so it
// would not generate any soft disallowed reason. We need to explicitly handle it.)
evaluation.addDataAllowedReason(DataAllowedReason.MMS_REQUEST);
}
} else if (!evaluation.containsHardDisallowedReasons()) {
if ((mPhone.isInEmergencyCall() || mPhone.isInEcm())
&& networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) {
// Check if it's SUPL during emergency call.
evaluation.addDataAllowedReason(DataAllowedReason.EMERGENCY_SUPL);
} else if (!networkRequest.hasCapability(
NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) && !networkRequest
.hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN)) {
// Check if request is restricted and not for tethering, which always comes with
// a restricted network request.
evaluation.addDataAllowedReason(DataAllowedReason.RESTRICTED_REQUEST);
} else if (transport == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
// Check if request is unmetered (WiFi or unmetered APN).
evaluation.addDataAllowedReason(DataAllowedReason.UNMETERED_USAGE);
} else if (transport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
if (!networkRequest.isMeteredRequest()) {
evaluation.addDataAllowedReason(DataAllowedReason.UNMETERED_USAGE);
}
}
}
// Check if there is any compatible data profile
int networkType = getDataNetworkType(transport);
if (networkType == TelephonyManager.NETWORK_TYPE_UNKNOWN
&& transport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
// reach here when data is OOS but serviceStateAllowsPSAttach == true, so we adopt the
// voice RAT to select data profile
networkType = mServiceState.getVoiceNetworkType();
}
DataProfile dataProfile = mDataProfileManager
.getDataProfileForNetworkRequest(networkRequest, networkType,
// If the evaluation is due to environmental changes, then we should ignore
// the permanent failure reached earlier.
reason.isConditionBased());
if (dataProfile == null) {
evaluation.addDataDisallowedReason(DataDisallowedReason.NO_SUITABLE_DATA_PROFILE);
} else if (reason == DataEvaluationReason.NEW_REQUEST
&& (mDataRetryManager.isAnySetupRetryScheduled(dataProfile, transport)
|| mDataRetryManager.isSimilarNetworkRequestRetryScheduled(
networkRequest, transport))) {
// If this is a new request, check if there is any retry already scheduled. For all
// other evaluation reasons, since they are all condition changes, so if there is any
// retry scheduled, we still want to go ahead and setup the data network.
evaluation.addDataDisallowedReason(DataDisallowedReason.RETRY_SCHEDULED);
} else if (mDataRetryManager.isDataProfileThrottled(dataProfile, transport)) {
evaluation.addDataDisallowedReason(DataDisallowedReason.DATA_THROTTLED);
}
if (!evaluation.containsDisallowedReasons()) {
evaluation.setCandidateDataProfile(dataProfile);
}
networkRequest.setEvaluation(evaluation);
// EXTERNAL_QUERY generates too many log spam.
if (reason != DataEvaluationReason.EXTERNAL_QUERY) {
log(evaluation.toString() + ", network type="
+ TelephonyManager.getNetworkTypeName(getDataNetworkType(transport))
+ ", reg state="
+ NetworkRegistrationInfo.registrationStateToString(
getDataRegistrationState(mServiceState, transport))
+ ", " + networkRequest);
}
return evaluation;
}
/**
* @return The grouped unsatisfied network requests. The network requests that have the same
* network capabilities is grouped into one {@link NetworkRequestList}.
*/
private @NonNull List<NetworkRequestList> getGroupedUnsatisfiedNetworkRequests() {
NetworkRequestList networkRequestList = new NetworkRequestList();
for (TelephonyNetworkRequest networkRequest : mAllNetworkRequestList) {
if (networkRequest.getState() == TelephonyNetworkRequest.REQUEST_STATE_UNSATISFIED) {
networkRequestList.add(networkRequest);
}
}
return DataUtils.getGroupedNetworkRequestList(networkRequestList);
}
/**
* Called when it's needed to evaluate all unsatisfied network requests.
*
* @param reason The reason for evaluation.
*/
private void onReevaluateUnsatisfiedNetworkRequests(@NonNull DataEvaluationReason reason) {
// First, try to group similar network request together.
List<NetworkRequestList> networkRequestLists = getGroupedUnsatisfiedNetworkRequests();
log("Re-evaluating " + networkRequestLists.stream().mapToInt(List::size).sum()
+ " unsatisfied network requests in " + networkRequestLists.size()
+ " groups, " + networkRequestLists.stream().map(
requestList -> DataUtils.networkCapabilitiesToString(
requestList.get(0).getCapabilities()))
.collect(Collectors.joining(", ")) + " due to " + reason);
// Second, see if any existing network can satisfy those network requests.
for (NetworkRequestList requestList : networkRequestLists) {
if (findCompatibleDataNetworkAndAttach(requestList)) {
continue;
}
// If no data network can satisfy the requests, then start the evaluation process. Since
// all the requests in the list have the same capabilities, we can only evaluate one
// of them.
DataEvaluation evaluation = evaluateNetworkRequest(requestList.get(0), reason);
if (!evaluation.containsDisallowedReasons()) {
DataProfile dataProfile = evaluation.getCandidateDataProfile();
if (dataProfile != null) {
setupDataNetwork(dataProfile, null,
evaluation.getDataAllowedReason());
}
}
}
}
/**
* Evaluate an existing data network to see if it is still allowed to exist. For example, if
* RAT changes from LTE to UMTS, an IMS data network is not allowed anymore. Or when SIM is
* removal, all data networks (except emergency) should be torn down.
*
* @param dataNetwork The data network to evaluate.
* @param reason The reason for evaluation.
*
* @return The data evaluation result.
*/
private @NonNull DataEvaluation evaluateDataNetwork(@NonNull DataNetwork dataNetwork,
@NonNull DataEvaluationReason reason) {
DataEvaluation evaluation = new DataEvaluation(reason);
// Bypass all checks for emergency data network.
if (dataNetwork.getNetworkCapabilities().hasCapability(
NetworkCapabilities.NET_CAPABILITY_EIMS)) {
evaluation.addDataAllowedReason(DataAllowedReason.EMERGENCY_REQUEST);
log(evaluation.toString());
return evaluation;
}
// Check SIM state
if (mSimState != TelephonyManager.SIM_STATE_LOADED) {
evaluation.addDataDisallowedReason(DataDisallowedReason.SIM_NOT_READY);
}
// Check if device is in CDMA ECBM
if (mPhone.isInCdmaEcm()) {
evaluation.addDataDisallowedReason(DataDisallowedReason.CDMA_EMERGENCY_CALLBACK_MODE);
}
// Check if there are other network that has higher priority, and only single data network
// is allowed.
if (isOnlySingleDataNetworkAllowed(dataNetwork.getTransport())
&& !hasCapabilityExemptsFromSinglePdnRule(
dataNetwork.getNetworkCapabilities().getCapabilities())) {
// If there is network request that has higher priority than this data network, then
// tear down the network, regardless that network request is satisfied or not.
if (mAllNetworkRequestList.stream()
.filter(request -> dataNetwork.getTransport()
== mAccessNetworksManager.getPreferredTransportByNetworkCapability(
request.getApnTypeNetworkCapability()))
.filter(request
-> !hasCapabilityExemptsFromSinglePdnRule(request.getCapabilities()))
.anyMatch(request -> request.getPriority() > dataNetwork.getPriority())) {
evaluation.addDataDisallowedReason(
DataDisallowedReason.ONLY_ALLOWED_SINGLE_NETWORK);
} else {
log("evaluateDataNetwork: " + dataNetwork + " has the highest priority. "
+ "No need to tear down");
}
}
// If the data network is IMS that supports voice call, and has MMTEL request (client
// specified VoPS is required.)
if (dataNetwork.getAttachedNetworkRequestList().get(
new int[]{NetworkCapabilities.NET_CAPABILITY_MMTEL}) != null) {
// When reaching here, it means the network supports MMTEL, and also has MMTEL request
// attached to it.
if (!dataNetwork.shouldDelayImsTearDown()) {
if (dataNetwork.getTransport() == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
NetworkRegistrationInfo nri = mServiceState.getNetworkRegistrationInfo(
NetworkRegistrationInfo.DOMAIN_PS,
AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
if (nri != null) {
DataSpecificRegistrationInfo dsri = nri.getDataSpecificInfo();
if (dsri != null && dsri.getVopsSupportInfo() != null
&& !dsri.getVopsSupportInfo().isVopsSupported()
&& !mDataConfigManager.shouldKeepNetworkUpInNonVops()) {
evaluation.addDataDisallowedReason(
DataDisallowedReason.VOPS_NOT_SUPPORTED);
}
}
}
} else {
log("Ignored VoPS check due to delay IMS tear down until call ends.");
}
}
// Check if data is disabled
boolean dataDisabled = false;
if (!mDataSettingsManager.isDataEnabled()) {
dataDisabled = true;
}
// Check if data roaming is disabled
if (mServiceState.getDataRoaming() && !mDataSettingsManager.isDataRoamingEnabled()) {
evaluation.addDataDisallowedReason(DataDisallowedReason.ROAMING_DISABLED);
}
// Check if current data network type is allowed by the data profile. Use the lingering
// network type. Some data network is allowed to create on certain RATs, but can linger
// to extended RATs. For example, IMS is allowed to be created on LTE only, but can
// extend its life cycle to 3G.
int networkType = getDataNetworkType(dataNetwork.getTransport());
DataProfile dataProfile = dataNetwork.getDataProfile();
if (dataProfile.getApnSetting() != null) {
// Check if data is disabled for the APN type
dataDisabled = !mDataSettingsManager.isDataEnabled(DataUtils
.networkCapabilityToApnType(DataUtils
.getHighestPriorityNetworkCapabilityFromDataProfile(
mDataConfigManager, dataProfile)));
// Sometimes network temporarily OOS and network type becomes UNKNOWN. We don't
// tear down network in that case.
if (networkType != TelephonyManager.NETWORK_TYPE_UNKNOWN
&& !dataProfile.getApnSetting().canSupportLingeringNetworkType(networkType)) {
log("networkType=" + TelephonyManager.getNetworkTypeName(networkType)
+ ", networkTypeBitmask="
+ TelephonyManager.convertNetworkTypeBitmaskToString(
dataProfile.getApnSetting().getNetworkTypeBitmask())
+ ", lingeringNetworkTypeBitmask="
+ TelephonyManager.convertNetworkTypeBitmaskToString(
dataProfile.getApnSetting().getLingeringNetworkTypeBitmask()));
evaluation.addDataDisallowedReason(
DataDisallowedReason.DATA_NETWORK_TYPE_NOT_ALLOWED);
}
}
if (dataDisabled) {
evaluation.addDataDisallowedReason(DataDisallowedReason.DATA_DISABLED);
}
// Check if the data profile is still compatible, sometimes the users can remove it from the
// APN editor. If some of the important fields are changed in APN settings, we need to
// tear down the network. Note traffic descriptor from the data profile will not be checked.
if (!mDataProfileManager.isDataProfileCompatible(dataProfile)) {
evaluation.addDataDisallowedReason(DataDisallowedReason.DATA_PROFILE_INVALID);
}
// If users switch preferred profile in APN editor, we need to tear down network.
if (dataNetwork.isInternetSupported()
&& !mDataProfileManager.isDataProfilePreferred(dataProfile)
&& mDataProfileManager.isAnyPreferredDataProfileExisting()) {
evaluation.addDataDisallowedReason(DataDisallowedReason.DATA_PROFILE_NOT_PREFERRED);
}
// Check whether if there are any reason we should tear down the network.
if (!evaluation.containsDisallowedReasons()) {
// The data is allowed in the current condition.
evaluation.addDataAllowedReason(DataAllowedReason.NORMAL);
} else if (!evaluation.containsHardDisallowedReasons()) {
// If there are reasons we should tear down the network, check if those are hard reasons
// or soft reasons. In some scenarios, we can make exceptions if they are soft
// disallowed reasons.
if ((mPhone.isInEmergencyCall() || mPhone.isInEcm()) && dataNetwork.isEmergencySupl()) {
// Check if it's SUPL during emergency call.
evaluation.addDataAllowedReason(DataAllowedReason.EMERGENCY_SUPL);
} else if (!dataNetwork.getNetworkCapabilities().hasCapability(
NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
&& !dataNetwork.hasNetworkCapabilityInNetworkRequests(
NetworkCapabilities.NET_CAPABILITY_DUN)) {
// Check if request is restricted and there are no DUN network requests attached to
// the network.
evaluation.addDataAllowedReason(DataAllowedReason.RESTRICTED_REQUEST);
} else if (dataNetwork.getTransport() == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
// Check if request is unmetered (WiFi or unmetered APN)
evaluation.addDataAllowedReason(DataAllowedReason.UNMETERED_USAGE);
} else {
boolean unmeteredNetwork = !mDataConfigManager.isAnyMeteredCapability(
dataNetwork.getNetworkCapabilities()
.getCapabilities(), mServiceState.getDataRoaming());
if (unmeteredNetwork) {
evaluation.addDataAllowedReason(DataAllowedReason.UNMETERED_USAGE);
}
}
}
log("Evaluated " + dataNetwork + ", " + evaluation);
return evaluation;
}
/**
* Called when needed to re-evaluate existing data networks and tear down networks if needed.
*
* @param reason The reason for this data evaluation.
*/
private void onReevaluateExistingDataNetworks(@NonNull DataEvaluationReason reason) {
if (mDataNetworkList.isEmpty()) {
log("onReevaluateExistingDataNetworks: No existing data networks to re-evaluate.");
return;
}
log("Re-evaluating " + mDataNetworkList.size() + " existing data networks due to "
+ reason);
for (DataNetwork dataNetwork : mDataNetworkList) {
if (dataNetwork.isConnecting() || dataNetwork.isConnected()) {
DataEvaluation dataEvaluation = evaluateDataNetwork(dataNetwork, reason);
if (dataEvaluation.containsDisallowedReasons()) {
tearDownGracefully(dataNetwork, getTearDownReason(dataEvaluation));
}
}
}
}
/**
* Evaluate if it is allowed to handover the data network between IWLAN and cellular. Some
* carriers do not allow handover in certain conditions.
*
* @param dataNetwork The data network to be handover.
* @return The evaluation result.
*
* @see CarrierConfigManager#KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY
*/
private @NonNull DataEvaluation evaluateDataNetworkHandover(@NonNull DataNetwork dataNetwork) {
DataEvaluation dataEvaluation = new DataEvaluation(DataEvaluationReason.DATA_HANDOVER);
if (!dataNetwork.isConnecting() && !dataNetwork.isConnected()) {
dataEvaluation.addDataDisallowedReason(DataDisallowedReason.ILLEGAL_STATE);
return dataEvaluation;
}
// If enhanced handover check is enabled, perform extra checks.
if (mDataConfigManager.isEnhancedIwlanHandoverCheckEnabled()) {
int targetTransport = DataUtils.getTargetTransport(dataNetwork.getTransport());
NetworkRegistrationInfo nri = mServiceState.getNetworkRegistrationInfo(
NetworkRegistrationInfo.DOMAIN_PS, targetTransport);
if (nri != null) {
// Check if OOS on target transport.
if (!nri.isInService()) {
dataEvaluation.addDataDisallowedReason(DataDisallowedReason.NOT_IN_SERVICE);
}
// Check if VoPS is required, but the target transport is non-VoPS.
NetworkRequestList networkRequestList =
dataNetwork.getAttachedNetworkRequestList();
if (networkRequestList.stream().anyMatch(request
-> request.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMTEL))) {
DataSpecificRegistrationInfo dsri = nri.getDataSpecificInfo();
// Check if the network is non-VoPS.
if (dsri != null && dsri.getVopsSupportInfo() != null
&& !dsri.getVopsSupportInfo().isVopsSupported()
&& !mDataConfigManager.shouldKeepNetworkUpInNonVops()) {
dataEvaluation.addDataDisallowedReason(
DataDisallowedReason.VOPS_NOT_SUPPORTED);
}
}
if (dataEvaluation.containsDisallowedReasons()) {
return dataEvaluation;
}
}
}
if (mDataConfigManager.isIwlanHandoverPolicyEnabled()) {
List<HandoverRule> handoverRules = mDataConfigManager.getHandoverRules();
int sourceNetworkType = getDataNetworkType(dataNetwork.getTransport());
if (sourceNetworkType == TelephonyManager.NETWORK_TYPE_UNKNOWN) {
// Using the data network type stored in the data network. We
// cache the last known network type in data network controller
// because data network has much shorter life cycle. It can prevent
// the obsolete last known network type cached in data network
// type controller.
sourceNetworkType = dataNetwork.getLastKnownDataNetworkType();
}
int sourceAccessNetwork = DataUtils.networkTypeToAccessNetworkType(
sourceNetworkType);
int targetAccessNetwork = DataUtils.networkTypeToAccessNetworkType(
getDataNetworkType(DataUtils.getTargetTransport(dataNetwork.getTransport())));
NetworkCapabilities capabilities = dataNetwork.getNetworkCapabilities();
log("evaluateDataNetworkHandover: "
+ "source=" + AccessNetworkType.toString(sourceAccessNetwork)
+ ", target=" + AccessNetworkType.toString(targetAccessNetwork)
+ ", ServiceState=" + mServiceState
+ ", capabilities=" + capabilities);
// Matching the rules by the configured order. Bail out if find first matching rule.
for (HandoverRule rule : handoverRules) {
// Check if the rule is only for roaming and we are not roaming. Use the real
// roaming state reported by modem instead of using the overridden roaming state.
if (rule.isOnlyForRoaming && !mServiceState.getDataRoamingFromRegistration()) {
// If the rule is for roaming only, and the device is not roaming, then bypass
// this rule.
continue;
}
if (rule.sourceAccessNetworks.contains(sourceAccessNetwork)
&& rule.targetAccessNetworks.contains(targetAccessNetwork)) {
// if no capability rule specified,
// data network capability is considered matched.
// otherwise, any capabilities overlap is also considered matched.
if (rule.networkCapabilities.isEmpty()
|| rule.networkCapabilities.stream()
.anyMatch(capabilities::hasCapability)) {
log("evaluateDataNetworkHandover: Matched " + rule);
if (rule.type == HandoverRule.RULE_TYPE_DISALLOWED) {
dataEvaluation.addDataDisallowedReason(
DataDisallowedReason.NOT_ALLOWED_BY_POLICY);
} else {
dataEvaluation.addDataAllowedReason(DataAllowedReason.NORMAL);
}
log("evaluateDataNetworkHandover: " + dataEvaluation);
return dataEvaluation;
}
}
}
log("evaluateDataNetworkHandover: Did not find matching rule.");
} else {
log("evaluateDataNetworkHandover: IWLAN handover policy not enabled.");
}
// Allow handover by default if no rule is found/not enabled by config.
dataEvaluation.addDataAllowedReason(DataAllowedReason.NORMAL);
return dataEvaluation;
}
/**
* Get tear down reason from the evaluation result.
*
* @param dataEvaluation The evaluation result from
* {@link #evaluateDataNetwork(DataNetwork, DataEvaluationReason)}.
* @return The tear down reason.
*/
private static @TearDownReason int getTearDownReason(@NonNull DataEvaluation dataEvaluation) {
if (dataEvaluation.containsDisallowedReasons()) {
switch (dataEvaluation.getDataDisallowedReasons().get(0)) {
case DATA_DISABLED:
return DataNetwork.TEAR_DOWN_REASON_DATA_DISABLED;
case ROAMING_DISABLED:
return DataNetwork.TEAR_DOWN_REASON_ROAMING_DISABLED;
case DEFAULT_DATA_UNSELECTED:
return DataNetwork.TEAR_DOWN_REASON_DEFAULT_DATA_UNSELECTED;
case NOT_IN_SERVICE:
return DataNetwork.TEAR_DOWN_REASON_NOT_IN_SERVICE;
case DATA_CONFIG_NOT_READY:
return DataNetwork.TEAR_DOWN_REASON_DATA_CONFIG_NOT_READY;
case SIM_NOT_READY:
return DataNetwork.TEAR_DOWN_REASON_SIM_REMOVAL;
case CONCURRENT_VOICE_DATA_NOT_ALLOWED:
return DataNetwork.TEAR_DOWN_REASON_CONCURRENT_VOICE_DATA_NOT_ALLOWED;
case RADIO_POWER_OFF:
return DataNetwork.TEAR_DOWN_REASON_AIRPLANE_MODE_ON;
case PENDING_TEAR_DOWN_ALL:
return DataNetwork.TEAR_DOWN_REASON_PENDING_TEAR_DOWN_ALL;
case RADIO_DISABLED_BY_CARRIER:
return DataNetwork.TEAR_DOWN_REASON_POWER_OFF_BY_CARRIER;
case DATA_SERVICE_NOT_READY:
return DataNetwork.TEAR_DOWN_REASON_DATA_SERVICE_NOT_READY;
case NO_SUITABLE_DATA_PROFILE:
return DataNetwork.TEAR_DOWN_REASON_NO_SUITABLE_DATA_PROFILE;
case DATA_NETWORK_TYPE_NOT_ALLOWED:
return DataNetwork.TEAR_DOWN_REASON_RAT_NOT_ALLOWED;
case CDMA_EMERGENCY_CALLBACK_MODE:
return DataNetwork.TEAR_DOWN_REASON_CDMA_EMERGENCY_CALLBACK_MODE;
case RETRY_SCHEDULED:
return DataNetwork.TEAR_DOWN_REASON_RETRY_SCHEDULED;
case DATA_THROTTLED:
return DataNetwork.TEAR_DOWN_REASON_DATA_THROTTLED;
case DATA_PROFILE_INVALID:
return DataNetwork.TEAR_DOWN_REASON_DATA_PROFILE_INVALID;
case DATA_PROFILE_NOT_PREFERRED:
return DataNetwork.TEAR_DOWN_REASON_DATA_PROFILE_NOT_PREFERRED;
case NOT_ALLOWED_BY_POLICY:
return DataNetwork.TEAR_DOWN_REASON_NOT_ALLOWED_BY_POLICY;
case ILLEGAL_STATE:
return DataNetwork.TEAR_DOWN_REASON_ILLEGAL_STATE;
case VOPS_NOT_SUPPORTED:
return DataNetwork.TEAR_DOWN_REASON_VOPS_NOT_SUPPORTED;
case ONLY_ALLOWED_SINGLE_NETWORK:
return DataNetwork.TEAR_DOWN_REASON_ONLY_ALLOWED_SINGLE_NETWORK;
}
}
return DataNetwork.TEAR_DOWN_REASON_NONE;
}
/**
* Check whether a dataNetwork is actively capable of internet connection
* @param cid dataNetwork unique identifier
* @return true if the dataNetwork is connected and capable of internet connection
*/
public boolean isInternetNetwork(int cid) {
for (DataNetwork dataNetwork : mDataNetworkList) {
if (dataNetwork.getId() == cid
&& dataNetwork.isConnected()
&& dataNetwork.getNetworkCapabilities()
.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
return true;
}
}
return false;
}
/**
* @return {@code true} if data is dormant.
*/
private boolean isDataDormant() {
return mDataNetworkList.stream().anyMatch(
dataNetwork -> dataNetwork.getLinkStatus()
== DataCallResponse.LINK_STATUS_DORMANT)
&& mDataNetworkList.stream().noneMatch(
dataNetwork -> dataNetwork.getLinkStatus()
== DataCallResponse.LINK_STATUS_ACTIVE);
}
/**
* Update data activity.
*/
private void updateDataActivity() {
int dataActivity = TelephonyManager.DATA_ACTIVITY_NONE;
if (isDataDormant()) {
dataActivity = TelephonyManager.DATA_ACTIVITY_DORMANT;
} else if (mPhone.getLinkBandwidthEstimator() != null) {
dataActivity = mPhone.getLinkBandwidthEstimator().getDataActivity();
}
if (mDataActivity != dataActivity) {
logv("updateDataActivity: dataActivity="
+ DataUtils.dataActivityToString(dataActivity));
mDataActivity = dataActivity;
mPhone.notifyDataActivity();
}
}
/**
* Remove a network request, which is originated from the apps. Note that remove a network
* will not result in tearing down the network. The tear down request directly comes from
* {@link com.android.server.ConnectivityService} through
* {@link NetworkAgent#onNetworkUnwanted()}.
*
* @param networkRequest Network request
*/
public void removeNetworkRequest(@NonNull TelephonyNetworkRequest networkRequest) {
sendMessage(obtainMessage(EVENT_REMOVE_NETWORK_REQUEST, networkRequest));
}
private void onRemoveNetworkRequest(@NonNull TelephonyNetworkRequest request) {
// The request generated from telephony network factory does not contain the information
// the original request has, for example, attached data network. We need to find the
// original one.
TelephonyNetworkRequest networkRequest = mAllNetworkRequestList.stream()
.filter(r -> r.equals(request))
.findFirst()
.orElse(null);
if (networkRequest == null || !mAllNetworkRequestList.remove(networkRequest)) {
loge("onRemoveNetworkRequest: Network request does not exist. " + networkRequest);
return;
}
if (networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS)) {
mImsThrottleCounter.addOccurrence();
mLastReleasedImsRequestCapabilities = networkRequest.getCapabilities();
mLastImsOperationIsRelease = true;
}
if (networkRequest.getAttachedNetwork() != null) {
networkRequest.getAttachedNetwork().detachNetworkRequest(networkRequest);
}
log("onRemoveNetworkRequest: Removed " + networkRequest);
}
/**
* Check if the network request is existing. Note this method is not thread safe so can be only
* called within the modules in {@link com.android.internal.telephony.data}.
*
* @param networkRequest Telephony network request to check.
* @return {@code true} if the network request exists.
*/
public boolean isNetworkRequestExisting(@NonNull TelephonyNetworkRequest networkRequest) {
return mAllNetworkRequestList.contains(networkRequest);
}
/**
* Get data network by interface name.
*
* @param interfaceName The network interface name.
* @return The data network if found.
*/
@Nullable
public DataNetwork getDataNetworkByInterface(@NonNull String interfaceName) {
return mDataNetworkList.stream()
.filter(dataNetwork -> !dataNetwork.isDisconnecting())
.filter(dataNetwork -> interfaceName.equals(
dataNetwork.getLinkProperties().getInterfaceName()))
.findFirst()
.orElse(null);
}
/**
* Register for IMS feature registration state.
*
* @param subId The subscription index.
* @param imsFeature The IMS feature. Only {@link ImsFeature#FEATURE_MMTEL} and
* {@link ImsFeature#FEATURE_RCS} are supported at this point.
*/
private void registerImsFeatureRegistrationState(int subId,
@ImsFeature.FeatureType int imsFeature) {
RegistrationManager.RegistrationCallback callback =
new RegistrationManager.RegistrationCallback() {
@Override
public void onRegistered(ImsRegistrationAttributes attributes) {
log("IMS " + DataUtils.imsFeatureToString(imsFeature)
+ " registered. Attributes=" + attributes);
mRegisteredImsFeatures.add(imsFeature);
}
@Override
public void onUnregistered(ImsReasonInfo info) {
log("IMS " + DataUtils.imsFeatureToString(imsFeature)
+ " deregistered. Info=" + info);
mRegisteredImsFeatures.remove(imsFeature);
evaluatePendingImsDeregDataNetworks();
}
};
try {
// Use switch here as we can't make a generic callback registration logic because
// RcsManager does not implement RegistrationManager.
switch (imsFeature) {
case ImsFeature.FEATURE_MMTEL:
mImsManager.getImsMmTelManager(subId).registerImsRegistrationCallback(
DataNetworkController.this::post, callback);
break;
case ImsFeature.FEATURE_RCS:
mImsManager.getImsRcsManager(subId).registerImsRegistrationCallback(
DataNetworkController.this::post, callback);
break;
}
// Store the callback so that we can unregister in the future.
mImsFeatureRegistrationCallbacks.put(imsFeature, callback);
log("Successfully register " + DataUtils.imsFeatureToString(imsFeature)
+ " registration state. subId=" + subId);
} catch (ImsException e) {
loge("updateImsFeatureRegistrationStateListening: subId=" + subId
+ ", imsFeature=" + DataUtils.imsFeatureToString(imsFeature) + ", " + e);
}
}
/**
* Unregister IMS feature callback.
*
* @param subId The subscription index.
* @param imsFeature The IMS feature. Only {@link ImsFeature#FEATURE_MMTEL} and
* {@link ImsFeature#FEATURE_RCS} are supported at this point.
*/
private void unregisterImsFeatureRegistrationState(int subId,
@ImsFeature.FeatureType int imsFeature) {
RegistrationManager.RegistrationCallback oldCallback =
mImsFeatureRegistrationCallbacks.get(imsFeature);
if (oldCallback != null) {
if (imsFeature == ImsFeature.FEATURE_MMTEL) {
mImsManager.getImsMmTelManager(subId)
.unregisterImsRegistrationCallback(oldCallback);
} else if (imsFeature == ImsFeature.FEATURE_RCS) {
mImsManager.getImsRcsManager(subId)
.unregisterImsRegistrationCallback(oldCallback);
}
log("Successfully unregistered " + DataUtils.imsFeatureToString(imsFeature)
+ " registration state. sudId=" + subId);
mImsFeatureRegistrationCallbacks.remove(imsFeature);
}
}
/**
* Register IMS state callback.
*
* @param subId Subscription index.
*/
private void registerImsStateCallback(int subId) {
Function<Integer, ImsStateCallback> imsFeatureStateCallbackFactory =
imsFeature -> new ImsStateCallback() {
@Override
public void onUnavailable(int reason) {
// Unregister registration state update when IMS service is unbound.
unregisterImsFeatureRegistrationState(subId, imsFeature);
}
@Override
public void onAvailable() {
mImsFeaturePackageName.put(imsFeature, ImsResolver.getInstance()
.getConfiguredImsServicePackageName(mPhone.getPhoneId(),
imsFeature));
// Once IMS service is bound, register for registration state update.
registerImsFeatureRegistrationState(subId, imsFeature);
}
@Override
public void onError() {
}
};
try {
ImsStateCallback callback = imsFeatureStateCallbackFactory
.apply(ImsFeature.FEATURE_MMTEL);
mImsManager.getImsMmTelManager(subId).registerImsStateCallback(this::post,
callback);
mImsStateCallbacks.put(ImsFeature.FEATURE_MMTEL, callback);
log("Successfully register MMTEL state on sub " + subId);
callback = imsFeatureStateCallbackFactory.apply(ImsFeature.FEATURE_RCS);
mImsManager.getImsRcsManager(subId).registerImsStateCallback(this::post, callback);
mImsStateCallbacks.put(ImsFeature.FEATURE_RCS, callback);
log("Successfully register RCS state on sub " + subId);
} catch (ImsException e) {
loge("Exception when registering IMS state callback. " + e);
}
}
/**
* Unregister IMS feature state callbacks.
*
* @param subId Subscription index.
*/
private void unregisterImsStateCallbacks(int subId) {
ImsStateCallback callback = mImsStateCallbacks.get(ImsFeature.FEATURE_MMTEL);
if (callback != null) {
mImsManager.getImsMmTelManager(subId).unregisterImsStateCallback(callback);
mImsStateCallbacks.remove(ImsFeature.FEATURE_MMTEL);
log("Unregister MMTEL state on sub " + subId);
}
callback = mImsStateCallbacks.get(ImsFeature.FEATURE_RCS);
if (callback != null) {
mImsManager.getImsRcsManager(subId).unregisterImsStateCallback(callback);
mImsStateCallbacks.remove(ImsFeature.FEATURE_RCS);
log("Unregister RCS state on sub " + subId);
}
}
/** Called when subscription info changed. */
private void onSubscriptionChanged() {
if (mSubId != mPhone.getSubId()) {
log("onDataConfigUpdated: mSubId changed from " + mSubId + " to "
+ mPhone.getSubId());
if (isImsGracefulTearDownSupported()) {
if (SubscriptionManager.isValidSubscriptionId(mPhone.getSubId())) {
registerImsStateCallback(mPhone.getSubId());
} else {
unregisterImsStateCallbacks(mSubId);
}
}
mSubId = mPhone.getSubId();
updateSubscriptionPlans();
}
}
/**
* Called when carrier config was updated.
*/
private void onCarrierConfigUpdated() {
log("onCarrierConfigUpdated: config is "
+ (mDataConfigManager.isConfigCarrierSpecific() ? "" : "not ")
+ "carrier specific. mSimState="
+ TelephonyManager.simStateToString(mSimState));
updateNetworkRequestsPriority();
onReevaluateUnsatisfiedNetworkRequests(DataEvaluationReason.DATA_CONFIG_CHANGED);
}
/**
* Called when device config was updated.
*/
private void onDeviceConfigUpdated() {
log("onDeviceConfigUpdated: DeviceConfig updated.");
updateAnomalySlidingWindowCounters();
}
/**
* Update each network request's priority.
*/
private void updateNetworkRequestsPriority() {
for (TelephonyNetworkRequest networkRequest : mAllNetworkRequestList) {
networkRequest.updatePriority();
}
}
/**
* Update the threshold of anomaly report counters
*/
private void updateAnomalySlidingWindowCounters() {
mImsThrottleCounter = new SlidingWindowEventCounter(
mDataConfigManager.getAnomalyImsReleaseRequestThreshold().timeWindow,
mDataConfigManager.getAnomalyImsReleaseRequestThreshold().eventNumOccurrence);
mNetworkUnwantedCounter = new SlidingWindowEventCounter(
mDataConfigManager.getAnomalyNetworkUnwantedThreshold().timeWindow,
mDataConfigManager.getAnomalyNetworkUnwantedThreshold().eventNumOccurrence);
mSetupDataCallWwanFailureCounter = new SlidingWindowEventCounter(
mDataConfigManager.getAnomalySetupDataCallThreshold().timeWindow,
mDataConfigManager.getAnomalySetupDataCallThreshold().eventNumOccurrence);
mSetupDataCallWlanFailureCounter = new SlidingWindowEventCounter(
mDataConfigManager.getAnomalySetupDataCallThreshold().timeWindow,
mDataConfigManager.getAnomalySetupDataCallThreshold().eventNumOccurrence);
}
/**
* There have been several bugs where a RECONNECT loop kicks off where a data network
* is brought up, but connectivity service indicates that the network is unwanted so telephony
* tears down the network. But later telephony bring up the data network again and becomes an
* infinite loop. By the time we get the bug report it's too late because there have already
* been hundreds of bring up/tear down. This is meant to capture the issue when it first starts.
*/
private void onTrackNetworkUnwanted() {
if (mNetworkUnwantedCounter.addOccurrence()) {
reportAnomaly("Network Unwanted called "
+ mNetworkUnwantedCounter.getFrequencyString(),
"9f3bc55b-bfa6-4e26-afaa-5031426a66d3");
}
}
/**
* Find unsatisfied network requests that can be satisfied by the given data profile.
*
* @param dataProfile The data profile.
* @return The network requests list.
*/
private @NonNull NetworkRequestList findSatisfiableNetworkRequests(
@NonNull DataProfile dataProfile) {
return new NetworkRequestList(mAllNetworkRequestList.stream()
.filter(request -> request.getState()
== TelephonyNetworkRequest.REQUEST_STATE_UNSATISFIED)
.filter(request -> request.canBeSatisfiedBy(dataProfile))
.collect(Collectors.toList()));
}
/**
* Setup data network.
*
* @param dataProfile The data profile to setup the data network.
* @param dataSetupRetryEntry Data retry entry. {@code null} if this data network setup is not
* initiated by a data retry.
* @param allowedReason The reason that why setting up this data network is allowed.
*/
private void setupDataNetwork(@NonNull DataProfile dataProfile,
@Nullable DataSetupRetryEntry dataSetupRetryEntry,
@NonNull DataAllowedReason allowedReason) {
log("onSetupDataNetwork: dataProfile=" + dataProfile + ", retryEntry="
+ dataSetupRetryEntry + ", allowed reason=" + allowedReason + ", service state="
+ mServiceState);
for (DataNetwork dataNetwork : mDataNetworkList) {
DataProfile currentDataProfile = dataNetwork.getDataProfile();
if (dataProfile.equals(currentDataProfile)
|| mDataProfileManager.areDataProfilesSharingApn(
dataProfile, currentDataProfile)) {
log("onSetupDataNetwork: Found existing data network " + dataNetwork
+ " using the same or a similar data profile.");
if (dataSetupRetryEntry != null) {
dataSetupRetryEntry.setState(DataRetryEntry.RETRY_STATE_CANCELLED);
}
return;
}
}
NetworkRequestList networkRequestList = findSatisfiableNetworkRequests(dataProfile);
if (networkRequestList.isEmpty()) {
log("Can't find any unsatisfied network requests that can be satisfied by this data "
+ "profile.");
if (dataSetupRetryEntry != null) {
dataSetupRetryEntry.setState(DataRetryEntry.RETRY_STATE_CANCELLED);
}
return;
}
int transport = mAccessNetworksManager.getPreferredTransportByNetworkCapability(
networkRequestList.get(0).getApnTypeNetworkCapability());
logl("Creating data network on "
+ AccessNetworkConstants.transportTypeToString(transport) + " with " + dataProfile
+ ", and attaching " + networkRequestList.size() + " network requests to it.");
mDataNetworkList.add(new DataNetwork(mPhone, getLooper(), mDataServiceManagers,
dataProfile, networkRequestList, transport, allowedReason,
new DataNetworkCallback(this::post) {
@Override
public void onSetupDataFailed(@NonNull DataNetwork dataNetwork,
@NonNull NetworkRequestList requestList, @DataFailureCause int cause,
long retryDelayMillis) {
if (dataSetupRetryEntry != null) {
dataSetupRetryEntry.setState(DataRetryEntry.RETRY_STATE_FAILED);
}
DataNetworkController.this.onDataNetworkSetupFailed(
dataNetwork, requestList, cause, retryDelayMillis);
}
@Override
public void onConnected(@NonNull DataNetwork dataNetwork) {
if (dataSetupRetryEntry != null) {
dataSetupRetryEntry.setState(DataRetryEntry.RETRY_STATE_SUCCEEDED);
}
DataNetworkController.this.onDataNetworkConnected(dataNetwork);
}
@Override
public void onAttachFailed(@NonNull DataNetwork dataNetwork,
@NonNull NetworkRequestList requestList) {
DataNetworkController.this.onAttachNetworkRequestsFailed(
dataNetwork, requestList);
}
@Override
public void onValidationStatusChanged(@NonNull DataNetwork dataNetwork,
@ValidationStatus int status, @Nullable Uri redirectUri) {
DataNetworkController.this.onDataNetworkValidationStatusChanged(
dataNetwork, status, redirectUri);
}
@Override
public void onSuspendedStateChanged(@NonNull DataNetwork dataNetwork,
boolean suspended) {
DataNetworkController.this.onDataNetworkSuspendedStateChanged(
dataNetwork, suspended);
}
@Override
public void onDisconnected(@NonNull DataNetwork dataNetwork,
@DataFailureCause int cause, @TearDownReason int tearDownReason) {
DataNetworkController.this.onDataNetworkDisconnected(
dataNetwork, cause, tearDownReason);
}
@Override
public void onHandoverSucceeded(@NonNull DataNetwork dataNetwork) {
DataNetworkController.this.onDataNetworkHandoverSucceeded(dataNetwork);
}
@Override
public void onHandoverFailed(@NonNull DataNetwork dataNetwork,
@DataFailureCause int cause, long retryDelayMillis,
@HandoverFailureMode int handoverFailureMode) {
DataNetworkController.this.onDataNetworkHandoverFailed(
dataNetwork, cause, retryDelayMillis, handoverFailureMode);
}
@Override
public void onLinkStatusChanged(@NonNull DataNetwork dataNetwork,
@LinkStatus int linkStatus) {
DataNetworkController.this.onLinkStatusChanged(dataNetwork, linkStatus);
}
@Override
public void onPcoDataChanged(@NonNull DataNetwork dataNetwork) {
DataNetworkController.this.onPcoDataChanged(dataNetwork);
}
@Override
public void onNetworkCapabilitiesChanged(@NonNull DataNetwork dataNetwork) {
DataNetworkController.this.onNetworkCapabilitiesChanged(dataNetwork);
}
@Override
public void onTrackNetworkUnwanted(@NonNull DataNetwork dataNetwork) {
DataNetworkController.this.onTrackNetworkUnwanted();
}
}
));
if (!mAnyDataNetworkExisting) {
mAnyDataNetworkExisting = true;
mDataNetworkControllerCallbacks.forEach(callback -> callback.invokeFromExecutor(
() -> callback.onAnyDataNetworkExistingChanged(mAnyDataNetworkExisting)));
}
}
/**
* Called when setup data network failed.
*
* @param dataNetwork The data network.
* @param requestList The network requests attached to the data network.
* @param cause The fail cause
* @param retryDelayMillis The retry timer suggested by the network/data service.
*/
private void onDataNetworkSetupFailed(@NonNull DataNetwork dataNetwork,
@NonNull NetworkRequestList requestList, @DataFailureCause int cause,
long retryDelayMillis) {
logl("onDataNetworkSetupDataFailed: " + dataNetwork + ", cause="
+ DataFailCause.toString(cause) + ", retryDelayMillis=" + retryDelayMillis + "ms.");
mDataNetworkList.remove(dataNetwork);
trackSetupDataCallFailure(dataNetwork.getTransport(), cause);
if (mAnyDataNetworkExisting && mDataNetworkList.isEmpty()) {
mPendingTearDownAllNetworks = false;
mAnyDataNetworkExisting = false;
mDataNetworkControllerCallbacks.forEach(callback -> callback.invokeFromExecutor(
() -> callback.onAnyDataNetworkExistingChanged(mAnyDataNetworkExisting)));
}
requestList.removeIf(request -> !mAllNetworkRequestList.contains(request));
if (requestList.isEmpty()) {
log("onDataNetworkSetupFailed: All requests have been released. "
+ "Will not evaluate retry.");
return;
}
// Data retry manager will determine if retry is needed. If needed, retry will be scheduled.
mDataRetryManager.evaluateDataSetupRetry(dataNetwork.getDataProfile(),
dataNetwork.getTransport(), requestList, cause, retryDelayMillis);
}
/**
* Track the frequency of setup data failure on each
* {@link AccessNetworkConstants.TransportType} data service.
*
* @param transport The transport of the data service.
* @param cause The fail cause
*/
private void trackSetupDataCallFailure(@TransportType int transport,
@DataFailureCause int cause) {
switch (transport) {
case AccessNetworkConstants.TRANSPORT_TYPE_WWAN:
// Skip when poor signal strength
if (mPhone.getSignalStrength().getLevel()
<= CellSignalStrength.SIGNAL_STRENGTH_POOR) {
return;
}
if (cause == DataFailCause.ERROR_UNSPECIFIED || cause == DataFailCause.UNKNOWN) {
reportAnomaly("RIL set up data call fails: unknown/unspecified error",
"ce7d1465-d8e4-404a-b76f-de2c60bee843");
}
if (mSetupDataCallWwanFailureCounter.addOccurrence()) {
reportAnomaly("RIL fails setup data call request "
+ mSetupDataCallWwanFailureCounter.getFrequencyString(),
"e6a98b97-9e34-4977-9a92-01d52a6691f6");
}
break;
case AccessNetworkConstants.TRANSPORT_TYPE_WLAN:
if (cause == DataFailCause.ERROR_UNSPECIFIED || cause == DataFailCause.UNKNOWN) {
reportAnomaly("IWLAN set up data call fails: unknown/unspecified error",
"a16fc15c-815b-4908-b8e6-5f3bc7cbc20b");
}
if (mSetupDataCallWlanFailureCounter.addOccurrence()) {
reportAnomaly("IWLAN data service fails setup data call request "
+ mSetupDataCallWlanFailureCounter.getFrequencyString(),
"e2248d8b-d55f-42bd-871c-0cfd80c3ddd1");
}
break;
default:
loge("trackSetupDataCallFailure: INVALID transport.");
}
}
/**
* Trigger the anomaly report with the specified UUID.
*
* @param anomalyMsg Description of the event
* @param uuid UUID associated with that event
*/
private void reportAnomaly(@NonNull String anomalyMsg, @NonNull String uuid) {
logl(anomalyMsg);
AnomalyReporter.reportAnomaly(UUID.fromString(uuid), anomalyMsg, mPhone.getCarrierId());
}
/**
* Called when data network is connected.
*
* @param dataNetwork The data network.
*/
private void onDataNetworkConnected(@NonNull DataNetwork dataNetwork) {
logl("onDataNetworkConnected: " + dataNetwork);
mDataNetworkControllerCallbacks.forEach(callback -> callback.invokeFromExecutor(
() -> callback.onDataNetworkConnected(dataNetwork.getTransport(),
dataNetwork.getDataProfile())));
mPreviousConnectedDataNetworkList.add(0, dataNetwork);
// Preserve the connected data networks for debugging purposes.
if (mPreviousConnectedDataNetworkList.size() > MAX_HISTORICAL_CONNECTED_DATA_NETWORKS) {
mPreviousConnectedDataNetworkList.remove(MAX_HISTORICAL_CONNECTED_DATA_NETWORKS);
}
updateOverallInternetDataState();
if (dataNetwork.getNetworkCapabilities().hasCapability(
NetworkCapabilities.NET_CAPABILITY_IMS)) {
logl("IMS data state changed from "
+ TelephonyUtils.dataStateToString(mImsDataNetworkState) + " to CONNECTED.");
mImsDataNetworkState = TelephonyManager.DATA_CONNECTED;
}
}
/**
* Called when needed to retry data setup.
*
* @param dataSetupRetryEntry The data setup retry entry scheduled by {@link DataRetryManager}.
*/
private void onDataNetworkSetupRetry(@NonNull DataSetupRetryEntry dataSetupRetryEntry) {
// The request might be already removed before retry happens. Remove them from the list
// if that's the case. Copy the list first. We don't want to remove the requests from
// the retry entry. They can be later used to determine what kind of retry it is.
NetworkRequestList requestList = new NetworkRequestList(
dataSetupRetryEntry.networkRequestList);
requestList.removeIf(request -> !mAllNetworkRequestList.contains(request));
if (requestList.isEmpty()) {
loge("onDataNetworkSetupRetry: Request list is empty. Abort retry.");
dataSetupRetryEntry.setState(DataRetryEntry.RETRY_STATE_CANCELLED);
return;
}
TelephonyNetworkRequest telephonyNetworkRequest = requestList.get(0);
int networkCapability = telephonyNetworkRequest.getApnTypeNetworkCapability();
int preferredTransport = mAccessNetworksManager.getPreferredTransportByNetworkCapability(
networkCapability);
if (preferredTransport != dataSetupRetryEntry.transport) {
log("Cannot re-satisfy " + telephonyNetworkRequest + " on "
+ AccessNetworkConstants.transportTypeToString(dataSetupRetryEntry.transport)
+ ". The preferred transport has switched to "
+ AccessNetworkConstants.transportTypeToString(preferredTransport)
+ ". " + dataSetupRetryEntry);
// Cancel the retry since the preferred transport has already changed, but then
// re-evaluate the unsatisfied network requests again so the new network can be brought
// up on the new target transport later.
dataSetupRetryEntry.setState(DataRetryEntry.RETRY_STATE_CANCELLED);
sendMessage(obtainMessage(EVENT_REEVALUATE_UNSATISFIED_NETWORK_REQUESTS,
DataEvaluationReason.PREFERRED_TRANSPORT_CHANGED));
return;
}
DataEvaluation evaluation = evaluateNetworkRequest(
telephonyNetworkRequest, DataEvaluationReason.DATA_RETRY);
if (!evaluation.containsDisallowedReasons()) {
DataProfile dataProfile = dataSetupRetryEntry.dataProfile;
if (dataProfile == null) {
dataProfile = evaluation.getCandidateDataProfile();
}
if (dataProfile != null) {
setupDataNetwork(dataProfile, dataSetupRetryEntry,
evaluation.getDataAllowedReason());
} else {
loge("onDataNetworkSetupRetry: Not able to find a suitable data profile to retry.");
dataSetupRetryEntry.setState(DataRetryEntry.RETRY_STATE_FAILED);
}
} else {
dataSetupRetryEntry.setState(DataRetryEntry.RETRY_STATE_FAILED);
}
}
/**
* Called when needed to retry data network handover.
*
* @param dataHandoverRetryEntry The handover entry.
*/
private void onDataNetworkHandoverRetry(
@NonNull DataHandoverRetryEntry dataHandoverRetryEntry) {
DataNetwork dataNetwork = dataHandoverRetryEntry.dataNetwork;
if (!mDataNetworkList.contains(dataNetwork)) {
log("onDataNetworkHandoverRetry: " + dataNetwork + " no longer exists.");
dataHandoverRetryEntry.setState(DataRetryEntry.RETRY_STATE_CANCELLED);
return;
}
if (!dataNetwork.isConnected()) {
log("onDataNetworkHandoverRetry: " + dataNetwork + " is not in the right state.");
dataHandoverRetryEntry.setState(DataRetryEntry.RETRY_STATE_CANCELLED);
return;
}
int preferredTransport = mAccessNetworksManager.getPreferredTransportByNetworkCapability(
dataNetwork.getApnTypeNetworkCapability());
if (dataNetwork.getTransport() == preferredTransport) {
log("onDataNetworkHandoverRetry: " + dataNetwork + " is already on the preferred "
+ "transport " + AccessNetworkConstants.transportTypeToString(
preferredTransport) + ".");
dataHandoverRetryEntry.setState(DataRetryEntry.RETRY_STATE_CANCELLED);
return;
}
logl("onDataNetworkHandoverRetry: Start handover " + dataNetwork + " to "
+ AccessNetworkConstants.transportTypeToString(preferredTransport)
+ ", " + dataHandoverRetryEntry);
tryHandoverDataNetwork(dataNetwork, preferredTransport, dataHandoverRetryEntry);
}
/**
* Called when data network validation status changed.
*
* @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}.
*
* @param dataNetwork The data network.
*/
private void onDataNetworkValidationStatusChanged(@NonNull DataNetwork dataNetwork,
@ValidationStatus int status, @Nullable Uri redirectUri) {
log("onDataNetworkValidationStatusChanged: " + dataNetwork + ", validation status="
+ DataUtils.validationStatusToString(status)
+ (redirectUri != null ? ", " + redirectUri : ""));
if (!TextUtils.isEmpty(redirectUri.toString())) {
Intent intent = new Intent(TelephonyManager.ACTION_CARRIER_SIGNAL_REDIRECTED);
intent.putExtra(TelephonyManager.EXTRA_REDIRECTION_URL, redirectUri);
mPhone.getCarrierSignalAgent().notifyCarrierSignalReceivers(intent);
log("Notify carrier signal receivers with redirectUri: " + redirectUri);
}
if (status != NetworkAgent.VALIDATION_STATUS_VALID
&& status != NetworkAgent.VALIDATION_STATUS_NOT_VALID) {
loge("Invalid validation status " + status + " received.");
return;
}
if (!mDataSettingsManager.isRecoveryOnBadNetworkEnabled()) {
log("Ignore data network validation status changed because "
+ "data stall recovery is disabled.");
return;
}
if (dataNetwork.isInternetSupported()) {
if (status == NetworkAgent.VALIDATION_STATUS_NOT_VALID
&& (dataNetwork.getCurrentState() == null || dataNetwork.isDisconnected())) {
log("Ignoring invalid validation status for disconnected DataNetwork");
return;
}
mDataNetworkControllerCallbacks.forEach(callback -> callback.invokeFromExecutor(
() -> callback.onInternetDataNetworkValidationStatusChanged(status)));
}
}
/**
* Called when data network suspended state changed.
*
* @param dataNetwork The data network.
* @param suspended {@code true} if data is suspended.
*/
private void onDataNetworkSuspendedStateChanged(@NonNull DataNetwork dataNetwork,
boolean suspended) {
updateOverallInternetDataState();
if (dataNetwork.getNetworkCapabilities().hasCapability(
NetworkCapabilities.NET_CAPABILITY_IMS)) {
logl("IMS data state changed from "
+ TelephonyUtils.dataStateToString(mImsDataNetworkState) + " to "
+ (suspended ? "SUSPENDED" : "CONNECTED"));
mImsDataNetworkState = suspended
? TelephonyManager.DATA_SUSPENDED : TelephonyManager.DATA_CONNECTED;
}
}
/**
* Called when data network disconnected.
*
* @param dataNetwork The data network.
* @param cause The disconnect cause.
* @param tearDownReason The reason the network was torn down
*/
private void onDataNetworkDisconnected(@NonNull DataNetwork dataNetwork,
@DataFailureCause int cause, @TearDownReason int tearDownReason) {
logl("onDataNetworkDisconnected: " + dataNetwork + ", cause="
+ DataFailCause.toString(cause) + "(" + cause + "), tearDownReason="
+ DataNetwork.tearDownReasonToString(tearDownReason));
mDataNetworkList.remove(dataNetwork);
mPendingImsDeregDataNetworks.remove(dataNetwork);
mDataRetryManager.cancelPendingHandoverRetry(dataNetwork);
updateOverallInternetDataState();
if (dataNetwork.getNetworkCapabilities().hasCapability(
NetworkCapabilities.NET_CAPABILITY_IMS)) {
logl("IMS data state changed from "
+ TelephonyUtils.dataStateToString(mImsDataNetworkState) + " to DISCONNECTED.");
mImsDataNetworkState = TelephonyManager.DATA_DISCONNECTED;
}
if (mAnyDataNetworkExisting && mDataNetworkList.isEmpty()) {
log("All data networks disconnected now.");
mPendingTearDownAllNetworks = false;
mAnyDataNetworkExisting = false;
mDataNetworkControllerCallbacks.forEach(callback -> callback.invokeFromExecutor(
() -> callback.onAnyDataNetworkExistingChanged(mAnyDataNetworkExisting)));
}
// Immediately reestablish on target transport if network was torn down due to policy
long delayMillis = tearDownReason == DataNetwork.TEAR_DOWN_REASON_HANDOVER_NOT_ALLOWED
? 0 : mDataConfigManager.getRetrySetupAfterDisconnectMillis();
// Sometimes network was unsolicitedly reported lost for reasons. We should re-evaluate
// and see if data network can be re-established again.
sendMessageDelayed(obtainMessage(EVENT_REEVALUATE_UNSATISFIED_NETWORK_REQUESTS,
DataEvaluationReason.RETRY_AFTER_DISCONNECTED), delayMillis);
}
/**
* Called when handover between IWLAN and cellular network succeeded.
*
* @param dataNetwork The data network.
*/
private void onDataNetworkHandoverSucceeded(@NonNull DataNetwork dataNetwork) {
logl("Handover successfully. " + dataNetwork + " to " + AccessNetworkConstants
.transportTypeToString(dataNetwork.getTransport()));
// The preferred transport might be changed when handover was in progress. We need to
// evaluate again to make sure we are not out-of-sync with the input from access network
// manager.
sendMessage(obtainMessage(EVENT_EVALUATE_PREFERRED_TRANSPORT,
dataNetwork.getApnTypeNetworkCapability(), 0));
// There might be network we didn't tear down in the last evaluation due to handover in
// progress. We should evaluate again.
sendMessage(obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS,
DataEvaluationReason.DATA_HANDOVER));
}
/**
* Called when data network handover between IWLAN and cellular network failed.
*
* @param dataNetwork The data network.
* @param cause The fail cause.
* @param retryDelayMillis Network suggested retry time in milliseconds.
* {@link Long#MAX_VALUE} indicates data retry should not occur.
* {@link DataCallResponse#RETRY_DURATION_UNDEFINED} indicates network did not suggest any
* retry duration.
* @param handoverFailureMode The handover failure mode that determine the behavior of
* how frameworks should handle the handover failure.
*/
private void onDataNetworkHandoverFailed(@NonNull DataNetwork dataNetwork,
@DataFailureCause int cause, long retryDelayMillis,
@HandoverFailureMode int handoverFailureMode) {
logl("Handover failed. " + dataNetwork + ", cause=" + DataFailCause.toString(cause)
+ ", retryDelayMillis=" + retryDelayMillis + "ms, handoverFailureMode="
+ DataCallResponse.failureModeToString(handoverFailureMode));
// There might be network we didn't tear down in the last evaluation due to handover in
// progress. We should evaluate again.
sendMessage(obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS,
DataEvaluationReason.DATA_HANDOVER));
if (dataNetwork.getAttachedNetworkRequestList().isEmpty()) {
log("onDataNetworkHandoverFailed: No network requests attached to " + dataNetwork
+ ". No need to retry since the network will be torn down soon.");
return;
}
if (handoverFailureMode == DataCallResponse.HANDOVER_FAILURE_MODE_DO_FALLBACK
|| (handoverFailureMode == DataCallResponse.HANDOVER_FAILURE_MODE_LEGACY
&& cause == DataFailCause.HANDOFF_PREFERENCE_CHANGED)) {
// Don't retry handover anymore. Give QNS some time to switch the preferred transport
// to the original one, but we should re-evaluate the preferred transport again to
// make sure QNS does change it back, if not, we still need to perform handover at that
// time.
sendMessageDelayed(obtainMessage(EVENT_EVALUATE_PREFERRED_TRANSPORT,
dataNetwork.getApnTypeNetworkCapability(), 0),
REEVALUATE_PREFERRED_TRANSPORT_DELAY_MILLIS);
} else if (handoverFailureMode == DataCallResponse
.HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL || handoverFailureMode
== DataCallResponse.HANDOVER_FAILURE_MODE_LEGACY) {
int preferredTransport = mAccessNetworksManager
.getPreferredTransportByNetworkCapability(
dataNetwork.getApnTypeNetworkCapability());
if (dataNetwork.getTransport() == preferredTransport) {
log("onDataNetworkHandoverFailed: Already on preferred transport "
+ AccessNetworkConstants.transportTypeToString(preferredTransport)
+ ". No further actions needed.");
return;
}
int targetTransport = DataUtils.getTargetTransport(dataNetwork.getTransport());
mDataRetryManager.evaluateDataSetupRetry(dataNetwork.getDataProfile(), targetTransport,
dataNetwork.getAttachedNetworkRequestList(), cause, retryDelayMillis);
// Tear down the data network on source transport. Retry manager will schedule
// setup a new data network on the target transport.
tearDownGracefully(dataNetwork, DataNetwork.TEAR_DOWN_REASON_HANDOVER_FAILED);
} else {
mDataRetryManager.evaluateDataHandoverRetry(dataNetwork, cause, retryDelayMillis);
}
}
/**
* Called when network requests failed to attach to the data network.
*
* @param dataNetwork The data network that can't be attached.
* @param requestList The requests failed to attach to the network.
*/
private void onAttachNetworkRequestsFailed(@NonNull DataNetwork dataNetwork,
@NonNull NetworkRequestList requestList) {
log("Failed to attach " + requestList + " to " + dataNetwork);
}
/**
* Called when data stall occurs and needed to tear down / setup a new data network for
* internet. This event is from {@link DataStallRecoveryManager}.
*/
private void onDataStallReestablishInternet() {
log("onDataStallReestablishInternet: Tear down data networks that support internet.");
// Tear down all data networks that support internet. After data disconnected, unsatisfied
// network requests will be re-evaluate again and data network controller will attempt to
// setup data networks to satisfy them.
mDataNetworkList.stream()
.filter(DataNetwork::isInternetSupported)
.forEach(dataNetwork -> dataNetwork.tearDown(
DataNetwork.TEAR_DOWN_REASON_DATA_STALL));
}
/**
* Called when data service binding changed.
*
* @param transport The transport of the changed data service.
* @param bound {@code true} if data service is bound.
*/
private void onDataServiceBindingChanged(@TransportType int transport, boolean bound) {
log("onDataServiceBindingChanged: " + AccessNetworkConstants
.transportTypeToString(transport) + " data service is "
+ (bound ? "bound." : "unbound."));
if (bound) {
mDataNetworkControllerCallbacks.forEach(callback -> callback.invokeFromExecutor(
() -> callback.onDataServiceBound(transport)));
}
mDataServiceBound.put(transport, bound);
}
/**
* Called when SIM is absent.
*/
private void onSimAbsent() {
log("onSimAbsent");
sendMessage(obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS,
DataEvaluationReason.SIM_REMOVAL));
}
/**
* Called when SIM state changes.
*
* @param simState SIM state. (Note this is mixed with card state and application state.)
*/
private void onSimStateChanged(@SimState int simState) {
log("onSimStateChanged: state=" + TelephonyManager.simStateToString(simState));
if (mSimState != simState) {
mSimState = simState;
if (simState == TelephonyManager.SIM_STATE_ABSENT) {
onSimAbsent();
} else if (simState == TelephonyManager.SIM_STATE_LOADED) {
sendMessage(obtainMessage(EVENT_REEVALUATE_UNSATISFIED_NETWORK_REQUESTS,
DataEvaluationReason.SIM_LOADED));
}
mDataNetworkControllerCallbacks.forEach(callback -> callback.invokeFromExecutor(
() -> callback.onSimStateChanged(mSimState)));
}
}
/**
* Called when needed to evaluate the preferred transport for certain capability.
*
* @param capability The network capability to evaluate.
*/
private void onEvaluatePreferredTransport(@NetCapability int capability) {
int preferredTransport = mAccessNetworksManager
.getPreferredTransportByNetworkCapability(capability);
log("onEvaluatePreferredTransport: " + DataUtils.networkCapabilityToString(capability)
+ " preferred on "
+ AccessNetworkConstants.transportTypeToString(preferredTransport));
for (DataNetwork dataNetwork : mDataNetworkList) {
if (dataNetwork.getApnTypeNetworkCapability() == capability) {
// Check if the data network's current transport is different than from the
// preferred transport. If it's different, then handover is needed.
if (dataNetwork.getTransport() == preferredTransport) {
log("onEvaluatePreferredTransport:" + dataNetwork + " already on "
+ AccessNetworkConstants.transportTypeToString(preferredTransport));
continue;
}
// If handover is ongoing, ignore the preference change for now. After handover
// succeeds or fails, preferred transport will be re-evaluate again. Handover will
// be performed at that time if needed.
if (dataNetwork.isHandoverInProgress()) {
log("onEvaluatePreferredTransport: " + dataNetwork + " handover in progress.");
continue;
}
tryHandoverDataNetwork(dataNetwork, preferredTransport, null/*handoverRetryEntry*/);
}
}
}
/**
* Perform data network handover if condition allows, otherwise tear down the network to allow
* new network setup on the target transport.
*
* @param dataNetwork The network on which the handover occurs
* @param targetTransport The target transport of the handover
* @param dataHandoverRetryEntry {@code null} if the handover attempt is not due to scheduled
* retry
*/
private void tryHandoverDataNetwork(@NonNull DataNetwork dataNetwork,
@TransportType int targetTransport,
@Nullable DataHandoverRetryEntry dataHandoverRetryEntry) {
if (dataHandoverRetryEntry == null // This handover is a new request
&& mDataRetryManager.isAnyHandoverRetryScheduled(dataNetwork)) {
log("tryHandoverDataNetwork: retry scheduled for" + dataNetwork
+ ", ignore this attempt");
return;
}
DataEvaluation dataEvaluation = evaluateDataNetworkHandover(dataNetwork);
log("tryHandoverDataNetwork: " + dataEvaluation + ", " + dataNetwork);
if (!dataEvaluation.containsDisallowedReasons()) {
logl("Start handover " + dataNetwork + " to "
+ AccessNetworkConstants.transportTypeToString(targetTransport));
dataNetwork.startHandover(targetTransport, dataHandoverRetryEntry);
} else if (dataEvaluation.containsAny(DataDisallowedReason.NOT_ALLOWED_BY_POLICY,
DataDisallowedReason.NOT_IN_SERVICE,
DataDisallowedReason.VOPS_NOT_SUPPORTED)) {
logl("tryHandoverDataNetwork: Handover not allowed. Tear down"
+ dataNetwork + " so a new network can be setup on "
+ AccessNetworkConstants.transportTypeToString(targetTransport));
tearDownGracefully(dataNetwork,
DataNetwork.TEAR_DOWN_REASON_HANDOVER_NOT_ALLOWED);
} else if (dataEvaluation.containsAny(DataDisallowedReason.ILLEGAL_STATE,
DataDisallowedReason.RETRY_SCHEDULED)) {
logl("tryHandoverDataNetwork: Handover not allowed. " + dataNetwork
+ " will remain on " + AccessNetworkConstants.transportTypeToString(
dataNetwork.getTransport()));
} else {
loge("tryHandoverDataNetwork: Unexpected handover evaluation result.");
}
}
/**
* Update {@link SubscriptionPlan}s from {@link NetworkPolicyManager}.
*/
private void updateSubscriptionPlans() {
SubscriptionPlan[] plans = mNetworkPolicyManager.getSubscriptionPlans(
mSubId, mPhone.getContext().getOpPackageName());
mSubscriptionPlans.clear();
mSubscriptionPlans.addAll(plans != null ? Arrays.asList(plans) : Collections.emptyList());
mCongestedOverrideNetworkTypes.clear();
mUnmeteredOverrideNetworkTypes.clear();
log("Subscription plans initialized: " + mSubscriptionPlans);
}
/**
* Called when data network's link status changed.
*
* @param dataNetwork The data network that has link status changed.
* @param linkStatus The link status (i.e. RRC state).
*/
private void onLinkStatusChanged(@NonNull DataNetwork dataNetwork, @LinkStatus int linkStatus) {
// TODO: Since this is only used for 5G icon display logic, so we only use internet data
// data network's link status. Consider expanding to all data networks if needed, and
// should use CarrierConfigManager.KEY_LTE_ENDC_USING_USER_DATA_FOR_RRC_DETECTION_BOOL
// to determine if using all data networks or only internet data networks.
int status = DataCallResponse.LINK_STATUS_INACTIVE;
boolean anyInternet = mDataNetworkList.stream()
.anyMatch(network -> network.isInternetSupported() && network.isConnected());
if (anyInternet) {
status = mDataNetworkList.stream()
.anyMatch(network -> network.isInternetSupported()
&& network.isConnected() && network.getLinkStatus()
== DataCallResponse.LINK_STATUS_ACTIVE)
? DataCallResponse.LINK_STATUS_ACTIVE
: DataCallResponse.LINK_STATUS_DORMANT;
}
if (mInternetLinkStatus != status) {
log("Internet link status changed to " + DataUtils.linkStatusToString(status));
mInternetLinkStatus = status;
mDataNetworkControllerCallbacks.forEach(callback -> callback.invokeFromExecutor(
() -> callback.onPhysicalLinkStatusChanged(mInternetLinkStatus)));
}
updateDataActivity();
}
/**
* Called when PCO data changed.
*
* @param dataNetwork The data network.
*/
private void onPcoDataChanged(@NonNull DataNetwork dataNetwork) {
// Check if any data network is using NR advanced bands.
int nrAdvancedPcoId = mDataConfigManager.getNrAdvancedCapablePcoId();
if (nrAdvancedPcoId != 0) {
boolean nrAdvancedCapableByPco = false;
for (DataNetwork network : mDataNetworkList) {
PcoData pcoData = network.getPcoData().get(nrAdvancedPcoId);
if (pcoData != null && pcoData.contents.length > 0
&& pcoData.contents[pcoData.contents.length - 1] == 1) {
nrAdvancedCapableByPco = true;
break;
}
}
if (nrAdvancedCapableByPco != mNrAdvancedCapableByPco) {
log("onPcoDataChanged: mNrAdvancedCapableByPco = " + nrAdvancedCapableByPco);
mNrAdvancedCapableByPco = nrAdvancedCapableByPco;
mDataNetworkControllerCallbacks.forEach(callback -> callback.invokeFromExecutor(
() -> callback.onNrAdvancedCapableByPcoChanged(mNrAdvancedCapableByPco)));
}
}
}
/**
* Called when network capabilities changed.
*
* @param dataNetwork The data network.
*/
private void onNetworkCapabilitiesChanged(@NonNull DataNetwork dataNetwork) {
// The network capabilities changed. See if there are unsatisfied network requests that
// become satisfiable.
NetworkRequestList networkRequestList = new NetworkRequestList();
for (TelephonyNetworkRequest networkRequest : mAllNetworkRequestList) {
if (networkRequest.getState() == TelephonyNetworkRequest.REQUEST_STATE_UNSATISFIED) {
if (networkRequest.canBeSatisfiedBy(dataNetwork.getNetworkCapabilities())) {
networkRequestList.add(networkRequest);
}
}
}
if (!networkRequestList.isEmpty()) {
log("Found more network requests that can be satisfied. " + networkRequestList);
dataNetwork.attachNetworkRequests(networkRequestList);
}
if (dataNetwork.getNetworkCapabilities().hasCapability(
NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
// Update because DataNetwork#isInternetSupported might have changed with capabilities.
updateOverallInternetDataState();
}
}
/**
* Check if needed to re-evaluate the existing data networks.
*
* @param oldNri Previous network registration info.
* @param newNri Current network registration info.
* @return {@code true} if needed to re-evaluate the existing data networks.
*/
private boolean shouldReevaluateDataNetworks(@Nullable NetworkRegistrationInfo oldNri,
@Nullable NetworkRegistrationInfo newNri) {
if (oldNri == null || newNri == null) return false;
if (newNri.getAccessNetworkTechnology() == TelephonyManager.NETWORK_TYPE_UNKNOWN) {
// Sometimes devices temporarily lose signal and RAT becomes unknown. We don't tear
// down data network in this case.
return false;
}
if (oldNri.getAccessNetworkTechnology() != newNri.getAccessNetworkTechnology()
|| (!oldNri.isRoaming() && newNri.isRoaming())) {
return true;
}
DataSpecificRegistrationInfo oldDsri = oldNri.getDataSpecificInfo();
DataSpecificRegistrationInfo newDsri = newNri.getDataSpecificInfo();
if (newDsri == null) return false;
if ((oldDsri == null || oldDsri.getVopsSupportInfo() == null
|| oldDsri.getVopsSupportInfo().isVopsSupported())
&& (newDsri.getVopsSupportInfo() != null && !newDsri.getVopsSupportInfo()
.isVopsSupported())) {
// If previously VoPS was supported (or does not exist), and now the network reports
// VoPS not supported, we should evaluate existing data networks to see if they need
// to be torn down.
return true;
}
return false;
}
/**
* Check if needed to re-evaluate the unsatisfied network requests.
*
* @param oldSS Previous raw service state.
* @param newSS Current raw service state.
* @param transport The network transport to be checked.
* @return {@code true} if needed to re-evaluate the unsatisfied network requests.
*/
private boolean shouldReevaluateNetworkRequests(@NonNull ServiceState oldSS,
@NonNull ServiceState newSS, @TransportType int transport) {
NetworkRegistrationInfo oldPsNri = oldSS.getNetworkRegistrationInfo(
NetworkRegistrationInfo.DOMAIN_PS, transport);
NetworkRegistrationInfo newPsNri = newSS.getNetworkRegistrationInfo(
NetworkRegistrationInfo.DOMAIN_PS, transport);
if (newPsNri == null) return false;
if (newPsNri.getAccessNetworkTechnology() == TelephonyManager.NETWORK_TYPE_UNKNOWN) {
// Sometimes devices temporarily lose signal and RAT becomes unknown. We don't setup
// data in this case.
return false;
}
if (oldPsNri == null
|| oldPsNri.getAccessNetworkTechnology() != newPsNri.getAccessNetworkTechnology()
|| (!oldPsNri.isInService() && newPsNri.isInService())) {
return true;
}
// If CS connection is back to service on non-DDS, reevaluate for potential PS
if (!serviceStateAllowsPSAttach(oldSS, transport)
&& serviceStateAllowsPSAttach(newSS, transport)) {
return true;
}
DataSpecificRegistrationInfo oldDsri = oldPsNri.getDataSpecificInfo();
DataSpecificRegistrationInfo newDsri = newPsNri.getDataSpecificInfo();
if (oldDsri == null) return false;
if ((newDsri == null || newDsri.getVopsSupportInfo() == null
|| newDsri.getVopsSupportInfo().isVopsSupported())
&& (oldDsri.getVopsSupportInfo() != null && !oldDsri.getVopsSupportInfo()
.isVopsSupported())) {
// If previously VoPS was not supported, and now the network reports
// VoPS supported (or does not report), we should evaluate the unsatisfied network
// request to see if the can be satisfied again.
return true;
}
return false;
}
/**
* Called when service state changed.
*/
// Note that this is only called when data RAT or data registration changed. If we need to know
// more "changed" events other than data RAT and data registration state, we should add
// a new listening ServiceStateTracker.registerForServiceStateChanged().
private void onServiceStateChanged() {
// Use the raw service state instead of the mPhone.getServiceState().
ServiceState newServiceState = mPhone.getServiceStateTracker().getServiceState();
StringBuilder debugMessage = new StringBuilder("onServiceStateChanged: ");
boolean evaluateNetworkRequests = false, evaluateDataNetworks = false;
if (!mServiceState.equals(newServiceState)) {
log("onServiceStateChanged: changed to " + newServiceState);
for (int transport : mAccessNetworksManager.getAvailableTransports()) {
NetworkRegistrationInfo oldNri = mServiceState.getNetworkRegistrationInfo(
NetworkRegistrationInfo.DOMAIN_PS, transport);
NetworkRegistrationInfo newNri = newServiceState.getNetworkRegistrationInfo(
NetworkRegistrationInfo.DOMAIN_PS, transport);
debugMessage.append("[").append(
AccessNetworkConstants.transportTypeToString(transport)).append(": ");
debugMessage.append(oldNri != null ? TelephonyManager.getNetworkTypeName(
oldNri.getAccessNetworkTechnology()) : null);
debugMessage.append("->").append(
newNri != null ? TelephonyManager.getNetworkTypeName(
newNri.getAccessNetworkTechnology()) : null).append(", ");
debugMessage.append(
oldNri != null ? NetworkRegistrationInfo.registrationStateToString(
oldNri.getRegistrationState()) : null);
debugMessage.append("->").append(newNri != null
? NetworkRegistrationInfo.registrationStateToString(
newNri.getRegistrationState()) : null).append("] ");
if (shouldReevaluateDataNetworks(oldNri, newNri)) {
if (!hasMessages(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS)) {
sendMessage(obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS,
DataEvaluationReason.DATA_SERVICE_STATE_CHANGED));
evaluateDataNetworks = true;
}
}
if (shouldReevaluateNetworkRequests(mServiceState, newServiceState, transport)) {
if (!hasMessages(EVENT_REEVALUATE_UNSATISFIED_NETWORK_REQUESTS)) {
sendMessage(obtainMessage(EVENT_REEVALUATE_UNSATISFIED_NETWORK_REQUESTS,
DataEvaluationReason.DATA_SERVICE_STATE_CHANGED));
evaluateNetworkRequests = true;
}
}
}
mServiceState = newServiceState;
} else {
debugMessage.append("not changed");
}
debugMessage.append(". Evaluating network requests is ").append(
evaluateNetworkRequests ? "" : "not ").append(
"needed, evaluating existing data networks is ").append(
evaluateDataNetworks ? "" : "not ").append("needed.");
log(debugMessage.toString());
}
/**
* Update the internet data network state. For now only {@link TelephonyManager#DATA_CONNECTED},
* {@link TelephonyManager#DATA_SUSPENDED}, and {@link TelephonyManager#DATA_DISCONNECTED}
* are supported.
*/
private void updateOverallInternetDataState() {
boolean anyInternetConnected = mDataNetworkList.stream()
.anyMatch(dataNetwork -> dataNetwork.isInternetSupported()
&& (dataNetwork.isConnected() || dataNetwork.isHandoverInProgress()));
// If any one is not suspended, then the overall is not suspended.
List<DataNetwork> allConnectedInternetDataNetworks = mDataNetworkList.stream()
.filter(DataNetwork::isInternetSupported)
.filter(dataNetwork -> dataNetwork.isConnected()
|| dataNetwork.isHandoverInProgress())
.collect(Collectors.toList());
boolean isSuspended = !allConnectedInternetDataNetworks.isEmpty()
&& allConnectedInternetDataNetworks.stream().allMatch(DataNetwork::isSuspended);
logv("isSuspended=" + isSuspended + ", anyInternetConnected=" + anyInternetConnected
+ ", mDataNetworkList=" + mDataNetworkList);
int dataNetworkState = TelephonyManager.DATA_DISCONNECTED;
if (isSuspended) {
dataNetworkState = TelephonyManager.DATA_SUSPENDED;
} else if (anyInternetConnected) {
dataNetworkState = TelephonyManager.DATA_CONNECTED;
}
if (mInternetDataNetworkState != dataNetworkState) {
logl("Internet data state changed from "
+ TelephonyUtils.dataStateToString(mInternetDataNetworkState) + " to "
+ TelephonyUtils.dataStateToString(dataNetworkState) + ".");
// TODO: Create a new route to notify TelephonyRegistry.
if (dataNetworkState == TelephonyManager.DATA_CONNECTED
&& mInternetDataNetworkState == TelephonyManager.DATA_DISCONNECTED) {
mDataNetworkControllerCallbacks.forEach(callback -> callback.invokeFromExecutor(
() -> callback.onInternetDataNetworkConnected(
allConnectedInternetDataNetworks.stream()
.map(DataNetwork::getDataProfile)
.collect(Collectors.toList()))));
} else if (dataNetworkState == TelephonyManager.DATA_DISCONNECTED
&& mInternetDataNetworkState == TelephonyManager.DATA_CONNECTED) {
mDataNetworkControllerCallbacks.forEach(callback -> callback.invokeFromExecutor(
callback::onInternetDataNetworkDisconnected));
} // TODO: Add suspended callback if needed.
mInternetDataNetworkState = dataNetworkState;
}
}
/**
* @return Data config manager instance.
*/
public @NonNull DataConfigManager getDataConfigManager() {
return mDataConfigManager;
}
/**
* @return Data profile manager instance.
*/
public @NonNull DataProfileManager getDataProfileManager() {
return mDataProfileManager;
}
/**
* @return Data settings manager instance.
*/
public @NonNull DataSettingsManager getDataSettingsManager() {
return mDataSettingsManager;
}
/**
* @return Data retry manager instance.
*/
public @NonNull DataRetryManager getDataRetryManager() {
return mDataRetryManager;
}
/**
* @return The list of SubscriptionPlans
*/
@VisibleForTesting
public @NonNull List<SubscriptionPlan> getSubscriptionPlans() {
return mSubscriptionPlans;
}
/**
* @return The set of network types an unmetered override applies to
*/
@VisibleForTesting
public @NonNull @NetworkType Set<Integer> getUnmeteredOverrideNetworkTypes() {
return mUnmeteredOverrideNetworkTypes;
}
/**
* @return The set of network types a congested override applies to
*/
@VisibleForTesting
public @NonNull @NetworkType Set<Integer> getCongestedOverrideNetworkTypes() {
return mCongestedOverrideNetworkTypes;
}
/**
* Get data network type based on transport.
*
* @param transport The transport.
* @return The current network type.
*/
private @NetworkType int getDataNetworkType(@TransportType int transport) {
NetworkRegistrationInfo nri = mServiceState.getNetworkRegistrationInfo(
NetworkRegistrationInfo.DOMAIN_PS, transport);
if (nri != null) {
return nri.getAccessNetworkTechnology();
}
return TelephonyManager.NETWORK_TYPE_UNKNOWN;
}
/**
* Get data registration state based on transport.
*
* @param ss The service state from which to extract the data registration state.
* @param transport The transport.
* @return The registration state.
*/
private @RegistrationState int getDataRegistrationState(@NonNull ServiceState ss,
@TransportType int transport) {
NetworkRegistrationInfo nri = ss.getNetworkRegistrationInfo(
NetworkRegistrationInfo.DOMAIN_PS, transport);
if (nri != null) {
return nri.getRegistrationState();
}
return NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN;
}
/**
* @return The data activity. Note this is only updated when screen is on.
*/
public @DataActivityType int getDataActivity() {
return mDataActivity;
}
/**
* Register data network controller callback.
*
* @param callback The callback.
*/
public void registerDataNetworkControllerCallback(
@NonNull DataNetworkControllerCallback callback) {
sendMessage(obtainMessage(EVENT_REGISTER_DATA_NETWORK_CONTROLLER_CALLBACK, callback));
}
/**
* Unregister data network controller callback.
*
* @param callback The callback.
*/
public void unregisterDataNetworkControllerCallback(
@NonNull DataNetworkControllerCallback callback) {
sendMessage(obtainMessage(EVENT_UNREGISTER_DATA_NETWORK_CONTROLLER_CALLBACK, callback));
}
/**
* Tear down all data networks.
*
* @param reason The reason to tear down.
*/
public void tearDownAllDataNetworks(@TearDownReason int reason) {
sendMessage(obtainMessage(EVENT_TEAR_DOWN_ALL_DATA_NETWORKS, reason, 0));
}
/**
* Called when needed to tear down all data networks.
*
* @param reason The reason to tear down.
*/
public void onTearDownAllDataNetworks(@TearDownReason int reason) {
log("onTearDownAllDataNetworks: reason=" + DataNetwork.tearDownReasonToString(reason));
if (mDataNetworkList.isEmpty()) {
log("tearDownAllDataNetworks: No pending networks. All disconnected now.");
return;
}
mPendingTearDownAllNetworks = true;
for (DataNetwork dataNetwork : mDataNetworkList) {
if (!dataNetwork.isDisconnecting()) {
tearDownGracefully(dataNetwork, reason);
}
}
}
/**
* Evaluate the pending IMS de-registration networks and tear it down if it is safe to do that.
*/
private void evaluatePendingImsDeregDataNetworks() {
Iterator<Map.Entry<DataNetwork, Runnable>> it =
mPendingImsDeregDataNetworks.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<DataNetwork, Runnable> entry = it.next();
if (isSafeToTearDown(entry.getKey())) {
// Now tear down the network.
log("evaluatePendingImsDeregDataNetworks: Safe to tear down data network "
+ entry.getKey() + " now.");
entry.getValue().run();
it.remove();
} else {
log("Still not safe to tear down " + entry.getKey() + ".");
}
}
}
/**
* Check if the data network is safe to tear down at this moment.
*
* @param dataNetwork The data network.
* @return {@code true} if the data network is safe to tear down. {@code false} indicates this
* data network has requests originated from the IMS/RCS service and IMS/RCS is not
* de-registered yet.
*/
private boolean isSafeToTearDown(@NonNull DataNetwork dataNetwork) {
for (int imsFeature : SUPPORTED_IMS_FEATURES) {
String imsFeaturePackage = mImsFeaturePackageName.get(imsFeature);
if (imsFeaturePackage != null) {
if (dataNetwork.getAttachedNetworkRequestList()
.hasNetworkRequestsFromPackage(imsFeaturePackage)) {
if (mRegisteredImsFeatures.contains(imsFeature)) {
return false;
}
}
}
}
// All IMS features are de-registered (or this data network has no requests from IMS feature
// packages.
return true;
}
/**
* @return {@code true} if IMS graceful tear down is supported by frameworks.
*/
private boolean isImsGracefulTearDownSupported() {
return mDataConfigManager.getImsDeregistrationDelay() > 0;
}
/**
* Tear down the data network gracefully.
*
* @param dataNetwork The data network.
*/
private void tearDownGracefully(@NonNull DataNetwork dataNetwork, @TearDownReason int reason) {
long deregDelay = mDataConfigManager.getImsDeregistrationDelay();
if (isImsGracefulTearDownSupported() && !isSafeToTearDown(dataNetwork)) {
log("tearDownGracefully: Not safe to tear down " + dataNetwork
+ " at this point. Wait for IMS de-registration or timeout. MMTEL="
+ (mRegisteredImsFeatures.contains(ImsFeature.FEATURE_MMTEL)
? "registered" : "not registered")
+ ", RCS="
+ (mRegisteredImsFeatures.contains(ImsFeature.FEATURE_RCS)
? "registered" : "not registered")
);
Runnable runnable = dataNetwork.tearDownWhenConditionMet(reason, deregDelay);
if (runnable != null) {
mPendingImsDeregDataNetworks.put(dataNetwork, runnable);
} else {
log(dataNetwork + " is being torn down already.");
}
} else {
// Graceful tear down is not turned on. Tear down the network immediately.
log("tearDownGracefully: Safe to tear down " + dataNetwork);
dataNetwork.tearDown(reason);
}
}
/**
* Get the internet data network state. Note that this is the best effort if more than one
* data network supports internet. For now only {@link TelephonyManager#DATA_CONNECTED},
* {@link TelephonyManager#DATA_SUSPENDED}, and {@link TelephonyManager#DATA_DISCONNECTED}
* are supported.
*
* @return The data network state.
*/
public @DataState int getInternetDataNetworkState() {
return mInternetDataNetworkState;
}
/**
* @return List of bound data service packages name on WWAN and WLAN.
*/
public @NonNull List<String> getDataServicePackages() {
List<String> packages = new ArrayList<>();
for (int i = 0; i < mDataServiceManagers.size(); i++) {
packages.add(mDataServiceManagers.valueAt(i).getDataServicePackageName());
}
return packages;
}
/**
* Log debug messages.
* @param s debug messages
*/
private void log(@NonNull String s) {
Rlog.d(mLogTag, s);
}
/**
* Log error messages.
* @param s error messages
*/
private void loge(@NonNull String s) {
Rlog.e(mLogTag, s);
}
/**
* Log verbose messages.
* @param s debug messages.
*/
private void logv(@NonNull String s) {
if (VDBG) Rlog.v(mLogTag, 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 DataNetworkController
*
* @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, " ");
pw.println(DataNetworkController.class.getSimpleName() + "-" + mPhone.getPhoneId() + ":");
pw.increaseIndent();
pw.println("Current data networks:");
pw.increaseIndent();
for (DataNetwork dn : mDataNetworkList) {
dn.dump(fd, pw, args);
}
pw.decreaseIndent();
pw.println("Pending tear down data networks:");
pw.increaseIndent();
for (DataNetwork dn : mPendingImsDeregDataNetworks.keySet()) {
dn.dump(fd, pw, args);
}
pw.decreaseIndent();
pw.println("Previously connected data networks: (up to "
+ MAX_HISTORICAL_CONNECTED_DATA_NETWORKS + ")");
pw.increaseIndent();
for (DataNetwork dn: mPreviousConnectedDataNetworkList) {
// Do not print networks which is already in current network list.
if (!mDataNetworkList.contains(dn)) {
dn.dump(fd, pw, args);
}
}
pw.decreaseIndent();
pw.println("All telephony network requests:");
pw.increaseIndent();
for (TelephonyNetworkRequest networkRequest : mAllNetworkRequestList) {
pw.println(networkRequest);
}
pw.decreaseIndent();
pw.println("IMS features registration state: MMTEL="
+ (mRegisteredImsFeatures.contains(ImsFeature.FEATURE_MMTEL)
? "registered" : "not registered")
+ ", RCS="
+ (mRegisteredImsFeatures.contains(ImsFeature.FEATURE_RCS)
? "registered" : "not registered"));
pw.println("mServiceState=" + mServiceState);
pw.println("mPsRestricted=" + mPsRestricted);
pw.println("mAnyDataNetworkExisting=" + mAnyDataNetworkExisting);
pw.println("mInternetDataNetworkState="
+ TelephonyUtils.dataStateToString(mInternetDataNetworkState));
pw.println("mImsDataNetworkState="
+ TelephonyUtils.dataStateToString(mImsDataNetworkState));
pw.println("mDataServiceBound=" + mDataServiceBound);
pw.println("mSimState=" + TelephonyManager.simStateToString(mSimState));
pw.println("mDataNetworkControllerCallbacks=" + mDataNetworkControllerCallbacks);
pw.println("Subscription plans:");
pw.increaseIndent();
mSubscriptionPlans.forEach(pw::println);
pw.decreaseIndent();
pw.println("Unmetered override network types=" + mUnmeteredOverrideNetworkTypes.stream()
.map(TelephonyManager::getNetworkTypeName).collect(Collectors.joining(",")));
pw.println("Congested override network types=" + mCongestedOverrideNetworkTypes.stream()
.map(TelephonyManager::getNetworkTypeName).collect(Collectors.joining(",")));
pw.println("mImsThrottleCounter=" + mImsThrottleCounter);
pw.println("mNetworkUnwantedCounter=" + mNetworkUnwantedCounter);
pw.println("Local logs:");
pw.increaseIndent();
mLocalLog.dump(fd, pw, args);
pw.decreaseIndent();
pw.println("-------------------------------------");
mDataProfileManager.dump(fd, pw, args);
pw.println("-------------------------------------");
mDataRetryManager.dump(fd, pw, args);
pw.println("-------------------------------------");
mDataSettingsManager.dump(fd, pw, args);
pw.println("-------------------------------------");
mDataStallRecoveryManager.dump(fd, pw, args);
pw.println("-------------------------------------");
mDataConfigManager.dump(fd, pw, args);
pw.decreaseIndent();
}
}