This adds a spinner to the VPN settings screen which can be used to toggle between the Settings app in the primary and managed profile so that the user can edit settings belonging to both profiles. Bug:16030111 Change-Id: I3b800264c634900412e8eebdbcf43a2b6913c2be
636 lines
22 KiB
Java
636 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 android.app.Activity;
|
|
import android.app.AlertDialog;
|
|
import android.app.Dialog;
|
|
import android.app.DialogFragment;
|
|
import android.content.Context;
|
|
import android.content.DialogInterface;
|
|
import android.content.Intent;
|
|
import android.content.res.Resources;
|
|
import android.net.ConnectivityManager;
|
|
import android.net.IConnectivityManager;
|
|
import android.os.Bundle;
|
|
import android.os.Handler;
|
|
import android.os.Message;
|
|
import android.os.ServiceManager;
|
|
import android.os.SystemProperties;
|
|
import android.os.UserHandle;
|
|
import android.os.UserManager;
|
|
import android.os.Process;
|
|
import android.preference.Preference;
|
|
import android.preference.PreferenceGroup;
|
|
import android.preference.PreferenceScreen;
|
|
import android.provider.Settings;
|
|
import android.security.Credentials;
|
|
import android.security.KeyStore;
|
|
import android.text.TextUtils;
|
|
import android.util.Log;
|
|
import android.view.ContextMenu;
|
|
import android.view.ContextMenu.ContextMenuInfo;
|
|
import android.view.LayoutInflater;
|
|
import android.view.Menu;
|
|
import android.view.MenuInflater;
|
|
import android.view.MenuItem;
|
|
import android.view.View;
|
|
import android.widget.AdapterView;
|
|
import android.widget.AdapterView.AdapterContextMenuInfo;
|
|
import android.widget.ArrayAdapter;
|
|
import android.widget.ListView;
|
|
import android.widget.TextView;
|
|
import android.widget.Toast;
|
|
import android.widget.AdapterView.OnItemSelectedListener;
|
|
import android.widget.Spinner;
|
|
|
|
import com.android.internal.net.LegacyVpnInfo;
|
|
import com.android.internal.net.VpnConfig;
|
|
import com.android.internal.net.VpnProfile;
|
|
import com.android.internal.util.ArrayUtils;
|
|
import com.android.settings.R;
|
|
import com.android.settings.SettingsPreferenceFragment;
|
|
import com.android.settings.UserSpinnerAdapter;
|
|
import com.android.settings.UserSpinnerAdapter.UserDetails;
|
|
import com.android.settings.Utils;
|
|
|
|
import com.google.android.collect.Lists;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
|
|
public class VpnSettings extends SettingsPreferenceFragment implements
|
|
Handler.Callback, Preference.OnPreferenceClickListener,
|
|
DialogInterface.OnClickListener, DialogInterface.OnDismissListener, OnItemSelectedListener {
|
|
private static final String TAG = "VpnSettings";
|
|
|
|
private static final String TAG_LOCKDOWN = "lockdown";
|
|
|
|
private static final String ACTION_VPN_SETTINGS = "android.net.vpn.SETTINGS";
|
|
private static final String EXTRA_PICK_LOCKDOWN = "android.net.vpn.PICK_LOCKDOWN";
|
|
|
|
// TODO: migrate to using DialogFragment when editing
|
|
|
|
private final IConnectivityManager mService = IConnectivityManager.Stub
|
|
.asInterface(ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
|
|
private final KeyStore mKeyStore = KeyStore.getInstance();
|
|
private boolean mUnlocking = false;
|
|
|
|
private HashMap<String, VpnPreference> mPreferences = new HashMap<String, VpnPreference>();
|
|
private VpnDialog mDialog;
|
|
|
|
private Handler mUpdater;
|
|
private LegacyVpnInfo mInfo;
|
|
private UserManager mUm;
|
|
|
|
// The key of the profile for the current ContextMenu.
|
|
private String mSelectedKey;
|
|
|
|
private boolean mUnavailable;
|
|
private UserSpinnerAdapter mProfileSpinnerAdapter;
|
|
|
|
@Override
|
|
public void onCreate(Bundle savedState) {
|
|
super.onCreate(savedState);
|
|
|
|
mUm = (UserManager) getSystemService(Context.USER_SERVICE);
|
|
|
|
if (mUm.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN)) {
|
|
mUnavailable = true;
|
|
setPreferenceScreen(new PreferenceScreen(getActivity(), null));
|
|
return;
|
|
}
|
|
|
|
setHasOptionsMenu(true);
|
|
addPreferencesFromResource(R.xml.vpn_settings2);
|
|
|
|
if (savedState != null) {
|
|
VpnProfile profile = VpnProfile.decode(savedState.getString("VpnKey"),
|
|
savedState.getByteArray("VpnProfile"));
|
|
if (profile != null) {
|
|
mDialog = new VpnDialog(getActivity(), this, profile,
|
|
savedState.getBoolean("VpnEditing"));
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onViewCreated(View view, Bundle savedInstanceState) {
|
|
super.onViewCreated(view, savedInstanceState);
|
|
|
|
Context context = getActivity();
|
|
mProfileSpinnerAdapter = Utils.createUserSpinnerAdapter(mUm, getActivity());
|
|
if (mProfileSpinnerAdapter != null) {
|
|
Spinner spinner = (Spinner) getActivity().getLayoutInflater().inflate(
|
|
R.layout.spinner_view, null);
|
|
|
|
spinner.setAdapter(mProfileSpinnerAdapter);
|
|
spinner.setOnItemSelectedListener(this);
|
|
setPinnedHeaderView(spinner);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
|
UserHandle selectedUser = mProfileSpinnerAdapter.getUserHandle(position);
|
|
if (selectedUser.getIdentifier() != UserHandle.myUserId()) {
|
|
Intent intent = new Intent(ACTION_VPN_SETTINGS);
|
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
Activity activity = getActivity();
|
|
activity.startActivityAsUser(intent, selectedUser);
|
|
activity.finish();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onNothingSelected(AdapterView<?> parent) {
|
|
// Nothing to do
|
|
}
|
|
|
|
@Override
|
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
|
super.onCreateOptionsMenu(menu, inflater);
|
|
inflater.inflate(R.menu.vpn, menu);
|
|
}
|
|
|
|
@Override
|
|
public void onPrepareOptionsMenu(Menu menu) {
|
|
super.onPrepareOptionsMenu(menu);
|
|
|
|
// Hide lockdown VPN on devices that require IMS authentication
|
|
if (SystemProperties.getBoolean("persist.radio.imsregrequired", false)) {
|
|
menu.findItem(R.id.vpn_lockdown).setVisible(false);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean onOptionsItemSelected(MenuItem item) {
|
|
switch (item.getItemId()) {
|
|
case R.id.vpn_create: {
|
|
// Generate a new key. Here we just use the current time.
|
|
long millis = System.currentTimeMillis();
|
|
while (mPreferences.containsKey(Long.toHexString(millis))) {
|
|
++millis;
|
|
}
|
|
mDialog = new VpnDialog(
|
|
getActivity(), this, new VpnProfile(Long.toHexString(millis)), true);
|
|
mDialog.setOnDismissListener(this);
|
|
mDialog.show();
|
|
return true;
|
|
}
|
|
case R.id.vpn_lockdown: {
|
|
LockdownConfigFragment.show(this);
|
|
return true;
|
|
}
|
|
}
|
|
return super.onOptionsItemSelected(item);
|
|
}
|
|
|
|
@Override
|
|
public void onSaveInstanceState(Bundle savedState) {
|
|
// We do not save view hierarchy, as they are just profiles.
|
|
if (mDialog != null) {
|
|
VpnProfile profile = mDialog.getProfile();
|
|
savedState.putString("VpnKey", profile.key);
|
|
savedState.putByteArray("VpnProfile", profile.encode());
|
|
savedState.putBoolean("VpnEditing", mDialog.isEditing());
|
|
}
|
|
// else?
|
|
}
|
|
|
|
@Override
|
|
public void onResume() {
|
|
super.onResume();
|
|
|
|
if (mUnavailable) {
|
|
TextView emptyView = (TextView) getView().findViewById(android.R.id.empty);
|
|
getListView().setEmptyView(emptyView);
|
|
if (emptyView != null) {
|
|
emptyView.setText(R.string.vpn_settings_not_available);
|
|
}
|
|
return;
|
|
}
|
|
|
|
final boolean pickLockdown = getActivity()
|
|
.getIntent().getBooleanExtra(EXTRA_PICK_LOCKDOWN, false);
|
|
if (pickLockdown) {
|
|
LockdownConfigFragment.show(this);
|
|
}
|
|
|
|
// Check KeyStore here, so others do not need to deal with it.
|
|
if (!mKeyStore.isUnlocked()) {
|
|
if (!mUnlocking) {
|
|
// Let us unlock KeyStore. See you later!
|
|
Credentials.getInstance().unlock(getActivity());
|
|
} else {
|
|
// We already tried, but it is still not working!
|
|
finishFragment();
|
|
}
|
|
mUnlocking = !mUnlocking;
|
|
return;
|
|
}
|
|
|
|
// Now KeyStore is always unlocked. Reset the flag.
|
|
mUnlocking = false;
|
|
|
|
// Currently we are the only user of profiles in KeyStore.
|
|
// Assuming KeyStore and KeyGuard do the right thing, we can
|
|
// safely cache profiles in the memory.
|
|
if (mPreferences.size() == 0) {
|
|
PreferenceGroup group = getPreferenceScreen();
|
|
|
|
final Context context = getActivity();
|
|
final List<VpnProfile> profiles = loadVpnProfiles(mKeyStore);
|
|
for (VpnProfile profile : profiles) {
|
|
final VpnPreference pref = new VpnPreference(context, profile);
|
|
pref.setOnPreferenceClickListener(this);
|
|
mPreferences.put(profile.key, pref);
|
|
group.addPreference(pref);
|
|
}
|
|
}
|
|
|
|
// Show the dialog if there is one.
|
|
if (mDialog != null) {
|
|
mDialog.setOnDismissListener(this);
|
|
mDialog.show();
|
|
}
|
|
|
|
// Start monitoring.
|
|
if (mUpdater == null) {
|
|
mUpdater = new Handler(this);
|
|
}
|
|
mUpdater.sendEmptyMessage(0);
|
|
|
|
// Register for context menu. Hmmm, getListView() is hidden?
|
|
registerForContextMenu(getListView());
|
|
}
|
|
|
|
@Override
|
|
public void onPause() {
|
|
super.onPause();
|
|
|
|
if (mUnavailable) {
|
|
return;
|
|
}
|
|
|
|
// Hide the dialog if there is one.
|
|
if (mDialog != null) {
|
|
mDialog.setOnDismissListener(null);
|
|
mDialog.dismiss();
|
|
}
|
|
|
|
// Unregister for context menu.
|
|
if (getView() != null) {
|
|
unregisterForContextMenu(getListView());
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onDismiss(DialogInterface dialog) {
|
|
// Here is the exit of a dialog.
|
|
mDialog = null;
|
|
}
|
|
|
|
@Override
|
|
public void onClick(DialogInterface dialog, int button) {
|
|
if (button == DialogInterface.BUTTON_POSITIVE) {
|
|
// Always save the profile.
|
|
VpnProfile profile = mDialog.getProfile();
|
|
mKeyStore.put(Credentials.VPN + profile.key, profile.encode(), KeyStore.UID_SELF,
|
|
KeyStore.FLAG_ENCRYPTED);
|
|
|
|
// Update the preference.
|
|
VpnPreference preference = mPreferences.get(profile.key);
|
|
if (preference != null) {
|
|
disconnect(profile.key);
|
|
preference.update(profile);
|
|
} else {
|
|
preference = new VpnPreference(getActivity(), profile);
|
|
preference.setOnPreferenceClickListener(this);
|
|
mPreferences.put(profile.key, preference);
|
|
getPreferenceScreen().addPreference(preference);
|
|
}
|
|
|
|
// If we are not editing, connect!
|
|
if (!mDialog.isEditing()) {
|
|
try {
|
|
connect(profile);
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "connect", e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) {
|
|
if (mDialog != null) {
|
|
Log.v(TAG, "onCreateContextMenu() is called when mDialog != null");
|
|
return;
|
|
}
|
|
|
|
if (info instanceof AdapterContextMenuInfo) {
|
|
Preference preference = (Preference) getListView().getItemAtPosition(
|
|
((AdapterContextMenuInfo) info).position);
|
|
if (preference instanceof VpnPreference) {
|
|
VpnProfile profile = ((VpnPreference) preference).getProfile();
|
|
mSelectedKey = profile.key;
|
|
menu.setHeaderTitle(profile.name);
|
|
menu.add(Menu.NONE, R.string.vpn_menu_edit, 0, R.string.vpn_menu_edit);
|
|
menu.add(Menu.NONE, R.string.vpn_menu_delete, 0, R.string.vpn_menu_delete);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean onContextItemSelected(MenuItem item) {
|
|
if (mDialog != null) {
|
|
Log.v(TAG, "onContextItemSelected() is called when mDialog != null");
|
|
return false;
|
|
}
|
|
|
|
VpnPreference preference = mPreferences.get(mSelectedKey);
|
|
if (preference == null) {
|
|
Log.v(TAG, "onContextItemSelected() is called but no preference is found");
|
|
return false;
|
|
}
|
|
|
|
switch (item.getItemId()) {
|
|
case R.string.vpn_menu_edit:
|
|
mDialog = new VpnDialog(getActivity(), this, preference.getProfile(), true);
|
|
mDialog.setOnDismissListener(this);
|
|
mDialog.show();
|
|
return true;
|
|
case R.string.vpn_menu_delete:
|
|
disconnect(mSelectedKey);
|
|
getPreferenceScreen().removePreference(preference);
|
|
mPreferences.remove(mSelectedKey);
|
|
mKeyStore.delete(Credentials.VPN + mSelectedKey);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean onPreferenceClick(Preference preference) {
|
|
if (mDialog != null) {
|
|
Log.v(TAG, "onPreferenceClick() is called when mDialog != null");
|
|
return true;
|
|
}
|
|
|
|
if (preference instanceof VpnPreference) {
|
|
VpnProfile profile = ((VpnPreference) preference).getProfile();
|
|
if (mInfo != null && profile.key.equals(mInfo.key) &&
|
|
mInfo.state == LegacyVpnInfo.STATE_CONNECTED) {
|
|
try {
|
|
mInfo.intent.send();
|
|
return true;
|
|
} catch (Exception e) {
|
|
// ignore
|
|
}
|
|
}
|
|
mDialog = new VpnDialog(getActivity(), this, profile, false);
|
|
} else {
|
|
// Generate a new key. Here we just use the current time.
|
|
long millis = System.currentTimeMillis();
|
|
while (mPreferences.containsKey(Long.toHexString(millis))) {
|
|
++millis;
|
|
}
|
|
mDialog = new VpnDialog(getActivity(), this,
|
|
new VpnProfile(Long.toHexString(millis)), true);
|
|
}
|
|
mDialog.setOnDismissListener(this);
|
|
mDialog.show();
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean handleMessage(Message message) {
|
|
mUpdater.removeMessages(0);
|
|
|
|
if (isResumed()) {
|
|
try {
|
|
LegacyVpnInfo info = mService.getLegacyVpnInfo();
|
|
if (mInfo != null) {
|
|
VpnPreference preference = mPreferences.get(mInfo.key);
|
|
if (preference != null) {
|
|
preference.update(-1);
|
|
}
|
|
mInfo = null;
|
|
}
|
|
if (info != null) {
|
|
VpnPreference preference = mPreferences.get(info.key);
|
|
if (preference != null) {
|
|
preference.update(info.state);
|
|
mInfo = info;
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
// ignore
|
|
}
|
|
mUpdater.sendEmptyMessageDelayed(0, 1000);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private void connect(VpnProfile profile) throws Exception {
|
|
try {
|
|
mService.startLegacyVpn(profile);
|
|
} catch (IllegalStateException e) {
|
|
Toast.makeText(getActivity(), R.string.vpn_no_network, Toast.LENGTH_LONG).show();
|
|
}
|
|
}
|
|
|
|
private void disconnect(String key) {
|
|
if (mInfo != null && key.equals(mInfo.key)) {
|
|
try {
|
|
mService.prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN);
|
|
} catch (Exception e) {
|
|
// ignore
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected int getHelpResource() {
|
|
return R.string.help_url_vpn;
|
|
}
|
|
|
|
private static class VpnPreference extends Preference {
|
|
private VpnProfile mProfile;
|
|
private int mState = -1;
|
|
|
|
VpnPreference(Context context, VpnProfile profile) {
|
|
super(context);
|
|
setPersistent(false);
|
|
setOrder(0);
|
|
|
|
mProfile = profile;
|
|
update();
|
|
}
|
|
|
|
VpnProfile getProfile() {
|
|
return mProfile;
|
|
}
|
|
|
|
void update(VpnProfile profile) {
|
|
mProfile = profile;
|
|
update();
|
|
}
|
|
|
|
void update(int state) {
|
|
mState = state;
|
|
update();
|
|
}
|
|
|
|
void update() {
|
|
if (mState < 0) {
|
|
String[] types = getContext().getResources()
|
|
.getStringArray(R.array.vpn_types_long);
|
|
setSummary(types[mProfile.type]);
|
|
} else {
|
|
String[] states = getContext().getResources()
|
|
.getStringArray(R.array.vpn_states);
|
|
setSummary(states[mState]);
|
|
}
|
|
setTitle(mProfile.name);
|
|
notifyHierarchyChanged();
|
|
}
|
|
|
|
@Override
|
|
public int compareTo(Preference preference) {
|
|
int result = -1;
|
|
if (preference instanceof VpnPreference) {
|
|
VpnPreference another = (VpnPreference) preference;
|
|
if ((result = another.mState - mState) == 0 &&
|
|
(result = mProfile.name.compareTo(another.mProfile.name)) == 0 &&
|
|
(result = mProfile.type - another.mProfile.type) == 0) {
|
|
result = mProfile.key.compareTo(another.mProfile.key);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dialog to configure always-on VPN.
|
|
*/
|
|
public static class LockdownConfigFragment extends DialogFragment {
|
|
private List<VpnProfile> mProfiles;
|
|
private List<CharSequence> mTitles;
|
|
private int mCurrentIndex;
|
|
|
|
private static class TitleAdapter extends ArrayAdapter<CharSequence> {
|
|
public TitleAdapter(Context context, List<CharSequence> objects) {
|
|
super(context, com.android.internal.R.layout.select_dialog_singlechoice_holo,
|
|
android.R.id.text1, objects);
|
|
}
|
|
}
|
|
|
|
public static void show(VpnSettings parent) {
|
|
if (!parent.isAdded()) return;
|
|
|
|
final LockdownConfigFragment dialog = new LockdownConfigFragment();
|
|
dialog.show(parent.getFragmentManager(), TAG_LOCKDOWN);
|
|
}
|
|
|
|
private static String getStringOrNull(KeyStore keyStore, String key) {
|
|
final byte[] value = keyStore.get(Credentials.LOCKDOWN_VPN);
|
|
return value == null ? null : new String(value);
|
|
}
|
|
|
|
private void initProfiles(KeyStore keyStore, Resources res) {
|
|
final String lockdownKey = getStringOrNull(keyStore, Credentials.LOCKDOWN_VPN);
|
|
|
|
mProfiles = loadVpnProfiles(keyStore, VpnProfile.TYPE_PPTP);
|
|
mTitles = Lists.newArrayList();
|
|
mTitles.add(res.getText(R.string.vpn_lockdown_none));
|
|
mCurrentIndex = 0;
|
|
|
|
for (VpnProfile profile : mProfiles) {
|
|
if (TextUtils.equals(profile.key, lockdownKey)) {
|
|
mCurrentIndex = mTitles.size();
|
|
}
|
|
mTitles.add(profile.name);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
|
final Context context = getActivity();
|
|
final KeyStore keyStore = KeyStore.getInstance();
|
|
|
|
initProfiles(keyStore, context.getResources());
|
|
|
|
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
|
final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
|
|
|
|
builder.setTitle(R.string.vpn_menu_lockdown);
|
|
|
|
final View view = dialogInflater.inflate(R.layout.vpn_lockdown_editor, null, false);
|
|
final ListView listView = (ListView) view.findViewById(android.R.id.list);
|
|
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
|
|
listView.setAdapter(new TitleAdapter(context, mTitles));
|
|
listView.setItemChecked(mCurrentIndex, true);
|
|
builder.setView(view);
|
|
|
|
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
|
@Override
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
final int newIndex = listView.getCheckedItemPosition();
|
|
if (mCurrentIndex == newIndex) return;
|
|
|
|
if (newIndex == 0) {
|
|
keyStore.delete(Credentials.LOCKDOWN_VPN);
|
|
|
|
} else {
|
|
final VpnProfile profile = mProfiles.get(newIndex - 1);
|
|
if (!profile.isValidLockdownProfile()) {
|
|
Toast.makeText(context, R.string.vpn_lockdown_config_error,
|
|
Toast.LENGTH_LONG).show();
|
|
return;
|
|
}
|
|
keyStore.put(Credentials.LOCKDOWN_VPN, profile.key.getBytes(),
|
|
KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED);
|
|
}
|
|
|
|
// kick profiles since we changed them
|
|
ConnectivityManager.from(getActivity()).updateLockdownVpn();
|
|
}
|
|
});
|
|
|
|
return builder.create();
|
|
}
|
|
}
|
|
|
|
private static List<VpnProfile> loadVpnProfiles(KeyStore keyStore, int... excludeTypes) {
|
|
final ArrayList<VpnProfile> result = Lists.newArrayList();
|
|
final String[] keys = keyStore.saw(Credentials.VPN);
|
|
if (keys != null) {
|
|
for (String key : keys) {
|
|
final VpnProfile profile = VpnProfile.decode(
|
|
key, keyStore.get(Credentials.VPN + key));
|
|
if (profile != null && !ArrayUtils.contains(excludeTypes, profile.type)) {
|
|
result.add(profile);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
}
|