Merge "Always-on app VPNs"
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -117,6 +117,8 @@ interface IConnectivityManager
|
||||
VpnInfo[] getAllVpnInfo();
|
||||
|
||||
boolean updateLockdownVpn();
|
||||
boolean setAlwaysOnVpnPackage(int userId, String packageName);
|
||||
String getAlwaysOnVpnPackage(int userId);
|
||||
|
||||
int checkMobileProvisioning(int suggestedTimeOutMs);
|
||||
|
||||
|
||||
@@ -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?
|
||||
|
||||
Reference in New Issue
Block a user