summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author TreeHugger Robot <treehugger-gerrit@google.com> 2016-06-08 10:46:41 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2016-06-08 10:46:42 +0000
commitee5e4cc83646a7a3b7fcb9079ef95b514a70324d (patch)
tree0b01e9863f6a4efce0b67bf2903fe55514016cde
parentfc7130964024dc80c2c9a5e878418a81d35df00b (diff)
parent812800cb92090db31f609b907c4458ba76cf7f42 (diff)
Merge "Package changed/removed listeners for always-on VPN" into nyc-dev
-rw-r--r--services/core/java/com/android/server/ConnectivityService.java34
-rw-r--r--services/core/java/com/android/server/connectivity/Vpn.java150
2 files changed, 142 insertions, 42 deletions
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 6a0d48872a6b..acf8009e34a6 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -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;
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 1bdb48a23d9d..ebacc71515bb 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -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
@@ -249,6 +319,69 @@ public class Vpn {
}
/**
+ * 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
* with {@code oldPackage}. If they are the same, the prepared
@@ -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();
}