1 /* 2 * Copyright (C) 2017 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.car.settings.wifi; 17 18 import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED; 19 20 import android.annotation.DrawableRes; 21 import android.annotation.Nullable; 22 import android.app.admin.DevicePolicyManager; 23 import android.content.ComponentName; 24 import android.content.ContentResolver; 25 import android.content.Context; 26 import android.content.pm.PackageManager; 27 import android.net.NetworkCapabilities; 28 import android.net.NetworkInfo; 29 import android.net.wifi.WifiConfiguration; 30 import android.net.wifi.WifiManager; 31 import android.provider.Settings; 32 import android.text.TextUtils; 33 import android.widget.Toast; 34 35 import androidx.annotation.NonNull; 36 import androidx.annotation.StringRes; 37 38 import com.android.car.settings.R; 39 import com.android.car.settings.common.Logger; 40 import com.android.settingslib.wifi.AccessPoint; 41 42 import java.util.regex.Pattern; 43 44 /** 45 * A collections of util functions for WIFI. 46 */ 47 public class WifiUtil { 48 49 private static final Logger LOG = new Logger(WifiUtil.class); 50 51 /** Value that is returned when we fail to connect wifi. */ 52 public static final int INVALID_NET_ID = -1; 53 private static final Pattern HEX_PATTERN = Pattern.compile("^[0-9A-F]+$"); 54 55 @DrawableRes getIconRes(int state)56 public static int getIconRes(int state) { 57 switch (state) { 58 case WifiManager.WIFI_STATE_ENABLING: 59 case WifiManager.WIFI_STATE_DISABLED: 60 return R.drawable.ic_settings_wifi_disabled; 61 default: 62 return R.drawable.ic_settings_wifi; 63 } 64 } 65 isWifiOn(int state)66 public static boolean isWifiOn(int state) { 67 switch (state) { 68 case WifiManager.WIFI_STATE_ENABLING: 69 case WifiManager.WIFI_STATE_DISABLED: 70 return false; 71 default: 72 return true; 73 } 74 } 75 76 /** 77 * @return 0 if no proper description can be found. 78 */ 79 @StringRes getStateDesc(int state)80 public static Integer getStateDesc(int state) { 81 switch (state) { 82 case WifiManager.WIFI_STATE_ENABLING: 83 return R.string.wifi_starting; 84 case WifiManager.WIFI_STATE_DISABLING: 85 return R.string.wifi_stopping; 86 case WifiManager.WIFI_STATE_DISABLED: 87 return R.string.wifi_disabled; 88 default: 89 return 0; 90 } 91 } 92 93 /** 94 * Returns {@Code true} if wifi is available on this device. 95 */ isWifiAvailable(Context context)96 public static boolean isWifiAvailable(Context context) { 97 return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI); 98 } 99 100 /** 101 * Gets a unique key for a {@link AccessPoint}. 102 */ getKey(AccessPoint accessPoint)103 public static String getKey(AccessPoint accessPoint) { 104 return String.valueOf(accessPoint.hashCode()); 105 } 106 107 /** 108 * This method is a stripped and negated version of WifiConfigStore.canModifyNetwork. 109 * 110 * @param context Context of caller 111 * @param config The WiFi config. 112 * @return {@code true} if Settings cannot modify the config due to lockDown. 113 */ isNetworkLockedDown(Context context, WifiConfiguration config)114 public static boolean isNetworkLockedDown(Context context, WifiConfiguration config) { 115 if (config == null) { 116 return false; 117 } 118 119 final DevicePolicyManager dpm = 120 (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); 121 final PackageManager pm = context.getPackageManager(); 122 123 // Check if device has DPM capability. If it has and dpm is still null, then we 124 // treat this case with suspicion and bail out. 125 if (pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN) && dpm == null) { 126 return true; 127 } 128 129 boolean isConfigEligibleForLockdown = false; 130 if (dpm != null) { 131 final ComponentName deviceOwner = dpm.getDeviceOwnerComponentOnAnyUser(); 132 if (deviceOwner != null) { 133 final int deviceOwnerUserId = dpm.getDeviceOwnerUserId(); 134 try { 135 final int deviceOwnerUid = pm.getPackageUidAsUser(deviceOwner.getPackageName(), 136 deviceOwnerUserId); 137 isConfigEligibleForLockdown = deviceOwnerUid == config.creatorUid; 138 } catch (PackageManager.NameNotFoundException e) { 139 // don't care 140 } 141 } 142 } 143 if (!isConfigEligibleForLockdown) { 144 return false; 145 } 146 147 final ContentResolver resolver = context.getContentResolver(); 148 final boolean isLockdownFeatureEnabled = Settings.Global.getInt(resolver, 149 Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) != 0; 150 return isLockdownFeatureEnabled; 151 } 152 153 /** 154 * Returns {@code true} if the network security type doesn't require authentication. 155 */ isOpenNetwork(int security)156 public static boolean isOpenNetwork(int security) { 157 return security == AccessPoint.SECURITY_NONE || security == AccessPoint.SECURITY_OWE; 158 } 159 160 /** 161 * Returns {@code true} if the provided NetworkCapabilities indicate a captive portal network. 162 */ canSignIntoNetwork(NetworkCapabilities capabilities)163 public static boolean canSignIntoNetwork(NetworkCapabilities capabilities) { 164 return (capabilities != null 165 && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL)); 166 } 167 168 /** 169 * Attempts to connect to a specified access point 170 * @param listener for callbacks on success or failure of connection attempt (can be null) 171 */ connectToAccessPoint(Context context, String ssid, int security, String password, boolean hidden, @Nullable WifiManager.ActionListener listener)172 public static void connectToAccessPoint(Context context, String ssid, int security, 173 String password, boolean hidden, @Nullable WifiManager.ActionListener listener) { 174 WifiManager wifiManager = context.getSystemService(WifiManager.class); 175 WifiConfiguration wifiConfig = getWifiConfig(ssid, security, password, hidden); 176 wifiManager.connect(wifiConfig, listener); 177 } 178 getWifiConfig(String ssid, int security, String password, boolean hidden)179 private static WifiConfiguration getWifiConfig(String ssid, int security, 180 String password, boolean hidden) { 181 WifiConfiguration wifiConfig = new WifiConfiguration(); 182 wifiConfig.SSID = String.format("\"%s\"", ssid); 183 wifiConfig.hiddenSSID = hidden; 184 185 return finishWifiConfig(wifiConfig, security, password); 186 } 187 188 /** Similar to above, but uses AccessPoint to get additional relevant information. */ getWifiConfig(@onNull AccessPoint accessPoint, String password)189 public static WifiConfiguration getWifiConfig(@NonNull AccessPoint accessPoint, 190 String password) { 191 if (accessPoint == null) { 192 throw new IllegalArgumentException("AccessPoint input is required."); 193 } 194 195 WifiConfiguration wifiConfig = new WifiConfiguration(); 196 if (!accessPoint.isSaved()) { 197 wifiConfig.SSID = AccessPoint.convertToQuotedString( 198 accessPoint.getSsidStr()); 199 } else { 200 wifiConfig.networkId = accessPoint.getConfig().networkId; 201 wifiConfig.hiddenSSID = accessPoint.getConfig().hiddenSSID; 202 } 203 204 return finishWifiConfig(wifiConfig, accessPoint.getSecurity(), password); 205 } 206 finishWifiConfig(WifiConfiguration wifiConfig, int security, String password)207 private static WifiConfiguration finishWifiConfig(WifiConfiguration wifiConfig, int security, 208 String password) { 209 switch (security) { 210 case AccessPoint.SECURITY_NONE: 211 wifiConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN); 212 break; 213 case AccessPoint.SECURITY_WEP: 214 wifiConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_WEP); 215 if (!TextUtils.isEmpty(password)) { 216 int length = password.length(); 217 // WEP-40, WEP-104, and 256-bit WEP (WEP-232?) 218 if ((length == 10 || length == 26 || length == 58) 219 && password.matches("[0-9A-Fa-f]*")) { 220 wifiConfig.wepKeys[0] = password; 221 } else { 222 wifiConfig.wepKeys[0] = '"' + password + '"'; 223 } 224 } 225 break; 226 case AccessPoint.SECURITY_PSK: 227 wifiConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK); 228 if (!TextUtils.isEmpty(password)) { 229 if (password.matches("[0-9A-Fa-f]{64}")) { 230 wifiConfig.preSharedKey = password; 231 } else { 232 wifiConfig.preSharedKey = '"' + password + '"'; 233 } 234 } 235 break; 236 case AccessPoint.SECURITY_EAP: 237 case AccessPoint.SECURITY_EAP_SUITE_B: 238 if (security == AccessPoint.SECURITY_EAP_SUITE_B) { 239 // allowedSuiteBCiphers will be set according to certificate type 240 wifiConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B); 241 } else { 242 wifiConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP); 243 } 244 if (!TextUtils.isEmpty(password)) { 245 wifiConfig.enterpriseConfig.setPassword(password); 246 } 247 break; 248 case AccessPoint.SECURITY_SAE: 249 wifiConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE); 250 if (!TextUtils.isEmpty(password)) { 251 wifiConfig.preSharedKey = '"' + password + '"'; 252 } 253 break; 254 case AccessPoint.SECURITY_OWE: 255 wifiConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OWE); 256 break; 257 default: 258 throw new IllegalArgumentException("unknown security type " + security); 259 } 260 return wifiConfig; 261 } 262 263 /** Forget the network specified by {@code accessPoint}. */ forget(Context context, AccessPoint accessPoint)264 public static void forget(Context context, AccessPoint accessPoint) { 265 WifiManager wifiManager = context.getSystemService(WifiManager.class); 266 if (!accessPoint.isSaved()) { 267 if (accessPoint.getNetworkInfo() != null 268 && accessPoint.getNetworkInfo().getState() != NetworkInfo.State.DISCONNECTED) { 269 // Network is active but has no network ID - must be ephemeral. 270 wifiManager.disableEphemeralNetwork( 271 AccessPoint.convertToQuotedString(accessPoint.getSsidStr())); 272 } else { 273 // Should not happen, but a monkey seems to trigger it 274 LOG.e("Failed to forget invalid network " + accessPoint.getConfig()); 275 return; 276 } 277 } else { 278 wifiManager.forget(accessPoint.getConfig().networkId, new WifiManager.ActionListener() { 279 @Override 280 public void onSuccess() { 281 LOG.d("Network successfully forgotten"); 282 } 283 284 @Override 285 public void onFailure(int reason) { 286 LOG.d("Could not forget network. Failure code: " + reason); 287 Toast.makeText(context, R.string.wifi_failed_forget_message, 288 Toast.LENGTH_SHORT).show(); 289 } 290 }); 291 } 292 } 293 294 /** Returns {@code true} if the access point was disabled due to the wrong password. */ isAccessPointDisabledByWrongPassword(AccessPoint accessPoint)295 public static boolean isAccessPointDisabledByWrongPassword(AccessPoint accessPoint) { 296 WifiConfiguration config = accessPoint.getConfig(); 297 if (config == null) { 298 return false; 299 } 300 WifiConfiguration.NetworkSelectionStatus networkStatus = 301 config.getNetworkSelectionStatus(); 302 if (networkStatus == null 303 || networkStatus.getNetworkSelectionStatus() == NETWORK_SELECTION_ENABLED) { 304 return false; 305 } 306 return networkStatus.getNetworkSelectionDisableReason() 307 == WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD; 308 } 309 isHexString(String password)310 private static boolean isHexString(String password) { 311 return HEX_PATTERN.matcher(password).matches(); 312 } 313 } 314