406 lines
15 KiB
Java
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;
|
|
}
|
|
}
|