Show app VPNs in always-on list
Bug: 22547950 Change-Id: I1b37f3b8d2a061f9f6fba3c8b9a95f3b7edcee64
This commit is contained in:
46
src/com/android/settings/vpn2/AppVpnInfo.java
Normal file
46
src/com/android/settings/vpn2/AppVpnInfo.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@@ -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();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user