Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ee5e4cc8 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

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

parents fc713096 812800cb
Loading
Loading
Loading
Loading
+2 −32
Original line number Diff line number Diff line
@@ -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;
    }
+140 −10
Original line number Diff line number Diff line
@@ -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();
    }