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",