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 17 package com.android.settings.vpn2; 18 19 import android.annotation.UiThread; 20 import android.annotation.WorkerThread; 21 import android.app.AppOpsManager; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.pm.PackageInfo; 25 import android.content.pm.PackageManager; 26 import android.net.ConnectivityManager.NetworkCallback; 27 import android.net.ConnectivityManager; 28 import android.net.IConnectivityManager; 29 import android.net.Network; 30 import android.net.NetworkCapabilities; 31 import android.net.NetworkRequest; 32 import android.os.Bundle; 33 import android.os.Handler; 34 import android.os.Message; 35 import android.os.RemoteException; 36 import android.os.ServiceManager; 37 import android.os.UserHandle; 38 import android.os.UserManager; 39 import android.security.Credentials; 40 import android.security.KeyStore; 41 import android.support.v7.preference.Preference; 42 import android.support.v7.preference.PreferenceGroup; 43 import android.support.v7.preference.PreferenceScreen; 44 import android.util.ArrayMap; 45 import android.util.ArraySet; 46 import android.util.Log; 47 import android.view.Menu; 48 import android.view.MenuInflater; 49 import android.view.MenuItem; 50 51 import com.android.internal.logging.MetricsProto.MetricsEvent; 52 import com.android.internal.net.LegacyVpnInfo; 53 import com.android.internal.net.VpnConfig; 54 import com.android.internal.net.VpnProfile; 55 import com.android.internal.util.ArrayUtils; 56 import com.android.settings.GearPreference; 57 import com.android.settings.GearPreference.OnGearClickListener; 58 import com.android.settings.R; 59 import com.android.settings.RestrictedSettingsFragment; 60 import com.android.settingslib.RestrictedLockUtils; 61 import com.google.android.collect.Lists; 62 63 import java.util.ArrayList; 64 import java.util.Collections; 65 import java.util.List; 66 import java.util.Map; 67 import java.util.Set; 68 69 import static android.app.AppOpsManager.OP_ACTIVATE_VPN; 70 71 /** 72 * Settings screen listing VPNs. Configured VPNs and networks managed by apps 73 * are shown in the same list. 74 */ 75 public class VpnSettings extends RestrictedSettingsFragment implements 76 Handler.Callback, Preference.OnPreferenceClickListener { 77 private static final String LOG_TAG = "VpnSettings"; 78 79 private static final int RESCAN_MESSAGE = 0; 80 private static final int RESCAN_INTERVAL_MS = 1000; 81 82 private static final NetworkRequest VPN_REQUEST = new NetworkRequest.Builder() 83 .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) 84 .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) 85 .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED) 86 .build(); 87 88 private final IConnectivityManager mConnectivityService = IConnectivityManager.Stub 89 .asInterface(ServiceManager.getService(Context.CONNECTIVITY_SERVICE)); 90 private ConnectivityManager mConnectivityManager; 91 private UserManager mUserManager; 92 93 private final KeyStore mKeyStore = KeyStore.getInstance(); 94 95 private Map<String, LegacyVpnPreference> mLegacyVpnPreferences = new ArrayMap<>(); 96 private Map<AppVpnInfo, AppPreference> mAppPreferences = new ArrayMap<>(); 97 98 private Handler mUpdater; 99 private LegacyVpnInfo mConnectedLegacyVpn; 100 101 private boolean mUnavailable; 102 VpnSettings()103 public VpnSettings() { 104 super(UserManager.DISALLOW_CONFIG_VPN); 105 } 106 107 @Override getMetricsCategory()108 protected int getMetricsCategory() { 109 return MetricsEvent.VPN; 110 } 111 112 @Override onActivityCreated(Bundle savedInstanceState)113 public void onActivityCreated(Bundle savedInstanceState) { 114 super.onActivityCreated(savedInstanceState); 115 116 mUserManager = (UserManager) getSystemService(Context.USER_SERVICE); 117 mConnectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); 118 119 mUnavailable = isUiRestricted(); 120 setHasOptionsMenu(!mUnavailable); 121 122 addPreferencesFromResource(R.xml.vpn_settings2); 123 } 124 125 @Override onCreateOptionsMenu(Menu menu, MenuInflater inflater)126 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 127 super.onCreateOptionsMenu(menu, inflater); 128 inflater.inflate(R.menu.vpn, menu); 129 } 130 131 @Override onPrepareOptionsMenu(Menu menu)132 public void onPrepareOptionsMenu(Menu menu) { 133 super.onPrepareOptionsMenu(menu); 134 135 // Disable all actions if VPN configuration has been disallowed 136 for (int i = 0; i < menu.size(); i++) { 137 if (isUiRestrictedByOnlyAdmin()) { 138 RestrictedLockUtils.setMenuItemAsDisabledByAdmin(getPrefContext(), 139 menu.getItem(i), getRestrictionEnforcedAdmin()); 140 } else { 141 menu.getItem(i).setEnabled(!mUnavailable); 142 } 143 } 144 } 145 146 @Override onOptionsItemSelected(MenuItem item)147 public boolean onOptionsItemSelected(MenuItem item) { 148 switch (item.getItemId()) { 149 case R.id.vpn_create: { 150 // Generate a new key. Here we just use the current time. 151 long millis = System.currentTimeMillis(); 152 while (mLegacyVpnPreferences.containsKey(Long.toHexString(millis))) { 153 ++millis; 154 } 155 VpnProfile profile = new VpnProfile(Long.toHexString(millis)); 156 ConfigDialogFragment.show(this, profile, true /* editing */, false /* exists */); 157 return true; 158 } 159 } 160 return super.onOptionsItemSelected(item); 161 } 162 163 @Override onResume()164 public void onResume() { 165 super.onResume(); 166 167 if (mUnavailable) { 168 // Show a message to explain that VPN settings have been disabled 169 if (!isUiRestrictedByOnlyAdmin()) { 170 getEmptyTextView().setText(R.string.vpn_settings_not_available); 171 } 172 getPreferenceScreen().removeAll(); 173 return; 174 } else { 175 getEmptyTextView().setText(R.string.vpn_no_vpns_added); 176 } 177 178 // Start monitoring 179 mConnectivityManager.registerNetworkCallback(VPN_REQUEST, mNetworkCallback); 180 181 // Trigger a refresh 182 if (mUpdater == null) { 183 mUpdater = new Handler(this); 184 } 185 mUpdater.sendEmptyMessage(RESCAN_MESSAGE); 186 } 187 188 @Override onPause()189 public void onPause() { 190 if (mUnavailable) { 191 super.onPause(); 192 return; 193 } 194 195 // Stop monitoring 196 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); 197 198 if (mUpdater != null) { 199 mUpdater.removeCallbacksAndMessages(null); 200 } 201 202 super.onPause(); 203 } 204 205 @Override handleMessage(Message message)206 public boolean handleMessage(Message message) { 207 mUpdater.removeMessages(RESCAN_MESSAGE); 208 209 // Run heavy RPCs before switching to UI thread 210 final List<VpnProfile> vpnProfiles = loadVpnProfiles(mKeyStore); 211 final List<AppVpnInfo> vpnApps = getVpnApps(getActivity(), /* includeProfiles */ true); 212 213 final Map<String, LegacyVpnInfo> connectedLegacyVpns = getConnectedLegacyVpns(); 214 final Set<AppVpnInfo> connectedAppVpns = getConnectedAppVpns(); 215 216 final Set<AppVpnInfo> alwaysOnAppVpnInfos = getAlwaysOnAppVpnInfos(); 217 final String lockdownVpnKey = VpnUtils.getLockdownVpn(); 218 219 // Refresh list of VPNs 220 getActivity().runOnUiThread(new Runnable() { 221 @Override 222 public void run() { 223 // Can't do anything useful if the context has gone away 224 if (!isAdded()) { 225 return; 226 } 227 228 // Find new VPNs by subtracting existing ones from the full set 229 final Set<Preference> updates = new ArraySet<>(); 230 231 for (VpnProfile profile : vpnProfiles) { 232 LegacyVpnPreference p = findOrCreatePreference(profile); 233 if (connectedLegacyVpns.containsKey(profile.key)) { 234 p.setState(connectedLegacyVpns.get(profile.key).state); 235 } else { 236 p.setState(LegacyVpnPreference.STATE_NONE); 237 } 238 p.setAlwaysOn(lockdownVpnKey != null && lockdownVpnKey.equals(profile.key)); 239 updates.add(p); 240 } 241 for (AppVpnInfo app : vpnApps) { 242 AppPreference p = findOrCreatePreference(app); 243 if (connectedAppVpns.contains(app)) { 244 p.setState(AppPreference.STATE_CONNECTED); 245 } else { 246 p.setState(AppPreference.STATE_DISCONNECTED); 247 } 248 p.setAlwaysOn(alwaysOnAppVpnInfos.contains(app)); 249 updates.add(p); 250 } 251 252 // Trim out deleted VPN preferences 253 mLegacyVpnPreferences.values().retainAll(updates); 254 mAppPreferences.values().retainAll(updates); 255 256 final PreferenceGroup vpnGroup = getPreferenceScreen(); 257 for (int i = vpnGroup.getPreferenceCount() - 1; i >= 0; i--) { 258 Preference p = vpnGroup.getPreference(i); 259 if (updates.contains(p)) { 260 updates.remove(p); 261 } else { 262 vpnGroup.removePreference(p); 263 } 264 } 265 266 // Show any new preferences on the screen 267 for (Preference pref : updates) { 268 vpnGroup.addPreference(pref); 269 } 270 } 271 }); 272 273 mUpdater.sendEmptyMessageDelayed(RESCAN_MESSAGE, RESCAN_INTERVAL_MS); 274 return true; 275 } 276 277 @Override onPreferenceClick(Preference preference)278 public boolean onPreferenceClick(Preference preference) { 279 if (preference instanceof LegacyVpnPreference) { 280 LegacyVpnPreference pref = (LegacyVpnPreference) preference; 281 VpnProfile profile = pref.getProfile(); 282 if (mConnectedLegacyVpn != null && profile.key.equals(mConnectedLegacyVpn.key) && 283 mConnectedLegacyVpn.state == LegacyVpnInfo.STATE_CONNECTED) { 284 try { 285 mConnectedLegacyVpn.intent.send(); 286 return true; 287 } catch (Exception e) { 288 Log.w(LOG_TAG, "Starting config intent failed", e); 289 } 290 } 291 ConfigDialogFragment.show(this, profile, false /* editing */, true /* exists */); 292 return true; 293 } else if (preference instanceof AppPreference) { 294 AppPreference pref = (AppPreference) preference; 295 boolean connected = (pref.getState() == AppPreference.STATE_CONNECTED); 296 297 if (!connected) { 298 try { 299 UserHandle user = UserHandle.of(pref.getUserId()); 300 Context userContext = getActivity().createPackageContextAsUser( 301 getActivity().getPackageName(), 0 /* flags */, user); 302 PackageManager pm = userContext.getPackageManager(); 303 Intent appIntent = pm.getLaunchIntentForPackage(pref.getPackageName()); 304 if (appIntent != null) { 305 userContext.startActivityAsUser(appIntent, user); 306 return true; 307 } 308 } catch (PackageManager.NameNotFoundException nnfe) { 309 Log.w(LOG_TAG, "VPN provider does not exist: " + pref.getPackageName(), nnfe); 310 } 311 } 312 313 // Already connected or no launch intent available - show an info dialog 314 PackageInfo pkgInfo = pref.getPackageInfo(); 315 AppDialogFragment.show(this, pkgInfo, pref.getLabel(), false /* editing */, connected); 316 return true; 317 } 318 return false; 319 } 320 321 @Override getHelpResource()322 protected int getHelpResource() { 323 return R.string.help_url_vpn; 324 } 325 326 private OnGearClickListener mGearListener = new OnGearClickListener() { 327 @Override 328 public void onGearClick(GearPreference p) { 329 if (p instanceof LegacyVpnPreference) { 330 LegacyVpnPreference pref = (LegacyVpnPreference) p; 331 ConfigDialogFragment.show(VpnSettings.this, pref.getProfile(), true /* editing */, 332 true /* exists */); 333 } else if (p instanceof AppPreference) { 334 AppPreference pref = (AppPreference) p;; 335 AppManagementFragment.show(getPrefContext(), pref); 336 } 337 } 338 }; 339 340 private NetworkCallback mNetworkCallback = new NetworkCallback() { 341 @Override 342 public void onAvailable(Network network) { 343 if (mUpdater != null) { 344 mUpdater.sendEmptyMessage(RESCAN_MESSAGE); 345 } 346 } 347 348 @Override 349 public void onLost(Network network) { 350 if (mUpdater != null) { 351 mUpdater.sendEmptyMessage(RESCAN_MESSAGE); 352 } 353 } 354 }; 355 356 @UiThread findOrCreatePreference(VpnProfile profile)357 private LegacyVpnPreference findOrCreatePreference(VpnProfile profile) { 358 LegacyVpnPreference pref = mLegacyVpnPreferences.get(profile.key); 359 if (pref == null) { 360 pref = new LegacyVpnPreference(getPrefContext()); 361 pref.setOnGearClickListener(mGearListener); 362 pref.setOnPreferenceClickListener(this); 363 mLegacyVpnPreferences.put(profile.key, pref); 364 } 365 // This may change as the profile can update and keep the same key. 366 pref.setProfile(profile); 367 return pref; 368 } 369 370 @UiThread findOrCreatePreference(AppVpnInfo app)371 private AppPreference findOrCreatePreference(AppVpnInfo app) { 372 AppPreference pref = mAppPreferences.get(app); 373 if (pref == null) { 374 pref = new AppPreference(getPrefContext(), app.userId, app.packageName); 375 pref.setOnGearClickListener(mGearListener); 376 pref.setOnPreferenceClickListener(this); 377 mAppPreferences.put(app, pref); 378 } 379 return pref; 380 } 381 382 @WorkerThread getConnectedLegacyVpns()383 private Map<String, LegacyVpnInfo> getConnectedLegacyVpns() { 384 try { 385 mConnectedLegacyVpn = mConnectivityService.getLegacyVpnInfo(UserHandle.myUserId()); 386 if (mConnectedLegacyVpn != null) { 387 return Collections.singletonMap(mConnectedLegacyVpn.key, mConnectedLegacyVpn); 388 } 389 } catch (RemoteException e) { 390 Log.e(LOG_TAG, "Failure updating VPN list with connected legacy VPNs", e); 391 } 392 return Collections.emptyMap(); 393 } 394 395 @WorkerThread getConnectedAppVpns()396 private Set<AppVpnInfo> getConnectedAppVpns() { 397 // Mark connected third-party services 398 Set<AppVpnInfo> connections = new ArraySet<>(); 399 try { 400 for (UserHandle profile : mUserManager.getUserProfiles()) { 401 VpnConfig config = mConnectivityService.getVpnConfig(profile.getIdentifier()); 402 if (config != null && !config.legacy) { 403 connections.add(new AppVpnInfo(profile.getIdentifier(), config.user)); 404 } 405 } 406 } catch (RemoteException e) { 407 Log.e(LOG_TAG, "Failure updating VPN list with connected app VPNs", e); 408 } 409 return connections; 410 } 411 412 @WorkerThread getAlwaysOnAppVpnInfos()413 private Set<AppVpnInfo> getAlwaysOnAppVpnInfos() { 414 Set<AppVpnInfo> result = new ArraySet<>(); 415 for (UserHandle profile : mUserManager.getUserProfiles()) { 416 final int profileId = profile.getIdentifier(); 417 final String packageName = mConnectivityManager.getAlwaysOnVpnPackageForUser(profileId); 418 if (packageName != null) { 419 result.add(new AppVpnInfo(profileId, packageName)); 420 } 421 } 422 return result; 423 } 424 getVpnApps(Context context, boolean includeProfiles)425 static List<AppVpnInfo> getVpnApps(Context context, boolean includeProfiles) { 426 List<AppVpnInfo> result = Lists.newArrayList(); 427 428 final Set<Integer> profileIds; 429 if (includeProfiles) { 430 profileIds = new ArraySet<>(); 431 for (UserHandle profile : UserManager.get(context).getUserProfiles()) { 432 profileIds.add(profile.getIdentifier()); 433 } 434 } else { 435 profileIds = Collections.singleton(UserHandle.myUserId()); 436 } 437 438 // Fetch VPN-enabled apps from AppOps. 439 AppOpsManager aom = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); 440 List<AppOpsManager.PackageOps> apps = aom.getPackagesForOps(new int[] {OP_ACTIVATE_VPN}); 441 if (apps != null) { 442 for (AppOpsManager.PackageOps pkg : apps) { 443 int userId = UserHandle.getUserId(pkg.getUid()); 444 if (!profileIds.contains(userId)) { 445 // Skip packages for users outside of our profile group. 446 continue; 447 } 448 // Look for a MODE_ALLOWED permission to activate VPN. 449 boolean allowed = false; 450 for (AppOpsManager.OpEntry op : pkg.getOps()) { 451 if (op.getOp() == OP_ACTIVATE_VPN && 452 op.getMode() == AppOpsManager.MODE_ALLOWED) { 453 allowed = true; 454 } 455 } 456 if (allowed) { 457 result.add(new AppVpnInfo(userId, pkg.getPackageName())); 458 } 459 } 460 } 461 462 Collections.sort(result); 463 return result; 464 } 465 loadVpnProfiles(KeyStore keyStore, int... excludeTypes)466 static List<VpnProfile> loadVpnProfiles(KeyStore keyStore, int... excludeTypes) { 467 final ArrayList<VpnProfile> result = Lists.newArrayList(); 468 469 for (String key : keyStore.list(Credentials.VPN)) { 470 final VpnProfile profile = VpnProfile.decode(key, keyStore.get(Credentials.VPN + key)); 471 if (profile != null && !ArrayUtils.contains(excludeTypes, profile.type)) { 472 result.add(profile); 473 } 474 } 475 return result; 476 } 477 } 478