Merge "Package changed/removed listeners for always-on VPN" into nyc-dev

This commit is contained in:
TreeHugger Robot
2016-06-08 10:46:41 +00:00
committed by Android (Google) Code Review
2 changed files with 142 additions and 42 deletions

View File

@@ -3388,7 +3388,6 @@ public class ConnectivityService extends IConnectivityManager.Stub
* was no always-on VPN to start. {@code false} otherwise.
*/
private boolean startAlwaysOnVpn(int userId) {
final String alwaysOnPackage;
synchronized (mVpns) {
Vpn vpn = mVpns.get(userId);
if (vpn == null) {
@@ -3397,27 +3396,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
Slog.wtf(TAG, "User " + userId + " has no Vpn configuration");
return false;
}
alwaysOnPackage = vpn.getAlwaysOnPackage();
// Skip if there is no service to start.
if (alwaysOnPackage == null) {
return true;
}
// 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.
if (vpn.getNetworkInfo().isConnected()) {
return true;
}
}
// Start the VPN service declared in the app's manifest.
Intent serviceIntent = new Intent(VpnConfig.SERVICE_INTERFACE);
serviceIntent.setPackage(alwaysOnPackage);
try {
return mContext.startServiceAsUser(serviceIntent, UserHandle.of(userId)) != null;
} catch (RuntimeException e) {
Slog.w(TAG, "VpnService " + serviceIntent + " failed to start", e);
return false;
return vpn.startAlwaysOnVpn();
}
}
@@ -3449,17 +3429,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
return false;
}
// Save the configuration
final long token = Binder.clearCallingIdentity();
try {
final ContentResolver cr = mContext.getContentResolver();
Settings.Secure.putStringForUser(cr, Settings.Secure.ALWAYS_ON_VPN_APP,
packageName, userId);
Settings.Secure.putIntForUser(cr, Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN,
(lockdown ? 1 : 0), userId);
} finally {
Binder.restoreCallingIdentity(token);
}
vpn.saveAlwaysOnPackage();
}
return true;
}

View File

@@ -30,6 +30,7 @@ import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -53,6 +54,7 @@ import android.net.NetworkInfo.DetailedState;
import android.net.NetworkMisc;
import android.net.RouteInfo;
import android.net.UidRange;
import android.net.Uri;
import android.os.Binder;
import android.os.FileUtils;
import android.os.IBinder;
@@ -60,12 +62,14 @@ import android.os.INetworkManagementService;
import android.os.Looper;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.PatternMatcher;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemService;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.security.Credentials;
import android.security.KeyStore;
import android.text.TextUtils;
@@ -163,6 +167,45 @@ public class Vpn {
// Handle of user initiating VPN.
private final int mUserHandle;
// Listen to package remove and change event in this user
private final BroadcastReceiver mPackageIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final Uri data = intent.getData();
final String packageName = data == null ? null : data.getSchemeSpecificPart();
if (packageName == null) {
return;
}
synchronized (Vpn.this) {
// Avoid race that always-on package has been unset
if (!packageName.equals(getAlwaysOnPackage())) {
return;
}
final String action = intent.getAction();
Log.i(TAG, "Received broadcast " + action + " for always-on package " + packageName
+ " in user " + mUserHandle);
switch(action) {
case Intent.ACTION_PACKAGE_REPLACED:
// Start vpn after app upgrade
startAlwaysOnVpn();
break;
case Intent.ACTION_PACKAGE_REMOVED:
final boolean isPackageRemoved = !intent.getBooleanExtra(
Intent.EXTRA_REPLACING, false);
if (isPackageRemoved) {
setAndSaveAlwaysOnPackage(null, false);
}
break;
}
}
}
};
private boolean mIsPackageIntentReceiverRegistered = false;
public Vpn(Looper looper, Context context, INetworkManagementService netService,
int userHandle) {
mContext = context;
@@ -233,10 +276,37 @@ public class Vpn {
mAlwaysOn = (packageName != null);
mLockdown = (mAlwaysOn && lockdown);
maybeRegisterPackageChangeReceiverLocked(packageName);
setVpnForcedLocked(mLockdown);
return true;
}
private void unregisterPackageChangeReceiverLocked() {
// register previous intent filter
if (mIsPackageIntentReceiverRegistered) {
mContext.unregisterReceiver(mPackageIntentReceiver);
mIsPackageIntentReceiverRegistered = false;
}
}
private void maybeRegisterPackageChangeReceiverLocked(String packageName) {
// Unregister IntentFilter listening for previous always-on package change
unregisterPackageChangeReceiverLocked();
if (packageName != null) {
mIsPackageIntentReceiverRegistered = true;
IntentFilter intentFilter = new IntentFilter();
// Protected intent can only be sent by system. No permission required in register.
intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
intentFilter.addDataScheme("package");
intentFilter.addDataSchemeSpecificPart(packageName, PatternMatcher.PATTERN_LITERAL);
mContext.registerReceiverAsUser(
mPackageIntentReceiver, UserHandle.of(mUserHandle), intentFilter, null, null);
}
}
/**
* @return the package name of the VPN controller responsible for always-on VPN,
* or {@code null} if none is set or always-on VPN is controlled through
@@ -248,6 +318,69 @@ public class Vpn {
return (mAlwaysOn ? mPackage : null);
}
/**
* Save the always-on package and lockdown config into Settings.Secure
*/
public synchronized void saveAlwaysOnPackage() {
final long token = Binder.clearCallingIdentity();
try {
final ContentResolver cr = mContext.getContentResolver();
Settings.Secure.putStringForUser(cr, Settings.Secure.ALWAYS_ON_VPN_APP,
getAlwaysOnPackage(), mUserHandle);
Settings.Secure.putIntForUser(cr, Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN,
(mLockdown ? 1 : 0), mUserHandle);
} finally {
Binder.restoreCallingIdentity(token);
}
}
/**
* Set and save always-on package and lockdown config
* @see Vpn#setAlwaysOnPackage(String, boolean)
* @see Vpn#saveAlwaysOnPackage()
*
* @return result of Vpn#setAndSaveAlwaysOnPackage(String, boolean)
*/
private synchronized boolean setAndSaveAlwaysOnPackage(String packageName, boolean lockdown) {
if (setAlwaysOnPackage(packageName, lockdown)) {
saveAlwaysOnPackage();
return true;
} else {
return false;
}
}
/**
* @return {@code true} if the service was started, the service was already connected, or there
* was no always-on VPN to start. {@code false} otherwise.
*/
public boolean startAlwaysOnVpn() {
final String alwaysOnPackage;
synchronized (this) {
alwaysOnPackage = getAlwaysOnPackage();
// Skip if there is no service to start.
if (alwaysOnPackage == null) {
return true;
}
// 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.
if (getNetworkInfo().isConnected()) {
return true;
}
}
// Start the VPN service declared in the app's manifest.
Intent serviceIntent = new Intent(VpnConfig.SERVICE_INTERFACE);
serviceIntent.setPackage(alwaysOnPackage);
try {
return mContext.startServiceAsUser(serviceIntent, UserHandle.of(mUserHandle)) != null;
} catch (RuntimeException e) {
Log.e(TAG, "VpnService " + serviceIntent + " failed to start", e);
return false;
}
}
/**
* Prepare for a VPN application. This method is designed to solve
* race conditions. It first compares the current prepared package
@@ -270,11 +403,12 @@ public class Vpn {
*
* - oldPackage non-null, newPackage null: App calling VpnService#prepare().
* - oldPackage null, newPackage non-null: ConfirmDialog calling prepareVpn().
* - oldPackage non-null, newPackage=LEGACY_VPN: Used internally to disconnect
* - oldPackage null, newPackage=LEGACY_VPN: Used internally to disconnect
* and revoke any current app VPN and re-prepare legacy vpn.
*
* TODO: Rename the variables - or split this method into two - and end this
* confusion.
* TODO: Rename the variables - or split this method into two - and end this confusion.
* TODO: b/29032008 Migrate code from prepare(oldPackage=non-null, newPackage=LEGACY_VPN)
* to prepare(oldPackage=null, newPackage=LEGACY_VPN)
*
* @param oldPackage The package name of the old VPN application
* @param newPackage The package name of the new VPN application
@@ -284,10 +418,7 @@ public class Vpn {
public synchronized boolean prepare(String oldPackage, String newPackage) {
if (oldPackage != null) {
// Stop an existing always-on VPN from being dethroned by other apps.
// TODO: Replace TextUtils.equals by isCurrentPreparedPackage when ConnectivityService
// can unset always-on after always-on package is uninstalled. Make sure when package
// is reinstalled, the consent dialog is not shown.
if (mAlwaysOn && !TextUtils.equals(mPackage, oldPackage)) {
if (mAlwaysOn && !isCurrentPreparedPackage(oldPackage)) {
return false;
}
@@ -318,9 +449,7 @@ public class Vpn {
enforceControlPermission();
// Stop an existing always-on VPN from being dethroned by other apps.
// TODO: Replace TextUtils.equals by isCurrentPreparedPackage when ConnectivityService
// can unset always-on after always-on package is uninstalled
if (mAlwaysOn && !TextUtils.equals(mPackage, newPackage)) {
if (mAlwaysOn && !isCurrentPreparedPackage(newPackage)) {
return false;
}
@@ -862,6 +991,7 @@ public class Vpn {
setVpnForcedLocked(false);
mAlwaysOn = false;
unregisterPackageChangeReceiverLocked();
// Quit any active connections
agentDisconnect();
}