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