• 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.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