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