Merge changes from topic "always-on-vpn"
* changes: Opt-out for always-on VPN: rename API. Opt-out for always-on VPN
This commit is contained in:
@@ -26002,6 +26002,7 @@ package android.net {
|
||||
method public boolean protect(java.net.DatagramSocket);
|
||||
method public boolean setUnderlyingNetworks(android.net.Network[]);
|
||||
field public static final java.lang.String SERVICE_INTERFACE = "android.net.VpnService";
|
||||
field public static final java.lang.String SERVICE_META_DATA_SUPPORTS_ALWAYS_ON = "android.net.VpnService.SUPPORTS_ALWAYS_ON";
|
||||
}
|
||||
|
||||
public class VpnService.Builder {
|
||||
|
||||
@@ -28266,6 +28266,7 @@ package android.net {
|
||||
method public boolean protect(java.net.DatagramSocket);
|
||||
method public boolean setUnderlyingNetworks(android.net.Network[]);
|
||||
field public static final java.lang.String SERVICE_INTERFACE = "android.net.VpnService";
|
||||
field public static final java.lang.String SERVICE_META_DATA_SUPPORTS_ALWAYS_ON = "android.net.VpnService.SUPPORTS_ALWAYS_ON";
|
||||
}
|
||||
|
||||
public class VpnService.Builder {
|
||||
|
||||
@@ -26111,6 +26111,7 @@ package android.net {
|
||||
method public boolean protect(java.net.DatagramSocket);
|
||||
method public boolean setUnderlyingNetworks(android.net.Network[]);
|
||||
field public static final java.lang.String SERVICE_INTERFACE = "android.net.VpnService";
|
||||
field public static final java.lang.String SERVICE_META_DATA_SUPPORTS_ALWAYS_ON = "android.net.VpnService.SUPPORTS_ALWAYS_ON";
|
||||
}
|
||||
|
||||
public class VpnService.Builder {
|
||||
|
||||
@@ -55,7 +55,6 @@ import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.provider.ContactsContract.Directory;
|
||||
import android.provider.Settings;
|
||||
import android.security.Credentials;
|
||||
import android.service.restrictions.RestrictionsReceiver;
|
||||
import android.telephony.TelephonyManager;
|
||||
@@ -3902,28 +3901,20 @@ public class DevicePolicyManager {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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#SERVICE_META_DATA_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.
|
||||
|
||||
@@ -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#SERVICE_META_DATA_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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -28,8 +28,6 @@ import android.content.Context;
|
||||
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;
|
||||
@@ -123,6 +121,36 @@ public class VpnService extends Service {
|
||||
*/
|
||||
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. It will only have effect on
|
||||
* {@link android.os.Build.VERSION_CODES#O_MR1} or higher.
|
||||
*/
|
||||
public static final String SERVICE_META_DATA_SUPPORTS_ALWAYS_ON =
|
||||
"android.net.VpnService.SUPPORTS_ALWAYS_ON";
|
||||
|
||||
/**
|
||||
* Use IConnectivityManager since those methods are hidden and not
|
||||
* available in ConnectivityManager.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -36,6 +36,7 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.pm.ResolveInfo;
|
||||
@@ -56,7 +57,10 @@ import android.net.NetworkMisc;
|
||||
import android.net.RouteInfo;
|
||||
import android.net.UidRange;
|
||||
import android.net.Uri;
|
||||
import android.net.VpnService;
|
||||
import android.os.Binder;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.os.Bundle;
|
||||
import android.os.FileUtils;
|
||||
import android.os.IBinder;
|
||||
import android.os.INetworkManagementService;
|
||||
@@ -295,6 +299,56 @@ public class Vpn {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON}
|
||||
* meta-data field.
|
||||
* </ul>
|
||||
*
|
||||
* @param packageName the canonical package name of the VPN app
|
||||
* @return {@code true} if and only if the VPN app exists and supports always-on mode
|
||||
*/
|
||||
public boolean isAlwaysOnPackageSupported(String packageName) {
|
||||
enforceSettingsPermission();
|
||||
|
||||
if (packageName == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PackageManager pm = mContext.getPackageManager();
|
||||
ApplicationInfo appInfo = null;
|
||||
try {
|
||||
appInfo = pm.getApplicationInfoAsUser(packageName, 0 /*flags*/, mUserHandle);
|
||||
} catch (NameNotFoundException unused) {
|
||||
Log.w(TAG, "Can't find \"" + packageName + "\" when checking always-on support");
|
||||
}
|
||||
if (appInfo == null || appInfo.targetSdkVersion < VERSION_CODES.N) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Intent intent = new Intent(VpnConfig.SERVICE_INTERFACE);
|
||||
intent.setPackage(packageName);
|
||||
List<ResolveInfo> services =
|
||||
pm.queryIntentServicesAsUser(intent, PackageManager.GET_META_DATA, mUserHandle);
|
||||
if (services == null || services.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (ResolveInfo rInfo : services) {
|
||||
final Bundle metaData = rInfo.serviceInfo.metaData;
|
||||
if (metaData != null &&
|
||||
!metaData.getBoolean(VpnService.SERVICE_META_DATA_SUPPORTS_ALWAYS_ON, true)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures an always-on VPN connection through a specific application.
|
||||
* This connection is automatically granted and persisted after a reboot.
|
||||
@@ -303,6 +357,10 @@ public class Vpn {
|
||||
* manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE},
|
||||
* otherwise the call will fail.
|
||||
*
|
||||
* <p>Note that this method does not check if the VPN app supports always-on mode. The check is
|
||||
* delayed to {@link #startAlwaysOnVpn()}, which is always called immediately after this
|
||||
* method in {@link android.net.IConnectivityManager#setAlwaysOnVpnPackage}.
|
||||
*
|
||||
* @param packageName the package to designate as always-on VPN supplier.
|
||||
* @param lockdown whether to prevent traffic outside of a VPN, for example while connecting.
|
||||
* @return {@code true} if the package has been set as always-on, {@code false} otherwise.
|
||||
@@ -443,6 +501,11 @@ public class Vpn {
|
||||
if (alwaysOnPackage == null) {
|
||||
return true;
|
||||
}
|
||||
// Remove always-on VPN if it's not supported.
|
||||
if (!isAlwaysOnPackageSupported(alwaysOnPackage)) {
|
||||
setAlwaysOnPackage(null, false);
|
||||
return false;
|
||||
}
|
||||
// Skip if the service is already established. This isn't bulletproof: it's not bound
|
||||
// until after establish(), so if it's mid-setup onStartCommand will be sent twice,
|
||||
// which may restart the connection.
|
||||
@@ -1219,6 +1282,11 @@ public class Vpn {
|
||||
"Unauthorized Caller");
|
||||
}
|
||||
|
||||
private void enforceSettingsPermission() {
|
||||
mContext.enforceCallingOrSelfPermission(Manifest.permission.NETWORK_SETTINGS,
|
||||
"Unauthorized Caller");
|
||||
}
|
||||
|
||||
private class Connection implements ServiceConnection {
|
||||
private IBinder mService;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user