Files
app_Settings/src/com/android/settings/vpn2/VpnSettings.java
Robin Lee 4c85639733 [VPN] save public legacy VPN details publicly
This does not include certificates, private keys etc. which are still
saved in the KeyStore with the encryption the user requested for them.

Makes connecting to lockdown vpn before user unlock possible.

Bug: 26108660
Change-Id: I56c1672c7a41e761c2791584b99900aff51b59e4
2016-01-05 18:48:46 +00:00

493 lines
18 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.annotation.NonNull;
import android.annotation.UiThread;
import android.annotation.WorkerThread;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.ConnectivityManager;
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.security.Credentials;
import android.security.KeyStore;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceGroup;
import android.support.v7.preference.PreferenceScreen;
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 android.view.View;
import android.widget.TextView;
import com.android.internal.logging.MetricsLogger;
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.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static android.app.AppOpsManager.OP_ACTIVATE_VPN;
/**
* 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 int RESCAN_MESSAGE = 0;
private static final int RESCAN_INTERVAL_MS = 1000;
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();
private final IConnectivityManager mConnectivityService = IConnectivityManager.Stub
.asInterface(ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
private ConnectivityManager mConnectivityManager;
private UserManager mUserManager;
private final KeyStore mKeyStore = KeyStore.getInstance();
private Map<String, ConfigPreference> mConfigPreferences = new ArrayMap<>();
private Map<AppVpnInfo, AppPreference> mAppPreferences = new ArrayMap<>();
private Handler mUpdater;
private LegacyVpnInfo mConnectedLegacyVpn;
private boolean mUnavailable;
@Override
protected int getMetricsCategory() {
return MetricsLogger.VPN;
}
@Override
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
if (mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN)) {
mUnavailable = true;
setPreferenceScreen(new PreferenceScreen(getPrefContext(), null));
setHasOptionsMenu(false);
return;
}
mConnectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
setHasOptionsMenu(true);
addPreferencesFromResource(R.xml.vpn_settings2);
}
@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);
// Disable all actions if VPN configuration has been disallowed
for (int i = 0; i < menu.size(); i++) {
menu.getItem(i).setEnabled(!mUnavailable);
}
// 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 (mConfigPreferences.containsKey(Long.toHexString(millis))) {
++millis;
}
VpnProfile profile = new VpnProfile(Long.toHexString(millis));
ConfigDialogFragment.show(this, profile, true /* editing */, false /* exists */);
return true;
}
case R.id.vpn_lockdown: {
LockdownConfigFragment.show(this);
return true;
}
}
return super.onOptionsItemSelected(item);
}
@Override
public void onResume() {
super.onResume();
if (mUnavailable) {
// Show a message to explain that VPN settings have been disabled
TextView emptyView = (TextView) getView().findViewById(android.R.id.empty);
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);
}
// Start monitoring
mConnectivityManager.registerNetworkCallback(VPN_REQUEST, mNetworkCallback);
// Trigger a refresh
if (mUpdater == null) {
mUpdater = new Handler(this);
}
mUpdater.sendEmptyMessage(RESCAN_MESSAGE);
}
@Override
public void onPause() {
if (mUnavailable) {
super.onPause();
return;
}
// Stop monitoring
mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
if (mUpdater != null) {
mUpdater.removeCallbacksAndMessages(null);
}
super.onPause();
}
@Override
public boolean handleMessage(Message message) {
mUpdater.removeMessages(RESCAN_MESSAGE);
final List<VpnProfile> vpnProfiles = loadVpnProfiles(mKeyStore);
final List<AppVpnInfo> vpnApps = getVpnApps();
final List<LegacyVpnInfo> connectedLegacyVpns = getConnectedLegacyVpns();
final List<AppVpnInfo> connectedAppVpns = getConnectedAppVpns();
// Refresh the PreferenceGroup which lists VPNs
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
// Find new VPNs by subtracting existing ones from the full set
final Set<Preference> updates = new ArraySet<>();
for (VpnProfile profile : vpnProfiles) {
ConfigPreference p = findOrCreatePreference(profile);
p.setState(ConfigPreference.STATE_NONE);
updates.add(p);
}
for (AppVpnInfo app : vpnApps) {
AppPreference p = findOrCreatePreference(app);
p.setState(AppPreference.STATE_DISCONNECTED);
updates.add(p);
}
// Trim preferences for deleted VPNs
mConfigPreferences.values().retainAll(updates);
mAppPreferences.values().retainAll(updates);
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);
}
// Mark connected VPNs
for (LegacyVpnInfo info : connectedLegacyVpns) {
final ConfigPreference preference = mConfigPreferences.get(info.key);
if (preference != null) {
preference.setState(info.state);
}
}
for (AppVpnInfo app : connectedAppVpns) {
final AppPreference preference = mAppPreferences.get(app);
if (preference != null) {
preference.setState(AppPreference.STATE_CONNECTED);
}
}
}
});
mUpdater.sendEmptyMessageDelayed(RESCAN_MESSAGE, RESCAN_INTERVAL_MS);
return true;
}
@Override
public boolean onPreferenceClick(Preference preference) {
if (preference instanceof ConfigPreference) {
VpnProfile profile = ((ConfigPreference) preference).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
protected int getHelpResource() {
return R.string.help_url_vpn;
}
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;
boolean connected = (pref.getState() == AppPreference.STATE_CONNECTED);
AppDialogFragment.show(VpnSettings.this, pref.getPackageInfo(), pref.getLabel(),
true /* editing */, connected);
}
}
};
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);
}
}
};
@UiThread
private ConfigPreference findOrCreatePreference(VpnProfile profile) {
ConfigPreference pref = mConfigPreferences.get(profile.key);
if (pref == null) {
pref = new ConfigPreference(getPrefContext(), mManageListener);
pref.setOnPreferenceClickListener(this);
mConfigPreferences.put(profile.key, pref);
}
pref.setProfile(profile);
return pref;
}
@UiThread
private AppPreference findOrCreatePreference(AppVpnInfo app) {
AppPreference pref = mAppPreferences.get(app);
if (pref == null) {
pref = new AppPreference(getPrefContext(), mManageListener);
pref.setOnPreferenceClickListener(this);
mAppPreferences.put(app, pref);
}
pref.setUserId(app.userId);
pref.setPackageName(app.packageName);
return pref;
}
@WorkerThread
private List<LegacyVpnInfo> getConnectedLegacyVpns() {
try {
mConnectedLegacyVpn = mConnectivityService.getLegacyVpnInfo(UserHandle.myUserId());
if (mConnectedLegacyVpn != null) {
return Collections.singletonList(mConnectedLegacyVpn);
}
} catch (RemoteException e) {
Log.e(LOG_TAG, "Failure updating VPN list with connected legacy VPNs", e);
}
return Collections.emptyList();
}
@WorkerThread
private List<AppVpnInfo> getConnectedAppVpns() {
// Mark connected third-party services
List<AppVpnInfo> connections = new ArrayList<>();
try {
for (UserHandle profile : mUserManager.getUserProfiles()) {
VpnConfig config = mConnectivityService.getVpnConfig(profile.getIdentifier());
if (config != null && !config.legacy) {
connections.add(new AppVpnInfo(profile.getIdentifier(), config.user));
}
}
} catch (RemoteException e) {
Log.e(LOG_TAG, "Failure updating VPN list with connected app VPNs", e);
}
return connections;
}
private List<AppVpnInfo> getVpnApps() {
List<AppVpnInfo> result = Lists.newArrayList();
// Build a filter of currently active user profiles.
Set<Integer> currentProfileIds = new ArraySet<>();
for (UserHandle profile : mUserManager.getUserProfiles()) {
currentProfileIds.add(profile.getIdentifier());
}
// 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.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.getMode() == AppOpsManager.MODE_ALLOWED) {
allowed = true;
}
}
if (allowed) {
result.add(new AppVpnInfo(userId, pkg.getPackageName()));
}
}
}
return result;
}
protected static List<VpnProfile> loadVpnProfiles(KeyStore keyStore, int... excludeTypes) {
final ArrayList<VpnProfile> result = Lists.newArrayList();
for (String key : keyStore.list(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;
}
/** Utility holder for packageName:userId pairs */
private static class AppVpnInfo {
public int userId;
public String packageName;
public AppVpnInfo(int userId, @NonNull String packageName) {
this.userId = userId;
this.packageName = packageName;
}
@Override
public boolean equals(Object other) {
if (other instanceof AppVpnInfo) {
AppVpnInfo that = (AppVpnInfo) other;
return userId == that.userId && packageName.equals(that.packageName);
}
return false;
}
@Override
public int hashCode() {
return (packageName != null ? packageName.hashCode() : 0) * 31 + userId;
}
}
}