Files
app_Settings/src/com/android/settings/vpn2/VpnSettings.java
Robin Lee c409605356 Clear connected legacy VPN during rescan
Change-Id: I0fec37b3f73ce5b538721d79e78e1128d7667380
2015-06-01 18:45:40 -07:00

406 lines
15 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.AppOpsManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.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.util.SparseArray;
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.HashMap;
import java.util.HashSet;
import java.util.List;
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 HashMap<String, ConfigPreference> mConfigPreferences = new HashMap<>();
private HashMap<String, AppPreference> mAppPreferences = new HashMap<>();
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(getActivity(), null));
return;
}
mConnectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
setHasOptionsMenu(true);
addPreferencesFromResource(R.xml.vpn_settings2);
}
@Override
public void onDestroy() {
mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
super.onDestroy();
}
@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 (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) {
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);
}
// 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() {
// Pause monitoring
mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
if (mUpdater != null) {
mUpdater.removeCallbacksAndMessages(null);
}
super.onPause();
}
@Override
public boolean handleMessage(Message message) {
mUpdater.removeMessages(RESCAN_MESSAGE);
// 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);
}
// 3rd-party VPN apps can change elsewhere. Reload them every time.
for (AppOpsManager.PackageOps pkg : getVpnApps()) {
String key = getVpnIdentifier(UserHandle.getUserId(pkg.getUid()), pkg.getPackageName());
final AppPreference pref = new AppPreference(getActivity(), mManageListener,
pkg.getPackageName(), pkg.getUid());
pref.setOnPreferenceClickListener(this);
mAppPreferences.put(key, pref);
vpnGroup.addPreference(pref);
}
// Mark out connections with a subtitle
try {
// Legacy VPNs
mConnectedLegacyVpn = null;
LegacyVpnInfo info = mConnectivityService.getLegacyVpnInfo();
if (info != null) {
ConfigPreference preference = mConfigPreferences.get(info.key);
if (preference != null) {
preference.setState(info.state);
mConnectedLegacyVpn = info;
}
}
// Third-party VPNs
for (UserHandle profile : mUserManager.getUserProfiles()) {
VpnConfig cfg = mConnectivityService.getVpnConfig(profile.getIdentifier());
if (cfg != null) {
final String key = getVpnIdentifier(profile.getIdentifier(), cfg.user);
final AppPreference preference = mAppPreferences.get(key);
if (preference != null) {
preference.setState(AppPreference.STATE_CONNECTED);
}
}
}
} catch (RemoteException e) {
// ignore
}
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) {
// ignore
}
}
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
}
}
// Already onnected 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;
}
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 static String getVpnIdentifier(int userId, String packageName) {
return Integer.toString(userId)+ "_" + packageName;
}
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);
}
}
};
@Override
protected int getHelpResource() {
return R.string.help_url_vpn;
}
private List<AppOpsManager.PackageOps> getVpnApps() {
List<AppOpsManager.PackageOps> result = Lists.newArrayList();
// Build a filter of currently active user profiles.
SparseArray<Boolean> currentProfileIds = new SparseArray<>();
for (UserHandle profile : mUserManager.getUserProfiles()) {
currentProfileIds.put(profile.getIdentifier(), Boolean.TRUE);
}
// 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;
}
// 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(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;
}
}