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