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