diff options
| author | 2016-06-08 10:46:41 +0000 | |
|---|---|---|
| committer | 2016-06-08 10:46:42 +0000 | |
| commit | ee5e4cc83646a7a3b7fcb9079ef95b514a70324d (patch) | |
| tree | 0b01e9863f6a4efce0b67bf2903fe55514016cde | |
| parent | fc7130964024dc80c2c9a5e878418a81d35df00b (diff) | |
| parent | 812800cb92090db31f609b907c4458ba76cf7f42 (diff) | |
Merge "Package changed/removed listeners for always-on VPN" into nyc-dev
| -rw-r--r-- | services/core/java/com/android/server/ConnectivityService.java | 34 | ||||
| -rw-r--r-- | services/core/java/com/android/server/connectivity/Vpn.java | 150 |
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(); } |