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