2016/11/25

Android で Wi-Fi ScanResult を WifiConfiguration に変換する方法

この記事は Android の Wi-Fi 実装に関する情報のまとめ の一部として書かれました

Android の Wi-Fi Access Point を表す ScanResult, WifiConfiguration, WifiInfo の違い」 で書いたように、接続先を追加する際には WifiConfiguration を新規作成する必要があります。 しかし、そのような変換ユーティリティメソッドなどは用意されていません。

AOSP ではどうしているのか

では、AOSP の設定アプリではどのようにしているのかを調べてみると1com.android.settings.wifi.WifiConfigController.getConfig() というメソッドで WifiConfiguration を作成していました。 以下2つのクラスを読むと大体の概要がわかるでしょう。

同等なメソッドを実装

com.android.settings.wifi.WifiConfigControllercom.android.settingslib.wifi.AccessPoint は基本的には外部から利用することが出来ません。

ただ、実際の getConfig() はかなりシンプルです2。 そこで、同等のメソッドを実装してみました。

以下が ScanResult から WifiConfiguration に変換するためのメソッドです3

最も需要がありそうな ScanResult からの変換を実装しましたが、 SSID4, SecurityType, password がわかっていれば WifiConfiguration は新規作成可能なので ScanResult 以外から作成することも難しくないはずです。

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
public static WifiConfiguration convertScanResultToWifiConfiguration(
        ScanResult scanResult,
        @Nullable String password) {
    WifiConfiguration config = new WifiConfiguration();
 
    // Quote を忘れないように
    config.SSID = '"' + scanResult.SSID + '"';
 
    // ScanResult を取得出来ているということは hiddenSSID ではない
    config.hiddenSSID = false;
 
    SecurityType securityType = getSecurity(scanResult);
 
    switch (securityType) {
        case SECURITY_NONE:
            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
            break;
        case SECURITY_WEP:
            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
            config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
            config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED);
            if (password != null) {
                int length = password.length();
                // WEP-40, WEP-104, and 256-bit WEP (WEP-232?)
                if ((length == 10 || length == 26 || length == 58)
                        && password.matches("[0-9A-Fa-f]*")) {
                    config.wepKeys[0] = password;
                } else {
                    config.wepKeys[0] = '"' + password + '"';
                }
            }
            break;
        case SECURITY_PSK:
            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
            if (password != null) {
                if (password.matches("[0-9A-Fa-f]{64}")) {
                    config.preSharedKey = password;
                } else {
                    config.preSharedKey = '"' + password + '"';
                }
            }
            break;
        case SECURITY_EAP:
            // EAP は省略
            // WifiConfigController.getConfig() 内の実装を参考にすれば
            // 同じように作れるはず
            Log.e(TAG, "EAP: Does not support");
            break;
        default:
            return null;
    }
 
    /**************************************************************
     以下は hide 属性のついたメソッド
     DHCP でつなぐ場合は問題ないが、それ以外の場合は設定しないと動かないかも
     その場合は Refrection しかないか?
     **************************************************************/
    //config.setIpConfiguration(
    //        new IpConfiguration(mIpAssignment, mProxySettings,
    //                mStaticIpConfiguration, mHttpProxy));
    return config;
}
 
private enum SecurityType {
    SECURITY_NONE,
    SECURITY_WEP,
    SECURITY_PSK,
    SECURITY_EAP
}
 
private static SecurityType getSecurity(ScanResult result) {
    if (result.capabilities.contains("WEP")) {
        return SecurityType.SECURITY_WEP;
    } else if (result.capabilities.contains("PSK")) {
        return SecurityType.SECURITY_PSK;
    } else if (result.capabilities.contains("EAP")) {
        return SecurityType.SECURITY_EAP;
    }
    return SecurityType.SECURITY_NONE;
}

気をつけなければならないのは、 WifiConfiguration.setIpConfiguration() が外部からのアクセス不可能なメソッドになっている点です。 調度良い環境がないので確かめていないのですが、DHCP を使っていなかったり、Proxy を使っていたりするとうまく IP がとれないかもしれません。

WifiConfigController 内の getConfig() の実装

Android 7.0.0_r19 のコードをあげておきます。 EAP が長いので読みづらく感じますが、先にあげたものとほとんど同じであることがわかると思います。

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
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
/* package */ WifiConfiguration getConfig() {
    if (mMode == WifiConfigUiBase.MODE_VIEW) {
        return null;
    }
    WifiConfiguration config = new WifiConfiguration();
    if (mAccessPoint == null) {
        config.SSID = AccessPoint.convertToQuotedString(
                mSsidView.getText().toString());
        // If the user adds a network manually, assume that it is hidden.
        config.hiddenSSID = true;
    } else if (!mAccessPoint.isSaved()) {
        config.SSID = AccessPoint.convertToQuotedString(
                mAccessPoint.getSsidStr());
    } else {
        config.networkId = mAccessPoint.getConfig().networkId;
    }
    config.shared = mSharedCheckBox.isChecked();
    switch (mAccessPointSecurity) {
        case AccessPoint.SECURITY_NONE:
            config.allowedKeyManagement.set(KeyMgmt.NONE);
            break;
        case AccessPoint.SECURITY_WEP:
            config.allowedKeyManagement.set(KeyMgmt.NONE);
            config.allowedAuthAlgorithms.set(AuthAlgorithm.OPEN);
            config.allowedAuthAlgorithms.set(AuthAlgorithm.SHARED);
            if (mPasswordView.length() != 0) {
                int length = mPasswordView.length();
                String password = mPasswordView.getText().toString();
                // WEP-40, WEP-104, and 256-bit WEP (WEP-232?)
                if ((length == 10 || length == 26 || length == 58)
                        && password.matches("[0-9A-Fa-f]*")) {
                    config.wepKeys[0] = password;
                } else {
                    config.wepKeys[0] = '"' + password + '"';
                }
            }
            break;
        case AccessPoint.SECURITY_PSK:
            config.allowedKeyManagement.set(KeyMgmt.WPA_PSK);
            if (mPasswordView.length() != 0) {
                String password = mPasswordView.getText().toString();
                if (password.matches("[0-9A-Fa-f]{64}")) {
                    config.preSharedKey = password;
                } else {
                    config.preSharedKey = '"' + password + '"';
                }
            }
            break;
        case AccessPoint.SECURITY_EAP:
            config.allowedKeyManagement.set(KeyMgmt.WPA_EAP);
            config.allowedKeyManagement.set(KeyMgmt.IEEE8021X);
            config.enterpriseConfig = new WifiEnterpriseConfig();
            int eapMethod = mEapMethodSpinner.getSelectedItemPosition();
            int phase2Method = mPhase2Spinner.getSelectedItemPosition();
            config.enterpriseConfig.setEapMethod(eapMethod);
            switch (eapMethod) {
                case Eap.PEAP:
                    // PEAP supports limited phase2 values
                    // Map the index from the mPhase2PeapAdapter to the one used
                    // by the API which has the full list of PEAP methods.
                    switch(phase2Method) {
                        case WIFI_PEAP_PHASE2_NONE:
                            config.enterpriseConfig.setPhase2Method(Phase2.NONE);
                            break;
                        case WIFI_PEAP_PHASE2_MSCHAPV2:
                            config.enterpriseConfig.setPhase2Method(Phase2.MSCHAPV2);
                            break;
                        case WIFI_PEAP_PHASE2_GTC:
                            config.enterpriseConfig.setPhase2Method(Phase2.GTC);
                            break;
                        default:
                            Log.e(TAG, "Unknown phase2 method" + phase2Method);
                            break;
                    }
                    break;
                default:
                    // The default index from mPhase2FullAdapter maps to the API
                    config.enterpriseConfig.setPhase2Method(phase2Method);
                    break;
            }
            String caCert = (String) mEapCaCertSpinner.getSelectedItem();
            config.enterpriseConfig.setCaCertificateAliases(null);
            config.enterpriseConfig.setCaPath(null);
            config.enterpriseConfig.setDomainSuffixMatch(mEapDomainView.getText().toString());
            if (caCert.equals(mUnspecifiedCertString)
                    || caCert.equals(mDoNotValidateEapServerString)) {
                // ca_cert already set to null, so do nothing.
            } else if (caCert.equals(mUseSystemCertsString)) {
                config.enterpriseConfig.setCaPath(SYSTEM_CA_STORE_PATH);
            } else if (caCert.equals(mMultipleCertSetString)) {
                if (mAccessPoint != null) {
                    if (!mAccessPoint.isSaved()) {
                        Log.e(TAG, "Multiple certs can only be set "
                                + "when editing saved network");
                    }
                    config.enterpriseConfig.setCaCertificateAliases(
                            mAccessPoint
                                    .getConfig()
                                    .enterpriseConfig
                                    .getCaCertificateAliases());
                }
            } else {
                config.enterpriseConfig.setCaCertificateAliases(new String[] {caCert});
            }
            // ca_cert or ca_path should not both be non-null, since we only intend to let
            // the use either their own certificate, or the system certificates, not both.
            // The variable that is not used must explicitly be set to null, so that a
            // previously-set value on a saved configuration will be erased on an update.
            if (config.enterpriseConfig.getCaCertificateAliases() != null
                    && config.enterpriseConfig.getCaPath() != null) {
                Log.e(TAG, "ca_cert ("
                        + config.enterpriseConfig.getCaCertificateAliases()
                        + ") and ca_path ("
                        + config.enterpriseConfig.getCaPath()
                        + ") should not both be non-null");
            }
            String clientCert = (String) mEapUserCertSpinner.getSelectedItem();
            if (clientCert.equals(mUnspecifiedCertString)
                    || clientCert.equals(mDoNotProvideEapUserCertString)) {
                // Note: |clientCert| should not be able to take the value |unspecifiedCert|,
                // since we prevent such configurations from being saved.
                clientCert = "";
            }
            config.enterpriseConfig.setClientCertificateAlias(clientCert);
            if (eapMethod == Eap.SIM || eapMethod == Eap.AKA || eapMethod == Eap.AKA_PRIME) {
                config.enterpriseConfig.setIdentity("");
                config.enterpriseConfig.setAnonymousIdentity("");
            } else if (eapMethod == Eap.PWD) {
                config.enterpriseConfig.setIdentity(mEapIdentityView.getText().toString());
                config.enterpriseConfig.setAnonymousIdentity("");
            } else {
                config.enterpriseConfig.setIdentity(mEapIdentityView.getText().toString());
                config.enterpriseConfig.setAnonymousIdentity(
                        mEapAnonymousView.getText().toString());
            }
            if (mPasswordView.isShown()) {
                // For security reasons, a previous password is not displayed to user.
                // Update only if it has been changed.
                if (mPasswordView.length() > 0) {
                    config.enterpriseConfig.setPassword(mPasswordView.getText().toString());
                }
            } else {
                // clear password
                config.enterpriseConfig.setPassword(mPasswordView.getText().toString());
            }
            break;
        default:
            return null;
    }
    config.setIpConfiguration(
            new IpConfiguration(mIpAssignment, mProxySettings,
                                mStaticIpConfiguration, mHttpProxy));
    return config;
}
  1. 参考にしたバージョンは Android 7.0.0_r19 
  2. 実装が簡単なので変換ユーティリティ等は用意されていないのだと思う 
  3. EAP を除く 
  4. もしくは networkId 
?

0 件のコメント: