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>
我也在努力应用这个意图,到目前为止运气不好。
这是我的发现:
- 您要配置的 Enrollee 设备必须支持 Wifi Easy Connect (DPP)
- 您可以使用 Raspberry Pi 4 作为兼容的 Enrollee(请参阅此处如何配置 http://w1.fi/cgit/hostap/plain/wpa_supplicant/README-DPP)
对于有效的 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);
}
因为 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>
我也在努力应用这个意图,到目前为止运气不好。 这是我的发现:
- 您要配置的 Enrollee 设备必须支持 Wifi Easy Connect (DPP)
- 您可以使用 Raspberry Pi 4 作为兼容的 Enrollee(请参阅此处如何配置 http://w1.fi/cgit/hostap/plain/wpa_supplicant/README-DPP)
对于有效的 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);
}