Adds request and response for handling network validation feature.
A new request network validation function has been added. Only QNS
can request this, and a network validation can be requested to each
DataNetwork through AccessNetworksManager. DataNetwork can request
validation to the DataServices such as Cellular or IWLAN through the
DataService API. If there is a DataService that does not support the
network validation feature, it may reply UNSUPPORTED as a default
result.
The Validation status variable has been added to DataCallResponse in
response to a network validation request. Network validation status
has also been added to PrecisedataConnectionState so that privileged
apps can use this variable to know.
Bug: 299346675
Test: atest FrameworksTelephonyTests
Change-Id: I77217967731ccadf9efdfe918d773ae8994f49d0
diff --git a/flags/data.aconfig b/flags/data.aconfig
index d65dc55..befb38d 100644
--- a/flags/data.aconfig
+++ b/flags/data.aconfig
@@ -41,3 +41,11 @@
description: "Expose apn setting supporting field"
bug: "307038091"
}
+
+flag {
+ name: "network_validation"
+ namespace: "telephony"
+ description: "Request network validation for data networks and response status."
+ bug:"286171724"
+}
+
diff --git a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
index 0f074a9..74d1786 100644
--- a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
+++ b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
@@ -441,9 +441,23 @@
* @param phone The phone instance
* @param looper Looper for the handler.
* @return The access networks manager
+ * @deprecated {@link #makeAccessNetworksManager(Phone, Looper, FeatureFlags)} instead
*/
public AccessNetworksManager makeAccessNetworksManager(Phone phone, Looper looper) {
- return new AccessNetworksManager(phone, looper);
+ return new AccessNetworksManager(phone, looper, new FeatureFlagsImpl());
+ }
+
+ /**
+ * Make access networks manager
+ *
+ * @param phone The phone instance
+ * @param looper Looper for the handler.
+ * @param featureFlags feature flags.
+ * @return The access networks manager
+ */
+ public AccessNetworksManager makeAccessNetworksManager(Phone phone, Looper looper,
+ @NonNull FeatureFlags featureFlags) {
+ return new AccessNetworksManager(phone, looper, featureFlags);
}
public CdmaSubscriptionSourceManager
diff --git a/src/java/com/android/internal/telephony/data/AccessNetworksManager.java b/src/java/com/android/internal/telephony/data/AccessNetworksManager.java
index 267f70b..a657ecd 100644
--- a/src/java/com/android/internal/telephony/data/AccessNetworksManager.java
+++ b/src/java/com/android/internal/telephony/data/AccessNetworksManager.java
@@ -41,6 +41,7 @@
import android.telephony.AnomalyReporter;
import android.telephony.CarrierConfigManager;
import android.telephony.data.ApnSetting;
+import android.telephony.data.DataServiceCallback;
import android.telephony.data.IQualifiedNetworksService;
import android.telephony.data.IQualifiedNetworksServiceCallback;
import android.telephony.data.QualifiedNetworksService;
@@ -51,8 +52,11 @@
import android.util.LocalLog;
import android.util.SparseArray;
+import com.android.internal.telephony.IIntegerConsumer;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.SlidingWindowEventCounter;
+import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.util.FunctionalUtils;
import com.android.telephony.Rlog;
import java.io.FileDescriptor;
@@ -65,6 +69,7 @@
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
@@ -133,6 +138,8 @@
private final @NonNull Set<AccessNetworksManagerCallback> mAccessNetworksManagerCallbacks =
new ArraySet<>();
+ private final FeatureFlags mFeatureFlags;
+
/**
* Represents qualified network types list on a specific APN type.
*/
@@ -294,6 +301,43 @@
mQualifiedNetworksChangedRegistrants.notifyResult(qualifiedNetworksList);
}
}
+
+ /**
+ * Called when QualifiedNetworksService requests network validation.
+ *
+ * Since the data network in the connected state corresponding to the given network
+ * capability must be validated, a request is tossed to the data network controller.
+ * @param networkCapability network capability
+ */
+ @Override
+ public void onNetworkValidationRequested(@NetCapability int networkCapability,
+ @NonNull IIntegerConsumer resultCodeCallback) {
+ DataNetworkController dnc = mPhone.getDataNetworkController();
+ if (!mFeatureFlags.networkValidation()) {
+ FunctionalUtils.ignoreRemoteException(resultCodeCallback::accept)
+ .accept(DataServiceCallback.RESULT_ERROR_UNSUPPORTED);
+ return;
+ }
+
+ log("onNetworkValidationRequested: networkCapability = ["
+ + DataUtils.networkCapabilityToString(networkCapability) + "]");
+
+ dnc.requestNetworkValidation(networkCapability, new Consumer<Integer>() {
+ @Override
+ public void accept(Integer result) {
+ post(() -> {
+ try {
+ log("onNetworkValidationRequestDone:"
+ + DataServiceCallback.resultCodeToString(result));
+ resultCodeCallback.accept(result.intValue());
+ } catch (RemoteException e) {
+ // Ignore if the remote process is no longer available to call back.
+ loge("onNetworkValidationRequestDone RemoteException" + e);
+ }
+ });
+ }
+ });
+ }
}
private void onEmergencyDataNetworkPreferredTransportChanged(
@@ -337,7 +381,8 @@
* @param phone The phone object.
* @param looper Looper for the handler.
*/
- public AccessNetworksManager(@NonNull Phone phone, @NonNull Looper looper) {
+ public AccessNetworksManager(@NonNull Phone phone, @NonNull Looper looper,
+ @NonNull FeatureFlags featureFlags) {
super(looper);
mPhone = phone;
mCarrierConfigManager = (CarrierConfigManager) phone.getContext().getSystemService(
@@ -346,6 +391,7 @@
mApnTypeToQnsChangeNetworkCounter = new SparseArray<>();
mAvailableTransports = new int[]{AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
AccessNetworkConstants.TRANSPORT_TYPE_WLAN};
+ mFeatureFlags = featureFlags;
// bindQualifiedNetworksService posts real work to handler thread. So here we can
// let the callback execute in binder thread to avoid post twice.
diff --git a/src/java/com/android/internal/telephony/data/DataNetwork.java b/src/java/com/android/internal/telephony/data/DataNetwork.java
index 4cf5ef9..48a18ee 100644
--- a/src/java/com/android/internal/telephony/data/DataNetwork.java
+++ b/src/java/com/android/internal/telephony/data/DataNetwork.java
@@ -103,6 +103,7 @@
import com.android.internal.telephony.metrics.DataCallSessionStats;
import com.android.internal.telephony.metrics.TelephonyMetrics;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FunctionalUtils;
import com.android.internal.util.IState;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
@@ -256,6 +257,12 @@
*/
private static final int EVENT_NOTIFY_HANDOVER_CANCELLED_RESPONSE = 27;
+ /** Event for data network validation request from the AccessNetworksManager. */
+ private static final int EVENT_DATA_NETWORK_VALIDATION_REQUESTED = 28;
+
+ /** Event for response to data network validation request. */
+ private static final int EVENT_DATA_NETWORK_VALIDATION_RESPONSE = 29;
+
/** Invalid context id. */
private static final int INVALID_CID = -1;
@@ -719,6 +726,19 @@
private @Nullable DataConfigManagerCallback mDataConfigManagerCallback;
/**
+ * Network validation status for this data network. If the data service provider does not
+ * support the network validation feature, should be UNSUPPORTED.
+ */
+ private @PreciseDataConnectionState.NetworkValidationStatus int mNetworkValidationStatus =
+ PreciseDataConnectionState.NETWORK_VALIDATION_UNSUPPORTED;
+
+ /**
+ * Callback used to respond to a network validation request to determine whether the request is
+ * successfully submitted. If the request has been submitted, change it to null.
+ */
+ private @Nullable Consumer<Integer> mNetworkValidationResultCodeCallback;
+
+ /**
* The network bandwidth.
*/
public static class NetworkBandwidth {
@@ -1277,6 +1297,14 @@
loge(eventToString(msg.what) + ": transition to disconnected state");
transitionTo(mDisconnectedState);
break;
+ case EVENT_DATA_NETWORK_VALIDATION_REQUESTED:
+ // If the data network is not connected, the request should be ignored.
+ handleErrorDataNetworkValidationRequest((Consumer<Integer>) msg.obj);
+ break;
+ case EVENT_DATA_NETWORK_VALIDATION_RESPONSE:
+ // handle the resultCode in response for the request.
+ handleDataNetworkValidationRequestResultCode(msg.arg1 /* resultCode */);
+ break;
default:
loge("Unhandled event " + eventToString(msg.what));
break;
@@ -1622,6 +1650,10 @@
updateSuspendState();
updateNetworkCapabilities();
break;
+ case EVENT_DATA_NETWORK_VALIDATION_REQUESTED:
+ // Network validation request can be accepted if the data is in connected state
+ handleDataNetworkValidationRequest((Consumer<Integer>) msg.obj);
+ break;
default:
return NOT_HANDLED;
}
@@ -1676,6 +1708,7 @@
case EVENT_CSS_INDICATOR_CHANGED:
case EVENT_VOICE_CALL_ENDED:
case EVENT_VOICE_CALL_STARTED:
+ case EVENT_DATA_NETWORK_VALIDATION_REQUESTED:
// Defer the request until handover succeeds or fails.
log("Defer message " + eventToString(msg.what));
deferMessage(msg);
@@ -1810,6 +1843,8 @@
if (mEverConnected) {
mLinkStatus = DataCallResponse.LINK_STATUS_INACTIVE;
+ mNetworkValidationStatus =
+ PreciseDataConnectionState.NETWORK_VALIDATION_UNSUPPORTED;
mDataNetworkCallback.invokeFromExecutor(() -> mDataNetworkCallback
.onLinkStatusChanged(DataNetwork.this, mLinkStatus));
mDataNetworkCallback.invokeFromExecutor(() -> mDataNetworkCallback
@@ -2601,6 +2636,7 @@
}
updateNetworkCapabilities();
+ updateValidationStatus(response.getNetworkValidationStatus());
}
/**
@@ -3245,6 +3281,7 @@
.setNetworkType(getDataNetworkType())
.setFailCause(mFailCause)
.setDefaultQos(mDefaultQos)
+ .setNetworkValidationStatus(mNetworkValidationStatus)
.build();
}
@@ -3515,6 +3552,81 @@
}
/**
+ * The network validation requests moves to process on the statemachich handler. A request is
+ * processed according to state of the data network.
+ */
+ public void requestNetworkValidation(@NonNull Consumer<Integer> resultCodeCallback) {
+ // request a network validation by DataNetwork state
+ sendMessage(EVENT_DATA_NETWORK_VALIDATION_REQUESTED, resultCodeCallback);
+ }
+
+ /**
+ * Request network validation to data service provider.
+ */
+ private void handleDataNetworkValidationRequest(@NonNull Consumer<Integer> resultCodeCallback) {
+ if (mNetworkValidationResultCodeCallback != null) {
+ loge("requestNetworkValidation: previous networkValidationRequest is in progress.");
+ FunctionalUtils.ignoreRemoteException(resultCodeCallback::accept)
+ .accept(DataServiceCallback.RESULT_ERROR_BUSY);
+ return;
+ }
+
+ mNetworkValidationResultCodeCallback = resultCodeCallback;
+
+ // Request validation directly from the data service.
+ mDataServiceManagers.get(mTransport).requestValidation(
+ mCid.get(mTransport), obtainMessage(EVENT_DATA_NETWORK_VALIDATION_RESPONSE));
+ log("handleDataNetworkValidationRequest, network validation requested");
+ }
+
+ private void handleErrorDataNetworkValidationRequest(
+ @NonNull Consumer<Integer> resultCodeCallback) {
+ loge("handleErrorDataNetworkValidationRequest: DataNetwork is not in Connected state");
+ FunctionalUtils.ignoreRemoteException(resultCodeCallback::accept)
+ .accept(DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
+ }
+
+ /**
+ * handle the resultCode in response for the request.
+ *
+ * @param resultCode {@link DataServiceCallback.ResultCode}
+ */
+ private void handleDataNetworkValidationRequestResultCode(
+ @DataServiceCallback.ResultCode int resultCode) {
+ if (mNetworkValidationResultCodeCallback != null) {
+ log("handleDataNetworkValidationRequestResultCode, resultCode:"
+ + DataServiceCallback.resultCodeToString(resultCode));
+ FunctionalUtils.ignoreRemoteException(mNetworkValidationResultCodeCallback::accept)
+ .accept(resultCode);
+ mNetworkValidationResultCodeCallback = null;
+ }
+ }
+
+ /**
+ * Update the validation status from {@link DataCallResponse}, convert to network validation
+ * status {@link PreciseDataConnectionState.NetworkValidationStatus} and notify to
+ * {@link PreciseDataConnectionState} if status was changed.
+ *
+ * @param networkValidationStatus {@link PreciseDataConnectionState.NetworkValidationStatus}
+ */
+ private void updateValidationStatus(
+ @PreciseDataConnectionState.NetworkValidationStatus int networkValidationStatus) {
+ if (!mFlags.networkValidation()) {
+ // Do nothing, if network validation feature is disabled
+ return;
+ }
+
+ // if network validation status is changed, notify preciseDataConnectionState.
+ if (mNetworkValidationStatus != networkValidationStatus) {
+ log("updateValidationStatus:"
+ + PreciseDataConnectionState.networkValidationStatusToString(
+ networkValidationStatus));
+ mNetworkValidationStatus = networkValidationStatus;
+ notifyPreciseDataConnectionState();
+ }
+ }
+
+ /**
* Convert the data tear down reason to string.
*
* @param reason Data deactivation reason.
@@ -3647,6 +3759,10 @@
return "EVENT_NOTIFY_HANDOVER_STARTED_RESPONSE";
case EVENT_NOTIFY_HANDOVER_CANCELLED_RESPONSE:
return "EVENT_NOTIFY_HANDOVER_CANCELLED_RESPONSE";
+ case EVENT_DATA_NETWORK_VALIDATION_REQUESTED:
+ return "EVENT_DATA_NETWORK_VALIDATION_REQUESTED";
+ case EVENT_DATA_NETWORK_VALIDATION_RESPONSE:
+ return "EVENT_DATA_NETWORK_VALIDATION_RESPONSE";
default:
return "Unknown(" + event + ")";
}
diff --git a/src/java/com/android/internal/telephony/data/DataNetworkController.java b/src/java/com/android/internal/telephony/data/DataNetworkController.java
index c29afa6..63fe7e5 100644
--- a/src/java/com/android/internal/telephony/data/DataNetworkController.java
+++ b/src/java/com/android/internal/telephony/data/DataNetworkController.java
@@ -60,10 +60,12 @@
import android.telephony.TelephonyManager.DataState;
import android.telephony.TelephonyManager.SimState;
import android.telephony.TelephonyRegistryManager;
+import android.telephony.data.ApnSetting;
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.data.DataServiceCallback;
import android.telephony.ims.ImsException;
import android.telephony.ims.ImsManager;
import android.telephony.ims.ImsReasonInfo;
@@ -104,6 +106,7 @@
import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
import com.android.internal.telephony.subscription.SubscriptionManagerService;
import com.android.internal.telephony.util.TelephonyUtils;
+import com.android.internal.util.FunctionalUtils;
import com.android.telephony.Rlog;
import java.io.FileDescriptor;
@@ -126,6 +129,7 @@
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -3887,6 +3891,43 @@
}
/**
+ * Request network validation.
+ *
+ * Nnetwork validation request is sent to the DataNetwork that matches the network capability
+ * in the list of DataNetwork owned by the DNC.
+ *
+ * @param capability network capability {@link NetCapability}
+ */
+ public void requestNetworkValidation(@NetCapability int capability,
+ @NonNull Consumer<Integer> resultCodeCallback) {
+
+ if (DataUtils.networkCapabilityToApnType(capability) == ApnSetting.TYPE_NONE) {
+ // If the capability is not an apn type based capability, sent an invalid argument.
+ loge("requestNetworkValidation: the capability is not an apn type based. capability:"
+ + capability);
+ FunctionalUtils.ignoreRemoteException(resultCodeCallback::accept)
+ .accept(DataServiceCallback.RESULT_ERROR_INVALID_ARG);
+ return;
+ }
+
+ // Find DataNetwork that matches the capability.
+ List<DataNetwork> list = mDataNetworkList.stream()
+ .filter(dataNetwork ->
+ dataNetwork.getNetworkCapabilities().hasCapability(capability))
+ .toList();
+
+ if (!list.isEmpty()) {
+ // request network validation.
+ list.forEach(dataNetwork -> dataNetwork.requestNetworkValidation(resultCodeCallback));
+ } else {
+ // If not found, sent an invalid argument.
+ loge("requestNetworkValidation: No matching DataNetwork was found");
+ FunctionalUtils.ignoreRemoteException(resultCodeCallback::accept)
+ .accept(DataServiceCallback.RESULT_ERROR_INVALID_ARG);
+ }
+ }
+
+ /**
* Log debug messages.
* @param s debug messages
*/
diff --git a/src/java/com/android/internal/telephony/data/DataServiceManager.java b/src/java/com/android/internal/telephony/data/DataServiceManager.java
index bb0e8c3..640399e 100644
--- a/src/java/com/android/internal/telephony/data/DataServiceManager.java
+++ b/src/java/com/android/internal/telephony/data/DataServiceManager.java
@@ -55,6 +55,7 @@
import android.telephony.data.TrafficDescriptor;
import android.text.TextUtils;
+import com.android.internal.telephony.IIntegerConsumer;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConfigurationManager;
import com.android.internal.telephony.util.TelephonyUtils;
@@ -947,6 +948,47 @@
}
/**
+ * Request data network validation.
+ *
+ * <p>Validates a given data network to ensure that the network can work properly.
+ *
+ * <p>Depending on the {@link DataServiceCallback.ResultCode}, Listener can determine whether
+ * validation has been triggered, has an error or whether it is a feature that is not supported.
+ *
+ * @param cid The identifier of the data network which is provided in DataCallResponse
+ * @param onCompleteMessage The result message for this request. Null if the client does not
+ * care about the result.
+ */
+ public void requestValidation(int cid, @Nullable Message onCompleteMessage) {
+ if (DBG) log("requestValidation");
+ if (!mBound) {
+ loge("DataService is not bound.");
+ sendCompleteMessage(onCompleteMessage, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
+ return;
+ }
+
+ IIntegerConsumer callback = new IIntegerConsumer.Stub() {
+ @Override
+ public void accept(int result) {
+ mMessageMap.remove(asBinder());
+ sendCompleteMessage(onCompleteMessage, result);
+ }
+ };
+ if (onCompleteMessage != null) {
+ mMessageMap.put(callback.asBinder(), onCompleteMessage);
+ }
+ try {
+ mIDataService.requestValidation(mPhone.getPhoneId(), cid, callback);
+ } catch (RemoteException e) {
+ loge("Cannot invoke requestValidation on data service.");
+ if (callback != null) {
+ mMessageMap.remove(callback.asBinder());
+ }
+ sendCompleteMessage(onCompleteMessage, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
+ }
+ }
+
+ /**
* Register for data service binding status changed event.
*
* @param h The target to post the event message to.
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/AccessNetworksManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/AccessNetworksManagerTest.java
index 4d116a8..38166aa 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/AccessNetworksManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/AccessNetworksManagerTest.java
@@ -116,7 +116,8 @@
}).when(mMockedCallback).invokeFromExecutor(any(Runnable.class));
mMockedDataConfigManager = Mockito.mock(DataConfigManager.class);
- mAccessNetworksManager = new AccessNetworksManager(mPhone, Looper.myLooper());
+ mAccessNetworksManager =
+ new AccessNetworksManager(mPhone, Looper.myLooper(), mFeatureFlags);
processAllMessages();
replaceInstance(AccessNetworksManager.class, "mDataConfigManager",