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.ConnectivityManager; 28 import android.net.NetworkCapabilities; 29 import android.net.NetworkScoreManager; 30 import android.net.wifi.ScanResult; 31 import android.net.wifi.WifiConfiguration; 32 import android.net.wifi.WifiManager; 33 import android.os.Handler; 34 import android.os.SimpleClock; 35 import android.provider.Settings; 36 import android.text.TextUtils; 37 38 import androidx.annotation.NonNull; 39 import androidx.annotation.StringRes; 40 import androidx.lifecycle.Lifecycle; 41 42 import com.android.car.settings.R; 43 import com.android.car.settings.common.Logger; 44 import com.android.wifitrackerlib.NetworkDetailsTracker; 45 import com.android.wifitrackerlib.WifiEntry; 46 import com.android.wifitrackerlib.WifiPickerTracker; 47 48 import java.time.Clock; 49 import java.time.ZoneOffset; 50 import java.util.regex.Pattern; 51 52 /** 53 * A collections of util functions for WIFI. 54 */ 55 public class WifiUtil { 56 57 private static final Logger LOG = new Logger(WifiUtil.class); 58 59 /** Value that is returned when we fail to connect wifi. */ 60 public static final int INVALID_NET_ID = -1; 61 /** Max age of tracked WifiEntries. */ 62 private static final long DEFAULT_MAX_SCAN_AGE_MILLIS = 15_000; 63 /** Interval between initiating WifiPickerTracker scans. */ 64 private static final long DEFAULT_SCAN_INTERVAL_MILLIS = 10_000; 65 private static final Pattern HEX_PATTERN = Pattern.compile("^[0-9A-F]+$"); 66 67 /** Clock used for evaluating the age of WiFi scans */ 68 private static final Clock ELAPSED_REALTIME_CLOCK = new SimpleClock(ZoneOffset.UTC) { 69 @Override 70 public long millis() { 71 return android.os.SystemClock.elapsedRealtime(); 72 } 73 }; 74 75 @DrawableRes getIconRes(int state)76 public static int getIconRes(int state) { 77 switch (state) { 78 case WifiManager.WIFI_STATE_ENABLING: 79 case WifiManager.WIFI_STATE_DISABLED: 80 return R.drawable.ic_settings_wifi_disabled; 81 default: 82 return R.drawable.ic_settings_wifi; 83 } 84 } 85 isWifiOn(int state)86 public static boolean isWifiOn(int state) { 87 switch (state) { 88 case WifiManager.WIFI_STATE_ENABLING: 89 case WifiManager.WIFI_STATE_DISABLED: 90 return false; 91 default: 92 return true; 93 } 94 } 95 96 /** 97 * @return 0 if no proper description can be found. 98 */ 99 @StringRes getStateDesc(int state)100 public static Integer getStateDesc(int state) { 101 switch (state) { 102 case WifiManager.WIFI_STATE_ENABLING: 103 return R.string.wifi_starting; 104 case WifiManager.WIFI_STATE_DISABLING: 105 return R.string.wifi_stopping; 106 case WifiManager.WIFI_STATE_DISABLED: 107 return R.string.wifi_disabled; 108 default: 109 return 0; 110 } 111 } 112 113 /** 114 * Returns {@Code true} if wifi is available on this device. 115 */ isWifiAvailable(Context context)116 public static boolean isWifiAvailable(Context context) { 117 return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI); 118 } 119 120 /** 121 * Gets a unique key for a {@link WifiEntry}. 122 */ getKey(WifiEntry wifiEntry)123 public static String getKey(WifiEntry wifiEntry) { 124 return String.valueOf(wifiEntry.hashCode()); 125 } 126 127 /** 128 * This method is a stripped and negated version of WifiConfigStore.canModifyNetwork. 129 * 130 * @param context Context of caller 131 * @param config The WiFi config. 132 * @return {@code true} if Settings cannot modify the config due to lockDown. 133 */ isNetworkLockedDown(Context context, WifiConfiguration config)134 public static boolean isNetworkLockedDown(Context context, WifiConfiguration config) { 135 if (config == null) { 136 return false; 137 } 138 139 final DevicePolicyManager dpm = 140 (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); 141 final PackageManager pm = context.getPackageManager(); 142 143 // Check if device has DPM capability. If it has and dpm is still null, then we 144 // treat this case with suspicion and bail out. 145 if (pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN) && dpm == null) { 146 return true; 147 } 148 149 boolean isConfigEligibleForLockdown = false; 150 if (dpm != null) { 151 final ComponentName deviceOwner = dpm.getDeviceOwnerComponentOnAnyUser(); 152 if (deviceOwner != null) { 153 final int deviceOwnerUserId = dpm.getDeviceOwnerUserId(); 154 try { 155 final int deviceOwnerUid = pm.getPackageUidAsUser(deviceOwner.getPackageName(), 156 deviceOwnerUserId); 157 isConfigEligibleForLockdown = deviceOwnerUid == config.creatorUid; 158 } catch (PackageManager.NameNotFoundException e) { 159 // don't care 160 } 161 } 162 } 163 if (!isConfigEligibleForLockdown) { 164 return false; 165 } 166 167 final ContentResolver resolver = context.getContentResolver(); 168 final boolean isLockdownFeatureEnabled = Settings.Global.getInt(resolver, 169 Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) != 0; 170 return isLockdownFeatureEnabled; 171 } 172 173 /** 174 * Returns {@code true} if the network security type doesn't require authentication. 175 */ isOpenNetwork(int security)176 public static boolean isOpenNetwork(int security) { 177 return security == WifiEntry.SECURITY_NONE || security == WifiEntry.SECURITY_OWE; 178 } 179 180 /** 181 * Returns {@code true} if the provided NetworkCapabilities indicate a captive portal network. 182 */ canSignIntoNetwork(NetworkCapabilities capabilities)183 public static boolean canSignIntoNetwork(NetworkCapabilities capabilities) { 184 return (capabilities != null 185 && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL)); 186 } 187 188 /** 189 * Attempts to connect to a specified Wi-Fi entry. 190 * 191 * @param listener for callbacks on success or failure of connection attempt (can be null) 192 */ connectToWifiEntry(Context context, String ssid, int security, String password, boolean hidden, @Nullable WifiManager.ActionListener listener)193 public static void connectToWifiEntry(Context context, String ssid, int security, 194 String password, boolean hidden, @Nullable WifiManager.ActionListener listener) { 195 WifiManager wifiManager = context.getSystemService(WifiManager.class); 196 WifiConfiguration wifiConfig = getWifiConfig(ssid, security, password, hidden); 197 wifiManager.connect(wifiConfig, listener); 198 } 199 getWifiConfig(String ssid, int security, String password, boolean hidden)200 private static WifiConfiguration getWifiConfig(String ssid, int security, 201 String password, boolean hidden) { 202 WifiConfiguration wifiConfig = new WifiConfiguration(); 203 wifiConfig.SSID = String.format("\"%s\"", ssid); 204 wifiConfig.hiddenSSID = hidden; 205 206 return finishWifiConfig(wifiConfig, security, password); 207 } 208 209 /** Similar to above, but uses WifiEntry to get additional relevant information. */ getWifiConfig(@onNull WifiEntry wifiEntry, String password)210 public static WifiConfiguration getWifiConfig(@NonNull WifiEntry wifiEntry, 211 String password) { 212 WifiConfiguration wifiConfig = new WifiConfiguration(); 213 if (wifiEntry.getWifiConfiguration() == null) { 214 wifiConfig.SSID = "\"" + wifiEntry.getSsid() + "\""; 215 } else { 216 wifiConfig.networkId = wifiEntry.getWifiConfiguration().networkId; 217 wifiConfig.hiddenSSID = wifiEntry.getWifiConfiguration().hiddenSSID; 218 } 219 220 return finishWifiConfig(wifiConfig, wifiEntry.getSecurity(), password); 221 } 222 finishWifiConfig(WifiConfiguration wifiConfig, int security, String password)223 private static WifiConfiguration finishWifiConfig(WifiConfiguration wifiConfig, int security, 224 String password) { 225 switch (security) { 226 case WifiEntry.SECURITY_NONE: 227 wifiConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN); 228 break; 229 case WifiEntry.SECURITY_WEP: 230 wifiConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_WEP); 231 if (!TextUtils.isEmpty(password)) { 232 int length = password.length(); 233 // WEP-40, WEP-104, and 256-bit WEP (WEP-232?) 234 if ((length == 10 || length == 26 || length == 58) 235 && password.matches("[0-9A-Fa-f]*")) { 236 wifiConfig.wepKeys[0] = password; 237 } else { 238 wifiConfig.wepKeys[0] = '"' + password + '"'; 239 } 240 } 241 break; 242 case WifiEntry.SECURITY_PSK: 243 wifiConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK); 244 if (!TextUtils.isEmpty(password)) { 245 if (password.matches("[0-9A-Fa-f]{64}")) { 246 wifiConfig.preSharedKey = password; 247 } else { 248 wifiConfig.preSharedKey = '"' + password + '"'; 249 } 250 } 251 break; 252 case WifiEntry.SECURITY_EAP: 253 case WifiEntry.SECURITY_EAP_SUITE_B: 254 if (security == WifiEntry.SECURITY_EAP_SUITE_B) { 255 // allowedSuiteBCiphers will be set according to certificate type 256 wifiConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B); 257 } else { 258 wifiConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP); 259 } 260 if (!TextUtils.isEmpty(password)) { 261 wifiConfig.enterpriseConfig.setPassword(password); 262 } 263 break; 264 case WifiEntry.SECURITY_SAE: 265 wifiConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE); 266 if (!TextUtils.isEmpty(password)) { 267 wifiConfig.preSharedKey = '"' + password + '"'; 268 } 269 break; 270 case WifiEntry.SECURITY_OWE: 271 wifiConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OWE); 272 break; 273 default: 274 throw new IllegalArgumentException("unknown security type " + security); 275 } 276 return wifiConfig; 277 } 278 279 /** Returns {@code true} if the Wi-Fi entry is connected or connecting. */ isWifiEntryConnectedOrConnecting(WifiEntry wifiEntry)280 public static boolean isWifiEntryConnectedOrConnecting(WifiEntry wifiEntry) { 281 if (wifiEntry == null) { 282 return false; 283 } 284 return wifiEntry.getConnectedState() != WifiEntry.CONNECTED_STATE_DISCONNECTED; 285 } 286 287 /** Returns {@code true} if the Wi-Fi entry was disabled due to the wrong password. */ isWifiEntryDisabledByWrongPassword(WifiEntry wifiEntry)288 public static boolean isWifiEntryDisabledByWrongPassword(WifiEntry wifiEntry) { 289 WifiConfiguration config = wifiEntry.getWifiConfiguration(); 290 if (config == null) { 291 return false; 292 } 293 WifiConfiguration.NetworkSelectionStatus networkStatus = 294 config.getNetworkSelectionStatus(); 295 if (networkStatus == null 296 || networkStatus.getNetworkSelectionStatus() == NETWORK_SELECTION_ENABLED) { 297 return false; 298 } 299 return networkStatus.getNetworkSelectionDisableReason() 300 == WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD; 301 } 302 isHexString(String password)303 private static boolean isHexString(String password) { 304 return HEX_PATTERN.matcher(password).matches(); 305 } 306 307 /** 308 * Gets the security value from a ScanResult. 309 * 310 * @return related security value based on {@link WifiEntry} 311 */ getWifiEntrySecurity(ScanResult result)312 public static int getWifiEntrySecurity(ScanResult result) { 313 if (result.capabilities.contains("WEP")) { 314 return WifiEntry.SECURITY_WEP; 315 } else if (result.capabilities.contains("SAE")) { 316 return WifiEntry.SECURITY_SAE; 317 } else if (result.capabilities.contains("PSK")) { 318 return WifiEntry.SECURITY_PSK; 319 } else if (result.capabilities.contains("EAP_SUITE_B_192")) { 320 return WifiEntry.SECURITY_EAP_SUITE_B; 321 } else if (result.capabilities.contains("EAP")) { 322 return WifiEntry.SECURITY_EAP; 323 } else if (result.capabilities.contains("OWE")) { 324 return WifiEntry.SECURITY_OWE; 325 } 326 return WifiEntry.SECURITY_NONE; 327 } 328 329 /** 330 * Creates an instance of WifiPickerTracker using the default MAX_SCAN_AGE and 331 * SCAN_INTERVAL values. 332 */ createWifiPickerTracker( Lifecycle lifecycle, Context context, Handler mainHandler, Handler workerHandler, WifiPickerTracker.WifiPickerTrackerCallback listener)333 public static WifiPickerTracker createWifiPickerTracker( 334 Lifecycle lifecycle, Context context, 335 Handler mainHandler, Handler workerHandler, 336 WifiPickerTracker.WifiPickerTrackerCallback listener) { 337 return createWifiPickerTracker(lifecycle, context, mainHandler, workerHandler, 338 DEFAULT_MAX_SCAN_AGE_MILLIS, DEFAULT_SCAN_INTERVAL_MILLIS, listener); 339 } 340 341 /** 342 * Creates an instance of WifiPickerTracker. 343 */ createWifiPickerTracker( Lifecycle lifecycle, Context context, Handler mainHandler, Handler workerHandler, long maxScanAgeMillis, long scanIntervalMillis, WifiPickerTracker.WifiPickerTrackerCallback listener)344 public static WifiPickerTracker createWifiPickerTracker( 345 Lifecycle lifecycle, Context context, 346 Handler mainHandler, Handler workerHandler, 347 long maxScanAgeMillis, long scanIntervalMillis, 348 WifiPickerTracker.WifiPickerTrackerCallback listener) { 349 return new WifiPickerTracker( 350 lifecycle, context, 351 context.getSystemService(WifiManager.class), 352 context.getSystemService(ConnectivityManager.class), 353 context.getSystemService(NetworkScoreManager.class), 354 mainHandler, workerHandler, ELAPSED_REALTIME_CLOCK, 355 maxScanAgeMillis, scanIntervalMillis, 356 listener); 357 } 358 359 /** 360 * Creates an instance of NetworkDetailsTracker using the default MAX_SCAN_AGE and 361 * SCAN_INTERVAL values. 362 */ createNetworkDetailsTracker( Lifecycle lifecycle, Context context, Handler mainHandler, Handler workerHandler, String key)363 public static NetworkDetailsTracker createNetworkDetailsTracker( 364 Lifecycle lifecycle, Context context, 365 Handler mainHandler, Handler workerHandler, 366 String key) { 367 return createNetworkDetailsTracker(lifecycle, context, mainHandler, workerHandler, 368 DEFAULT_MAX_SCAN_AGE_MILLIS, DEFAULT_SCAN_INTERVAL_MILLIS, key); 369 } 370 371 /** 372 * Creates an instance of NetworkDetailsTracker. 373 */ createNetworkDetailsTracker( Lifecycle lifecycle, Context context, Handler mainHandler, Handler workerHandler, long maxScanAgeMillis, long scanIntervalMillis, String key)374 public static NetworkDetailsTracker createNetworkDetailsTracker( 375 Lifecycle lifecycle, Context context, 376 Handler mainHandler, Handler workerHandler, 377 long maxScanAgeMillis, long scanIntervalMillis, 378 String key) { 379 return NetworkDetailsTracker.createNetworkDetailsTracker( 380 lifecycle, context, 381 context.getSystemService(WifiManager.class), 382 context.getSystemService(ConnectivityManager.class), 383 context.getSystemService(NetworkScoreManager.class), 384 mainHandler, workerHandler, ELAPSED_REALTIME_CLOCK, 385 maxScanAgeMillis, scanIntervalMillis, 386 key); 387 } 388 } 389