Android Wifi DPP (Wi-Fi Easy Connect)

Viewed 3459

Since Android 10+ doesn't let you connect to wifi networks programmatically anymore (you can only suggest/add networks to the list but they may never connect if you have an existing wifi connect), I'd like to use WiFi Easy Connect (https://source.android.com/devices/tech/connect/wifi-easy-connect) I'm assuming this is basically what the qr code scanning option is, that's accessed from wifi settings.

The documentation states that you should check whether it's supported:

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.

Which I've done as:

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)
        }
    }

However, this crashes as the activity is not found (I have tested on a Pixel 4XL and emulator both running 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 }

Is this possible to be used by apps? Is there another way to actually connect to a wifi network reliably? Is this the correct approach? or is there a way to launch the setting screen directly?

I found these in an android manifest but I can't find a way to launch them:

 <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>
5 Answers

If you want to connect to Wifi using QR Code scanner, following code will work fine

@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);
    }
}

I have found following code in 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>

Looks like you are missing DPP URI string, that should be set from the documentation

Here is how it should look like:

// 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=;;";

I took it here

You can look inside protocol specification, to build your own DPP URI.

Hope, this will help you to continue investigation. I'm still in this issue. So, please, share any updates.

Looks like DPP Uri's are only supported when launching this activity. These DPP Uri's that are sent are only for bootstrapping, not for exchanging information about the authentication for the WiFi network.

To connect to WiFi networks on Android 10, Google recommends their ConnectivityManager.requestNetwork method. From my experience, this connects to the WiFi. However, it does it without Internet connection. See: https://developer.android.com/guide/topics/connectivity/wifi-bootstrap

There is also the WifiNetworkSuggestion, which shows a notification and only suggests a user to connect to the WiFi, which can be kind of hidden, especially if your app is running in Immersive mode. See: https://developer.android.com/guide/topics/connectivity/wifi-suggest

Android 11 (API 30) will allow you to add WifiNetworkSuggestions using the android.provider.Settings.ACTION_WIFI_ADD_NETWORKS Intent. Which is similar to the Old WifiManager.addNetwork API. See: https://developer.android.com/guide/topics/connectivity/wifi-save-network-passpoint-config for more information.

For now the best option for Android 10 seems to either ask the user to manually connect through the WiFi Settings either scanning a QR code or entering WiFi detals. Or by using the WifiNetworkSuggestion API.

I am also struggling to apply this intent and no luck so far. Here are my findings:

For a valid URI you need the Enrollee mac address and public key generated with a WPA_CLI command

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

The key parameter is optional because WPA_CLI will generate one for you when not supplied

This code works to reach the Enrollee if you apply its mac address and public key

    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);
    }

I got stuck that the Android app is not acknowledging the Enrollee authentication respond:

<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

Hopefully this will trigger someone for new ideas to make this work.

To answer your question "Is there another way to actually connect to a wifi network reliably?"

Here is my working code as part of a Fragment activity to set up a Wifi connection programmatically to an AP. The code is meant to have a temporary connection with an access point, do some api stuff and unregister (disconnect) which falls back to your original Wifi connection. If you don't unregister the Wifi connection keeps active until you do unregister.

NOTE: Android API Level 29 does not support programmatic Wifi connections with Internet ! This is only meant for P2P Wifi connections. Use Intent(INTENT_ACTION_WIFI_QR_SCANNER) if you want a Wifi Internet connection using the Wifi QR code of the access point.

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);
}
Related