Merge "vpn2: show third-party VPN services"

This commit is contained in:
Robin Lee
2015-04-22 16:41:19 +00:00
committed by Android (Google) Code Review
12 changed files with 1085 additions and 406 deletions

View File

@@ -0,0 +1,100 @@
/*
* Copyright (C) 2015 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.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import com.android.internal.net.VpnConfig;
import com.android.settings.R;
/**
* UI for managing the connection controlled by an app.
*
* Among the actions available are (depending on context):
* <ul>
* <li><strong>Forget</strong>: revoke the managing app's VPN permission</li>
* <li><strong>Dismiss</strong>: continue to use the VPN</li>
* </ul>
*
* {@see ConfigDialog}
*/
class AppDialog extends AlertDialog implements DialogInterface.OnClickListener {
private final PackageInfo mPkgInfo;
private final Listener mListener;
private final boolean mConnected;
AppDialog(Context context, Listener listener, PackageInfo pkgInfo, boolean connected) {
super(context);
mListener = listener;
mPkgInfo = pkgInfo;
mConnected = connected;
}
public final PackageInfo getPackageInfo() {
return mPkgInfo;
}
@Override
protected void onCreate(Bundle savedState) {
CharSequence vpnName;
try {
vpnName = VpnConfig.getVpnLabel(getContext(), mPkgInfo.packageName);
} catch (PackageManager.NameNotFoundException ex) {
vpnName = mPkgInfo.packageName;
}
setTitle(vpnName);
setMessage(getContext().getString(R.string.vpn_version, mPkgInfo.versionName));
createButtons();
super.onCreate(savedState);
}
protected void createButtons() {
Context context = getContext();
if (mConnected) {
// Forget the network
setButton(DialogInterface.BUTTON_NEGATIVE,
context.getString(R.string.vpn_forget), this);
}
// Dismiss
setButton(DialogInterface.BUTTON_POSITIVE,
context.getString(R.string.vpn_done), this);
}
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == DialogInterface.BUTTON_NEGATIVE) {
mListener.onForget(dialog);
}
dismiss();
}
public interface Listener {
public void onForget(DialogInterface dialog);
}
}

View File

@@ -0,0 +1,136 @@
/*
* Copyright (C) 2015 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.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.IConnectivityManager;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import com.android.internal.net.VpnConfig;
import com.android.settings.R;
/**
* Fragment wrapper around an {@link AppDialog}.
*/
public class AppDialogFragment extends DialogFragment implements AppDialog.Listener {
private static final String TAG_APP_DIALOG = "vpnappdialog";
private static final String TAG = "AppDialogFragment";
private static final String ARG_MANAGING = "managing";
private static final String ARG_PACKAGE = "package";
private static final String ARG_CONNECTED = "connected";
private final IConnectivityManager mService = IConnectivityManager.Stub.asInterface(
ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
public static void show(VpnSettings parent, PackageInfo pkgInfo, boolean managing,
boolean connected) {
if (!parent.isAdded()) return;
Bundle args = new Bundle();
args.putParcelable(ARG_PACKAGE, pkgInfo);
args.putBoolean(ARG_MANAGING, managing);
args.putBoolean(ARG_CONNECTED, connected);
final AppDialogFragment frag = new AppDialogFragment();
frag.setArguments(args);
frag.setTargetFragment(parent, 0);
frag.show(parent.getFragmentManager(), TAG_APP_DIALOG);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Bundle args = getArguments();
PackageInfo pkgInfo = (PackageInfo) args.getParcelable(ARG_PACKAGE);
boolean managing = args.getBoolean(ARG_MANAGING);
boolean connected = args.getBoolean(ARG_CONNECTED);
if (managing) {
return new AppDialog(getActivity(), this, pkgInfo, connected);
} else {
// Build an AlertDialog with an option to disconnect.
CharSequence vpnName;
try {
vpnName = VpnConfig.getVpnLabel(getActivity(), pkgInfo.packageName);
} catch (PackageManager.NameNotFoundException ex) {
vpnName = pkgInfo.packageName;
}
AlertDialog.Builder dlog = new AlertDialog.Builder(getActivity())
.setTitle(vpnName)
.setMessage(getActivity().getString(R.string.vpn_disconnect_confirm))
.setNegativeButton(getActivity().getString(R.string.vpn_cancel), null);
if (connected) {
dlog.setPositiveButton(getActivity().getString(R.string.vpn_disconnect),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
onDisconnect(dialog);
}
});
}
return dlog.create();
}
}
@Override
public void dismiss() {
((VpnSettings) getTargetFragment()).update();
super.dismiss();
}
@Override
public void onCancel(DialogInterface dialog) {
dismiss();
super.onCancel(dialog);
}
@Override
public void onForget(final DialogInterface dialog) {
PackageInfo pkgInfo = (PackageInfo) getArguments().getParcelable(ARG_PACKAGE);
final String pkg = pkgInfo.packageName;
try {
VpnConfig vpnConfig = mService.getVpnConfig();
if (vpnConfig != null && pkg.equals(vpnConfig.user) && !vpnConfig.legacy) {
mService.setVpnPackageAuthorization(false);
onDisconnect(dialog);
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to forget authorization for " + pkg, e);
}
}
private void onDisconnect(final DialogInterface dialog) {
PackageInfo pkgInfo = (PackageInfo) getArguments().getParcelable(ARG_PACKAGE);
try {
mService.prepareVpn(pkgInfo.packageName, VpnConfig.LEGACY_VPN);
} catch (RemoteException e) {
Log.e(TAG, "Failed to disconnect package " + pkgInfo.packageName, e);
}
}
}

View File

@@ -0,0 +1,132 @@
/*
* Copyright (C) 2015 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.AppGlobals;
import android.content.Context;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.RemoteException;
import android.os.UserHandle;
import android.preference.Preference;
import android.view.View.OnClickListener;
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
import com.android.settings.R;
/**
* {@link android.preference.Preference} containing information about a VPN
* application. Tracks the package name and connection state.
*/
public class AppPreference extends ManageablePreference {
public static final int STATE_CONNECTED = LegacyVpnInfo.STATE_CONNECTED;
public static final int STATE_DISCONNECTED = LegacyVpnInfo.STATE_DISCONNECTED;
private int mState = STATE_DISCONNECTED;
private String mPackageName;
private String mName;
private int mUid;
public AppPreference(Context context, OnClickListener onManage, final String packageName,
int uid) {
super(context, null /* attrs */, onManage);
mPackageName = packageName;
mUid = uid;
update();
}
public PackageInfo getPackageInfo() {
UserHandle user = new UserHandle(UserHandle.getUserId(mUid));
try {
IPackageManager ipm = AppGlobals.getPackageManager();
return ipm.getPackageInfo(mPackageName, 0 /* flags */, user.getIdentifier());
} catch (RemoteException rme) {
return null;
}
}
public String getPackageName() {
return mPackageName;
}
public int getUid() {
return mUid;
}
public int getState() {
return mState;
}
public void setState(int state) {
mState = state;
update();
}
private void update() {
final String[] states = getContext().getResources().getStringArray(R.array.vpn_states);
setSummary(mState != STATE_DISCONNECTED ? states[mState] : "");
mName = mPackageName;
Drawable icon = null;
try {
// Make all calls to the package manager as the appropriate user.
int userId = UserHandle.getUserId(mUid);
Context userContext = getContext().createPackageContextAsUser(
getContext().getPackageName(), 0 /* flags */, new UserHandle(userId));
PackageManager pm = userContext.getPackageManager();
// Fetch icon and VPN label
PackageInfo pkgInfo = pm.getPackageInfo(mPackageName, 0 /* flags */);
if (pkgInfo != null) {
icon = pkgInfo.applicationInfo.loadIcon(pm);
mName = VpnConfig.getVpnLabel(userContext, mPackageName).toString();
}
} catch (PackageManager.NameNotFoundException nnfe) {
// Failed - use default app label and icon as fallback
}
if (icon == null) {
icon = getContext().getPackageManager().getDefaultActivityIcon();
}
setTitle(mName);
setIcon(icon);
notifyHierarchyChanged();
}
public int compareTo(Preference preference) {
if (preference instanceof AppPreference) {
AppPreference another = (AppPreference) preference;
int result;
if ((result = another.mState - mState) == 0 &&
(result = mName.compareToIgnoreCase(another.mName)) == 0 &&
(result = mPackageName.compareTo(another.mPackageName)) == 0) {
result = mUid - another.mUid;
}
return result;
} else if (preference instanceof ConfigPreference) {
// Use comparator from ConfigPreference
ConfigPreference another = (ConfigPreference) preference;
return -another.compareTo(this);
} else {
return super.compareTo(preference);
}
}
}

View File

@@ -16,9 +16,6 @@
package com.android.settings.vpn2;
import com.android.internal.net.VpnProfile;
import com.android.settings.R;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
@@ -35,15 +32,26 @@ import android.widget.CheckBox;
import android.widget.Spinner;
import android.widget.TextView;
import com.android.internal.net.VpnProfile;
import com.android.settings.R;
import java.net.InetAddress;
class VpnDialog extends AlertDialog implements TextWatcher,
/**
* Dialog showing information about a VPN configuration. The dialog
* can be launched to either edit or prompt for credentials to connect
* to a user-added VPN.
*
* {@see AppDialog}
*/
class ConfigDialog extends AlertDialog implements TextWatcher,
View.OnClickListener, AdapterView.OnItemSelectedListener {
private final KeyStore mKeyStore = KeyStore.getInstance();
private final DialogInterface.OnClickListener mListener;
private final VpnProfile mProfile;
private boolean mEditing;
private boolean mExists;
private View mView;
@@ -64,19 +72,20 @@ class VpnDialog extends AlertDialog implements TextWatcher,
private Spinner mIpsecServerCert;
private CheckBox mSaveLogin;
VpnDialog(Context context, DialogInterface.OnClickListener listener,
VpnProfile profile, boolean editing) {
ConfigDialog(Context context, DialogInterface.OnClickListener listener,
VpnProfile profile, boolean editing, boolean exists) {
super(context);
mListener = listener;
mProfile = profile;
mEditing = editing;
mExists = exists;
}
@Override
protected void onCreate(Bundle savedState) {
mView = getLayoutInflater().inflate(R.layout.vpn_dialog, null);
setView(mView);
setInverseBackgroundForced(true);
Context context = getContext();
@@ -154,6 +163,12 @@ class VpnDialog extends AlertDialog implements TextWatcher,
onClick(showOptions);
}
// Create a button to forget the profile if it has already been saved..
if (mExists) {
setButton(DialogInterface.BUTTON_NEUTRAL,
context.getString(R.string.vpn_forget), mListener);
}
// Create a button to save the profile.
setButton(DialogInterface.BUTTON_POSITIVE,
context.getString(R.string.vpn_save), mListener);
@@ -173,7 +188,7 @@ class VpnDialog extends AlertDialog implements TextWatcher,
context.getString(R.string.vpn_cancel), mListener);
// Let AlertDialog create everything.
super.onCreate(null);
super.onCreate(savedState);
// Disable the action button if necessary.
getButton(DialogInterface.BUTTON_POSITIVE)

View File

@@ -0,0 +1,160 @@
/*
* Copyright (C) 2015 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.Dialog;
import android.app.DialogFragment;
import android.content.Context;
import android.content.DialogInterface;
import android.net.IConnectivityManager;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.security.Credentials;
import android.security.KeyStore;
import android.util.Log;
import android.widget.Toast;
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
import com.android.internal.net.VpnProfile;
import com.android.settings.R;
/**
* Fragment wrapper around a {@link ConfigDialog}.
*/
public class ConfigDialogFragment extends DialogFragment implements
DialogInterface.OnClickListener {
private static final String TAG_CONFIG_DIALOG = "vpnconfigdialog";
private static final String TAG = "ConfigDialogFragment";
private static final String ARG_PROFILE = "profile";
private static final String ARG_EDITING = "editing";
private static final String ARG_EXISTS = "exists";
private final IConnectivityManager mService = IConnectivityManager.Stub.asInterface(
ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
private boolean mUnlocking = false;
public static void show(VpnSettings parent, VpnProfile profile, boolean edit, boolean exists) {
if (!parent.isAdded()) return;
Bundle args = new Bundle();
args.putParcelable(ARG_PROFILE, profile);
args.putBoolean(ARG_EDITING, edit);
args.putBoolean(ARG_EXISTS, exists);
final ConfigDialogFragment frag = new ConfigDialogFragment();
frag.setArguments(args);
frag.setTargetFragment(parent, 0);
frag.show(parent.getFragmentManager(), TAG_CONFIG_DIALOG);
}
@Override
public void onResume() {
super.onResume();
// Check KeyStore here, so others do not need to deal with it.
if (!KeyStore.getInstance().isUnlocked()) {
if (!mUnlocking) {
// Let us unlock KeyStore. See you later!
Credentials.getInstance().unlock(getActivity());
} else {
// We already tried, but it is still not working!
dismiss();
}
mUnlocking = !mUnlocking;
return;
}
// Now KeyStore is always unlocked. Reset the flag.
mUnlocking = false;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Bundle args = getArguments();
VpnProfile profile = (VpnProfile) args.getParcelable(ARG_PROFILE);
boolean editing = args.getBoolean(ARG_EDITING);
boolean exists = args.getBoolean(ARG_EXISTS);
return new ConfigDialog(getActivity(), this, profile, editing, exists);
}
@Override
public void onClick(DialogInterface dialogInterface, int button) {
ConfigDialog dialog = (ConfigDialog) getDialog();
VpnProfile profile = dialog.getProfile();
if (button == DialogInterface.BUTTON_POSITIVE) {
// Update KeyStore entry
KeyStore.getInstance().put(Credentials.VPN + profile.key, profile.encode(),
KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED);
// Flush out old version of profile
disconnect(profile);
// If we are not editing, connect!
if (!dialog.isEditing()) {
try {
connect(profile);
} catch (RemoteException e) {
Log.e(TAG, "Failed to connect", e);
}
}
} else if (button == DialogInterface.BUTTON_NEUTRAL) {
// Disable profile if connected
disconnect(profile);
// Delete from KeyStore
KeyStore.getInstance().delete(Credentials.VPN + profile.key, KeyStore.UID_SELF);
}
dismiss();
}
@Override
public void dismiss() {
((VpnSettings) getTargetFragment()).update();
super.dismiss();
}
@Override
public void onCancel(DialogInterface dialog) {
dismiss();
super.onCancel(dialog);
}
private void connect(VpnProfile profile) throws RemoteException {
try {
mService.startLegacyVpn(profile);
} catch (IllegalStateException e) {
Toast.makeText(getActivity(), R.string.vpn_no_network, Toast.LENGTH_LONG).show();
}
}
private void disconnect(VpnProfile profile) {
try {
LegacyVpnInfo connected = mService.getLegacyVpnInfo();
if (connected != null && profile.key.equals(connected.key)) {
mService.prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN);
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to disconnect", e);
}
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright (C) 2015 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.content.Context;
import android.preference.Preference;
import android.view.View.OnClickListener;
import static com.android.internal.net.LegacyVpnInfo.STATE_CONNECTED;
import com.android.internal.net.VpnProfile;
import com.android.settings.R;
/**
* {@link android.preference.Preference} referencing a VPN
* configuration. Tracks the underlying profile and its connection
* state.
*/
public class ConfigPreference extends ManageablePreference {
private VpnProfile mProfile;
private int mState = -1;
ConfigPreference(Context context, OnClickListener onManage, VpnProfile profile) {
super(context, null /* attrs */, onManage);
setProfile(profile);
}
public VpnProfile getProfile() {
return mProfile;
}
public void setProfile(VpnProfile profile) {
mProfile = profile;
update();
}
public void setState(int state) {
mState = state;
update();
}
private void update() {
if (mState < 0) {
setSummary("");
} else {
String[] states = getContext().getResources()
.getStringArray(R.array.vpn_states);
setSummary(states[mState]);
}
setIcon(R.mipmap.ic_launcher_settings);
setTitle(mProfile.name);
notifyHierarchyChanged();
}
@Override
public int compareTo(Preference preference) {
if (preference instanceof ConfigPreference) {
ConfigPreference another = (ConfigPreference) preference;
int result;
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;
} else if (preference instanceof AppPreference) {
// Try to sort connected VPNs first
AppPreference another = (AppPreference) preference;
if (mState != STATE_CONNECTED && another.getState() == AppPreference.STATE_CONNECTED) {
return 1;
}
// Show configured VPNs before app VPNs
return -1;
} else {
return super.compareTo(preference);
}
}
}

View File

@@ -0,0 +1,137 @@
/*
* Copyright (C) 2015 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.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.os.Bundle;
import android.security.Credentials;
import android.security.KeyStore;
import android.text.TextUtils;
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.VpnProfile;
import com.android.settings.R;
import com.google.android.collect.Lists;
import java.util.ArrayList;
import java.util.List;
/**
* Dialog to configure always-on VPN.
*/
public class LockdownConfigFragment extends DialogFragment {
private List<VpnProfile> mProfiles;
private List<CharSequence> mTitles;
private int mCurrentIndex;
private static final String TAG_LOCKDOWN = "lockdown";
private static class TitleAdapter extends ArrayAdapter<CharSequence> {
public TitleAdapter(Context context, List<CharSequence> objects) {
super(context, com.android.internal.R.layout.select_dialog_singlechoice_material,
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) {
if (!keyStore.isUnlocked()) {
return null;
}
final byte[] value = keyStore.get(key);
return value == null ? null : new String(value);
}
private void initProfiles(KeyStore keyStore, Resources res) {
final String lockdownKey = getStringOrNull(keyStore, Credentials.LOCKDOWN_VPN);
mProfiles = VpnSettings.loadVpnProfiles(keyStore, VpnProfile.TYPE_PPTP);
mTitles = new ArrayList<>(1 + mProfiles.size());
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();
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (C) 2015 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.content.Context;
import android.preference.Preference;
import android.util.AttributeSet;
import android.view.View;
import android.view.View.OnClickListener;
import com.android.settings.R;
/**
* Preference with an additional gear icon. Touching the gear icon triggers an
* onChange event.
*/
public class ManageablePreference extends Preference {
OnClickListener mListener;
View mManageView;
public ManageablePreference(Context context, AttributeSet attrs, OnClickListener onManage) {
super(context, attrs);
mListener = onManage;
setPersistent(false);
setOrder(0);
setWidgetLayoutResource(R.layout.preference_vpn);
}
@Override
protected void onBindView(View view) {
mManageView = view.findViewById(R.id.manage);
mManageView.setOnClickListener(mListener);
mManageView.setTag(this);
super.onBindView(view);
}
}

View File

@@ -16,39 +16,36 @@
package com.android.settings.vpn2;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
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.IConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.preference.Preference;
import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
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.util.SparseArray;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.net.LegacyVpnInfo;
@@ -61,33 +58,39 @@ import com.google.android.collect.Lists;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
public class VpnSettings extends SettingsPreferenceFragment implements
Handler.Callback, Preference.OnPreferenceClickListener,
DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
private static final String TAG = "VpnSettings";
import static android.app.AppOpsManager.OP_ACTIVATE_VPN;
private static final String TAG_LOCKDOWN = "lockdown";
/**
* Settings screen listing VPNs. Configured VPNs and networks managed by apps
* are shown in the same list.
*/
public class VpnSettings extends SettingsPreferenceFragment implements
Handler.Callback, Preference.OnPreferenceClickListener {
private static final String LOG_TAG = "VpnSettings";
private static final String EXTRA_PICK_LOCKDOWN = "android.net.vpn.PICK_LOCKDOWN";
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();
// TODO: migrate to using DialogFragment when editing
private final IConnectivityManager mService = IConnectivityManager.Stub
private final IConnectivityManager mConnectivityService = IConnectivityManager.Stub
.asInterface(ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
private final KeyStore mKeyStore = KeyStore.getInstance();
private boolean mUnlocking = false;
private ConnectivityManager mConnectivityManager;
private UserManager mUserManager;
private HashMap<String, VpnPreference> mPreferences = new HashMap<String, VpnPreference>();
private VpnDialog mDialog;
private final KeyStore mKeyStore = KeyStore.getInstance();
private HashMap<String, ConfigPreference> mConfigPreferences = new HashMap<>();
private HashMap<String, AppPreference> mAppPreferences = new HashMap<>();
private Handler mUpdater;
private LegacyVpnInfo mInfo;
private UserManager mUm;
// The key of the profile for the current ContextMenu.
private String mSelectedKey;
private LegacyVpnInfo mConnectedLegacyVpn;
private HashSet<String> mConnectedVpns = new HashSet<>();
private boolean mUnavailable;
@@ -100,25 +103,24 @@ public class VpnSettings extends SettingsPreferenceFragment implements
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
mUm = (UserManager) getSystemService(Context.USER_SERVICE);
if (mUm.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN)) {
mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
if (mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN)) {
mUnavailable = true;
setPreferenceScreen(new PreferenceScreen(getActivity(), null));
return;
}
mConnectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
mConnectivityManager.registerNetworkCallback(VPN_REQUEST, mNetworkCallback);
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 onDestroy() {
mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
super.onDestroy();
}
@Override
@@ -143,13 +145,11 @@ public class VpnSettings extends SettingsPreferenceFragment implements
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))) {
while (mConfigPreferences.containsKey(Long.toHexString(millis))) {
++millis;
}
mDialog = new VpnDialog(
getActivity(), this, new VpnProfile(Long.toHexString(millis)), true);
mDialog.setOnDismissListener(this);
mDialog.show();
VpnProfile profile = new VpnProfile(Long.toHexString(millis));
ConfigDialogFragment.show(this, profile, true /* editing */, false /* exists */);
return true;
}
case R.id.vpn_lockdown: {
@@ -160,18 +160,6 @@ public class VpnSettings extends SettingsPreferenceFragment implements
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();
@@ -191,42 +179,32 @@ public class VpnSettings extends SettingsPreferenceFragment implements
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;
update();
}
public void update() {
// Pref group within which to list VPNs
PreferenceGroup vpnGroup = getPreferenceScreen();
vpnGroup.removeAll();
mConfigPreferences.clear();
mAppPreferences.clear();
// Fetch configured VPN profiles from KeyStore
for (VpnProfile profile : loadVpnProfiles(mKeyStore)) {
final ConfigPreference pref = new ConfigPreference(getActivity(), mManageListener,
profile);
pref.setOnPreferenceClickListener(this);
mConfigPreferences.put(profile.key, pref);
vpnGroup.addPreference(pref);
}
// 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();
// 3rd-party VPN apps can change elsewhere. Reload them every time.
for (AppOpsManager.PackageOps pkg : getVpnApps()) {
final AppPreference pref = new AppPreference(getActivity(), mManageListener,
pkg.getPackageName(), pkg.getUid());
pref.setOnPreferenceClickListener(this);
mAppPreferences.put(pkg.getPackageName(), pref);
vpnGroup.addPreference(pref);
}
// Start monitoring.
@@ -234,172 +212,111 @@ public class VpnSettings extends SettingsPreferenceFragment implements
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) {
if (preference instanceof ConfigPreference) {
VpnProfile profile = ((ConfigPreference) preference).getProfile();
if (mConnectedLegacyVpn != null && profile.key.equals(mConnectedLegacyVpn.key) &&
mConnectedLegacyVpn.state == LegacyVpnInfo.STATE_CONNECTED) {
try {
mInfo.intent.send();
mConnectedLegacyVpn.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;
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 = new UserHandle(UserHandle.getUserId(pref.getUid()));
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) {
// Fall through
}
}
mDialog = new VpnDialog(getActivity(), this,
new VpnProfile(Long.toHexString(millis)), true);
// Already onnected or no launch intent available - show an info dialog
PackageInfo pkgInfo = pref.getPackageInfo();
AppDialogFragment.show(this, pkgInfo, false /* editing */, connected);
return true;
}
mDialog.setOnDismissListener(this);
mDialog.show();
return true;
return false;
}
private View.OnClickListener mManageListener = new View.OnClickListener() {
@Override
public void onClick(View view) {
Object tag = view.getTag();
if (tag instanceof ConfigPreference) {
ConfigPreference pref = (ConfigPreference) tag;
ConfigDialogFragment.show(VpnSettings.this, pref.getProfile(), true /* editing */,
true /* exists */);
} else if (tag instanceof AppPreference) {
AppPreference pref = (AppPreference) tag;
AppDialogFragment.show(VpnSettings.this, pref.getPackageInfo(), true /* editing */,
(pref.getState() == AppPreference.STATE_CONNECTED) /* connected */);
}
}
};
@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);
// Legacy VPNs
LegacyVpnInfo info = mConnectivityService.getLegacyVpnInfo();
if (mConnectedLegacyVpn != null) {
ConfigPreference preference = mConfigPreferences.get(mConnectedLegacyVpn.key);
if (preference != null) {
preference.update(-1);
preference.setState(-1);
}
mInfo = null;
mConnectedLegacyVpn = null;
}
if (info != null) {
VpnPreference preference = mPreferences.get(info.key);
ConfigPreference preference = mConfigPreferences.get(info.key);
if (preference != null) {
preference.update(info.state);
mInfo = info;
preference.setState(info.state);
mConnectedLegacyVpn = info;
}
}
} catch (Exception e) {
// VPN apps
for (String key : mConnectedVpns) {
AppPreference preference = mAppPreferences.get(key);
if (preference != null) {
preference.setState(AppPreference.STATE_DISCONNECTED);
}
}
mConnectedVpns.clear();
// TODO: also query VPN services in user profiles STOPSHIP
VpnConfig cfg = mConnectivityService.getVpnConfig();
if (cfg != null) {
mConnectedVpns.add(cfg.user);
}
for (String key : mConnectedVpns) {
AppPreference preference = mAppPreferences.get(key);
if (preference != null) {
preference.setState(AppPreference.STATE_CONNECTED);
}
}
} catch (RemoteException e) {
// ignore
}
mUpdater.sendEmptyMessageDelayed(0, 1000);
@@ -407,188 +324,78 @@ public class VpnSettings extends SettingsPreferenceFragment implements
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
private NetworkCallback mNetworkCallback = new NetworkCallback() {
@Override
public void onAvailable(Network network) {
if (mUpdater != null) {
mUpdater.sendEmptyMessage(0);
}
}
}
@Override
public void onLost(Network network) {
if (mUpdater != null) {
mUpdater.sendEmptyMessage(0);
}
}
};
@Override
protected int getHelpResource() {
return R.string.help_url_vpn;
}
private static class VpnPreference extends Preference {
private VpnProfile mProfile;
private int mState = -1;
private List<AppOpsManager.PackageOps> getVpnApps() {
List<AppOpsManager.PackageOps> result = Lists.newArrayList();
VpnPreference(Context context, VpnProfile profile) {
super(context);
setPersistent(false);
setOrder(0);
mProfile = profile;
update();
// Build a filter of currently active user profiles.
SparseArray<Boolean> currentProfileIds = new SparseArray<>();
for (UserHandle profile : mUserManager.getUserProfiles()) {
currentProfileIds.put(profile.getIdentifier(), Boolean.TRUE);
}
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);
// Fetch VPN-enabled apps from AppOps.
AppOpsManager aom = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
List<AppOpsManager.PackageOps> apps = aom.getPackagesForOps(new int[] {OP_ACTIVATE_VPN});
if (apps != null) {
for (AppOpsManager.PackageOps pkg : apps) {
int userId = UserHandle.getUserId(pkg.getUid());
if (currentProfileIds.get(userId) == null) {
// Skip packages for users outside of our profile group.
continue;
}
}
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_material,
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);
// 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.getMode() == AppOpsManager.MODE_ALLOWED) {
allowed = true;
}
// 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);
if (allowed) {
result.add(pkg);
}
}
}
return result;
}
protected static List<VpnProfile> loadVpnProfiles(KeyStore keyStore, int... excludeTypes) {
final ArrayList<VpnProfile> result = Lists.newArrayList();
// This might happen if the user does not yet have a keystore. Quietly short-circuit because
// no keystore means no VPN configs.
if (!keyStore.isUnlocked()) {
return result;
}
// We are the only user of profiles in KeyStore so no locks are needed.
for (String key : keyStore.saw(Credentials.VPN)) {
final VpnProfile profile = VpnProfile.decode(key, keyStore.get(Credentials.VPN + key));
if (profile != null && !ArrayUtils.contains(excludeTypes, profile.type)) {
result.add(profile);
}
}
return result;
}
}