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