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