• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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 package com.android.settings.network;
17 
18 import android.content.Context;
19 import android.content.pm.PackageManager;
20 import android.content.pm.UserInfo;
21 import android.net.ConnectivityManager;
22 import android.net.Network;
23 import android.net.NetworkCapabilities;
24 import android.net.NetworkRequest;
25 import android.net.VpnManager;
26 import android.os.UserHandle;
27 import android.os.UserManager;
28 import android.provider.Settings;
29 import android.security.Credentials;
30 import android.security.LegacyVpnProfileStore;
31 import android.util.Log;
32 
33 import androidx.annotation.VisibleForTesting;
34 import androidx.preference.Preference;
35 import androidx.preference.PreferenceScreen;
36 
37 import com.android.internal.net.LegacyVpnInfo;
38 import com.android.internal.net.VpnConfig;
39 import com.android.internal.net.VpnProfile;
40 import com.android.settings.R;
41 import com.android.settings.core.PreferenceControllerMixin;
42 import com.android.settings.vpn2.VpnInfoPreference;
43 import com.android.settingslib.RestrictedLockUtilsInternal;
44 import com.android.settingslib.core.AbstractPreferenceController;
45 import com.android.settingslib.core.lifecycle.LifecycleObserver;
46 import com.android.settingslib.core.lifecycle.events.OnPause;
47 import com.android.settingslib.core.lifecycle.events.OnResume;
48 import com.android.settingslib.utils.ThreadUtils;
49 
50 import java.util.Arrays;
51 import java.util.function.Function;
52 
53 public class VpnPreferenceController extends AbstractPreferenceController
54         implements PreferenceControllerMixin, LifecycleObserver, OnResume, OnPause {
55 
56     private static final String KEY_VPN_SETTINGS = "vpn_settings";
57     private static final NetworkRequest REQUEST = new NetworkRequest.Builder()
58             .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
59             .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
60             .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
61             .build();
62     private static final String TAG = "VpnPreferenceController";
63 
64     private ConnectivityManager mConnectivityManager;
65     private Preference mPreference;
66 
VpnPreferenceController(Context context)67     public VpnPreferenceController(Context context) {
68         super(context);
69     }
70 
71     @Override
displayPreference(PreferenceScreen screen)72     public void displayPreference(PreferenceScreen screen) {
73         super.displayPreference(screen);
74         mPreference = getEffectivePreference(screen);
75     }
76 
77     @VisibleForTesting
getEffectivePreference(PreferenceScreen screen)78     protected Preference getEffectivePreference(PreferenceScreen screen) {
79         Preference preference = screen.findPreference(KEY_VPN_SETTINGS);
80         if (preference == null) {
81             return null;
82         }
83         String toggleable = Settings.Global.getString(mContext.getContentResolver(),
84                 Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS);
85         // Manually set dependencies for Wifi when not toggleable.
86         if (toggleable == null || !toggleable.contains(Settings.Global.RADIO_WIFI)) {
87             preference.setDependency(Settings.Global.AIRPLANE_MODE_ON);
88         }
89         return preference;
90     }
91 
92     @Override
isAvailable()93     public boolean isAvailable() {
94         return !RestrictedLockUtilsInternal.hasBaseUserRestriction(mContext,
95                 UserManager.DISALLOW_CONFIG_VPN, UserHandle.myUserId());
96     }
97 
98     @Override
getPreferenceKey()99     public String getPreferenceKey() {
100         return KEY_VPN_SETTINGS;
101     }
102 
103     @Override
onPause()104     public void onPause() {
105         if (mConnectivityManager != null) {
106             mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
107             mConnectivityManager = null;
108         }
109     }
110 
111     @Override
onResume()112     public void onResume() {
113         if (isAvailable()) {
114             mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
115             mConnectivityManager.registerNetworkCallback(REQUEST, mNetworkCallback);
116         } else {
117             mConnectivityManager = null;
118         }
119     }
120 
121     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
updateSummary()122     void updateSummary() {
123         if (mPreference == null) {
124             return;
125         }
126         UserManager userManager = mContext.getSystemService(UserManager.class);
127         VpnManager vpnManager = mContext.getSystemService(VpnManager.class);
128         String summary = getInsecureVpnSummaryOverride(userManager, vpnManager);
129         if (summary == null) {
130             final UserInfo userInfo = userManager.getUserInfo(UserHandle.myUserId());
131             final int uid;
132             if (userInfo.isRestricted()) {
133                 uid = userInfo.restrictedProfileParentId;
134             } else {
135                 uid = userInfo.id;
136             }
137             VpnConfig vpn = vpnManager.getVpnConfig(uid);
138             if ((vpn != null) && vpn.legacy) {
139                 // Copied from SystemUI::SecurityControllerImpl
140                 // Legacy VPNs should do nothing if the network is disconnected. Third-party
141                 // VPN warnings need to continue as traffic can still go to the app.
142                 final LegacyVpnInfo legacyVpn = vpnManager.getLegacyVpnInfo(uid);
143                 if (legacyVpn == null || legacyVpn.state != LegacyVpnInfo.STATE_CONNECTED) {
144                     vpn = null;
145                 }
146             }
147             if (vpn == null) {
148                 summary = mContext.getString(R.string.vpn_disconnected_summary);
149             } else {
150                 summary = getNameForVpnConfig(vpn, UserHandle.of(uid));
151             }
152         }
153         final String finalSummary = summary;
154         ThreadUtils.postOnMainThread(() -> mPreference.setSummary(finalSummary));
155     }
156 
getNumberOfNonLegacyVpn(UserManager userManager, VpnManager vpnManager)157     protected int getNumberOfNonLegacyVpn(UserManager userManager, VpnManager vpnManager) {
158         // Converted from SystemUI::SecurityControllerImpl
159         return (int) userManager.getUsers().stream()
160                 .map(user -> vpnManager.getVpnConfig(user.id))
161                 .filter(cfg -> (cfg != null) && (!cfg.legacy))
162                 .count();
163     }
164 
getInsecureVpnSummaryOverride(UserManager userManager, VpnManager vpnManager)165     protected String getInsecureVpnSummaryOverride(UserManager userManager,
166             VpnManager vpnManager) {
167         // Optionally add warning icon if an insecure VPN is present.
168         if (mPreference instanceof VpnInfoPreference) {
169             String [] legacyVpnProfileKeys = LegacyVpnProfileStore.list(Credentials.VPN);
170             final int insecureVpnCount = getInsecureVpnCount(legacyVpnProfileKeys);
171             boolean isInsecureVPN = insecureVpnCount > 0;
172             ((VpnInfoPreference) mPreference).setInsecureVpn(isInsecureVPN);
173 
174             // Set the summary based on the total number of VPNs and insecure VPNs.
175             if (isInsecureVPN) {
176                 // Add the users and the number of legacy vpns to determine if there is more than
177                 // one vpn, since there can be more than one VPN per user.
178                 int vpnCount = legacyVpnProfileKeys.length;
179                 if (vpnCount <= 1) {
180                     vpnCount += getNumberOfNonLegacyVpn(userManager, vpnManager);
181                     if (vpnCount == 1) {
182                         return mContext.getString(R.string.vpn_settings_insecure_single);
183                     }
184                 }
185                 if (insecureVpnCount == 1) {
186                     return mContext.getString(
187                             R.string.vpn_settings_single_insecure_multiple_total,
188                             insecureVpnCount);
189                 } else {
190                     return mContext.getString(
191                             R.string.vpn_settings_multiple_insecure_multiple_total,
192                             insecureVpnCount);
193                 }
194             }
195         }
196         return null;
197     }
198 
199     @VisibleForTesting
getNameForVpnConfig(VpnConfig cfg, UserHandle user)200     String getNameForVpnConfig(VpnConfig cfg, UserHandle user) {
201         if (cfg.legacy) {
202             return mContext.getString(R.string.wifi_display_status_connected);
203         }
204         // The package name for an active VPN is stored in the 'user' field of its VpnConfig
205         final String vpnPackage = cfg.user;
206         try {
207             Context userContext = mContext.createPackageContextAsUser(mContext.getPackageName(),
208                     0 /* flags */, user);
209             return VpnConfig.getVpnLabel(userContext, vpnPackage).toString();
210         } catch (PackageManager.NameNotFoundException nnfe) {
211             Log.e(TAG, "Package " + vpnPackage + " is not present", nnfe);
212             return null;
213         }
214     }
215 
216     @VisibleForTesting
getInsecureVpnCount(String [] legacyVpnProfileKeys)217     protected int getInsecureVpnCount(String [] legacyVpnProfileKeys) {
218         final Function<String, VpnProfile> keyToProfile = key ->
219                 VpnProfile.decode(key, LegacyVpnProfileStore.get(Credentials.VPN + key));
220         return (int) Arrays.stream(legacyVpnProfileKeys)
221                 .map(keyToProfile)
222                 // Return whether any profile is an insecure type.
223                 .filter(profile -> VpnProfile.isLegacyType(profile.type))
224                 .count();
225     }
226 
227     // Copied from SystemUI::SecurityControllerImpl
228     private final ConnectivityManager.NetworkCallback
229             mNetworkCallback = new ConnectivityManager.NetworkCallback() {
230         @Override
231         public void onAvailable(Network network) {
232             updateSummary();
233         }
234 
235         @Override
236         public void onLost(Network network) {
237             updateSummary();
238         }
239     };
240 }
241