Opt-out for always-on VPN

Always-on VPN is a feature introduced in N. Since then, all VPN apps
targeting N+ are assumed to support the feature, and the user or the DPC
can turn on / off always-on for any such VPN app. However, a few VPN
apps are not designed to support the always-on feature. Enabling
always-on for these apps will result in undefined behavior and confusing
"Always-on VPN disconnected" notification.

This feature provides a new manifest meta-data field through which a VPN
app can opt out of the always-on feature explicitly. This will stop the
always-on feature from being enabled for the app, both by the user and
by the DPC, and will clear its existing always-on state.

A @hide API is provided to check whether an app supports always-on VPN.
Documentation is updated to reflect the behavior change.

Bug: 36650087
Test: runtest --path java/com/android/server/connectivity/VpnTest.java
Test: cts-tradefed run cts --module CtsDevicePolicyManagerTestCases --test 'com.android.cts.devicepolicy.MixedDeviceOwnerTest#testAlwaysOnVpnUnsupportedPackage'
Test: cts-tradefed run cts --module CtsDevicePolicyManagerTestCases --test 'com.android.cts.devicepolicy.MixedDeviceOwnerTest#testAlwaysOnVpnUnsupportedPackageReplaced'
Test: cts-tradefed run cts --module CtsDevicePolicyManagerTestCases --test 'com.android.cts.devicepolicy.MixedProfileOwnerTest#testAlwaysOnVpnUnsupportedPackage'
Test: cts-tradefed run cts --module CtsDevicePolicyManagerTestCases --test 'com.android.cts.devicepolicy.MixedProfileOwnerTest#testAlwaysOnVpnUnsupportedPackageReplaced'
Test: cts-tradefed run cts --module CtsDevicePolicyManagerTestCases --test 'com.android.cts.devicepolicy.MixedManagedProfileOwnerTest#testAlwaysOnVpnUnsupportedPackage'
Test: cts-tradefed run cts --module CtsDevicePolicyManagerTestCases --test 'com.android.cts.devicepolicy.MixedManagedProfileOwnerTest#testAlwaysOnVpnUnsupportedPackageReplaced'

Change-Id: I477897a29175e3994d4ecf8ec546e26043c90f13
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index c4d22a3..d8da8c5 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3929,26 +3929,18 @@
 
     /**
      * Called by a device or profile owner to configure an always-on VPN connection through a
-     * specific application for the current user.
-     *
-     * @deprecated this version only exists for compability with previous developer preview builds.
-     *             TODO: delete once there are no longer any live references.
-     * @hide
-     */
-    @Deprecated
-    public void setAlwaysOnVpnPackage(@NonNull ComponentName admin, @Nullable String vpnPackage)
-            throws NameNotFoundException, UnsupportedOperationException {
-        setAlwaysOnVpnPackage(admin, vpnPackage, /* lockdownEnabled */ true);
-    }
-
-    /**
-     * Called by a device or profile owner to configure an always-on VPN connection through a
      * specific application for the current user. This connection is automatically granted and
      * persisted after a reboot.
      * <p>
-     * The designated package should declare a {@link android.net.VpnService} in its manifest
-     * guarded by {@link android.Manifest.permission#BIND_VPN_SERVICE}, otherwise the call will
-     * fail.
+     * To support the always-on feature, an app must
+     * <ul>
+     *     <li>declare a {@link android.net.VpnService} in its manifest, guarded by
+     *         {@link android.Manifest.permission#BIND_VPN_SERVICE};</li>
+     *     <li>target {@link android.os.Build.VERSION_CODES#N API 24} or above; and</li>
+     *     <li><i>not</i> explicitly opt out of the feature through
+     *         {@link android.net.VpnService#METADATA_SUPPORTS_ALWAYS_ON}.</li>
+     * </ul>
+     * The call will fail if called with the package name of an unsupported VPN app.
      *
      * @param vpnPackage The package name for an installed VPN app on the device, or {@code null} to
      *        remove an existing always-on VPN configuration.
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 7a1d85c..48123fe 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -835,6 +835,29 @@
     }
 
     /**
+     * Checks if a VPN app supports always-on mode.
+     *
+     * In order to support the always-on feature, an app has to
+     * <ul>
+     *     <li>target {@link VERSION_CODES#N API 24} or above, and
+     *     <li>not opt out through the {@link VpnService#METADATA_SUPPORTS_ALWAYS_ON} meta-data
+     *         field.
+     * </ul>
+     *
+     * @param userId The identifier of the user for whom the VPN app is installed.
+     * @param vpnPackage The canonical package name of the VPN app.
+     * @return {@code true} if and only if the VPN app exists and supports always-on mode.
+     * @hide
+     */
+    public boolean isAlwaysOnVpnPackageSupportedForUser(int userId, @Nullable String vpnPackage) {
+        try {
+            return mService.isAlwaysOnVpnPackageSupported(userId, vpnPackage);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Configures an always-on VPN connection through a specific application.
      * This connection is automatically granted and persisted after a reboot.
      *
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 14cee36..a6fe738 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -123,6 +123,7 @@
     VpnInfo[] getAllVpnInfo();
 
     boolean updateLockdownVpn();
+    boolean isAlwaysOnVpnPackageSupported(int userId, String packageName);
     boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown);
     String getAlwaysOnVpnPackage(int userId);
 
diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java
index 4b79cbb..7fb0c47 100644
--- a/core/java/android/net/VpnService.java
+++ b/core/java/android/net/VpnService.java
@@ -28,8 +28,6 @@
 import android.content.Intent;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
-import android.net.Network;
-import android.net.NetworkUtils;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.Parcel;
@@ -131,6 +129,35 @@
     public static final String SERVICE_INTERFACE = VpnConfig.SERVICE_INTERFACE;
 
     /**
+     * Key for boolean meta-data field indicating whether this VpnService supports always-on mode.
+     *
+     * <p>For a VPN app targeting {@link android.os.Build.VERSION_CODES#N API 24} or above, Android
+     * provides users with the ability to set it as always-on, so that VPN connection is
+     * persisted after device reboot and app upgrade. Always-on VPN can also be enabled by device
+     * owner and profile owner apps through
+     * {@link android.app.admin.DevicePolicyManager#setAlwaysOnVpnPackage}.
+     *
+     * <p>VPN apps not supporting this feature should opt out by adding this meta-data field to the
+     * {@code VpnService} component of {@code AndroidManifest.xml}. In case there is more than one
+     * {@code VpnService} component defined in {@code AndroidManifest.xml}, opting out any one of
+     * them will opt out the entire app. For example,
+     * <pre> {@code
+     * <service android:name=".ExampleVpnService"
+     *         android:permission="android.permission.BIND_VPN_SERVICE">
+     *     <intent-filter>
+     *         <action android:name="android.net.VpnService"/>
+     *     </intent-filter>
+     *     <meta-data android:name="android.net.VpnService.SUPPORTS_ALWAYS_ON"
+     *             android:value=false/>
+     * </service>
+     * } </pre>
+     *
+     * <p>This meta-data field defaults to {@code true} if absent.
+     */
+    public static final String METADATA_SUPPORTS_ALWAYS_ON =
+            "android.net.VpnService.SUPPORTS_ALWAYS_ON";
+
+    /**
      * Use IConnectivityManager since those methods are hidden and not
      * available in ConnectivityManager.
      */