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