Merge "Always-on app VPNs"

This commit is contained in:
Robin Lee
2016-01-14 11:37:18 +00:00
committed by Android (Google) Code Review
3 changed files with 127 additions and 8 deletions

View File

@@ -17,6 +17,7 @@ package android.net;
import static com.android.internal.util.Preconditions.checkNotNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
@@ -685,6 +686,47 @@ public class ConnectivityManager {
}
}
/**
* Configures an always-on VPN connection through a specific application.
* This connection is automatically granted and persisted after a reboot.
*
* <p>The designated package should declare a {@link VpnService} in its
* manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE},
* otherwise the call will fail.
*
* @param userId The identifier of the user to set an always-on VPN for.
* @param vpnPackage The package name for an installed VPN app on the device, or {@code null}
* to remove an existing always-on VPN configuration.
* @return {@code true} if the package is set as always-on VPN controller;
* {@code false} otherwise.
* @hide
*/
public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage) {
try {
return mService.setAlwaysOnVpnPackage(userId, vpnPackage);
} catch (RemoteException e) {
return false;
}
}
/**
* Returns the package name of the currently set always-on VPN application.
* If there is no always-on VPN set, or the VPN is provided by the system instead
* of by an app, {@code null} will be returned.
*
* @return Package name of VPN controller responsible for always-on VPN,
* or {@code null} if none is set.
* @hide
*/
public String getAlwaysOnVpnPackageForUser(int userId) {
try {
return mService.getAlwaysOnVpnPackage(userId);
} catch (RemoteException e) {
return null;
}
}
/**
* Returns details about the currently active default data network
* for a given uid. This is for internal use only to avoid spying

View File

@@ -117,6 +117,8 @@ interface IConnectivityManager
VpnInfo[] getAllVpnInfo();
boolean updateLockdownVpn();
boolean setAlwaysOnVpnPackage(int userId, String packageName);
String getAlwaysOnVpnPackage(int userId);
int checkMobileProvisioning(int suggestedTimeOutMs);

View File

@@ -1568,12 +1568,11 @@ public class ConnectivityService extends IConnectivityManager.Stub
// load the global proxy at startup
mHandler.sendMessage(mHandler.obtainMessage(EVENT_APPLY_GLOBAL_HTTP_PROXY));
// Try bringing up tracker, but if KeyStore isn't ready yet, wait
// for user to unlock device.
if (!updateLockdownVpn()) {
// Try bringing up tracker, but KeyStore won't be ready yet for secondary users so wait
// for user to unlock device too.
updateLockdownVpn();
final IntentFilter filter = new IntentFilter(Intent.ACTION_USER_PRESENT);
mContext.registerReceiver(mUserPresentReceiver, filter);
}
mContext.registerReceiverAsUser(mUserPresentReceiver, UserHandle.ALL, filter, null, null);
// Configure whether mobile data is always on.
mHandler.sendMessage(mHandler.obtainMessage(EVENT_CONFIGURE_MOBILE_DATA_ALWAYS_ON));
@@ -1586,10 +1585,16 @@ public class ConnectivityService extends IConnectivityManager.Stub
private BroadcastReceiver mUserPresentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// User that sent this intent = user that was just unlocked
final int unlockedUser = getSendingUserId();
// Try creating lockdown tracker, since user present usually means
// unlocked keystore.
if (updateLockdownVpn()) {
mContext.unregisterReceiver(this);
if (mUserManager.getUserInfo(unlockedUser).isPrimary() &&
LockdownVpnTracker.isEnabled()) {
updateLockdownVpn();
} else {
updateAlwaysOnVpn(unlockedUser);
}
}
};
@@ -3258,6 +3263,76 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
}
/**
* Sets up or tears down the always-on VPN for user {@param user} as appropriate.
*
* @return {@code false} in case of errors; {@code true} otherwise.
*/
private boolean updateAlwaysOnVpn(int user) {
final String lockdownPackage = getAlwaysOnVpnPackage(user);
if (lockdownPackage == null) {
return true;
}
// Create an intent to start the VPN service declared in the app's manifest.
Intent serviceIntent = new Intent(VpnConfig.SERVICE_INTERFACE);
serviceIntent.setPackage(lockdownPackage);
try {
return mContext.startServiceAsUser(serviceIntent, UserHandle.of(user)) != null;
} catch (RuntimeException e) {
return false;
}
}
@Override
public boolean setAlwaysOnVpnPackage(int userId, String packageName) {
enforceConnectivityInternalPermission();
enforceCrossUserPermission(userId);
// Can't set always-on VPN if legacy VPN is already in lockdown mode.
if (LockdownVpnTracker.isEnabled()) {
return false;
}
// If the current VPN package is the same as the new one, this is a no-op
final String oldPackage = getAlwaysOnVpnPackage(userId);
if (TextUtils.equals(oldPackage, packageName)) {
return true;
}
synchronized (mVpns) {
Vpn vpn = mVpns.get(userId);
if (vpn == null) {
Slog.w(TAG, "User " + userId + " has no Vpn configuration");
return false;
}
if (!vpn.setAlwaysOnPackage(packageName)) {
return false;
}
if (!updateAlwaysOnVpn(userId)) {
vpn.setAlwaysOnPackage(null);
return false;
}
}
return true;
}
@Override
public String getAlwaysOnVpnPackage(int userId) {
enforceConnectivityInternalPermission();
enforceCrossUserPermission(userId);
synchronized (mVpns) {
Vpn vpn = mVpns.get(userId);
if (vpn == null) {
Slog.w(TAG, "User " + userId + " has no Vpn configuration");
return null;
}
return vpn.getAlwaysOnPackage();
}
}
@Override
public int checkMobileProvisioning(int suggestedTimeOutMs) {
// TODO: Remove? Any reason to trigger a provisioning check?