diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index d91482331f..3460f564fc 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -834,6 +834,29 @@ public class ConnectivityManager {
}
}
+ /**
+ * Checks if a VPN app supports always-on mode.
+ *
+ * In order to support the always-on feature, an app has to
+ *
+ * - target {@link VERSION_CODES#N API 24} or above, and
+ *
- not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON}
+ * meta-data field.
+ *
+ *
+ * @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 27729dcce7..b9dd207aaa 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -123,6 +123,7 @@ interface IConnectivityManager
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/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 31fc874db2..f39bd7f71e 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -128,9 +128,9 @@ import com.android.server.LocalServices;
import com.android.server.am.BatteryStatsService;
import com.android.server.connectivity.DataConnectionStats;
import com.android.server.connectivity.KeepaliveTracker;
+import com.android.server.connectivity.LingerMonitor;
import com.android.server.connectivity.MockableSystemProperties;
import com.android.server.connectivity.Nat464Xlat;
-import com.android.server.connectivity.LingerMonitor;
import com.android.server.connectivity.NetworkAgentInfo;
import com.android.server.connectivity.NetworkDiagnostics;
import com.android.server.connectivity.NetworkMonitor;
@@ -1515,6 +1515,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
ConnectivityManager.enforceChangePermission(mContext);
}
+ private void enforceSettingsPermission() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.NETWORK_SETTINGS,
+ "ConnectivityService");
+ }
+
private void enforceTetherAccessPermission() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.ACCESS_NETWORK_STATE,
@@ -3645,6 +3651,21 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
}
+ @Override
+ public boolean isAlwaysOnVpnPackageSupported(int userId, String packageName) {
+ enforceSettingsPermission();
+ enforceCrossUserPermission(userId);
+
+ synchronized (mVpns) {
+ Vpn vpn = mVpns.get(userId);
+ if (vpn == null) {
+ Slog.w(TAG, "User " + userId + " has no Vpn configuration");
+ return false;
+ }
+ return vpn.isAlwaysOnPackageSupported(packageName);
+ }
+ }
+
@Override
public boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown) {
enforceConnectivityInternalPermission();
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index 506d9e5043..296cb76560 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -27,13 +27,16 @@ import android.annotation.UserIdInt;
import android.app.AppOpsManager;
import android.app.NotificationManager;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.UidRange;
-import android.os.Build;
+import android.net.VpnService;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
import android.os.INetworkManagementService;
import android.os.Looper;
import android.os.UserHandle;
@@ -45,22 +48,22 @@ import android.util.ArraySet;
import com.android.internal.net.VpnConfig;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Map;
-import java.util.Set;
-
import org.mockito.Answers;
-import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
/**
* Tests for {@link Vpn}.
*
* Build, install and run with:
- * runtest --path src/com/android/server/connectivity/VpnTest.java
+ * runtest --path java/com/android/server/connectivity/VpnTest.java
*/
public class VpnTest extends AndroidTestCase {
private static final String TAG = "VpnTest";
@@ -116,7 +119,7 @@ public class VpnTest extends AndroidTestCase {
// Used by {@link Notification.Builder}
ApplicationInfo applicationInfo = new ApplicationInfo();
- applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+ applicationInfo.targetSdkVersion = VERSION_CODES.CUR_DEVELOPMENT;
when(mContext.getApplicationInfo()).thenReturn(applicationInfo);
doNothing().when(mNetService).registerObserver(any());
@@ -314,6 +317,40 @@ public class VpnTest extends AndroidTestCase {
order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(entireUser));
}
+ @SmallTest
+ public void testIsAlwaysOnPackageSupported() throws Exception {
+ final Vpn vpn = createVpn(primaryUser.id);
+
+ ApplicationInfo appInfo = new ApplicationInfo();
+ when(mPackageManager.getApplicationInfoAsUser(eq(PKGS[0]), anyInt(), eq(primaryUser.id)))
+ .thenReturn(appInfo);
+
+ ServiceInfo svcInfo = new ServiceInfo();
+ ResolveInfo resInfo = new ResolveInfo();
+ resInfo.serviceInfo = svcInfo;
+ when(mPackageManager.queryIntentServicesAsUser(any(), eq(PackageManager.GET_META_DATA),
+ eq(primaryUser.id)))
+ .thenReturn(Collections.singletonList(resInfo));
+
+ // null package name should return false
+ assertFalse(vpn.isAlwaysOnPackageSupported(null));
+
+ // Pre-N apps are not supported
+ appInfo.targetSdkVersion = VERSION_CODES.M;
+ assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0]));
+
+ // N+ apps are supported by default
+ appInfo.targetSdkVersion = VERSION_CODES.N;
+ assertTrue(vpn.isAlwaysOnPackageSupported(PKGS[0]));
+
+ // Apps that opt out explicitly are not supported
+ appInfo.targetSdkVersion = VERSION_CODES.CUR_DEVELOPMENT;
+ Bundle metaData = new Bundle();
+ metaData.putBoolean(VpnService.SERVICE_META_DATA_SUPPORTS_ALWAYS_ON, false);
+ svcInfo.metaData = metaData;
+ assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0]));
+ }
+
@SmallTest
public void testNotificationShownForAlwaysOnApp() {
final UserHandle userHandle = UserHandle.of(primaryUser.id);