On a secondary user, the fragments ApnSettings, TetherSettings and VpnSettings should not be started. But an app can still send an intent to start these fragments. Prevent this by checkin the userId in these fragments. BUG:15313261 BUG:15312781 BUG:15312494 Change-Id: I2a36bb89a8e37e4440aa28372d7a1800b33dae37
407 lines
15 KiB
Java
407 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)
|
|
|| UserHandle.myUserId() != UserHandle.USER_OWNER) {
|
|
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;
|
|
}
|
|
}
|