summaryrefslogtreecommitdiff
path: root/src/com/android/settings/wifi/dpp/WifiDppUtils.java
blob: b2ebb2322905ce9a6d754d824bd0efa819faa114 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settings.wifi.dpp;

import android.app.KeyguardManager;
import android.content.Context;
import android.content.Intent;
import android.hardware.biometrics.BiometricPrompt;
import android.net.wifi.SoftApConfiguration;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiConfiguration.KeyMgmt;
import android.net.wifi.WifiManager;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Looper;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.text.TextUtils;

import com.android.settings.R;
import com.android.settingslib.wifi.AccessPoint;
import com.android.wifitrackerlib.WifiEntry;

import java.time.Duration;
import java.util.List;

/**
 * Here are the items shared by both WifiDppConfiguratorActivity & WifiDppEnrolleeActivity
 *
 * @see WifiQrCode
 */
public class WifiDppUtils {
    /**
     * The fragment tag specified to FragmentManager for container activities to manage fragments.
     */
    static final String TAG_FRAGMENT_QR_CODE_SCANNER = "qr_code_scanner_fragment";

    /**
     * @see #TAG_FRAGMENT_QR_CODE_SCANNER
     */
    static final String TAG_FRAGMENT_QR_CODE_GENERATOR = "qr_code_generator_fragment";

    /**
     * @see #TAG_FRAGMENT_QR_CODE_SCANNER
     */
    static final String TAG_FRAGMENT_CHOOSE_SAVED_WIFI_NETWORK =
            "choose_saved_wifi_network_fragment";

    /**
     * @see #TAG_FRAGMENT_QR_CODE_SCANNER
     */
    static final String TAG_FRAGMENT_ADD_DEVICE = "add_device_fragment";

    /** The data is one of the static String SECURITY_* in {@link WifiQrCode} */
    static final String EXTRA_WIFI_SECURITY = "security";

    /** The data corresponding to {@code WifiConfiguration} SSID */
    static final String EXTRA_WIFI_SSID = "ssid";

    /** The data corresponding to {@code WifiConfiguration} preSharedKey */
    static final String EXTRA_WIFI_PRE_SHARED_KEY = "preSharedKey";

    /** The data corresponding to {@code WifiConfiguration} hiddenSSID */
    static final String EXTRA_WIFI_HIDDEN_SSID = "hiddenSsid";

    /** The data corresponding to {@code WifiConfiguration} networkId */
    static final String EXTRA_WIFI_NETWORK_ID = "networkId";

    /** The data to recognize if it's a Wi-Fi hotspot for configuration */
    static final String EXTRA_IS_HOTSPOT = "isHotspot";

    /**
     * Default status code for Easy Connect
     */
    static final int EASY_CONNECT_EVENT_FAILURE_NONE = 0;

    /**
     * Success status code for Easy Connect.
     */
    static final int EASY_CONNECT_EVENT_SUCCESS = 1;

    private static final Duration VIBRATE_DURATION_QR_CODE_RECOGNITION = Duration.ofMillis(3);

    /**
     * Returns whether the device support WiFi DPP.
     */
    static boolean isWifiDppEnabled(Context context) {
        final WifiManager manager = context.getSystemService(WifiManager.class);
        return manager.isEasyConnectSupported();
    }

    /**
     * Returns an intent to launch QR code scanner for Wi-Fi DPP enrollee.
     *
     * After enrollee success, the callee activity will return connecting WifiConfiguration by
     * putExtra {@code WifiDialogActivity.KEY_WIFI_CONFIGURATION} for
     * {@code Activity#setResult(int resultCode, Intent data)}. The calling object should check
     * if it's available before using it.
     *
     * @param ssid The data corresponding to {@code WifiConfiguration} SSID
     * @return Intent for launching QR code scanner
     */
    public static Intent getEnrolleeQrCodeScannerIntent(Context context, String ssid) {
        final Intent intent = new Intent(context, WifiDppEnrolleeActivity.class);
        intent.setAction(WifiDppEnrolleeActivity.ACTION_ENROLLEE_QR_CODE_SCANNER);
        if (!TextUtils.isEmpty(ssid)) {
            intent.putExtra(EXTRA_WIFI_SSID, ssid);
        }
        return intent;
    }

    private static String getPresharedKey(WifiManager wifiManager,
            WifiConfiguration wifiConfiguration) {
        final List<WifiConfiguration> privilegedWifiConfigurations =
                wifiManager.getPrivilegedConfiguredNetworks();

        for (WifiConfiguration privilegedWifiConfiguration : privilegedWifiConfigurations) {
            if (privilegedWifiConfiguration.networkId == wifiConfiguration.networkId) {
                // WEP uses a shared key hence the AuthAlgorithm.SHARED is used
                // to identify it.
                if (wifiConfiguration.allowedKeyManagement.get(KeyMgmt.NONE)
                        && wifiConfiguration.allowedAuthAlgorithms.get(
                        WifiConfiguration.AuthAlgorithm.SHARED)) {
                    return privilegedWifiConfiguration
                            .wepKeys[privilegedWifiConfiguration.wepTxKeyIndex];
                } else {
                    return privilegedWifiConfiguration.preSharedKey;
                }
            }
        }
        return wifiConfiguration.preSharedKey;
    }

    static String removeFirstAndLastDoubleQuotes(String str) {
        if (TextUtils.isEmpty(str)) {
            return str;
        }

        int begin = 0;
        int end = str.length() - 1;
        if (str.charAt(begin) == '\"') {
            begin++;
        }
        if (str.charAt(end) == '\"') {
            end--;
        }
        return str.substring(begin, end+1);
    }

    static String getSecurityString(WifiConfiguration config) {
        if (config.allowedKeyManagement.get(KeyMgmt.SAE)) {
            return WifiQrCode.SECURITY_SAE;
        }
        if (config.allowedKeyManagement.get(KeyMgmt.OWE)) {
            return WifiQrCode.SECURITY_NO_PASSWORD;
        }
        if (config.allowedKeyManagement.get(KeyMgmt.WPA_PSK) ||
                config.allowedKeyManagement.get(KeyMgmt.WPA2_PSK)) {
            return WifiQrCode.SECURITY_WPA_PSK;
        }
        return (config.wepKeys[0] == null) ?
                WifiQrCode.SECURITY_NO_PASSWORD : WifiQrCode.SECURITY_WEP;
    }

    static String getSecurityString(WifiEntry wifiEntry) {
        final int security = wifiEntry.getSecurity();
        switch (security) {
            case WifiEntry.SECURITY_SAE:
                return WifiQrCode.SECURITY_SAE;
            case WifiEntry.SECURITY_PSK:
                return WifiQrCode.SECURITY_WPA_PSK;
            case WifiEntry.SECURITY_WEP:
                return WifiQrCode.SECURITY_WEP;
            case WifiEntry.SECURITY_OWE:
            case WifiEntry.SECURITY_NONE:
            default:
                return WifiQrCode.SECURITY_NO_PASSWORD;
        }
    }

    /**
     * Returns an intent to launch QR code generator. It may return null if the security is not
     * supported by QR code generator.
     *
     * Do not use this method for Wi-Fi hotspot network, use
     * {@code getHotspotConfiguratorIntentOrNull} instead.
     *
     * @param context     The context to use for the content resolver
     * @param wifiManager An instance of {@link WifiManager}
     * @param accessPoint An instance of {@link AccessPoint}
     * @return Intent for launching QR code generator
     */
    public static Intent getConfiguratorQrCodeGeneratorIntentOrNull(Context context,
            WifiManager wifiManager, AccessPoint accessPoint) {
        final Intent intent = new Intent(context, WifiDppConfiguratorActivity.class);
        if (isSupportConfiguratorQrCodeGenerator(context, accessPoint)) {
            intent.setAction(WifiDppConfiguratorActivity.ACTION_CONFIGURATOR_QR_CODE_GENERATOR);
        } else {
            return null;
        }

        final WifiConfiguration wifiConfiguration = accessPoint.getConfig();
        setConfiguratorIntentExtra(intent, wifiManager, wifiConfiguration);

        // For a transition mode Wi-Fi AP, creates a QR code that's compatible with more devices
        if (accessPoint.isPskSaeTransitionMode()) {
            intent.putExtra(EXTRA_WIFI_SECURITY, WifiQrCode.SECURITY_WPA_PSK);
        }

        return intent;
    }

    /**
     * Returns an intent to launch QR code generator. It may return null if the security is not
     * supported by QR code generator.
     *
     * Do not use this method for Wi-Fi hotspot network, use
     * {@code getHotspotConfiguratorIntentOrNull} instead.
     *
     * @param context     The context to use for the content resolver
     * @param wifiManager An instance of {@link WifiManager}
     * @param wifiEntry An instance of {@link WifiEntry}
     * @return Intent for launching QR code generator
     */
    public static Intent getConfiguratorQrCodeGeneratorIntentOrNull(Context context,
            WifiManager wifiManager, WifiEntry wifiEntry) {
        final Intent intent = new Intent(context, WifiDppConfiguratorActivity.class);
        if (wifiEntry.canShare()) {
            intent.setAction(WifiDppConfiguratorActivity.ACTION_CONFIGURATOR_QR_CODE_GENERATOR);
        } else {
            return null;
        }

        final WifiConfiguration wifiConfiguration = wifiEntry.getWifiConfiguration();
        setConfiguratorIntentExtra(intent, wifiManager, wifiConfiguration);

        return intent;
    }

    /**
     * Returns an intent to launch QR code scanner. It may return null if the security is not
     * supported by QR code scanner.
     *
     * @param context     The context to use for the content resolver
     * @param wifiManager An instance of {@link WifiManager}
     * @param wifiEntry An instance of {@link WifiEntry}
     * @return Intent for launching QR code scanner
     */
    public static Intent getConfiguratorQrCodeScannerIntentOrNull(Context context,
            WifiManager wifiManager, WifiEntry wifiEntry) {
        final Intent intent = new Intent(context, WifiDppConfiguratorActivity.class);
        if (wifiEntry.canEasyConnect()) {
            intent.setAction(WifiDppConfiguratorActivity.ACTION_CONFIGURATOR_QR_CODE_SCANNER);
        } else {
            return null;
        }

        final WifiConfiguration wifiConfiguration = wifiEntry.getWifiConfiguration();
        setConfiguratorIntentExtra(intent, wifiManager, wifiConfiguration);

        if (wifiConfiguration.networkId == WifiConfiguration.INVALID_NETWORK_ID) {
            throw new IllegalArgumentException("Invalid network ID");
        } else {
            intent.putExtra(EXTRA_WIFI_NETWORK_ID, wifiConfiguration.networkId);
        }

        return intent;
    }

    /**
     * Returns an intent to launch QR code generator for the Wi-Fi hotspot. It may return null if
     * the security is not supported by QR code generator.
     *
     * @param context The context to use for the content resolver
     * @param wifiManager An instance of {@link WifiManager}
     * @param softApConfiguration {@link SoftApConfiguration} of the Wi-Fi hotspot
     * @return Intent for launching QR code generator
     */
    public static Intent getHotspotConfiguratorIntentOrNull(Context context,
            WifiManager wifiManager, SoftApConfiguration softApConfiguration) {
        final Intent intent = new Intent(context, WifiDppConfiguratorActivity.class);
        if (isSupportHotspotConfiguratorQrCodeGenerator(softApConfiguration)) {
            intent.setAction(WifiDppConfiguratorActivity.ACTION_CONFIGURATOR_QR_CODE_GENERATOR);
        } else {
            return null;
        }

        final String ssid = removeFirstAndLastDoubleQuotes(softApConfiguration.getSsid());
        String security;
        final int securityType = softApConfiguration.getSecurityType();
        if (securityType == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE) {
            security = WifiQrCode.SECURITY_SAE;
        } else if (securityType == SoftApConfiguration.SECURITY_TYPE_WPA2_PSK
                || securityType == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION) {
            security = WifiQrCode.SECURITY_WPA_PSK;
        } else {
            security = WifiQrCode.SECURITY_NO_PASSWORD;
        }

        // When the value of this key is read, the actual key is not returned, just a "*".
        // Call privileged system API to obtain actual key.
        final String preSharedKey = removeFirstAndLastDoubleQuotes(
                softApConfiguration.getPassphrase());

        if (!TextUtils.isEmpty(ssid)) {
            intent.putExtra(EXTRA_WIFI_SSID, ssid);
        }
        if (!TextUtils.isEmpty(security)) {
            intent.putExtra(EXTRA_WIFI_SECURITY, security);
        }
        if (!TextUtils.isEmpty(preSharedKey)) {
            intent.putExtra(EXTRA_WIFI_PRE_SHARED_KEY, preSharedKey);
        }
        intent.putExtra(EXTRA_WIFI_HIDDEN_SSID, softApConfiguration.isHiddenSsid());


        intent.putExtra(EXTRA_WIFI_NETWORK_ID, WifiConfiguration.INVALID_NETWORK_ID);
        intent.putExtra(EXTRA_IS_HOTSPOT, true);

        return intent;
    }

    /**
     * Set all extra except {@code EXTRA_WIFI_NETWORK_ID} for the intent to
     * launch configurator activity later.
     *
     * @param intent the target to set extra
     * @param wifiManager an instance of {@code WifiManager}
     * @param wifiConfiguration the Wi-Fi network for launching configurator activity
     */
    private static void setConfiguratorIntentExtra(Intent intent, WifiManager wifiManager,
            WifiConfiguration wifiConfiguration) {
        final String ssid = removeFirstAndLastDoubleQuotes(wifiConfiguration.SSID);
        final String security = getSecurityString(wifiConfiguration);

        // When the value of this key is read, the actual key is not returned, just a "*".
        // Call privileged system API to obtain actual key.
        final String preSharedKey = removeFirstAndLastDoubleQuotes(getPresharedKey(wifiManager,
                wifiConfiguration));

        if (!TextUtils.isEmpty(ssid)) {
            intent.putExtra(EXTRA_WIFI_SSID, ssid);
        }
        if (!TextUtils.isEmpty(security)) {
            intent.putExtra(EXTRA_WIFI_SECURITY, security);
        }
        if (!TextUtils.isEmpty(preSharedKey)) {
            intent.putExtra(EXTRA_WIFI_PRE_SHARED_KEY, preSharedKey);
        }
        intent.putExtra(EXTRA_WIFI_HIDDEN_SSID, wifiConfiguration.hiddenSSID);
    }

    /**
     * Shows authentication screen to confirm credentials (pin, pattern or password) for the current
     * user of the device.
     *
     * @param context The {@code Context} used to get {@code KeyguardManager} service
     * @param successRunnable The {@code Runnable} which will be executed if the user does not setup
     *                        device security or if lock screen is unlocked
     */
    public static void showLockScreen(Context context, Runnable successRunnable) {
        final KeyguardManager keyguardManager = (KeyguardManager) context.getSystemService(
                Context.KEYGUARD_SERVICE);

        if (keyguardManager.isKeyguardSecure()) {
            final BiometricPrompt.AuthenticationCallback authenticationCallback =
                    new BiometricPrompt.AuthenticationCallback() {
                        @Override
                        public void onAuthenticationSucceeded(
                                    BiometricPrompt.AuthenticationResult result) {
                            successRunnable.run();
                        }

                        @Override
                        public void onAuthenticationError(int errorCode, CharSequence errString) {
                            //Do nothing
                        }
            };

            final BiometricPrompt.Builder builder = new BiometricPrompt.Builder(context)
                    .setTitle(context.getText(R.string.wifi_dpp_lockscreen_title));

            if (keyguardManager.isDeviceSecure()) {
                builder.setDeviceCredentialAllowed(true);
            }

            final BiometricPrompt bp = builder.build();
            final Handler handler = new Handler(Looper.getMainLooper());
            bp.authenticate(new CancellationSignal(),
                    runnable -> handler.post(runnable),
                    authenticationCallback);
        } else {
            successRunnable.run();
        }
    }

    /**
     * Checks if QR code generator supports to config other devices with the Wi-Fi network
     *
     * @param context The context to use for {@code WifiManager}
     * @param accessPoint The {@link AccessPoint} of the Wi-Fi network
     */
    public static boolean isSupportConfiguratorQrCodeGenerator(Context context,
            AccessPoint accessPoint) {
        if (accessPoint.isPasspoint()) {
            return false;
        }
        return isSupportZxing(context, accessPoint.getSecurity());
    }

    /**
     * Checks if this device supports to be configured by the Wi-Fi network of the security
     *
     * @param context The context to use for {@code WifiManager}
     * @param wifiEntrySecurity The security constants defined in {@link WifiEntry}
     */
    public static boolean isSupportEnrolleeQrCodeScanner(Context context, int wifiEntrySecurity) {
        return isSupportWifiDpp(context, wifiEntrySecurity)
                || isSupportZxing(context, wifiEntrySecurity);
    }

    private static boolean isSupportHotspotConfiguratorQrCodeGenerator(
            SoftApConfiguration softApConfiguration) {
        final int securityType = softApConfiguration.getSecurityType();
        return securityType == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE
                || securityType == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION
                || securityType == SoftApConfiguration.SECURITY_TYPE_WPA2_PSK
                || securityType == SoftApConfiguration.SECURITY_TYPE_OPEN
                || securityType == SoftApConfiguration.SECURITY_TYPE_OWE;
    }

    private static boolean isSupportWifiDpp(Context context, int wifiEntrySecurity) {
        if (!isWifiDppEnabled(context)) {
            return false;
        }

        // DPP 1.0 only supports SAE and PSK.
        final WifiManager wifiManager = context.getSystemService(WifiManager.class);
        switch (wifiEntrySecurity) {
            case WifiEntry.SECURITY_SAE:
                if (wifiManager.isWpa3SaeSupported()) {
                    return true;
                }
                break;
            case WifiEntry.SECURITY_PSK:
                return true;
            default:
        }
        return false;
    }

    private static boolean isSupportZxing(Context context, int wifiEntrySecurity) {
        final WifiManager wifiManager = context.getSystemService(WifiManager.class);
        switch (wifiEntrySecurity) {
            case WifiEntry.SECURITY_PSK:
            case WifiEntry.SECURITY_WEP:
            case WifiEntry.SECURITY_NONE:
                return true;
            case WifiEntry.SECURITY_SAE:
                if (wifiManager.isWpa3SaeSupported()) {
                    return true;
                }
                break;
            case WifiEntry.SECURITY_OWE:
                if (wifiManager.isEnhancedOpenSupported()) {
                    return true;
                }
                break;
            default:
        }
        return false;
    }

    static void triggerVibrationForQrCodeRecognition(Context context) {
        Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
        if (vibrator == null) {
          return;
        }
        vibrator.vibrate(VibrationEffect.createOneShot(
                VIBRATE_DURATION_QR_CODE_RECOGNITION.toMillis(),
                VibrationEffect.DEFAULT_AMPLITUDE));
    }

    @WifiEntry.Security
    static int getSecurityTypeFromWifiConfiguration(WifiConfiguration config) {
        if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) {
            return WifiEntry.SECURITY_SAE;
        }
        if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
            return WifiEntry.SECURITY_PSK;
        }
        if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SUITE_B_192)) {
            return WifiEntry.SECURITY_EAP_SUITE_B;
        }
        if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP)
                || config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X)) {
            return WifiEntry.SECURITY_EAP;
        }
        if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
            return WifiEntry.SECURITY_OWE;
        }
        return (config.wepKeys[0] != null) ? WifiEntry.SECURITY_WEP : WifiEntry.SECURITY_NONE;
    }
}