From 003a4b563a61144513688f6508101c7d2ee48f6f Mon Sep 17 00:00:00 2001 From: Robin Lee Date: Tue, 5 Jan 2016 18:34:23 +0000 Subject: [PATCH] Show app VPNs in always-on list Bug: 22547950 Change-Id: I1b37f3b8d2a061f9f6fba3c8b9a95f3b7edcee64 --- src/com/android/settings/vpn2/AppVpnInfo.java | 46 +++++++++++++++++ .../settings/vpn2/LockdownConfigFragment.java | 51 +++++++++++++++++-- .../android/settings/vpn2/VpnSettings.java | 50 ++++++------------ 3 files changed, 107 insertions(+), 40 deletions(-) create mode 100644 src/com/android/settings/vpn2/AppVpnInfo.java diff --git a/src/com/android/settings/vpn2/AppVpnInfo.java b/src/com/android/settings/vpn2/AppVpnInfo.java new file mode 100644 index 00000000000..079f8d5e365 --- /dev/null +++ b/src/com/android/settings/vpn2/AppVpnInfo.java @@ -0,0 +1,46 @@ +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); + } +} diff --git a/src/com/android/settings/vpn2/LockdownConfigFragment.java b/src/com/android/settings/vpn2/LockdownConfigFragment.java index d60cebcc848..1a06565f279 100644 --- a/src/com/android/settings/vpn2/LockdownConfigFragment.java +++ b/src/com/android/settings/vpn2/LockdownConfigFragment.java @@ -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 mProfiles; + private List mApps; private List mTitles; private int mCurrentIndex; private static final String TAG_LOCKDOWN = "lockdown"; + private static final String LOG_TAG = "LockdownConfigFragment"; private static class TitleAdapter extends ArrayAdapter { public TitleAdapter(Context context, List 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()); - mProfiles = VpnSettings.loadVpnProfiles(keyStore, VpnProfile.TYPE_PPTP); - mTitles = new ArrayList<>(1 + mProfiles.size()); + // 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); + } else { + mProfiles = Collections.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(); } }); diff --git a/src/com/android/settings/vpn2/VpnSettings.java b/src/com/android/settings/vpn2/VpnSettings.java index 40b05c593fa..85d1928b49f 100644 --- a/src/com/android/settings/vpn2/VpnSettings.java +++ b/src/com/android/settings/vpn2/VpnSettings.java @@ -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 vpnProfiles = loadVpnProfiles(mKeyStore); - final List vpnApps = getVpnApps(); + final List vpnApps = getVpnApps(getActivity(), /* includeProfiles */ true); final List connectedLegacyVpns = getConnectedLegacyVpns(); final List connectedAppVpns = getConnectedAppVpns(); @@ -418,22 +417,26 @@ public class VpnSettings extends SettingsPreferenceFragment implements return connections; } - private List getVpnApps() { + static List getVpnApps(Context context, boolean includeProfiles) { List result = Lists.newArrayList(); - // Build a filter of currently active user profiles. - Set currentProfileIds = new ArraySet<>(); - for (UserHandle profile : mUserManager.getUserProfiles()) { - currentProfileIds.add(profile.getIdentifier()); + final Set 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 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 loadVpnProfiles(KeyStore keyStore, int... excludeTypes) { + static List loadVpnProfiles(KeyStore keyStore, int... excludeTypes) { final ArrayList 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; - } - } }