Convert VcnNetworkProvider to use NetworkOffers

This change brings VcnNetworkProvider to full functionality in the new
NetworkProvider paradigm, where NetworkProviders offer networks, and are
notified based on NetworkOfferCallbacks.

Bug: 185204197
Test: atest FrameworksVcnTests
Change-Id: I88c69c0be9f6fd81839fb1595ed00341001694a5
Merged-In: I88c69c0be9f6fd81839fb1595ed00341001694a5
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index d59ad6f..86cd23d 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -83,7 +83,12 @@
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     static final int MIN_MTU_V6 = 1280;
 
-    private static final Set<Integer> ALLOWED_CAPABILITIES;
+    /**
+     * The set of allowed capabilities for exposed capabilities.
+     *
+     * @hide
+     */
+    public static final Set<Integer> ALLOWED_CAPABILITIES;
 
     static {
         Set<Integer> allowedCaps = new ArraySet<>();
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 1718052..70b0fc1 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -372,8 +372,7 @@
 
     /** Notifies the VcnManagementService that external dependencies can be set up. */
     public void systemReady() {
-        mContext.getSystemService(ConnectivityManager.class)
-                .registerNetworkProvider(mNetworkProvider);
+        mNetworkProvider.register();
         mContext.getSystemService(ConnectivityManager.class)
                 .registerNetworkCallback(
                         new NetworkRequest.Builder().clearCapabilities().build(),
diff --git a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
index ebabe2a..31ee247 100644
--- a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
+++ b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
@@ -16,12 +16,24 @@
 
 package com.android.server.vcn;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+
 import static com.android.server.VcnManagementService.VDBG;
 
 import android.annotation.NonNull;
 import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkCapabilities;
 import android.net.NetworkProvider;
 import android.net.NetworkRequest;
+import android.net.NetworkScore;
+import android.net.vcn.VcnGatewayConnectionConfig;
+import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.Looper;
 import android.util.ArraySet;
 import android.util.Slog;
@@ -30,7 +42,9 @@
 import com.android.internal.annotations.VisibleForTesting.Visibility;
 import com.android.internal.util.IndentingPrintWriter;
 
+import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.Executor;
 
 /**
  * VCN Network Provider routes NetworkRequests to listeners to bring up tunnels as needed.
@@ -45,6 +59,10 @@
 
     private final Set<NetworkRequestListener> mListeners = new ArraySet<>();
 
+    private final Context mContext;
+    private final Handler mHandler;
+    private final Dependencies mDeps;
+
     /**
      * Cache of NetworkRequest(s).
      *
@@ -52,8 +70,59 @@
      */
     private final Set<NetworkRequest> mRequests = new ArraySet<>();
 
-    public VcnNetworkProvider(Context context, Looper looper) {
-        super(context, looper, VcnNetworkProvider.class.getSimpleName());
+    public VcnNetworkProvider(@NonNull Context context, @NonNull Looper looper) {
+        this(context, looper, new Dependencies());
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public VcnNetworkProvider(
+            @NonNull Context context, @NonNull Looper looper, @NonNull Dependencies dependencies) {
+        super(
+                Objects.requireNonNull(context, "Missing context"),
+                Objects.requireNonNull(looper, "Missing looper"),
+                TAG);
+
+        mContext = context;
+        mHandler = new Handler(looper);
+        mDeps = Objects.requireNonNull(dependencies, "Missing dependencies");
+    }
+
+    /** Registers this VcnNetworkProvider and a generic network offer with ConnectivityService. */
+    public void register() {
+        mContext.getSystemService(ConnectivityManager.class).registerNetworkProvider(this);
+        mDeps.registerNetworkOffer(
+                this,
+                Vcn.getNetworkScore(), // score filter
+                buildCapabilityFilter(),
+                new HandlerExecutor(mHandler),
+                new NetworkOfferCallback() {
+                    @Override
+                    public void onNetworkNeeded(@NonNull NetworkRequest request) {
+                        handleNetworkRequested(request);
+                    }
+
+                    @Override
+                    public void onNetworkUnneeded(@NonNull NetworkRequest request) {
+                        handleNetworkRequestWithdrawn(request);
+                    }
+                });
+    }
+
+    /** Builds the filter for NetworkRequests that can be served by the VcnNetworkProvider. */
+    private NetworkCapabilities buildCapabilityFilter() {
+        final NetworkCapabilities.Builder builder =
+                new NetworkCapabilities.Builder()
+                        .addTransportType(TRANSPORT_CELLULAR)
+                        .addCapability(NET_CAPABILITY_TRUSTED)
+                        .addCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                        .addCapability(NET_CAPABILITY_NOT_VPN)
+                        .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
+
+        for (int cap : VcnGatewayConnectionConfig.ALLOWED_CAPABILITIES) {
+            builder.addCapability(cap);
+        }
+
+        return builder.build();
     }
 
     /**
@@ -88,8 +157,7 @@
         listener.onNetworkRequested(request);
     }
 
-    @Override
-    public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) {
+    private void handleNetworkRequested(@NonNull NetworkRequest request) {
         if (VDBG) {
             Slog.v(TAG, "Network requested: Request = " + request);
         }
@@ -103,8 +171,7 @@
         }
     }
 
-    @Override
-    public void onNetworkRequestWithdrawn(@NonNull NetworkRequest request) {
+    private void handleNetworkRequestWithdrawn(@NonNull NetworkRequest request) {
         if (VDBG) {
             Slog.v(TAG, "Network request withdrawn: Request = " + request);
         }
@@ -144,4 +211,18 @@
 
         pw.decreaseIndent();
     }
+
+    /** Proxy class for dependencies used for testing. */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class Dependencies {
+        /** Registers a given network offer for the given provider. */
+        public void registerNetworkOffer(
+                @NonNull VcnNetworkProvider provider,
+                @NonNull NetworkScore score,
+                @NonNull NetworkCapabilities capabilitiesFilter,
+                @NonNull Executor executor,
+                @NonNull NetworkOfferCallback callback) {
+            provider.registerNetworkOffer(score, capabilitiesFilter, executor, callback);
+        }
+    }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java b/tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java
index 79cf746..72db55b 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java
@@ -16,12 +16,18 @@
 
 package com.android.server.vcn;
 
+import static android.net.NetworkProvider.NetworkOfferCallback;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 
 import android.annotation.NonNull;
 import android.content.Context;
+import android.net.ConnectivityManager;
 import android.net.NetworkRequest;
 import android.os.test.TestLooper;
 
@@ -33,6 +39,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -47,6 +54,8 @@
     @NonNull private final Context mContext;
     @NonNull private final TestLooper mTestLooper;
 
+    @NonNull private VcnNetworkProvider.Dependencies mDeps;
+    @NonNull private ConnectivityManager mConnMgr;
     @NonNull private VcnNetworkProvider mVcnNetworkProvider;
     @NonNull private NetworkRequestListener mListener;
 
@@ -57,16 +66,47 @@
 
     @Before
     public void setUp() throws Exception {
-        mVcnNetworkProvider = new VcnNetworkProvider(mContext, mTestLooper.getLooper());
+        mDeps = mock(VcnNetworkProvider.Dependencies.class);
+        mConnMgr = mock(ConnectivityManager.class);
+        VcnTestUtils.setupSystemService(
+                mContext, mConnMgr, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class);
+
+        mVcnNetworkProvider = new VcnNetworkProvider(mContext, mTestLooper.getLooper(), mDeps);
         mListener = mock(NetworkRequestListener.class);
     }
 
+    private NetworkOfferCallback verifyRegisterAndGetOfferCallback() throws Exception {
+        mVcnNetworkProvider.register();
+
+        final ArgumentCaptor<NetworkOfferCallback> cbCaptor =
+                ArgumentCaptor.forClass(NetworkOfferCallback.class);
+
+        verify(mConnMgr).registerNetworkProvider(eq(mVcnNetworkProvider));
+        verify(mDeps)
+                .registerNetworkOffer(
+                        eq(mVcnNetworkProvider),
+                        argThat(
+                                score ->
+                                        score.getLegacyInt()
+                                                == Vcn.getNetworkScore().getLegacyInt()),
+                        any(),
+                        any(),
+                        cbCaptor.capture());
+
+        return cbCaptor.getValue();
+    }
+
+    @Test
+    public void testRegister() throws Exception {
+        verifyRegisterAndGetOfferCallback();
+    }
+
     @Test
     public void testRequestsPassedToRegisteredListeners() throws Exception {
         mVcnNetworkProvider.registerListener(mListener);
 
         final NetworkRequest request = mock(NetworkRequest.class);
-        mVcnNetworkProvider.onNetworkRequested(request, TEST_SCORE_UNSATISFIED, TEST_PROVIDER_ID);
+        verifyRegisterAndGetOfferCallback().onNetworkNeeded(request);
         verify(mListener).onNetworkRequested(request);
     }
 
@@ -76,13 +116,14 @@
         mVcnNetworkProvider.unregisterListener(mListener);
 
         final NetworkRequest request = mock(NetworkRequest.class);
-        mVcnNetworkProvider.onNetworkRequested(request, TEST_SCORE_UNSATISFIED, TEST_PROVIDER_ID);
+        verifyRegisterAndGetOfferCallback().onNetworkNeeded(request);
         verifyNoMoreInteractions(mListener);
     }
 
     @Test
     public void testCachedRequestsPassedOnRegister() throws Exception {
         final List<NetworkRequest> requests = new ArrayList<>();
+        final NetworkOfferCallback offerCb = verifyRegisterAndGetOfferCallback();
 
         for (int i = 0; i < 10; i++) {
             // Build unique network requests; in this case, iterate down the capabilities as a way
@@ -91,12 +132,12 @@
                     new NetworkRequest.Builder().clearCapabilities().addCapability(i).build();
 
             requests.add(request);
-            mVcnNetworkProvider.onNetworkRequested(request, i, i + 1);
+            offerCb.onNetworkNeeded(request);
         }
 
         // Remove one, and verify that it is never sent to the listeners.
         final NetworkRequest removed = requests.remove(0);
-        mVcnNetworkProvider.onNetworkRequestWithdrawn(removed);
+        offerCb.onNetworkUnneeded(removed);
 
         mVcnNetworkProvider.registerListener(mListener);
         for (NetworkRequest request : requests) {