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

Commit 4ccc2c35 authored by Robin Lee's avatar Robin Lee Committed by Android (Google) Code Review
Browse files

Merge "Show app VPNs in always-on list"

parents 918eb33a 003a4b56
Loading
Loading
Loading
Loading
+46 −0
Original line number Diff line number Diff line
package com.android.settings.vpn2;

import android.annotation.NonNull;

import com.android.internal.util.Preconditions;

import java.util.Objects;

/**
 * Holds packageName:userId pairs without any heavyweight fields.
 * {@see ApplicationInfo}
 */
class AppVpnInfo implements Comparable {
    public final int userId;
    public final String packageName;

    public AppVpnInfo(int userId, @NonNull String packageName) {
        this.userId = userId;
        this.packageName = Preconditions.checkNotNull(packageName);
    }

    @Override
    public int compareTo(Object other) {
        AppVpnInfo that = (AppVpnInfo) other;

        int result = packageName.compareTo(that.packageName);
        if (result == 0) {
            result = that.userId - userId;
        }
        return result;
    }

    @Override
    public boolean equals(Object other) {
        if (other instanceof AppVpnInfo) {
            AppVpnInfo that = (AppVpnInfo) other;
            return userId == that.userId && Objects.equals(packageName, that.packageName);
        }
        return false;
    }

    @Override
    public int hashCode() {
        return Objects.hash(packageName, userId);
    }
}
+46 −5
Original line number Diff line number Diff line
@@ -21,22 +21,28 @@ import android.app.Dialog;
import android.app.DialogFragment;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.security.Credentials;
import android.security.KeyStore;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;

import com.android.internal.net.VpnConfig;
import com.android.internal.net.VpnProfile;
import com.android.settings.R;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
@@ -44,10 +50,12 @@ import java.util.List;
 */
public class LockdownConfigFragment extends DialogFragment {
    private List<VpnProfile> mProfiles;
    private List<AppVpnInfo> mApps;
    private List<CharSequence> mTitles;
    private int mCurrentIndex;

    private static final String TAG_LOCKDOWN = "lockdown";
    private static final String LOG_TAG = "LockdownConfigFragment";

    private static class TitleAdapter extends ArrayAdapter<CharSequence> {
        public TitleAdapter(Context context, List<CharSequence> objects) {
@@ -69,19 +77,43 @@ public class LockdownConfigFragment extends DialogFragment {
    }

    private void initProfiles(KeyStore keyStore, Resources res) {
        final ConnectivityManager cm = ConnectivityManager.from(getActivity());
        final String lockdownKey = getStringOrNull(keyStore, Credentials.LOCKDOWN_VPN);
        final String alwaysOnPackage =  cm.getAlwaysOnVpnPackageForUser(UserHandle.myUserId());

        // Legacy VPN has a separate always-on mechanism which takes over the whole device, so
        // this option is restricted to the primary user only.
        if (UserManager.get(getContext()).isPrimaryUser()) {
            mProfiles = VpnSettings.loadVpnProfiles(keyStore, VpnProfile.TYPE_PPTP);
        mTitles = new ArrayList<>(1 + mProfiles.size());
        mTitles.add(res.getText(R.string.vpn_lockdown_none));
        } else {
            mProfiles = Collections.<VpnProfile>emptyList();
        }
        mApps = VpnSettings.getVpnApps(getActivity(), /* includeProfiles */ false);

        mTitles = new ArrayList<>(1 + mProfiles.size() + mApps.size());
        mTitles.add(res.getText(R.string.vpn_lockdown_none));
        mCurrentIndex = 0;

        // Add true lockdown VPNs to the list first.
        for (VpnProfile profile : mProfiles) {
            if (TextUtils.equals(profile.key, lockdownKey)) {
                mCurrentIndex = mTitles.size();
            }
            mTitles.add(profile.name);
        }

        // Add third-party app VPNs (VpnService) for the current profile to set as always-on.
        for (AppVpnInfo app : mApps) {
            try {
                String appName = VpnConfig.getVpnLabel(getContext(), app.packageName).toString();
                if (TextUtils.equals(app.packageName, alwaysOnPackage)) {
                    mCurrentIndex = mTitles.size();
                }
                mTitles.add(appName);
            } catch (PackageManager.NameNotFoundException pkgNotFound) {
                Log.w(LOG_TAG, "VPN package not found: '" + app.packageName + "'", pkgNotFound);
            }
        }
    }

    @Override
@@ -109,21 +141,30 @@ public class LockdownConfigFragment extends DialogFragment {
                final int newIndex = listView.getCheckedItemPosition();
                if (mCurrentIndex == newIndex) return;

                final ConnectivityManager conn = ConnectivityManager.from(getActivity());

                if (newIndex == 0) {
                    keyStore.delete(Credentials.LOCKDOWN_VPN);
                } else {
                    conn.setAlwaysOnVpnPackageForUser(UserHandle.myUserId(), null);
                } else if (newIndex <= mProfiles.size()) {
                    final VpnProfile profile = mProfiles.get(newIndex - 1);
                    if (!profile.isValidLockdownProfile()) {
                        Toast.makeText(context, R.string.vpn_lockdown_config_error,
                                Toast.LENGTH_LONG).show();
                        return;
                    }
                    conn.setAlwaysOnVpnPackageForUser(UserHandle.myUserId(), null);
                    keyStore.put(Credentials.LOCKDOWN_VPN, profile.key.getBytes(),
                            KeyStore.UID_SELF, /* flags */ 0);
                } else {
                    keyStore.delete(Credentials.LOCKDOWN_VPN);

                    final AppVpnInfo appVpn = mApps.get(newIndex - 1 - mProfiles.size());
                    conn.setAlwaysOnVpnPackageForUser(appVpn.userId, appVpn.packageName);
                }

                // kick profiles since we changed them
                ConnectivityManager.from(getActivity()).updateLockdownVpn();
                conn.updateLockdownVpn();
            }
        });

+15 −35
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

package com.android.settings.vpn2;

import android.annotation.NonNull;
import android.annotation.UiThread;
import android.annotation.WorkerThread;
import android.app.AppOpsManager;
@@ -219,7 +218,7 @@ public class VpnSettings extends SettingsPreferenceFragment implements
        mUpdater.removeMessages(RESCAN_MESSAGE);

        final List<VpnProfile> vpnProfiles = loadVpnProfiles(mKeyStore);
        final List<AppVpnInfo> vpnApps = getVpnApps();
        final List<AppVpnInfo> vpnApps = getVpnApps(getActivity(), /* includeProfiles */ true);

        final List<LegacyVpnInfo> connectedLegacyVpns = getConnectedLegacyVpns();
        final List<AppVpnInfo> connectedAppVpns = getConnectedAppVpns();
@@ -418,22 +417,26 @@ public class VpnSettings extends SettingsPreferenceFragment implements
        return connections;
    }

    private List<AppVpnInfo> getVpnApps() {
    static List<AppVpnInfo> getVpnApps(Context context, boolean includeProfiles) {
        List<AppVpnInfo> result = Lists.newArrayList();

        // Build a filter of currently active user profiles.
        Set<Integer> currentProfileIds = new ArraySet<>();
        for (UserHandle profile : mUserManager.getUserProfiles()) {
            currentProfileIds.add(profile.getIdentifier());
        final Set<Integer> profileIds;
        if (includeProfiles) {
            profileIds = new ArraySet<>();
            for (UserHandle profile : UserManager.get(context).getUserProfiles()) {
                profileIds.add(profile.getIdentifier());
            }
        } else {
            profileIds = Collections.singleton(UserHandle.myUserId());
        }

        // Fetch VPN-enabled apps from AppOps.
        AppOpsManager aom = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
        AppOpsManager aom = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
        List<AppOpsManager.PackageOps> apps = aom.getPackagesForOps(new int[] {OP_ACTIVATE_VPN});
        if (apps != null) {
            for (AppOpsManager.PackageOps pkg : apps) {
                int userId = UserHandle.getUserId(pkg.getUid());
                if (!currentProfileIds.contains(userId)) {
                if (!profileIds.contains(userId)) {
                    // Skip packages for users outside of our profile group.
                    continue;
                }
@@ -450,10 +453,12 @@ public class VpnSettings extends SettingsPreferenceFragment implements
                }
            }
        }

        Collections.sort(result);
        return result;
    }

    protected static List<VpnProfile> loadVpnProfiles(KeyStore keyStore, int... excludeTypes) {
    static List<VpnProfile> loadVpnProfiles(KeyStore keyStore, int... excludeTypes) {
        final ArrayList<VpnProfile> result = Lists.newArrayList();

        for (String key : keyStore.list(Credentials.VPN)) {
@@ -464,29 +469,4 @@ public class VpnSettings extends SettingsPreferenceFragment implements
        }
        return result;
    }

    /** Utility holder for packageName:userId pairs */
    private static class AppVpnInfo {
        public int userId;
        public String packageName;

        public AppVpnInfo(int userId, @NonNull String packageName) {
            this.userId = userId;
            this.packageName = packageName;
        }

        @Override
        public boolean equals(Object other) {
            if (other instanceof AppVpnInfo) {
                AppVpnInfo that = (AppVpnInfo) other;
                return userId == that.userId && packageName.equals(that.packageName);
            }
            return false;
        }

        @Override
        public int hashCode() {
            return (packageName != null ? packageName.hashCode() : 0) * 31 + userId;
        }
    }
}