Show app VPNs in always-on list

Bug: 22547950
Change-Id: I1b37f3b8d2a061f9f6fba3c8b9a95f3b7edcee64
This commit is contained in:
Robin Lee
2016-01-05 18:34:23 +00:00
parent 8e7c979bad
commit 003a4b563a
3 changed files with 107 additions and 40 deletions

View File

@@ -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);
}
}

View File

@@ -21,22 +21,28 @@ import android.app.Dialog;
import android.app.DialogFragment; import android.app.DialogFragment;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.content.res.Resources; import android.content.res.Resources;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.os.Bundle; import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.security.Credentials; import android.security.Credentials;
import android.security.KeyStore; import android.security.KeyStore;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.ListView; import android.widget.ListView;
import android.widget.Toast; import android.widget.Toast;
import com.android.internal.net.VpnConfig;
import com.android.internal.net.VpnProfile; import com.android.internal.net.VpnProfile;
import com.android.settings.R; import com.android.settings.R;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
/** /**
@@ -44,10 +50,12 @@ import java.util.List;
*/ */
public class LockdownConfigFragment extends DialogFragment { public class LockdownConfigFragment extends DialogFragment {
private List<VpnProfile> mProfiles; private List<VpnProfile> mProfiles;
private List<AppVpnInfo> mApps;
private List<CharSequence> mTitles; private List<CharSequence> mTitles;
private int mCurrentIndex; private int mCurrentIndex;
private static final String TAG_LOCKDOWN = "lockdown"; private static final String TAG_LOCKDOWN = "lockdown";
private static final String LOG_TAG = "LockdownConfigFragment";
private static class TitleAdapter extends ArrayAdapter<CharSequence> { private static class TitleAdapter extends ArrayAdapter<CharSequence> {
public TitleAdapter(Context context, List<CharSequence> objects) { public TitleAdapter(Context context, List<CharSequence> objects) {
@@ -69,19 +77,43 @@ public class LockdownConfigFragment extends DialogFragment {
} }
private void initProfiles(KeyStore keyStore, Resources res) { private void initProfiles(KeyStore keyStore, Resources res) {
final ConnectivityManager cm = ConnectivityManager.from(getActivity());
final String lockdownKey = getStringOrNull(keyStore, Credentials.LOCKDOWN_VPN); 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); mProfiles = VpnSettings.loadVpnProfiles(keyStore, VpnProfile.TYPE_PPTP);
mTitles = new ArrayList<>(1 + mProfiles.size()); } else {
mTitles.add(res.getText(R.string.vpn_lockdown_none)); 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; mCurrentIndex = 0;
// Add true lockdown VPNs to the list first.
for (VpnProfile profile : mProfiles) { for (VpnProfile profile : mProfiles) {
if (TextUtils.equals(profile.key, lockdownKey)) { if (TextUtils.equals(profile.key, lockdownKey)) {
mCurrentIndex = mTitles.size(); mCurrentIndex = mTitles.size();
} }
mTitles.add(profile.name); 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 @Override
@@ -109,21 +141,30 @@ public class LockdownConfigFragment extends DialogFragment {
final int newIndex = listView.getCheckedItemPosition(); final int newIndex = listView.getCheckedItemPosition();
if (mCurrentIndex == newIndex) return; if (mCurrentIndex == newIndex) return;
final ConnectivityManager conn = ConnectivityManager.from(getActivity());
if (newIndex == 0) { if (newIndex == 0) {
keyStore.delete(Credentials.LOCKDOWN_VPN); keyStore.delete(Credentials.LOCKDOWN_VPN);
} else { conn.setAlwaysOnVpnPackageForUser(UserHandle.myUserId(), null);
} else if (newIndex <= mProfiles.size()) {
final VpnProfile profile = mProfiles.get(newIndex - 1); final VpnProfile profile = mProfiles.get(newIndex - 1);
if (!profile.isValidLockdownProfile()) { if (!profile.isValidLockdownProfile()) {
Toast.makeText(context, R.string.vpn_lockdown_config_error, Toast.makeText(context, R.string.vpn_lockdown_config_error,
Toast.LENGTH_LONG).show(); Toast.LENGTH_LONG).show();
return; return;
} }
conn.setAlwaysOnVpnPackageForUser(UserHandle.myUserId(), null);
keyStore.put(Credentials.LOCKDOWN_VPN, profile.key.getBytes(), keyStore.put(Credentials.LOCKDOWN_VPN, profile.key.getBytes(),
KeyStore.UID_SELF, /* flags */ 0); 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 // kick profiles since we changed them
ConnectivityManager.from(getActivity()).updateLockdownVpn(); conn.updateLockdownVpn();
} }
}); });

View File

@@ -16,7 +16,6 @@
package com.android.settings.vpn2; package com.android.settings.vpn2;
import android.annotation.NonNull;
import android.annotation.UiThread; import android.annotation.UiThread;
import android.annotation.WorkerThread; import android.annotation.WorkerThread;
import android.app.AppOpsManager; import android.app.AppOpsManager;
@@ -219,7 +218,7 @@ public class VpnSettings extends SettingsPreferenceFragment implements
mUpdater.removeMessages(RESCAN_MESSAGE); mUpdater.removeMessages(RESCAN_MESSAGE);
final List<VpnProfile> vpnProfiles = loadVpnProfiles(mKeyStore); 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<LegacyVpnInfo> connectedLegacyVpns = getConnectedLegacyVpns();
final List<AppVpnInfo> connectedAppVpns = getConnectedAppVpns(); final List<AppVpnInfo> connectedAppVpns = getConnectedAppVpns();
@@ -418,22 +417,26 @@ public class VpnSettings extends SettingsPreferenceFragment implements
return connections; return connections;
} }
private List<AppVpnInfo> getVpnApps() { static List<AppVpnInfo> getVpnApps(Context context, boolean includeProfiles) {
List<AppVpnInfo> result = Lists.newArrayList(); List<AppVpnInfo> result = Lists.newArrayList();
// Build a filter of currently active user profiles. final Set<Integer> profileIds;
Set<Integer> currentProfileIds = new ArraySet<>(); if (includeProfiles) {
for (UserHandle profile : mUserManager.getUserProfiles()) { profileIds = new ArraySet<>();
currentProfileIds.add(profile.getIdentifier()); for (UserHandle profile : UserManager.get(context).getUserProfiles()) {
profileIds.add(profile.getIdentifier());
}
} else {
profileIds = Collections.singleton(UserHandle.myUserId());
} }
// Fetch VPN-enabled apps from AppOps. // 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}); List<AppOpsManager.PackageOps> apps = aom.getPackagesForOps(new int[] {OP_ACTIVATE_VPN});
if (apps != null) { if (apps != null) {
for (AppOpsManager.PackageOps pkg : apps) { for (AppOpsManager.PackageOps pkg : apps) {
int userId = UserHandle.getUserId(pkg.getUid()); int userId = UserHandle.getUserId(pkg.getUid());
if (!currentProfileIds.contains(userId)) { if (!profileIds.contains(userId)) {
// Skip packages for users outside of our profile group. // Skip packages for users outside of our profile group.
continue; continue;
} }
@@ -450,10 +453,12 @@ public class VpnSettings extends SettingsPreferenceFragment implements
} }
} }
} }
Collections.sort(result);
return 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(); final ArrayList<VpnProfile> result = Lists.newArrayList();
for (String key : keyStore.list(Credentials.VPN)) { for (String key : keyStore.list(Credentials.VPN)) {
@@ -464,29 +469,4 @@ public class VpnSettings extends SettingsPreferenceFragment implements
} }
return result; 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;
}
}
} }