1 /* 2 * Copyright (C) 2018 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 17 package com.android.settings.wifi.dpp; 18 19 import android.app.KeyguardManager; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.hardware.biometrics.BiometricPrompt; 23 import android.net.wifi.SoftApConfiguration; 24 import android.net.wifi.WifiConfiguration; 25 import android.net.wifi.WifiConfiguration.KeyMgmt; 26 import android.net.wifi.WifiManager; 27 import android.os.CancellationSignal; 28 import android.os.Handler; 29 import android.os.Looper; 30 import android.os.VibrationEffect; 31 import android.os.Vibrator; 32 import android.text.TextUtils; 33 34 import com.android.settings.R; 35 import com.android.settingslib.wifi.AccessPoint; 36 import com.android.wifitrackerlib.WifiEntry; 37 38 import java.time.Duration; 39 import java.util.List; 40 41 /** 42 * Here are the items shared by both WifiDppConfiguratorActivity & WifiDppEnrolleeActivity 43 * 44 * @see WifiQrCode 45 */ 46 public class WifiDppUtils { 47 /** 48 * The fragment tag specified to FragmentManager for container activities to manage fragments. 49 */ 50 static final String TAG_FRAGMENT_QR_CODE_SCANNER = "qr_code_scanner_fragment"; 51 52 /** 53 * @see #TAG_FRAGMENT_QR_CODE_SCANNER 54 */ 55 static final String TAG_FRAGMENT_QR_CODE_GENERATOR = "qr_code_generator_fragment"; 56 57 /** 58 * @see #TAG_FRAGMENT_QR_CODE_SCANNER 59 */ 60 static final String TAG_FRAGMENT_CHOOSE_SAVED_WIFI_NETWORK = 61 "choose_saved_wifi_network_fragment"; 62 63 /** 64 * @see #TAG_FRAGMENT_QR_CODE_SCANNER 65 */ 66 static final String TAG_FRAGMENT_ADD_DEVICE = "add_device_fragment"; 67 68 /** The data is one of the static String SECURITY_* in {@link WifiQrCode} */ 69 static final String EXTRA_WIFI_SECURITY = "security"; 70 71 /** The data corresponding to {@code WifiConfiguration} SSID */ 72 static final String EXTRA_WIFI_SSID = "ssid"; 73 74 /** The data corresponding to {@code WifiConfiguration} preSharedKey */ 75 static final String EXTRA_WIFI_PRE_SHARED_KEY = "preSharedKey"; 76 77 /** The data corresponding to {@code WifiConfiguration} hiddenSSID */ 78 static final String EXTRA_WIFI_HIDDEN_SSID = "hiddenSsid"; 79 80 /** The data corresponding to {@code WifiConfiguration} networkId */ 81 static final String EXTRA_WIFI_NETWORK_ID = "networkId"; 82 83 /** The data to recognize if it's a Wi-Fi hotspot for configuration */ 84 static final String EXTRA_IS_HOTSPOT = "isHotspot"; 85 86 /** 87 * Default status code for Easy Connect 88 */ 89 static final int EASY_CONNECT_EVENT_FAILURE_NONE = 0; 90 91 /** 92 * Success status code for Easy Connect. 93 */ 94 static final int EASY_CONNECT_EVENT_SUCCESS = 1; 95 96 private static final Duration VIBRATE_DURATION_QR_CODE_RECOGNITION = Duration.ofMillis(3); 97 98 /** 99 * Returns whether the device support WiFi DPP. 100 */ isWifiDppEnabled(Context context)101 static boolean isWifiDppEnabled(Context context) { 102 final WifiManager manager = context.getSystemService(WifiManager.class); 103 return manager.isEasyConnectSupported(); 104 } 105 106 /** 107 * Returns an intent to launch QR code scanner for Wi-Fi DPP enrollee. 108 * 109 * After enrollee success, the callee activity will return connecting WifiConfiguration by 110 * putExtra {@code WifiDialogActivity.KEY_WIFI_CONFIGURATION} for 111 * {@code Activity#setResult(int resultCode, Intent data)}. The calling object should check 112 * if it's available before using it. 113 * 114 * @param ssid The data corresponding to {@code WifiConfiguration} SSID 115 * @return Intent for launching QR code scanner 116 */ getEnrolleeQrCodeScannerIntent(String ssid)117 public static Intent getEnrolleeQrCodeScannerIntent(String ssid) { 118 final Intent intent = new Intent( 119 WifiDppEnrolleeActivity.ACTION_ENROLLEE_QR_CODE_SCANNER); 120 if (!TextUtils.isEmpty(ssid)) { 121 intent.putExtra(EXTRA_WIFI_SSID, ssid); 122 } 123 return intent; 124 } 125 getPresharedKey(WifiManager wifiManager, WifiConfiguration wifiConfiguration)126 private static String getPresharedKey(WifiManager wifiManager, 127 WifiConfiguration wifiConfiguration) { 128 final List<WifiConfiguration> privilegedWifiConfigurations = 129 wifiManager.getPrivilegedConfiguredNetworks(); 130 131 for (WifiConfiguration privilegedWifiConfiguration : privilegedWifiConfigurations) { 132 if (privilegedWifiConfiguration.networkId == wifiConfiguration.networkId) { 133 // WEP uses a shared key hence the AuthAlgorithm.SHARED is used 134 // to identify it. 135 if (wifiConfiguration.allowedKeyManagement.get(KeyMgmt.NONE) 136 && wifiConfiguration.allowedAuthAlgorithms.get( 137 WifiConfiguration.AuthAlgorithm.SHARED)) { 138 return privilegedWifiConfiguration 139 .wepKeys[privilegedWifiConfiguration.wepTxKeyIndex]; 140 } else { 141 return privilegedWifiConfiguration.preSharedKey; 142 } 143 } 144 } 145 return wifiConfiguration.preSharedKey; 146 } 147 removeFirstAndLastDoubleQuotes(String str)148 static String removeFirstAndLastDoubleQuotes(String str) { 149 if (TextUtils.isEmpty(str)) { 150 return str; 151 } 152 153 int begin = 0; 154 int end = str.length() - 1; 155 if (str.charAt(begin) == '\"') { 156 begin++; 157 } 158 if (str.charAt(end) == '\"') { 159 end--; 160 } 161 return str.substring(begin, end+1); 162 } 163 getSecurityString(WifiConfiguration config)164 static String getSecurityString(WifiConfiguration config) { 165 if (config.allowedKeyManagement.get(KeyMgmt.SAE)) { 166 return WifiQrCode.SECURITY_SAE; 167 } 168 if (config.allowedKeyManagement.get(KeyMgmt.OWE)) { 169 return WifiQrCode.SECURITY_NO_PASSWORD; 170 } 171 if (config.allowedKeyManagement.get(KeyMgmt.WPA_PSK) || 172 config.allowedKeyManagement.get(KeyMgmt.WPA2_PSK)) { 173 return WifiQrCode.SECURITY_WPA_PSK; 174 } 175 return (config.wepKeys[0] == null) ? 176 WifiQrCode.SECURITY_NO_PASSWORD : WifiQrCode.SECURITY_WEP; 177 } 178 getSecurityString(WifiEntry wifiEntry)179 static String getSecurityString(WifiEntry wifiEntry) { 180 final int security = wifiEntry.getSecurity(); 181 switch (security) { 182 case WifiEntry.SECURITY_SAE: 183 return WifiQrCode.SECURITY_SAE; 184 case WifiEntry.SECURITY_PSK: 185 return WifiQrCode.SECURITY_WPA_PSK; 186 case WifiEntry.SECURITY_WEP: 187 return WifiQrCode.SECURITY_WEP; 188 case WifiEntry.SECURITY_OWE: 189 case WifiEntry.SECURITY_NONE: 190 default: 191 return WifiQrCode.SECURITY_NO_PASSWORD; 192 } 193 } 194 195 /** 196 * Returns an intent to launch QR code generator. It may return null if the security is not 197 * supported by QR code generator. 198 * 199 * Do not use this method for Wi-Fi hotspot network, use 200 * {@code getHotspotConfiguratorIntentOrNull} instead. 201 * 202 * @param context The context to use for the content resolver 203 * @param wifiManager An instance of {@link WifiManager} 204 * @param accessPoint An instance of {@link AccessPoint} 205 * @return Intent for launching QR code generator 206 */ getConfiguratorQrCodeGeneratorIntentOrNull(Context context, WifiManager wifiManager, AccessPoint accessPoint)207 public static Intent getConfiguratorQrCodeGeneratorIntentOrNull(Context context, 208 WifiManager wifiManager, AccessPoint accessPoint) { 209 final Intent intent = new Intent(context, WifiDppConfiguratorActivity.class); 210 if (isSupportConfiguratorQrCodeGenerator(context, accessPoint)) { 211 intent.setAction(WifiDppConfiguratorActivity.ACTION_CONFIGURATOR_QR_CODE_GENERATOR); 212 } else { 213 return null; 214 } 215 216 final WifiConfiguration wifiConfiguration = accessPoint.getConfig(); 217 setConfiguratorIntentExtra(intent, wifiManager, wifiConfiguration); 218 219 // For a transition mode Wi-Fi AP, creates a QR code that's compatible with more devices 220 if (accessPoint.isPskSaeTransitionMode()) { 221 intent.putExtra(EXTRA_WIFI_SECURITY, WifiQrCode.SECURITY_WPA_PSK); 222 } 223 224 return intent; 225 } 226 227 /** 228 * Returns an intent to launch QR code generator. It may return null if the security is not 229 * supported by QR code generator. 230 * 231 * Do not use this method for Wi-Fi hotspot network, use 232 * {@code getHotspotConfiguratorIntentOrNull} instead. 233 * 234 * @param context The context to use for the content resolver 235 * @param wifiManager An instance of {@link WifiManager} 236 * @param wifiEntry An instance of {@link WifiEntry} 237 * @return Intent for launching QR code generator 238 */ getConfiguratorQrCodeGeneratorIntentOrNull(Context context, WifiManager wifiManager, WifiEntry wifiEntry)239 public static Intent getConfiguratorQrCodeGeneratorIntentOrNull(Context context, 240 WifiManager wifiManager, WifiEntry wifiEntry) { 241 final Intent intent = new Intent(context, WifiDppConfiguratorActivity.class); 242 if (wifiEntry.canShare()) { 243 intent.setAction(WifiDppConfiguratorActivity.ACTION_CONFIGURATOR_QR_CODE_GENERATOR); 244 } else { 245 return null; 246 } 247 248 final WifiConfiguration wifiConfiguration = wifiEntry.getWifiConfiguration(); 249 setConfiguratorIntentExtra(intent, wifiManager, wifiConfiguration); 250 251 return intent; 252 } 253 254 /** 255 * Returns an intent to launch QR code scanner. It may return null if the security is not 256 * supported by QR code scanner. 257 * 258 * @param context The context to use for the content resolver 259 * @param wifiManager An instance of {@link WifiManager} 260 * @param wifiEntry An instance of {@link WifiEntry} 261 * @return Intent for launching QR code scanner 262 */ getConfiguratorQrCodeScannerIntentOrNull(Context context, WifiManager wifiManager, WifiEntry wifiEntry)263 public static Intent getConfiguratorQrCodeScannerIntentOrNull(Context context, 264 WifiManager wifiManager, WifiEntry wifiEntry) { 265 final Intent intent = new Intent(context, WifiDppConfiguratorActivity.class); 266 if (wifiEntry.canEasyConnect()) { 267 intent.setAction(WifiDppConfiguratorActivity.ACTION_CONFIGURATOR_QR_CODE_SCANNER); 268 } else { 269 return null; 270 } 271 272 final WifiConfiguration wifiConfiguration = wifiEntry.getWifiConfiguration(); 273 setConfiguratorIntentExtra(intent, wifiManager, wifiConfiguration); 274 275 if (wifiConfiguration.networkId == WifiConfiguration.INVALID_NETWORK_ID) { 276 throw new IllegalArgumentException("Invalid network ID"); 277 } else { 278 intent.putExtra(EXTRA_WIFI_NETWORK_ID, wifiConfiguration.networkId); 279 } 280 281 return intent; 282 } 283 284 /** 285 * Returns an intent to launch QR code generator for the Wi-Fi hotspot. It may return null if 286 * the security is not supported by QR code generator. 287 * 288 * @param context The context to use for the content resolver 289 * @param wifiManager An instance of {@link WifiManager} 290 * @param softApConfiguration {@link SoftApConfiguration} of the Wi-Fi hotspot 291 * @return Intent for launching QR code generator 292 */ getHotspotConfiguratorIntentOrNull(Context context, WifiManager wifiManager, SoftApConfiguration softApConfiguration)293 public static Intent getHotspotConfiguratorIntentOrNull(Context context, 294 WifiManager wifiManager, SoftApConfiguration softApConfiguration) { 295 final Intent intent = new Intent(context, WifiDppConfiguratorActivity.class); 296 if (isSupportHotspotConfiguratorQrCodeGenerator(softApConfiguration)) { 297 intent.setAction(WifiDppConfiguratorActivity.ACTION_CONFIGURATOR_QR_CODE_GENERATOR); 298 } else { 299 return null; 300 } 301 302 final String ssid = removeFirstAndLastDoubleQuotes(softApConfiguration.getSsid()); 303 String security; 304 final int securityType = softApConfiguration.getSecurityType(); 305 if (securityType == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE) { 306 security = WifiQrCode.SECURITY_SAE; 307 } else if (securityType == SoftApConfiguration.SECURITY_TYPE_WPA2_PSK 308 || securityType == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION) { 309 security = WifiQrCode.SECURITY_WPA_PSK; 310 } else { 311 security = WifiQrCode.SECURITY_NO_PASSWORD; 312 } 313 314 // When the value of this key is read, the actual key is not returned, just a "*". 315 // Call privileged system API to obtain actual key. 316 final String preSharedKey = removeFirstAndLastDoubleQuotes( 317 softApConfiguration.getPassphrase()); 318 319 if (!TextUtils.isEmpty(ssid)) { 320 intent.putExtra(EXTRA_WIFI_SSID, ssid); 321 } 322 if (!TextUtils.isEmpty(security)) { 323 intent.putExtra(EXTRA_WIFI_SECURITY, security); 324 } 325 if (!TextUtils.isEmpty(preSharedKey)) { 326 intent.putExtra(EXTRA_WIFI_PRE_SHARED_KEY, preSharedKey); 327 } 328 intent.putExtra(EXTRA_WIFI_HIDDEN_SSID, softApConfiguration.isHiddenSsid()); 329 330 331 intent.putExtra(EXTRA_WIFI_NETWORK_ID, WifiConfiguration.INVALID_NETWORK_ID); 332 intent.putExtra(EXTRA_IS_HOTSPOT, true); 333 334 return intent; 335 } 336 337 /** 338 * Set all extra except {@code EXTRA_WIFI_NETWORK_ID} for the intent to 339 * launch configurator activity later. 340 * 341 * @param intent the target to set extra 342 * @param wifiManager an instance of {@code WifiManager} 343 * @param wifiConfiguration the Wi-Fi network for launching configurator activity 344 */ setConfiguratorIntentExtra(Intent intent, WifiManager wifiManager, WifiConfiguration wifiConfiguration)345 private static void setConfiguratorIntentExtra(Intent intent, WifiManager wifiManager, 346 WifiConfiguration wifiConfiguration) { 347 final String ssid = removeFirstAndLastDoubleQuotes(wifiConfiguration.SSID); 348 final String security = getSecurityString(wifiConfiguration); 349 350 // When the value of this key is read, the actual key is not returned, just a "*". 351 // Call privileged system API to obtain actual key. 352 final String preSharedKey = removeFirstAndLastDoubleQuotes(getPresharedKey(wifiManager, 353 wifiConfiguration)); 354 355 if (!TextUtils.isEmpty(ssid)) { 356 intent.putExtra(EXTRA_WIFI_SSID, ssid); 357 } 358 if (!TextUtils.isEmpty(security)) { 359 intent.putExtra(EXTRA_WIFI_SECURITY, security); 360 } 361 if (!TextUtils.isEmpty(preSharedKey)) { 362 intent.putExtra(EXTRA_WIFI_PRE_SHARED_KEY, preSharedKey); 363 } 364 intent.putExtra(EXTRA_WIFI_HIDDEN_SSID, wifiConfiguration.hiddenSSID); 365 } 366 367 /** 368 * Shows authentication screen to confirm credentials (pin, pattern or password) for the current 369 * user of the device. 370 * 371 * @param context The {@code Context} used to get {@code KeyguardManager} service 372 * @param successRunnable The {@code Runnable} which will be executed if the user does not setup 373 * device security or if lock screen is unlocked 374 */ showLockScreen(Context context, Runnable successRunnable)375 public static void showLockScreen(Context context, Runnable successRunnable) { 376 final KeyguardManager keyguardManager = (KeyguardManager) context.getSystemService( 377 Context.KEYGUARD_SERVICE); 378 379 if (keyguardManager.isKeyguardSecure()) { 380 final BiometricPrompt.AuthenticationCallback authenticationCallback = 381 new BiometricPrompt.AuthenticationCallback() { 382 @Override 383 public void onAuthenticationSucceeded( 384 BiometricPrompt.AuthenticationResult result) { 385 successRunnable.run(); 386 } 387 388 @Override 389 public void onAuthenticationError(int errorCode, CharSequence errString) { 390 //Do nothing 391 } 392 }; 393 394 final BiometricPrompt.Builder builder = new BiometricPrompt.Builder(context) 395 .setTitle(context.getText(R.string.wifi_dpp_lockscreen_title)); 396 397 if (keyguardManager.isDeviceSecure()) { 398 builder.setDeviceCredentialAllowed(true); 399 } 400 401 final BiometricPrompt bp = builder.build(); 402 final Handler handler = new Handler(Looper.getMainLooper()); 403 bp.authenticate(new CancellationSignal(), 404 runnable -> handler.post(runnable), 405 authenticationCallback); 406 } else { 407 successRunnable.run(); 408 } 409 } 410 411 /** 412 * Checks if QR code generator supports to config other devices with the Wi-Fi network 413 * 414 * @param context The context to use for {@code WifiManager} 415 * @param accessPoint The {@link AccessPoint} of the Wi-Fi network 416 */ isSupportConfiguratorQrCodeGenerator(Context context, AccessPoint accessPoint)417 public static boolean isSupportConfiguratorQrCodeGenerator(Context context, 418 AccessPoint accessPoint) { 419 if (accessPoint.isPasspoint()) { 420 return false; 421 } 422 return isSupportZxing(context, accessPoint.getSecurity()); 423 } 424 425 /** 426 * Checks if this device supports to be configured by the Wi-Fi network of the security 427 * 428 * @param context The context to use for {@code WifiManager} 429 * @param wifiEntrySecurity The security constants defined in {@link WifiEntry} 430 */ isSupportEnrolleeQrCodeScanner(Context context, int wifiEntrySecurity)431 public static boolean isSupportEnrolleeQrCodeScanner(Context context, int wifiEntrySecurity) { 432 return isSupportWifiDpp(context, wifiEntrySecurity) 433 || isSupportZxing(context, wifiEntrySecurity); 434 } 435 isSupportHotspotConfiguratorQrCodeGenerator( SoftApConfiguration softApConfiguration)436 private static boolean isSupportHotspotConfiguratorQrCodeGenerator( 437 SoftApConfiguration softApConfiguration) { 438 final int securityType = softApConfiguration.getSecurityType(); 439 return securityType == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE 440 || securityType == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION 441 || securityType == SoftApConfiguration.SECURITY_TYPE_WPA2_PSK 442 || securityType == SoftApConfiguration.SECURITY_TYPE_OPEN; 443 } 444 isSupportWifiDpp(Context context, int wifiEntrySecurity)445 private static boolean isSupportWifiDpp(Context context, int wifiEntrySecurity) { 446 if (!isWifiDppEnabled(context)) { 447 return false; 448 } 449 450 // DPP 1.0 only supports SAE and PSK. 451 final WifiManager wifiManager = context.getSystemService(WifiManager.class); 452 switch (wifiEntrySecurity) { 453 case WifiEntry.SECURITY_SAE: 454 if (wifiManager.isWpa3SaeSupported()) { 455 return true; 456 } 457 break; 458 case WifiEntry.SECURITY_PSK: 459 return true; 460 default: 461 } 462 return false; 463 } 464 isSupportZxing(Context context, int wifiEntrySecurity)465 private static boolean isSupportZxing(Context context, int wifiEntrySecurity) { 466 final WifiManager wifiManager = context.getSystemService(WifiManager.class); 467 switch (wifiEntrySecurity) { 468 case WifiEntry.SECURITY_PSK: 469 case WifiEntry.SECURITY_WEP: 470 case WifiEntry.SECURITY_NONE: 471 return true; 472 case WifiEntry.SECURITY_SAE: 473 if (wifiManager.isWpa3SaeSupported()) { 474 return true; 475 } 476 break; 477 case WifiEntry.SECURITY_OWE: 478 if (wifiManager.isEnhancedOpenSupported()) { 479 return true; 480 } 481 break; 482 default: 483 } 484 return false; 485 } 486 triggerVibrationForQrCodeRecognition(Context context)487 static void triggerVibrationForQrCodeRecognition(Context context) { 488 Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); 489 if (vibrator == null) { 490 return; 491 } 492 vibrator.vibrate(VibrationEffect.createOneShot( 493 VIBRATE_DURATION_QR_CODE_RECOGNITION.toMillis(), 494 VibrationEffect.DEFAULT_AMPLITUDE)); 495 } 496 497 @WifiEntry.Security getSecurityTypeFromWifiConfiguration(WifiConfiguration config)498 static int getSecurityTypeFromWifiConfiguration(WifiConfiguration config) { 499 if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) { 500 return WifiEntry.SECURITY_SAE; 501 } 502 if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) { 503 return WifiEntry.SECURITY_PSK; 504 } 505 if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SUITE_B_192)) { 506 return WifiEntry.SECURITY_EAP_SUITE_B; 507 } 508 if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP) 509 || config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X)) { 510 return WifiEntry.SECURITY_EAP; 511 } 512 if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) { 513 return WifiEntry.SECURITY_OWE; 514 } 515 return (config.wepKeys[0] != null) ? WifiEntry.SECURITY_WEP : WifiEntry.SECURITY_NONE; 516 } 517 } 518