Saturday, March 4, 2023

The How of Wi-Fi P2P Programming

 With the prevalence of Wi-Fi, it's now possible to share information without a SIM card using Wi-Fi Direct (also known as Wi-Fi P2P). 

You may refer to https://developer.android.com/guide/topics/connectivity/wifip2p for the details of Wi-Fi Direct (P2P). Or you can scroll down this page to find the appendix of this article for a brief introduction with 5W1H method. 

As a programmer, I once wondered how to create an app that uses this technology, and years ago I was able to make a simple app that could communicate between two phones. 

In 2023, I wanted to try communicating among three phones, but I couldn't find my old source code after changing laptops, so I had to start from scratch. 

However, I ran into some issues - for example, new privacy control policies caused the app to stop working. After several weekends of troubleshooting with my available phones, the app now runs smoothly most of the time. 

I'd like to share my findings with others who are curious about exploring this technology.


The sequential diagram of my App

 


A basic Wi-Fi P2P application should have MainActivity and the BroadcastReceiver classes. In general, the steps to develop a Wi-Fi P2P application are as following:

1. Add the following permissions to AndroidManifest.xml file

<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />

<uses-permission android:name="android.permission.INTERNET" />

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

<uses-permission

    android:name="android.permission.NEARBY_WIFI_DEVICES"

    android:usesPermissionFlags="neverForLocation" />

Explicit codes should be added into the JAVA code as shown in my findings below.

2. In MainActivity Java class, create a WifiP2pManager object, which is used to manage Wi-Fi P2P operations, and a WifiP2pManager.Channel object, which represents the channel used for communication with the Wi-Fi P2P framework

WifiP2pManager manager;

WifiP2pManager.Channel channel;

3. Register a broadcast receiver to receive Wi-Fi P2P intents

Do the following in MainActivity’s onCreate():

filter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);

filter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);

filter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);

receiver = new WiFiDirectBroadcastReceiver(manager, channel, this);

Handle above intents in the BroadcastReceiver: 

public class WiFiDirectBroadcastReceiver extends BroadcastReceiver {

    @Override

    public void onReceive(Context context, Intent intent) {

        String action = intent.getAction();

        if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {

            // Wi-Fi P2P peers changed

        } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {

            // Wi-Fi P2P connection changed

        } else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {

            // Wi-Fi P2P device information changed (self)

        }

    }

};

Register the receiver in onResume(): 

registerReceiver(receiver, filter);


4. Initialize the WifiP2pManager and WifiP2pManager.Channel objects in onCreate()

manager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);

channel = manager.initialize(this, getMainLooper(), null);


5. Discover other Wi-Fi P2P devices on the network

manager.discoverPeers(channel, new WifiP2pManager.ActionListener() {

    @Override

    public void onSuccess() {

        // Discovery initiated successfully

    }


    @Override

    public void onFailure(int reasonCode) {

        // Discovery initiation failed

    }

});

6. Handle the list of discovered peers

manager.requestPeers(channel, new WifiP2pManager.PeerListListener() {

    @Override

    public void onPeersAvailable(WifiP2pDeviceList peerList) {

        // Handle the list of discovered peers

    }

});


Finding 1: the permission control is different among Android OS versions

This is the first headache point. Eventually, following code solves it on my 3 phones:

private void CheckPermission() {

if (ContextCompat.checkSelfPermission(this, Manifest.permission.CHANGE_WIFI_STATE)

!= PackageManager.PERMISSION_GRANTED) {

Log.i(TAG, "CHANGE_WIFI_STATE was not granted");

ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CHANGE_WIFI_STATE}, 1);

}

if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.TIRAMISU ) {

if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)

!= PackageManager.PERMISSION_GRANTED) {

Log.i(TAG, " <TIRAMISU: ACCESS_FINE_LOCATION not granted");

// Permission is not granted

ActivityCompat.requestPermissions(this,

new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 2);

}

}

else

{

if (checkSelfPermission(Manifest.permission.NEARBY_WIFI_DEVICES)

== PackageManager.PERMISSION_GRANTED) {

Log.i(TAG, "TIRAMISU+: NEARBY_WIFI_DEVICES granted");

// Permission is granted, proceed with using the NEARBY_WIFI_DEVICES permission

// ...

} else {

Log.i(TAG, "TIRAMISU+: NEARBY_WIFI_DEVICES not granted");

// Permission is not granted, request the location permission again

ActivityCompat.requestPermissions(this,

new String[]{Manifest.permission.NEARBY_WIFI_DEVICES}, 3);

}

}

}


Finding 2: IP for different group owners are the same (192.168.49.1)

At first I thought devices in the same WiFi network should have their own unique IP (this could be true because I can check from the WiFi Router). When I set 2 phones as group owner, their IPs are the same. Actually, every time when a group is formed, the client’s IP will change. My efforts were in vain in trying to get each device’s IP to be used for socket communication. 

Knowing this will save much time in developing your own App.


Finding 3: Client can only see the group owner

In a Wi-Fi P2P group, there will be only one group owner which the client can get its IP address. The clients cannot see each other. 


Finding 4: Set a group owner with createGroup

A group would be created after one device connected to the other with manager.connect(). However, there is no guaranty which will be the group owner. Of my 3 phones, Huawei Mate 20 Pro would be always the group owner even I manipulate the value of groupOwnerIntent. But once you call createGroup, the phone will become group owner and then the others can connect to it to be a client.


Finding 5: You cannot enable or disable your Wi-Fi function from WifiP2pManager

Meaning you can only manually turn on or turn off the phone’s Wi-Fi feature from the phone settings.


Finding 6: Connect success doesn’t mean the connection succeeded

manager.connect(channel, config, new WifiP2pManager.ActionListener() {

    @Override

    public void onSuccess() {

        DisplayStatus("Connecting...", ContextCompat.getColor(getApplicationContext(), R.color.Color_Info));

    }

    @Override

    public void onFailure(int i) {

    }

});

It simply means this command has been executed successfully, while the connection is established or not you have to check WIFI_P2P_CONNECTION_CHANGED_ACTION.


Finding 7: different behaviours when connect

In my Android 10 phone, after connect() or createGroup() command is called, the BroadcastReceiver’s WIFI_P2P_CONNECTION_CHANGED_ACTION message is only received once with Connected, however in the Android 13 phone, the message is first received with Disconnected, then received again with Connected:

NetworkInfo networkInfo = (NetworkInfo) intent.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);

Log.i(TAG, "WIFI_P2P_CONNECTION_CHANGED_ACTION: "+ networkInfo.isConnected());


Finding 8: Disconnect at startup

Somehow the Android OS may memorize the WifiP2P group. So after initialization of the App, I just disconnect the phone from any group:

manager.removeGroup(channel, disconnectListener);


Finding 9: Group information is only meaning to the group owner

You can check the group information like this:

manager.requestGroupInfo(channel,groupInfoListener);

WifiP2pManager.GroupInfoListener groupInfoListener = new WifiP2pManager.GroupInfoListener() {

    @Override

    public void onGroupInfoAvailable(WifiP2pGroup group) {

        //List<WifiP2pDevice> clients = (List<WifiP2pDevice>) group.getClientList();

        if (group != null) {

            Collection<WifiP2pDevice> clients = group.getClientList();

            int numClients = clients.size();

            Log.d(TAG, "onGroupInfoAvailable: Number of clients connected: " + numClients);

            Toast.makeText(getApplicationContext(), "Group size " + numClients, Toast.LENGTH_SHORT).show();

        }

        else {

            Log.d(TAG, "onGroupInfoAvailable: no group but size "+recvThreads.size());

            Toast.makeText(getApplicationContext(), "no group", Toast.LENGTH_SHORT).show();

            ResetData();

        }

    }

};


However, for the group owner phone, the client list size is 1 or 2 (total 3 phones); in client phone, the size is always 0. 


Finding 10: Sending payload with your phone’s identification

You can send the string information as this:

new SendStringTask(socket).execute(WELCOME_INFO);

private class SendStringTask extends AsyncTask<String, Void, Void> {

    private Socket socket;

    String ipAddressOutput;

    public SendStringTask(Socket sk) {

        ipAddressOutput = sk.getInetAddress().getHostAddress();

        socket = sk;

    }

    @Override

    protected Void doInBackground(String... params) {

        String msg = params[0];

        try {

            if (socket == null)

            {

                Log.i(TAG, "SendStringTask do nothing to " + ipAddressOutput);

                return null;

            }

            Log.i(TAG, "SendStringTask write with socket "+ socket);

            OutputStream outputStream = socket.getOutputStream();

            outputStream.write((myDeviceName+": "+msg).getBytes());

        } catch (IOException e) {

            Log.i(TAG, "SendStringTask IOException: "+ e.toString());

            e.printStackTrace();

        }

        return null;

    }

}


The 3 phones I used for testing: 

Huawei Mate 20 Pro (Android 10, API 29) 

Samsung A11 (Android 12, API 31)

Samsung A13 (Android 13, API 33)



Sample App


Details of the source codes can be found at https://github.com/happytong/FunWifiP2P. 

You can download the App with WiFiP2P here: https://play.google.com/store/apps/details?id=com.tongs.funpatternwifi



Appendix: Introduction of Wi-Fi P2P with 5W1H (by chatGPT)


Wi-Fi Direct or Wi-Fi Peer-to-Peer (P2P) is a wireless networking technology that allows devices to communicate directly with each other without the need for a traditional wireless access point or network infrastructure. With Wi-Fi P2P, devices can connect and communicate with each other, regardless of their operating system, manufacturer or device type. Here is an introduction to Wi-Fi P2P technology using the 5W1H framework:

What is Wi-Fi P2P technology?
Wi-Fi P2P technology is a wireless networking standard that enables two or more devices to connect and communicate with each other directly, without the need for a traditional Wi-Fi access point or network infrastructure. It is a fast, easy, and secure way to share files, media, and other data between devices.

Why is Wi-Fi P2P technology important?
Wi-Fi P2P technology is important because it enables devices to communicate directly with each other without the need for a traditional network infrastructure. This is useful in situations where there is no Wi-Fi access point available, or where it is not practical or desirable to connect to a network. Wi-Fi P2P also enables faster data transfer speeds and reduces battery consumption compared to traditional Wi-Fi connections.

Who uses Wi-Fi P2P technology?
Wi-Fi P2P technology is used by a wide range of devices, including smartphones, tablets, laptops, cameras, printers, and other consumer electronics. It is particularly useful for sharing files, photos, and videos between mobile devices, as well as for printing and scanning documents without the need for cables or network infrastructure.

Where is Wi-Fi P2P technology used?
Wi-Fi P2P technology can be used in a variety of settings, including homes, offices, public spaces, and outdoor environments. It is particularly useful in situations where there is no Wi-Fi access point available, such as in remote locations, outdoor events, or in areas with poor network coverage. Wi-Fi P2P can also be used for peer-to-peer gaming and other interactive applications.

When was Wi-Fi P2P technology developed?
Wi-Fi P2P technology was developed by the Wi-Fi Alliance, a nonprofit organization that develops and promotes Wi-Fi standards. The Wi-Fi Alliance introduced Wi-Fi Direct in 2010, which became the first Wi-Fi P2P standard. Since then, Wi-Fi P2P has become increasingly popular and is now widely supported by a variety of devices and operating systems.

How does Wi-Fi P2P technology work?
Wi-Fi P2P technology works by establishing a direct connection between two or more devices using Wi-Fi Direct. To establish a connection, one device needs to discover the other device and send a connection request. Once the connection is established, the devices can communicate directly with each other, without the need for a network infrastructure. Wi-Fi P2P also supports various security features, including WPA2 encryption and device authentication, to ensure that the connection is secure.

No comments:

Post a Comment

How to draw countless lines without rendering delay in Android Phone

If you just call the drawing API to draw lines on the screen, you may be comfortable using it when the line number is small, you will see th...