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
Merged-In: I477897a29175e3994d4ecf8ec546e26043c90f13
(cherry picked from commit 9369e61e2d)
This commit is contained in:
@@ -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
|
||||||
|
* <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.
|
* Configures an always-on VPN connection through a specific application.
|
||||||
* This connection is automatically granted and persisted after a reboot.
|
* This connection is automatically granted and persisted after a reboot.
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ interface IConnectivityManager
|
|||||||
VpnInfo[] getAllVpnInfo();
|
VpnInfo[] getAllVpnInfo();
|
||||||
|
|
||||||
boolean updateLockdownVpn();
|
boolean updateLockdownVpn();
|
||||||
|
boolean isAlwaysOnVpnPackageSupported(int userId, String packageName);
|
||||||
boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown);
|
boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown);
|
||||||
String getAlwaysOnVpnPackage(int userId);
|
String getAlwaysOnVpnPackage(int userId);
|
||||||
|
|
||||||
|
|||||||
@@ -128,9 +128,9 @@ import com.android.server.LocalServices;
|
|||||||
import com.android.server.am.BatteryStatsService;
|
import com.android.server.am.BatteryStatsService;
|
||||||
import com.android.server.connectivity.DataConnectionStats;
|
import com.android.server.connectivity.DataConnectionStats;
|
||||||
import com.android.server.connectivity.KeepaliveTracker;
|
import com.android.server.connectivity.KeepaliveTracker;
|
||||||
|
import com.android.server.connectivity.LingerMonitor;
|
||||||
import com.android.server.connectivity.MockableSystemProperties;
|
import com.android.server.connectivity.MockableSystemProperties;
|
||||||
import com.android.server.connectivity.Nat464Xlat;
|
import com.android.server.connectivity.Nat464Xlat;
|
||||||
import com.android.server.connectivity.LingerMonitor;
|
|
||||||
import com.android.server.connectivity.NetworkAgentInfo;
|
import com.android.server.connectivity.NetworkAgentInfo;
|
||||||
import com.android.server.connectivity.NetworkDiagnostics;
|
import com.android.server.connectivity.NetworkDiagnostics;
|
||||||
import com.android.server.connectivity.NetworkMonitor;
|
import com.android.server.connectivity.NetworkMonitor;
|
||||||
@@ -1515,6 +1515,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||||||
ConnectivityManager.enforceChangePermission(mContext);
|
ConnectivityManager.enforceChangePermission(mContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void enforceSettingsPermission() {
|
||||||
|
mContext.enforceCallingOrSelfPermission(
|
||||||
|
android.Manifest.permission.NETWORK_SETTINGS,
|
||||||
|
"ConnectivityService");
|
||||||
|
}
|
||||||
|
|
||||||
private void enforceTetherAccessPermission() {
|
private void enforceTetherAccessPermission() {
|
||||||
mContext.enforceCallingOrSelfPermission(
|
mContext.enforceCallingOrSelfPermission(
|
||||||
android.Manifest.permission.ACCESS_NETWORK_STATE,
|
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
|
@Override
|
||||||
public boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown) {
|
public boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown) {
|
||||||
enforceConnectivityInternalPermission();
|
enforceConnectivityInternalPermission();
|
||||||
|
|||||||
@@ -27,13 +27,16 @@ import android.annotation.UserIdInt;
|
|||||||
import android.app.AppOpsManager;
|
import android.app.AppOpsManager;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
|
import android.content.pm.ServiceInfo;
|
||||||
import android.content.pm.UserInfo;
|
import android.content.pm.UserInfo;
|
||||||
import android.net.NetworkInfo.DetailedState;
|
import android.net.NetworkInfo.DetailedState;
|
||||||
import android.net.UidRange;
|
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.INetworkManagementService;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
@@ -45,22 +48,22 @@ import android.util.ArraySet;
|
|||||||
|
|
||||||
import com.android.internal.net.VpnConfig;
|
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.Answers;
|
||||||
import org.mockito.ArgumentCaptor;
|
|
||||||
import org.mockito.InOrder;
|
import org.mockito.InOrder;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.MockitoAnnotations;
|
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}.
|
* Tests for {@link Vpn}.
|
||||||
*
|
*
|
||||||
* Build, install and run with:
|
* 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 {
|
public class VpnTest extends AndroidTestCase {
|
||||||
private static final String TAG = "VpnTest";
|
private static final String TAG = "VpnTest";
|
||||||
@@ -116,7 +119,7 @@ public class VpnTest extends AndroidTestCase {
|
|||||||
|
|
||||||
// Used by {@link Notification.Builder}
|
// Used by {@link Notification.Builder}
|
||||||
ApplicationInfo applicationInfo = new ApplicationInfo();
|
ApplicationInfo applicationInfo = new ApplicationInfo();
|
||||||
applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
|
applicationInfo.targetSdkVersion = VERSION_CODES.CUR_DEVELOPMENT;
|
||||||
when(mContext.getApplicationInfo()).thenReturn(applicationInfo);
|
when(mContext.getApplicationInfo()).thenReturn(applicationInfo);
|
||||||
|
|
||||||
doNothing().when(mNetService).registerObserver(any());
|
doNothing().when(mNetService).registerObserver(any());
|
||||||
@@ -314,6 +317,40 @@ public class VpnTest extends AndroidTestCase {
|
|||||||
order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(entireUser));
|
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.METADATA_SUPPORTS_ALWAYS_ON, false);
|
||||||
|
svcInfo.metaData = metaData;
|
||||||
|
assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0]));
|
||||||
|
}
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
public void testNotificationShownForAlwaysOnApp() {
|
public void testNotificationShownForAlwaysOnApp() {
|
||||||
final UserHandle userHandle = UserHandle.of(primaryUser.id);
|
final UserHandle userHandle = UserHandle.of(primaryUser.id);
|
||||||
|
|||||||
Reference in New Issue
Block a user