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