VpnSettings.java revision 2bd92d5d0685144aad566b9d29454fb519ff0371
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.settings.vpn2; 18 19import android.app.AppOpsManager; 20import android.content.Context; 21import android.content.Intent; 22import android.content.pm.PackageInfo; 23import android.content.pm.PackageManager; 24import android.net.ConnectivityManager; 25import android.net.ConnectivityManager.NetworkCallback; 26import android.net.IConnectivityManager; 27import android.net.Network; 28import android.net.NetworkCapabilities; 29import android.net.NetworkRequest; 30import android.os.Bundle; 31import android.os.Handler; 32import android.os.Message; 33import android.os.RemoteException; 34import android.os.ServiceManager; 35import android.os.SystemProperties; 36import android.os.UserHandle; 37import android.os.UserManager; 38import android.preference.Preference; 39import android.preference.PreferenceGroup; 40import android.preference.PreferenceScreen; 41import android.security.Credentials; 42import android.security.KeyStore; 43import android.util.SparseArray; 44import android.view.Menu; 45import android.view.MenuInflater; 46import android.view.MenuItem; 47import android.view.View; 48import android.widget.TextView; 49 50import com.android.internal.logging.MetricsLogger; 51import com.android.internal.net.LegacyVpnInfo; 52import com.android.internal.net.VpnConfig; 53import com.android.internal.net.VpnProfile; 54import com.android.internal.util.ArrayUtils; 55import com.android.settings.R; 56import com.android.settings.SettingsPreferenceFragment; 57import com.google.android.collect.Lists; 58 59import java.util.ArrayList; 60import java.util.HashMap; 61import java.util.HashSet; 62import java.util.List; 63 64import static android.app.AppOpsManager.OP_ACTIVATE_VPN; 65 66/** 67 * Settings screen listing VPNs. Configured VPNs and networks managed by apps 68 * are shown in the same list. 69 */ 70public class VpnSettings extends SettingsPreferenceFragment implements 71 Handler.Callback, Preference.OnPreferenceClickListener { 72 private static final String LOG_TAG = "VpnSettings"; 73 74 private static final String EXTRA_PICK_LOCKDOWN = "android.net.vpn.PICK_LOCKDOWN"; 75 private static final NetworkRequest VPN_REQUEST = new NetworkRequest.Builder() 76 .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) 77 .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) 78 .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED) 79 .build(); 80 81 private final IConnectivityManager mConnectivityService = IConnectivityManager.Stub 82 .asInterface(ServiceManager.getService(Context.CONNECTIVITY_SERVICE)); 83 private ConnectivityManager mConnectivityManager; 84 private UserManager mUserManager; 85 86 private final KeyStore mKeyStore = KeyStore.getInstance(); 87 88 private HashMap<String, ConfigPreference> mConfigPreferences = new HashMap<>(); 89 private HashMap<String, AppPreference> mAppPreferences = new HashMap<>(); 90 91 private Handler mUpdater; 92 private LegacyVpnInfo mConnectedLegacyVpn; 93 private HashSet<String> mConnectedVpns = new HashSet<>(); 94 95 private boolean mUnavailable; 96 97 @Override 98 protected int getMetricsCategory() { 99 return MetricsLogger.VPN; 100 } 101 102 @Override 103 public void onCreate(Bundle savedState) { 104 super.onCreate(savedState); 105 106 mUserManager = (UserManager) getSystemService(Context.USER_SERVICE); 107 if (mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN)) { 108 mUnavailable = true; 109 setPreferenceScreen(new PreferenceScreen(getActivity(), null)); 110 return; 111 } 112 113 mConnectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); 114 mConnectivityManager.registerNetworkCallback(VPN_REQUEST, mNetworkCallback); 115 116 setHasOptionsMenu(true); 117 addPreferencesFromResource(R.xml.vpn_settings2); 118 } 119 120 @Override 121 public void onDestroy() { 122 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); 123 super.onDestroy(); 124 } 125 126 @Override 127 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 128 super.onCreateOptionsMenu(menu, inflater); 129 inflater.inflate(R.menu.vpn, menu); 130 } 131 132 @Override 133 public void onPrepareOptionsMenu(Menu menu) { 134 super.onPrepareOptionsMenu(menu); 135 136 // Hide lockdown VPN on devices that require IMS authentication 137 if (SystemProperties.getBoolean("persist.radio.imsregrequired", false)) { 138 menu.findItem(R.id.vpn_lockdown).setVisible(false); 139 } 140 } 141 142 @Override 143 public boolean onOptionsItemSelected(MenuItem item) { 144 switch (item.getItemId()) { 145 case R.id.vpn_create: { 146 // Generate a new key. Here we just use the current time. 147 long millis = System.currentTimeMillis(); 148 while (mConfigPreferences.containsKey(Long.toHexString(millis))) { 149 ++millis; 150 } 151 VpnProfile profile = new VpnProfile(Long.toHexString(millis)); 152 ConfigDialogFragment.show(this, profile, true /* editing */, false /* exists */); 153 return true; 154 } 155 case R.id.vpn_lockdown: { 156 LockdownConfigFragment.show(this); 157 return true; 158 } 159 } 160 return super.onOptionsItemSelected(item); 161 } 162 163 @Override 164 public void onResume() { 165 super.onResume(); 166 167 if (mUnavailable) { 168 TextView emptyView = (TextView) getView().findViewById(android.R.id.empty); 169 getListView().setEmptyView(emptyView); 170 if (emptyView != null) { 171 emptyView.setText(R.string.vpn_settings_not_available); 172 } 173 return; 174 } 175 176 final boolean pickLockdown = getActivity() 177 .getIntent().getBooleanExtra(EXTRA_PICK_LOCKDOWN, false); 178 if (pickLockdown) { 179 LockdownConfigFragment.show(this); 180 } 181 182 update(); 183 } 184 185 public void update() { 186 // Pref group within which to list VPNs 187 PreferenceGroup vpnGroup = getPreferenceScreen(); 188 vpnGroup.removeAll(); 189 mConfigPreferences.clear(); 190 mAppPreferences.clear(); 191 192 // Fetch configured VPN profiles from KeyStore 193 for (VpnProfile profile : loadVpnProfiles(mKeyStore)) { 194 final ConfigPreference pref = new ConfigPreference(getActivity(), mManageListener, 195 profile); 196 pref.setOnPreferenceClickListener(this); 197 mConfigPreferences.put(profile.key, pref); 198 vpnGroup.addPreference(pref); 199 } 200 201 // 3rd-party VPN apps can change elsewhere. Reload them every time. 202 for (AppOpsManager.PackageOps pkg : getVpnApps()) { 203 final AppPreference pref = new AppPreference(getActivity(), mManageListener, 204 pkg.getPackageName(), pkg.getUid()); 205 pref.setOnPreferenceClickListener(this); 206 mAppPreferences.put(pkg.getPackageName(), pref); 207 vpnGroup.addPreference(pref); 208 } 209 210 // Start monitoring. 211 if (mUpdater == null) { 212 mUpdater = new Handler(this); 213 } 214 mUpdater.sendEmptyMessage(0); 215 } 216 217 @Override 218 public boolean onPreferenceClick(Preference preference) { 219 if (preference instanceof ConfigPreference) { 220 VpnProfile profile = ((ConfigPreference) preference).getProfile(); 221 if (mConnectedLegacyVpn != null && profile.key.equals(mConnectedLegacyVpn.key) && 222 mConnectedLegacyVpn.state == LegacyVpnInfo.STATE_CONNECTED) { 223 try { 224 mConnectedLegacyVpn.intent.send(); 225 return true; 226 } catch (Exception e) { 227 // ignore 228 } 229 } 230 ConfigDialogFragment.show(this, profile, false /* editing */, true /* exists */); 231 return true; 232 } else if (preference instanceof AppPreference) { 233 AppPreference pref = (AppPreference) preference; 234 boolean connected = (pref.getState() == AppPreference.STATE_CONNECTED); 235 236 if (!connected) { 237 try { 238 UserHandle user = new UserHandle(UserHandle.getUserId(pref.getUid())); 239 Context userContext = getActivity().createPackageContextAsUser( 240 getActivity().getPackageName(), 0 /* flags */, user); 241 PackageManager pm = userContext.getPackageManager(); 242 Intent appIntent = pm.getLaunchIntentForPackage(pref.getPackageName()); 243 if (appIntent != null) { 244 userContext.startActivityAsUser(appIntent, user); 245 return true; 246 } 247 } catch (PackageManager.NameNotFoundException nnfe) { 248 // Fall through 249 } 250 } 251 252 // Already onnected or no launch intent available - show an info dialog 253 PackageInfo pkgInfo = pref.getPackageInfo(); 254 AppDialogFragment.show(this, pkgInfo, false /* editing */, connected); 255 return true; 256 } 257 return false; 258 } 259 260 private View.OnClickListener mManageListener = new View.OnClickListener() { 261 @Override 262 public void onClick(View view) { 263 Object tag = view.getTag(); 264 265 if (tag instanceof ConfigPreference) { 266 ConfigPreference pref = (ConfigPreference) tag; 267 ConfigDialogFragment.show(VpnSettings.this, pref.getProfile(), true /* editing */, 268 true /* exists */); 269 } else if (tag instanceof AppPreference) { 270 AppPreference pref = (AppPreference) tag; 271 AppDialogFragment.show(VpnSettings.this, pref.getPackageInfo(), true /* editing */, 272 (pref.getState() == AppPreference.STATE_CONNECTED) /* connected */); 273 } 274 } 275 }; 276 277 @Override 278 public boolean handleMessage(Message message) { 279 mUpdater.removeMessages(0); 280 281 if (isResumed()) { 282 try { 283 // Legacy VPNs 284 LegacyVpnInfo info = mConnectivityService.getLegacyVpnInfo(); 285 if (mConnectedLegacyVpn != null) { 286 ConfigPreference preference = mConfigPreferences.get(mConnectedLegacyVpn.key); 287 if (preference != null) { 288 preference.setState(-1); 289 } 290 mConnectedLegacyVpn = null; 291 } 292 if (info != null) { 293 ConfigPreference preference = mConfigPreferences.get(info.key); 294 if (preference != null) { 295 preference.setState(info.state); 296 mConnectedLegacyVpn = info; 297 } 298 } 299 300 // VPN apps 301 for (String key : mConnectedVpns) { 302 AppPreference preference = mAppPreferences.get(key); 303 if (preference != null) { 304 preference.setState(AppPreference.STATE_DISCONNECTED); 305 } 306 } 307 mConnectedVpns.clear(); 308 // TODO: also query VPN services in user profiles STOPSHIP 309 VpnConfig cfg = mConnectivityService.getVpnConfig(); 310 if (cfg != null) { 311 mConnectedVpns.add(cfg.user); 312 } 313 for (String key : mConnectedVpns) { 314 AppPreference preference = mAppPreferences.get(key); 315 if (preference != null) { 316 preference.setState(AppPreference.STATE_CONNECTED); 317 } 318 } 319 } catch (RemoteException e) { 320 // ignore 321 } 322 mUpdater.sendEmptyMessageDelayed(0, 1000); 323 } 324 return true; 325 } 326 327 private NetworkCallback mNetworkCallback = new NetworkCallback() { 328 @Override 329 public void onAvailable(Network network) { 330 if (mUpdater != null) { 331 mUpdater.sendEmptyMessage(0); 332 } 333 } 334 335 @Override 336 public void onLost(Network network) { 337 if (mUpdater != null) { 338 mUpdater.sendEmptyMessage(0); 339 } 340 } 341 }; 342 343 @Override 344 protected int getHelpResource() { 345 return R.string.help_url_vpn; 346 } 347 348 private List<AppOpsManager.PackageOps> getVpnApps() { 349 List<AppOpsManager.PackageOps> result = Lists.newArrayList(); 350 351 // Build a filter of currently active user profiles. 352 SparseArray<Boolean> currentProfileIds = new SparseArray<>(); 353 for (UserHandle profile : mUserManager.getUserProfiles()) { 354 currentProfileIds.put(profile.getIdentifier(), Boolean.TRUE); 355 } 356 357 // Fetch VPN-enabled apps from AppOps. 358 AppOpsManager aom = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE); 359 List<AppOpsManager.PackageOps> apps = aom.getPackagesForOps(new int[] {OP_ACTIVATE_VPN}); 360 if (apps != null) { 361 for (AppOpsManager.PackageOps pkg : apps) { 362 int userId = UserHandle.getUserId(pkg.getUid()); 363 if (currentProfileIds.get(userId) == null) { 364 // Skip packages for users outside of our profile group. 365 continue; 366 } 367 // Look for a MODE_ALLOWED permission to activate VPN. 368 boolean allowed = false; 369 for (AppOpsManager.OpEntry op : pkg.getOps()) { 370 if (op.getOp() == OP_ACTIVATE_VPN && 371 op.getMode() == AppOpsManager.MODE_ALLOWED) { 372 allowed = true; 373 } 374 } 375 if (allowed) { 376 result.add(pkg); 377 } 378 } 379 } 380 return result; 381 } 382 383 protected static List<VpnProfile> loadVpnProfiles(KeyStore keyStore, int... excludeTypes) { 384 final ArrayList<VpnProfile> result = Lists.newArrayList(); 385 386 // This might happen if the user does not yet have a keystore. Quietly short-circuit because 387 // no keystore means no VPN configs. 388 if (!keyStore.isUnlocked()) { 389 return result; 390 } 391 392 // We are the only user of profiles in KeyStore so no locks are needed. 393 for (String key : keyStore.saw(Credentials.VPN)) { 394 final VpnProfile profile = VpnProfile.decode(key, keyStore.get(Credentials.VPN + key)); 395 if (profile != null && !ArrayUtils.contains(excludeTypes, profile.type)) { 396 result.add(profile); 397 } 398 } 399 return result; 400 } 401} 402