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