Android Wifi DPP(Wi-Fi 轻松连接)

Android Wifi DPP (Wi-Fi Easy Connect)

因为 Android 10+ 不再允许您以编程方式连接到 wifi 网络(您只能 suggest/add 网络到列表,但如果您有现有的 wifi 连接,它们可能永远不会连接) , 我想使用 WiFi Easy Connect (https://source.android.com/devices/tech/connect/wifi-easy-connect) 我假设这基本上就是二维码扫描选项,可以从 wifi 设置访问。

文档指出您应该检查它是否受支持:

Public APIs are available in Android 10 for use by apps:

    WifiManager#isEasyConnectSupported: Queries the framework to determine whether the device supports Wi-Fi Easy Connect.
    Activity#startActivityForResult(ACTION_PROCESS_WIFI_EASY_CONNECT_URI): Allows apps to integrate Wi-Fi Easy Connect into their onboarding/setup flow.

我的做法是:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
    {
        val wifiManager = getSystemService(Context.WIFI_SERVICE) as WifiManager
        if (wifiManager.isEasyConnectSupported)
        {
            startActivityForResult(Intent(android.provider.Settings.ACTION_PROCESS_WIFI_EASY_CONNECT_URI), 1237)
        }
    }

但是,由于找不到 activity 而导致崩溃(我已经在 Pixel 4XL 和模拟器上测试过 运行 R):

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: xxx, PID: 8498
    android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.settings.PROCESS_WIFI_EASY_CONNECT_URI }

这可以被应用程序使用吗? 还有另一种方法可以可靠地实际连接到 wifi 网络吗? 这是正确的方法吗?或者有没有办法直接启动设置屏幕?

我在 android 清单中找到了这些,但找不到启动它们的方法:

 <activity
    android:name=".wifi.dpp.WifiDppConfiguratorActivity">
    <intent-filter>
        <action android:name="android.settings.WIFI_DPP_CONFIGURATOR_QR_CODE_SCANNER"/>
        <action android:name="android.settings.WIFI_DPP_CONFIGURATOR_QR_CODE_GENERATOR"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
    <intent-filter>
        <action android:name="android.settings.PROCESS_WIFI_EASY_CONNECT_URI"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:scheme="DPP"/>
    </intent-filter>
</activity>

<activity
    android:name=".wifi.dpp.WifiDppEnrolleeActivity">
    <intent-filter>
        <action android:name="android.settings.WIFI_DPP_ENROLLEE_QR_CODE_SCANNER"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</activity>

看起来您缺少 DPP URI 字符串,应该设置 from the documentation

它应该是这样的:

// Valid Wi-Fi DPP QR code & it's parameters
private static final String VALID_WIFI_DPP_QR_CODE = "DPP:I:SN=4774LH2b4044;M:010203040506;K:"
        + "MDkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDIgADURzxmttZoIRIPWGoQMV00XHWCAQIhXruVWOz0NjlkIA=;;";

我拍了here

您可以查看内部 protocol specification,以构建您自己的 DPP URI。

希望这对您继续调查有所帮助。我还在这个问题上。所以,请分享任何更新。

看起来 DPP Uri 仅在启动此 activity 时受支持。发送的这些 DPP Uri 仅用于引导,不用于交换有关 WiFi 网络身份验证的信息。

要在 Android 10 上连接到 WiFi 网络,Google 推荐他们的 ConnectivityManager.requestNetwork 方法。根据我的经验,这会连接到 WiFi。但是,它在没有 Internet 连接的情况下执行此操作。参见:https://developer.android.com/guide/topics/connectivity/wifi-bootstrap

还有 WifiNetworkSuggestion,它会显示通知并仅建议用户连接到 WiFi,这可能是隐藏的,尤其是当您的应用处于沉浸模式时 运行。参见:https://developer.android.com/guide/topics/connectivity/wifi-suggest

Android 11 (API 30) 将允许您使用 android.provider.Settings.ACTION_WIFI_ADD_NETWORKS Intent 添加 WifiNetworkSuggestions。这类似于旧 WifiManager.addNetwork API。请参阅:https://developer.android.com/guide/topics/connectivity/wifi-save-network-passpoint-config 了解更多信息。

目前 Android 10 的最佳选择似乎是要求用户通过 WiFi 设置手动连接,扫描二维码或输入 WiFi 详细信息。或者使用 WifiNetworkSuggestion API.

如果您想使用二维码扫描器连接到 Wifi,以下代码可以正常工作

@RequiresApi(api = Build.VERSION_CODES.Q)
private void startWifiQRCodeScanner(Context context)
{
    final String INTENT_ACTION_WIFI_QR_SCANNER = "android.settings.WIFI_DPP_ENROLLEE_QR_CODE_SCANNER"; 
    WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);

    if(wifiManager.isEasyConnectSupported())
    {
        final Intent intent = new Intent(INTENT_ACTION_WIFI_QR_SCANNER);
        startActivityForResult(intent, 5000);
    }
}

我在 packages/apps/Settings/AndroidManifest 中找到了以下代码。xml:

        <activity
            android:name=".wifi.dpp.WifiDppEnrolleeActivity">
            <intent-filter>
                <action android:name="android.settings.WIFI_DPP_ENROLLEE_QR_CODE_SCANNER"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>

我也在努力应用这个意图,到目前为止运气不好。 这是我的发现:

对于有效的 URI,您需要 Enrollee mac 地址和使用 WPA_CLI 命令生成的 public 密钥

dpp_bootstrap_gen type=qrcode mac=<mac-address-of-device> chan=<operating-class/channel> key=<key of the device>

key 参数是可选的,因为 WPA_CLI 会在未提供时为您生成一个

如果您应用其 mac 地址和 public 密钥

,此代码可以到达登记者
    final String VALID_WIFI_DPP_QR_CODE = "" +
            "DPP:" +
            "I:SN=4774LH2b4044;" +
            "M:dea6327ee40a;" + //put here the Enrollee mac address
            "K:MDkwEwYHKoZIzj0CAQYIKoZIzj0DAQDDIgADUAQGkdCbThkC1omyOCRX1mCxXZJo8h8yqQ7Jx4WsxFA=;;"; // put here the Enrollee public key
    WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);

    if(wifiManager.isEasyConnectSupported())
    {
        final Intent intent = new Intent(INTENT_PROCESS_WIFI_EASY_CONNECT_URI);
        intent.setData(Uri.parse(VALID_WIFI_DPP_QR_CODE));
        startActivityForResult(intent, 5000);
    }

我卡住了 Android 应用程序未确认 Enrollee 身份验证响应:

<3>DPP-RX src=96:cc:02:1d:5d:ca freq=2412 type=0
<3>DPP-TX dst=96:cc:02:1d:5d:ca freq=2412 type=1
<3>DPP-TX-STATUS dst=96:cc:02:1d:5d:ca freq=2412 result=no-ACK

希望这会激发人们的新想法来完成这项工作。

回答您的问题“是否有其他方法可以可靠地连接到 wifi 网络?”

这是我的工作代码,作为 片段 activity 的一部分,用于以编程方式设置与 AP 的 Wifi 连接。 该代码旨在与接入点建立临时连接,执行一些 api 操作并取消注册(断开连接),这会回退到您原来的 Wifi 连接。如果您不取消注册,Wifi 连接将保持活动状态,直到您取消注册。

注意: Android API 29 级不支持程序化 Wifi 连接与互联网!这仅适用于 P2P Wifi 连接。 如果您想使用接入点的 Wifi 二维码进行 Wifi 互联网连接,请使用 Intent(INTENT_ACTION_WIFI_QR_SCANNER)。

private final String LOGTAG = "Connecting Wifi AP";
public boolean OnConnectionInit = true; // is used to prevent multiple api calls with OnAvailable event
public void ConnectToAP(String Ssid, String wifipassword) {
    if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
        final WifiManager wifiManager = (WifiManager) getActivity().getApplicationContext().getSystemService(Context.WIFI_SERVICE);
        final NetworkSpecifier specifier =
                new WifiNetworkSpecifier.Builder()
                        .setSsid(Ssid)
                        .setWpa2Passphrase(wifipassword)
                        .build();
        final NetworkRequest request =
                new NetworkRequest.Builder()
                        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
                        .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                       

        final ConnectivityManager connectivityManager = (ConnectivityManager)
                getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);

        final String InhomeSSID = wifiManager.getConnectionInfo().getSSID();
        Log.d(LOGTAG, "Current SSID: " + InhomeSSID);
 

        Log.d(LOGTAG, "build up Wifi connection");            
        final ConnectivityManager.NetworkCallback networkCallback = new ConnectivityManager.NetworkCallback() {
            @Override
            public void onAvailable(Network network) {
                // do success processing here..
                connectivityManager.bindProcessToNetwork(network);
                Log.d(LOGTAG, "Network available");                    
                String wifiName = wifiManager.getConnectionInfo().getSSID();
                Log.d(LOGTAG, "New SSID: " + wifiName);                    
                WifiInfo Info = wifiManager.getConnectionInfo();
                String IPAddress = Formatter.formatIpAddress(Info.getIpAddress());
                Log.d(LOGTAG, "New IPAddress: " + IPAddress);                    

                if (OnConnectionInit) {
                    // set wifi config request:                        
                    HttpFetchAndUnregister("http://10.0.0.1/cgi-bin/ui_system.cgi?cmd=get_config", connectivityManager, this);
                    OnConnectionInit = false;
                }
            }

            public void onUnavailable() {
                // do failure processing here..
                Log.d(LOGTAG, "Network unavailable");
                AlertDialog EndDialog = new AlertDialog.Builder(getActivity()).create();
                EndDialog.setTitle("Error connecting");
                EndDialog.setMessage("Could not connect to " + Ssid);
                EndDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "OK",
                        new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {
                                dialog.dismiss();
                            }
                        });
                EndDialog.show();
            }
        };
        connectivityManager.requestNetwork(request, networkCallback);
        Log.d(LOGTAG, "Network requested");            
        connectivityManager.registerNetworkCallback(request, networkCallback);
        Log.d(LOGTAG, "Network registered");           
    }
}

private void HttpFetchAndUnregister(String Url, ConnectivityManager connectivityManager, ConnectivityManager.NetworkCallback networkConnectionReceiver) {        
    RequestQueue requestQueue = Volley.newRequestQueue(getActivity());
    Log.d(LOGTAG, "url: " + Url);
    StringRequest stringRequest = new StringRequest(Request.Method.GET, Url, new Response.Listener<String>() {
        @Override
        public void onResponse(String response) {
            //This code is executed if the server responds, whether or not the response contains data.
            Log.d(LOGTAG, "Http request response: " + response);                
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                connectivityManager.unregisterNetworkCallback(networkConnectionReceiver);
            }                
        }
    }, new Response.ErrorListener() { //Create an error listener to handle errors appropriately.
        @Override
        public void onErrorResponse(VolleyError error) {
            //This code is executed if there is an error.
            Log.d(LOGTAG, "error: " + error);                
        }
    });

    requestQueue.add(stringRequest);
}