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