Merge "Package changed/removed listeners for always-on VPN" into nyc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
ee5e4cc836
@@ -3388,7 +3388,6 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||||||
* was no always-on VPN to start. {@code false} otherwise.
|
* was no always-on VPN to start. {@code false} otherwise.
|
||||||
*/
|
*/
|
||||||
private boolean startAlwaysOnVpn(int userId) {
|
private boolean startAlwaysOnVpn(int userId) {
|
||||||
final String alwaysOnPackage;
|
|
||||||
synchronized (mVpns) {
|
synchronized (mVpns) {
|
||||||
Vpn vpn = mVpns.get(userId);
|
Vpn vpn = mVpns.get(userId);
|
||||||
if (vpn == null) {
|
if (vpn == null) {
|
||||||
@@ -3397,27 +3396,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||||||
Slog.wtf(TAG, "User " + userId + " has no Vpn configuration");
|
Slog.wtf(TAG, "User " + userId + " has no Vpn configuration");
|
||||||
return false;
|
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.
|
return vpn.startAlwaysOnVpn();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3449,17 +3429,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the configuration
|
vpn.saveAlwaysOnPackage();
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import android.app.AppOpsManager;
|
|||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
@@ -53,6 +54,7 @@ import android.net.NetworkInfo.DetailedState;
|
|||||||
import android.net.NetworkMisc;
|
import android.net.NetworkMisc;
|
||||||
import android.net.RouteInfo;
|
import android.net.RouteInfo;
|
||||||
import android.net.UidRange;
|
import android.net.UidRange;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.FileUtils;
|
import android.os.FileUtils;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
@@ -60,12 +62,14 @@ import android.os.INetworkManagementService;
|
|||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.ParcelFileDescriptor;
|
import android.os.ParcelFileDescriptor;
|
||||||
|
import android.os.PatternMatcher;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.os.SystemService;
|
import android.os.SystemService;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import android.os.UserManager;
|
import android.os.UserManager;
|
||||||
|
import android.provider.Settings;
|
||||||
import android.security.Credentials;
|
import android.security.Credentials;
|
||||||
import android.security.KeyStore;
|
import android.security.KeyStore;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
@@ -163,6 +167,45 @@ public class Vpn {
|
|||||||
// Handle of user initiating VPN.
|
// Handle of user initiating VPN.
|
||||||
private final int mUserHandle;
|
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,
|
public Vpn(Looper looper, Context context, INetworkManagementService netService,
|
||||||
int userHandle) {
|
int userHandle) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
@@ -233,10 +276,37 @@ public class Vpn {
|
|||||||
|
|
||||||
mAlwaysOn = (packageName != null);
|
mAlwaysOn = (packageName != null);
|
||||||
mLockdown = (mAlwaysOn && lockdown);
|
mLockdown = (mAlwaysOn && lockdown);
|
||||||
|
maybeRegisterPackageChangeReceiverLocked(packageName);
|
||||||
setVpnForcedLocked(mLockdown);
|
setVpnForcedLocked(mLockdown);
|
||||||
return true;
|
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,
|
* @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
|
* 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);
|
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
|
* Prepare for a VPN application. This method is designed to solve
|
||||||
* race conditions. It first compares the current prepared package
|
* 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 non-null, newPackage null: App calling VpnService#prepare().
|
||||||
* - oldPackage null, newPackage non-null: ConfirmDialog calling prepareVpn().
|
* - 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.
|
* and revoke any current app VPN and re-prepare legacy vpn.
|
||||||
*
|
*
|
||||||
* TODO: Rename the variables - or split this method into two - and end this
|
* TODO: Rename the variables - or split this method into two - and end this confusion.
|
||||||
* 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 oldPackage The package name of the old VPN application
|
||||||
* @param newPackage The package name of the new 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) {
|
public synchronized boolean prepare(String oldPackage, String newPackage) {
|
||||||
if (oldPackage != null) {
|
if (oldPackage != null) {
|
||||||
// Stop an existing always-on VPN from being dethroned by other apps.
|
// Stop an existing always-on VPN from being dethroned by other apps.
|
||||||
// TODO: Replace TextUtils.equals by isCurrentPreparedPackage when ConnectivityService
|
if (mAlwaysOn && !isCurrentPreparedPackage(oldPackage)) {
|
||||||
// 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)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,9 +449,7 @@ public class Vpn {
|
|||||||
enforceControlPermission();
|
enforceControlPermission();
|
||||||
|
|
||||||
// Stop an existing always-on VPN from being dethroned by other apps.
|
// Stop an existing always-on VPN from being dethroned by other apps.
|
||||||
// TODO: Replace TextUtils.equals by isCurrentPreparedPackage when ConnectivityService
|
if (mAlwaysOn && !isCurrentPreparedPackage(newPackage)) {
|
||||||
// can unset always-on after always-on package is uninstalled
|
|
||||||
if (mAlwaysOn && !TextUtils.equals(mPackage, newPackage)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -862,6 +991,7 @@ public class Vpn {
|
|||||||
setVpnForcedLocked(false);
|
setVpnForcedLocked(false);
|
||||||
mAlwaysOn = false;
|
mAlwaysOn = false;
|
||||||
|
|
||||||
|
unregisterPackageChangeReceiverLocked();
|
||||||
// Quit any active connections
|
// Quit any active connections
|
||||||
agentDisconnect();
|
agentDisconnect();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user