Remove the call sites that don't have the flags specified. This is to ensure that callers know what flags they're setting. Bug: 8122243 Change-Id: I59e38efbab6ad60a905ce8678a340da2088b19c9
562 lines
20 KiB
Java
562 lines
20 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.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.net.IConnectivityManager;
|
|
import android.os.Bundle;
|
|
import android.os.Handler;
|
|
import android.os.Message;
|
|
import android.os.ServiceManager;
|
|
import android.os.SystemProperties;
|
|
import android.preference.Preference;
|
|
import android.preference.PreferenceGroup;
|
|
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.AdapterContextMenuInfo;
|
|
import android.widget.ArrayAdapter;
|
|
import android.widget.ListView;
|
|
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.internal.util.ArrayUtils;
|
|
import com.android.settings.R;
|
|
import com.android.settings.SettingsPreferenceFragment;
|
|
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 {
|
|
private static final String TAG = "VpnSettings";
|
|
|
|
private static final String TAG_LOCKDOWN = "lockdown";
|
|
|
|
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;
|
|
|
|
// The key of the profile for the current ContextMenu.
|
|
private String mSelectedKey;
|
|
|
|
@Override
|
|
public void onCreate(Bundle savedState) {
|
|
super.onCreate(savedState);
|
|
|
|
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 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();
|
|
|
|
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();
|
|
|
|
// 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;
|
|
}
|
|
}
|