If the provider model is turned on, all new VPNs must have the secure type. However, there is a system property which is necessary in order to create secure VPNs. So if the FEATURE_IPSEC_TUNNELS is missing from the device then we should not enable new 1st party VPNs. Note, it has been confirmed with the VPN team that we don't expect any devices released on S to actually be missing this property, but it's still good to check just in case. Bug: 176821216 Test: atest -c SettingsUnitTest Change-Id: I712d342e1970243520612196ba57227b1ff05bbf
568 lines
22 KiB
Java
568 lines
22 KiB
Java
/*
|
|
* Copyright (C) 2011 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.android.settings.vpn2;
|
|
|
|
import static android.app.AppOpsManager.OP_ACTIVATE_PLATFORM_VPN;
|
|
import static android.app.AppOpsManager.OP_ACTIVATE_VPN;
|
|
|
|
import android.annotation.UiThread;
|
|
import android.annotation.WorkerThread;
|
|
import android.app.Activity;
|
|
import android.app.AppOpsManager;
|
|
import android.app.settings.SettingsEnums;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.pm.PackageInfo;
|
|
import android.content.pm.PackageManager;
|
|
import android.net.ConnectivityManager;
|
|
import android.net.ConnectivityManager.NetworkCallback;
|
|
import android.net.Network;
|
|
import android.net.NetworkCapabilities;
|
|
import android.net.NetworkRequest;
|
|
import android.net.VpnManager;
|
|
import android.os.Bundle;
|
|
import android.os.Handler;
|
|
import android.os.HandlerThread;
|
|
import android.os.Message;
|
|
import android.os.UserHandle;
|
|
import android.os.UserManager;
|
|
import android.security.Credentials;
|
|
import android.security.LegacyVpnProfileStore;
|
|
import android.util.ArrayMap;
|
|
import android.util.ArraySet;
|
|
import android.util.Log;
|
|
import android.view.Menu;
|
|
import android.view.MenuInflater;
|
|
import android.view.MenuItem;
|
|
|
|
import androidx.annotation.VisibleForTesting;
|
|
import androidx.preference.Preference;
|
|
import androidx.preference.PreferenceGroup;
|
|
|
|
import com.android.internal.annotations.GuardedBy;
|
|
import com.android.internal.net.LegacyVpnInfo;
|
|
import com.android.internal.net.VpnConfig;
|
|
import com.android.internal.net.VpnProfile;
|
|
import com.android.settings.R;
|
|
import com.android.settings.RestrictedSettingsFragment;
|
|
import com.android.settings.Utils;
|
|
import com.android.settings.widget.GearPreference;
|
|
import com.android.settings.widget.GearPreference.OnGearClickListener;
|
|
import com.android.settingslib.RestrictedLockUtilsInternal;
|
|
|
|
import com.google.android.collect.Lists;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
|
|
/**
|
|
* Settings screen listing VPNs. Configured VPNs and networks managed by apps
|
|
* are shown in the same list.
|
|
*/
|
|
public class VpnSettings extends RestrictedSettingsFragment implements
|
|
Handler.Callback, Preference.OnPreferenceClickListener {
|
|
private static final String LOG_TAG = "VpnSettings";
|
|
|
|
private static final int RESCAN_MESSAGE = 0;
|
|
private static final int RESCAN_INTERVAL_MS = 1000;
|
|
|
|
private static final NetworkRequest VPN_REQUEST = new NetworkRequest.Builder()
|
|
.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
|
|
.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
|
|
.removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
|
|
.build();
|
|
|
|
private ConnectivityManager mConnectivityManager;
|
|
private UserManager mUserManager;
|
|
private VpnManager mVpnManager;
|
|
|
|
private Map<String, LegacyVpnPreference> mLegacyVpnPreferences = new ArrayMap<>();
|
|
private Map<AppVpnInfo, AppPreference> mAppPreferences = new ArrayMap<>();
|
|
|
|
@GuardedBy("this")
|
|
private Handler mUpdater;
|
|
private HandlerThread mUpdaterThread;
|
|
private LegacyVpnInfo mConnectedLegacyVpn;
|
|
|
|
private boolean mUnavailable;
|
|
|
|
public VpnSettings() {
|
|
super(UserManager.DISALLOW_CONFIG_VPN);
|
|
}
|
|
|
|
@Override
|
|
public int getMetricsCategory() {
|
|
return SettingsEnums.VPN;
|
|
}
|
|
|
|
@Override
|
|
public void onActivityCreated(Bundle savedInstanceState) {
|
|
super.onActivityCreated(savedInstanceState);
|
|
|
|
mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
|
|
mConnectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
|
|
mVpnManager = (VpnManager) getSystemService(Context.VPN_MANAGEMENT_SERVICE);
|
|
|
|
mUnavailable = isUiRestricted();
|
|
setHasOptionsMenu(!mUnavailable);
|
|
|
|
addPreferencesFromResource(R.xml.vpn_settings2);
|
|
}
|
|
|
|
@Override
|
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
|
super.onCreateOptionsMenu(menu, inflater);
|
|
// Although FEATURE_IPSEC_TUNNELS should always be present in android S,
|
|
// keep this check here just to be safe.
|
|
if (Utils.isProviderModelEnabled(getContext())
|
|
&& !getContext().getPackageManager().hasSystemFeature(
|
|
PackageManager.FEATURE_IPSEC_TUNNELS)) {
|
|
Log.w(LOG_TAG, "FEATURE_IPSEC_TUNNELS missing from system, cannot create new VPNs");
|
|
return;
|
|
} else {
|
|
// By default, we should inflate this menu.
|
|
inflater.inflate(R.menu.vpn, menu);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onPrepareOptionsMenu(Menu menu) {
|
|
super.onPrepareOptionsMenu(menu);
|
|
|
|
// Disable all actions if VPN configuration has been disallowed
|
|
for (int i = 0; i < menu.size(); i++) {
|
|
if (isUiRestrictedByOnlyAdmin()) {
|
|
RestrictedLockUtilsInternal.setMenuItemAsDisabledByAdmin(getPrefContext(),
|
|
menu.getItem(i), getRestrictionEnforcedAdmin());
|
|
} else {
|
|
menu.getItem(i).setEnabled(!mUnavailable);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean onOptionsItemSelected(MenuItem item) {
|
|
// Generate a new key. Here we just use the current time.
|
|
if (item.getItemId() == R.id.vpn_create) {
|
|
long millis = System.currentTimeMillis();
|
|
while (mLegacyVpnPreferences.containsKey(Long.toHexString(millis))) {
|
|
++millis;
|
|
}
|
|
VpnProfile profile = new VpnProfile(Long.toHexString(millis));
|
|
ConfigDialogFragment.show(this, profile, true /* editing */, false /* exists */);
|
|
return true;
|
|
}
|
|
return super.onOptionsItemSelected(item);
|
|
}
|
|
|
|
@Override
|
|
public void onResume() {
|
|
super.onResume();
|
|
|
|
mUnavailable = mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN);
|
|
if (mUnavailable) {
|
|
// Show a message to explain that VPN settings have been disabled
|
|
if (!isUiRestrictedByOnlyAdmin()) {
|
|
getEmptyTextView().setText(R.string.vpn_settings_not_available);
|
|
}
|
|
getPreferenceScreen().removeAll();
|
|
return;
|
|
} else {
|
|
setEmptyView(getEmptyTextView());
|
|
getEmptyTextView().setText(R.string.vpn_no_vpns_added);
|
|
}
|
|
|
|
// Start monitoring
|
|
mConnectivityManager.registerNetworkCallback(VPN_REQUEST, mNetworkCallback);
|
|
|
|
// Trigger a refresh
|
|
mUpdaterThread = new HandlerThread("Refresh VPN list in background");
|
|
mUpdaterThread.start();
|
|
mUpdater = new Handler(mUpdaterThread.getLooper(), this);
|
|
mUpdater.sendEmptyMessage(RESCAN_MESSAGE);
|
|
}
|
|
|
|
@Override
|
|
public void onPause() {
|
|
if (mUnavailable) {
|
|
super.onPause();
|
|
return;
|
|
}
|
|
|
|
// Stop monitoring
|
|
mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
|
|
|
|
synchronized (this) {
|
|
mUpdater.removeCallbacksAndMessages(null);
|
|
mUpdater = null;
|
|
mUpdaterThread.quit();
|
|
mUpdaterThread = null;
|
|
}
|
|
|
|
super.onPause();
|
|
}
|
|
|
|
@Override @WorkerThread
|
|
public boolean handleMessage(Message message) {
|
|
//Return if activity has been recycled
|
|
final Activity activity = getActivity();
|
|
if (activity == null) {
|
|
return true;
|
|
}
|
|
final Context context = activity.getApplicationContext();
|
|
|
|
// Run heavy RPCs before switching to UI thread
|
|
final List<VpnProfile> vpnProfiles = loadVpnProfiles();
|
|
final List<AppVpnInfo> vpnApps = getVpnApps(context, /* includeProfiles */ true);
|
|
|
|
final Map<String, LegacyVpnInfo> connectedLegacyVpns = getConnectedLegacyVpns();
|
|
final Set<AppVpnInfo> connectedAppVpns = getConnectedAppVpns();
|
|
|
|
final Set<AppVpnInfo> alwaysOnAppVpnInfos = getAlwaysOnAppVpnInfos();
|
|
final String lockdownVpnKey = VpnUtils.getLockdownVpn();
|
|
|
|
// Refresh list of VPNs
|
|
activity.runOnUiThread(new UpdatePreferences(this)
|
|
.legacyVpns(vpnProfiles, connectedLegacyVpns, lockdownVpnKey)
|
|
.appVpns(vpnApps, connectedAppVpns, alwaysOnAppVpnInfos));
|
|
|
|
synchronized (this) {
|
|
if (mUpdater != null) {
|
|
mUpdater.removeMessages(RESCAN_MESSAGE);
|
|
mUpdater.sendEmptyMessageDelayed(RESCAN_MESSAGE, RESCAN_INTERVAL_MS);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@VisibleForTesting
|
|
static class UpdatePreferences implements Runnable {
|
|
private List<VpnProfile> vpnProfiles = Collections.<VpnProfile>emptyList();
|
|
private List<AppVpnInfo> vpnApps = Collections.<AppVpnInfo>emptyList();
|
|
|
|
private Map<String, LegacyVpnInfo> connectedLegacyVpns =
|
|
Collections.<String, LegacyVpnInfo>emptyMap();
|
|
private Set<AppVpnInfo> connectedAppVpns = Collections.<AppVpnInfo>emptySet();
|
|
|
|
private Set<AppVpnInfo> alwaysOnAppVpnInfos = Collections.<AppVpnInfo>emptySet();
|
|
private String lockdownVpnKey = null;
|
|
|
|
private final VpnSettings mSettings;
|
|
|
|
public UpdatePreferences(VpnSettings settings) {
|
|
mSettings = settings;
|
|
}
|
|
|
|
public final UpdatePreferences legacyVpns(List<VpnProfile> vpnProfiles,
|
|
Map<String, LegacyVpnInfo> connectedLegacyVpns, String lockdownVpnKey) {
|
|
this.vpnProfiles = vpnProfiles;
|
|
this.connectedLegacyVpns = connectedLegacyVpns;
|
|
this.lockdownVpnKey = lockdownVpnKey;
|
|
return this;
|
|
}
|
|
|
|
public final UpdatePreferences appVpns(List<AppVpnInfo> vpnApps,
|
|
Set<AppVpnInfo> connectedAppVpns, Set<AppVpnInfo> alwaysOnAppVpnInfos) {
|
|
this.vpnApps = vpnApps;
|
|
this.connectedAppVpns = connectedAppVpns;
|
|
this.alwaysOnAppVpnInfos = alwaysOnAppVpnInfos;
|
|
return this;
|
|
}
|
|
|
|
@Override @UiThread
|
|
public void run() {
|
|
if (!mSettings.canAddPreferences()) {
|
|
return;
|
|
}
|
|
|
|
// Find new VPNs by subtracting existing ones from the full set
|
|
final Set<Preference> updates = new ArraySet<>();
|
|
|
|
// Add legacy VPNs
|
|
for (VpnProfile profile : vpnProfiles) {
|
|
LegacyVpnPreference p = mSettings.findOrCreatePreference(profile, true);
|
|
if (connectedLegacyVpns.containsKey(profile.key)) {
|
|
p.setState(connectedLegacyVpns.get(profile.key).state);
|
|
} else {
|
|
p.setState(LegacyVpnPreference.STATE_NONE);
|
|
}
|
|
p.setAlwaysOn(lockdownVpnKey != null && lockdownVpnKey.equals(profile.key));
|
|
p.setInsecureVpn(VpnProfile.isLegacyType(profile.type));
|
|
updates.add(p);
|
|
}
|
|
|
|
// Show connected VPNs even if the original entry in keystore is gone
|
|
for (LegacyVpnInfo vpn : connectedLegacyVpns.values()) {
|
|
final VpnProfile stubProfile = new VpnProfile(vpn.key);
|
|
LegacyVpnPreference p = mSettings.findOrCreatePreference(stubProfile, false);
|
|
p.setState(vpn.state);
|
|
p.setAlwaysOn(lockdownVpnKey != null && lockdownVpnKey.equals(vpn.key));
|
|
// (b/184921649) do not call setInsecureVpn() for connectedLegacyVpns, since the
|
|
// LegacyVpnInfo does not contain VPN type information, and the profile already
|
|
// exists within vpnProfiles.
|
|
updates.add(p);
|
|
}
|
|
|
|
// Add VpnService VPNs
|
|
for (AppVpnInfo app : vpnApps) {
|
|
AppPreference p = mSettings.findOrCreatePreference(app);
|
|
if (connectedAppVpns.contains(app)) {
|
|
p.setState(AppPreference.STATE_CONNECTED);
|
|
} else {
|
|
p.setState(AppPreference.STATE_DISCONNECTED);
|
|
}
|
|
p.setAlwaysOn(alwaysOnAppVpnInfos.contains(app));
|
|
updates.add(p);
|
|
}
|
|
|
|
// Trim out deleted VPN preferences
|
|
mSettings.setShownPreferences(updates);
|
|
}
|
|
}
|
|
|
|
@VisibleForTesting
|
|
public boolean canAddPreferences() {
|
|
return isAdded();
|
|
}
|
|
|
|
@VisibleForTesting @UiThread
|
|
public void setShownPreferences(final Collection<Preference> updates) {
|
|
mLegacyVpnPreferences.values().retainAll(updates);
|
|
mAppPreferences.values().retainAll(updates);
|
|
|
|
// Change {@param updates} in-place to only contain new preferences that were not already
|
|
// added to the preference screen.
|
|
final PreferenceGroup vpnGroup = getPreferenceScreen();
|
|
for (int i = vpnGroup.getPreferenceCount() - 1; i >= 0; i--) {
|
|
Preference p = vpnGroup.getPreference(i);
|
|
if (updates.contains(p)) {
|
|
updates.remove(p);
|
|
} else {
|
|
vpnGroup.removePreference(p);
|
|
}
|
|
}
|
|
|
|
// Show any new preferences on the screen
|
|
for (Preference pref : updates) {
|
|
vpnGroup.addPreference(pref);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean onPreferenceClick(Preference preference) {
|
|
if (preference instanceof LegacyVpnPreference) {
|
|
LegacyVpnPreference pref = (LegacyVpnPreference) preference;
|
|
VpnProfile profile = pref.getProfile();
|
|
if (mConnectedLegacyVpn != null && profile.key.equals(mConnectedLegacyVpn.key) &&
|
|
mConnectedLegacyVpn.state == LegacyVpnInfo.STATE_CONNECTED) {
|
|
try {
|
|
mConnectedLegacyVpn.intent.send();
|
|
return true;
|
|
} catch (Exception e) {
|
|
Log.w(LOG_TAG, "Starting config intent failed", e);
|
|
}
|
|
}
|
|
ConfigDialogFragment.show(this, profile, false /* editing */, true /* exists */);
|
|
return true;
|
|
} else if (preference instanceof AppPreference) {
|
|
AppPreference pref = (AppPreference) preference;
|
|
boolean connected = (pref.getState() == AppPreference.STATE_CONNECTED);
|
|
|
|
if (!connected) {
|
|
try {
|
|
UserHandle user = UserHandle.of(pref.getUserId());
|
|
Context userContext = getActivity().createPackageContextAsUser(
|
|
getActivity().getPackageName(), 0 /* flags */, user);
|
|
PackageManager pm = userContext.getPackageManager();
|
|
Intent appIntent = pm.getLaunchIntentForPackage(pref.getPackageName());
|
|
if (appIntent != null) {
|
|
userContext.startActivityAsUser(appIntent, user);
|
|
return true;
|
|
}
|
|
} catch (PackageManager.NameNotFoundException nnfe) {
|
|
Log.w(LOG_TAG, "VPN provider does not exist: " + pref.getPackageName(), nnfe);
|
|
}
|
|
}
|
|
|
|
// Already connected or no launch intent available - show an info dialog
|
|
PackageInfo pkgInfo = pref.getPackageInfo();
|
|
AppDialogFragment.show(this, pkgInfo, pref.getLabel(), false /* editing */, connected);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public int getHelpResource() {
|
|
return R.string.help_url_vpn;
|
|
}
|
|
|
|
private OnGearClickListener mGearListener = new OnGearClickListener() {
|
|
@Override
|
|
public void onGearClick(GearPreference p) {
|
|
if (p instanceof LegacyVpnPreference) {
|
|
LegacyVpnPreference pref = (LegacyVpnPreference) p;
|
|
ConfigDialogFragment.show(VpnSettings.this, pref.getProfile(), true /* editing */,
|
|
true /* exists */);
|
|
} else if (p instanceof AppPreference) {
|
|
AppPreference pref = (AppPreference) p;
|
|
AppManagementFragment.show(getPrefContext(), pref, getMetricsCategory());
|
|
}
|
|
}
|
|
};
|
|
|
|
private NetworkCallback mNetworkCallback = new NetworkCallback() {
|
|
@Override
|
|
public void onAvailable(Network network) {
|
|
if (mUpdater != null) {
|
|
mUpdater.sendEmptyMessage(RESCAN_MESSAGE);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onLost(Network network) {
|
|
if (mUpdater != null) {
|
|
mUpdater.sendEmptyMessage(RESCAN_MESSAGE);
|
|
}
|
|
}
|
|
};
|
|
|
|
@VisibleForTesting @UiThread
|
|
public LegacyVpnPreference findOrCreatePreference(VpnProfile profile, boolean update) {
|
|
LegacyVpnPreference pref = mLegacyVpnPreferences.get(profile.key);
|
|
boolean created = false;
|
|
if (pref == null ) {
|
|
pref = new LegacyVpnPreference(getPrefContext());
|
|
pref.setOnGearClickListener(mGearListener);
|
|
pref.setOnPreferenceClickListener(this);
|
|
mLegacyVpnPreferences.put(profile.key, pref);
|
|
created = true;
|
|
}
|
|
if (created || update) {
|
|
// This can change call-to-call because the profile can update and keep the same key.
|
|
pref.setProfile(profile);
|
|
}
|
|
return pref;
|
|
}
|
|
|
|
@VisibleForTesting @UiThread
|
|
public AppPreference findOrCreatePreference(AppVpnInfo app) {
|
|
AppPreference pref = mAppPreferences.get(app);
|
|
if (pref == null) {
|
|
pref = new AppPreference(getPrefContext(), app.userId, app.packageName);
|
|
pref.setOnGearClickListener(mGearListener);
|
|
pref.setOnPreferenceClickListener(this);
|
|
mAppPreferences.put(app, pref);
|
|
}
|
|
return pref;
|
|
}
|
|
|
|
@WorkerThread
|
|
private Map<String, LegacyVpnInfo> getConnectedLegacyVpns() {
|
|
mConnectedLegacyVpn = mVpnManager.getLegacyVpnInfo(UserHandle.myUserId());
|
|
if (mConnectedLegacyVpn != null) {
|
|
return Collections.singletonMap(mConnectedLegacyVpn.key, mConnectedLegacyVpn);
|
|
}
|
|
return Collections.emptyMap();
|
|
}
|
|
|
|
@WorkerThread
|
|
private Set<AppVpnInfo> getConnectedAppVpns() {
|
|
// Mark connected third-party services
|
|
Set<AppVpnInfo> connections = new ArraySet<>();
|
|
for (UserHandle profile : mUserManager.getUserProfiles()) {
|
|
VpnConfig config = mVpnManager.getVpnConfig(profile.getIdentifier());
|
|
if (config != null && !config.legacy) {
|
|
connections.add(new AppVpnInfo(profile.getIdentifier(), config.user));
|
|
}
|
|
}
|
|
return connections;
|
|
}
|
|
|
|
@WorkerThread
|
|
private Set<AppVpnInfo> getAlwaysOnAppVpnInfos() {
|
|
Set<AppVpnInfo> result = new ArraySet<>();
|
|
for (UserHandle profile : mUserManager.getUserProfiles()) {
|
|
final int profileId = profile.getIdentifier();
|
|
final String packageName = mVpnManager.getAlwaysOnVpnPackageForUser(profileId);
|
|
if (packageName != null) {
|
|
result.add(new AppVpnInfo(profileId, packageName));
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static List<AppVpnInfo> getVpnApps(Context context, boolean includeProfiles) {
|
|
List<AppVpnInfo> result = Lists.newArrayList();
|
|
|
|
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) context.getSystemService(Context.APP_OPS_SERVICE);
|
|
List<AppOpsManager.PackageOps> apps =
|
|
aom.getPackagesForOps(new int[] {OP_ACTIVATE_VPN, OP_ACTIVATE_PLATFORM_VPN});
|
|
if (apps != null) {
|
|
for (AppOpsManager.PackageOps pkg : apps) {
|
|
int userId = UserHandle.getUserId(pkg.getUid());
|
|
if (!profileIds.contains(userId)) {
|
|
// Skip packages for users outside of our profile group.
|
|
continue;
|
|
}
|
|
// Look for a MODE_ALLOWED permission to activate VPN.
|
|
boolean allowed = false;
|
|
for (AppOpsManager.OpEntry op : pkg.getOps()) {
|
|
if ((op.getOp() == OP_ACTIVATE_VPN || op.getOp() == OP_ACTIVATE_PLATFORM_VPN)
|
|
&& op.getMode() == AppOpsManager.MODE_ALLOWED) {
|
|
allowed = true;
|
|
}
|
|
}
|
|
if (allowed) {
|
|
result.add(new AppVpnInfo(userId, pkg.getPackageName()));
|
|
}
|
|
}
|
|
}
|
|
|
|
Collections.sort(result);
|
|
return result;
|
|
}
|
|
|
|
private static List<VpnProfile> loadVpnProfiles() {
|
|
final ArrayList<VpnProfile> result = Lists.newArrayList();
|
|
|
|
for (String key : LegacyVpnProfileStore.list(Credentials.VPN)) {
|
|
final VpnProfile profile = VpnProfile.decode(key,
|
|
LegacyVpnProfileStore.get(Credentials.VPN + key));
|
|
if (profile != null) {
|
|
result.add(profile);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
}
|