A Wi-Fi Direct Chat for a huge amount of users
Wi-Fi Direct MultiChat (aka Pigeon Messenger) is a demo Android's application that try to overcome some Wi-Fi Direct's limitations. At the moment, the wifi diver of commercial devices doesn't allow a device to partecipate concurrently in two Wi-Fi Direct's groups. This app tries to overcome this limitation, indeed, a user can communicate with a large number of nearby people without an internet connection, using point-to-point communications. The main goal is the simultaneous management of multiple chats, queuing messages sent when the connection is not available and send them at the same time as soon as possible.
Pigeon Messenger requires Android 4.4 KitKat (API 19) or higher. This choice is related to to the fact that in previous versions, this protocol was unstable and unreliable.
It's important to remember that this is a demo application, so features like the management of screen's rotation, standby device, wifi not available and so on, are not managed as a commercial product.
Pigeon Messenger works with good performances. The main problems are the "Discovery Phase" of this protocol and the Wi-Fi Direct's implementation in Android, in fact:
This shows that it's possible to extend the Wi-Fi Direct protocol in Android in some particular and limited scenarios, for example a chat.
You can:
If you want to configure this app as you prefer, pay attention to: Configuration.java
.
If you want to release this application without debug messages inside chats, change this constant to "false" :
public static final boolean DEBUG_VERSION = true;
If you want to change client's and GO's ports, change this:
public static final int GROUPOWNER_PORT = 4545;
public static final int CLIENT_PORT = 5000;
If you want to change the maximum number of devices that a GO can manage for the chat, change this:
public static final int THREAD_COUNT = 20;
This attributes are used inside this app to exchange information between devices, like a protocol, to initialize the associated chat:
public static final String MAGICADDRESSKEYWORD = "4<D<D<R<3<5<5";
public static final String PLUSSYMBOLS = "++++++++++++++++++++++++++";
If yuo want to change the termination String of all device's names change this attribute :
public static final String SERVICE_INSTANCE = "polimip2p";
The following attributes are used inside this app:
public static final int THREAD_POOL_EXECUTOR_KEEP_ALIVE_TIME = 10; //don't touch this!
public static final String TXTRECORD_PROP_AVAILABLE = "available";
public static final String SERVICE_REG_TYPE = "_presence._tcp";
public static final int MESSAGE_READ = 0x400 + 1;
public static final int FIRSTMESSAGEXCHANGE = 0x400 + 2;
public static final String MESSAGE_READ_MSG = "MESSAGE_READ";
public static final String FIRSTMESSAGEXCHANGE_MSG = "FIRSTMESSAGEXCHANGE";
To change/add blacklisted words pay attention to MessageFilter.java
.
Every message that contains one or more of this words will be filtered on reception.
Example: i want remove every message that contains at least one of this words: "illegal", "piracy", "crack", "Piracy". Add to the lowerCaseBlackList this words in this way:
/**
* Private constructor, because is a singleton class.
*/
private MessageFilter() {
lowerCaseBlackList = new ArrayList<>();
//add here all the words that you want to blacklist
lowerCaseBlackList.add("illegal"); // OK
lowerCaseBlackList.add("piracy"); // OK
lowerCaseBlackList.add("crack"); // OK
//useless because ev ery words in this list are elaborated as "lower case".
//lowerCaseBlackList.add("Piracy"); // USELESS
}
I created this annotation as a custom java annotation to advise developers that some attributes must be private.
Obviously, if you want you can make every public attribute, also with this annotation, but can be very dangerous.
As you can see in DestinationDeviceTabList
(attribute deviceList) and ServiceList
(attribute serviceList) there is this annotation, because if you access or change this attributes without the custom logic that i implemented in these classes, to do secure operations, you can obtain Exceptions or other problems.
These classes remap list's indexes, add/set object without duplicates and in a particular way, that is very necessary to this software. Every time that you want to change something here, you should create a secure method to manage these attributes.
In Android you can't retrieve the current IP Address in a quick and easy way, because this method is not available in Google APIs. Its possible to do this in two ways: the first one requires to execute a shell's command, the second one requires only java. I chose the second solution, because it's quicker to implement. In particular, i get the GO's IP Address inside onConnectionInfoAvailable, but to retrieve the client's IP Address i need to ask this information to the socket on GO's side when the connection has been established. Only GOs can retrieve Client's IP addresses, because clients can obtain only the GO's IP address. Therefore, it's necessary to send the client's IP address to the client itself from its GO, to be able to store them in a variable and use this information for something else, for example to show the ip in the UI or to open other sockets.
In MainActivity.java
@Override
public void onConnectionInfoAvailable(WifiP2pInfo p2pInfo) {
(...)
//set Group Owner ip address
TabFragment.getWiFiP2pServicesFragment().setLocalDeviceIpAddress(p2pInfo.groupOwnerAddress.getHostAddress());
(..)
}
In GroupOwnerSocketHandler.java
(GO)
Socket clientSocket = socket.accept(); //because now i'm connected with the client/peer device
pool.execute(new ChatManager(clientSocket, handler));
ipAddress = clientSocket.getInetAddress(); //local variable with a get method
In MainActivity.java
(GO)
private void sendAddress(String deviceMacAddress, String name, ChatManager chatManager) {
if (chatManager != null) {
InetAddress ipAddress;
if (socketHandler instanceof GroupOwnerSocketHandler) {
ipAddress = ((GroupOwnerSocketHandler) socketHandler).getIpAddress();
Log.d(TAG, "sending message with MAGICADDRESSKEYWORD, with ipaddress= " + ipAddress.getHostAddress());
chatManager.write((Configuration.PLUSSYMBOLS + Configuration.MAGICADDRESSKEYWORD +
"___" + deviceMacAddress + "___" + name + "___" + ipAddress.getHostAddress()).getBytes());
} else {
Log.d(TAG, "sending message with MAGICADDRESSKEYWORD, without ipaddress");
//i use "+" symbols as initial spacing to be sure that also if some initial character will be lost i'll have always
//the Configuration.MAGICADDRESSKEYWORD and i can set the associated device to the correct WifiChatFragment.
chatManager.write((Configuration.PLUSSYMBOLS + Configuration.MAGICADDRESSKEYWORD +
"___" + deviceMacAddress + "___" + name).getBytes());
}
}
}
In MainActivity.java
(CLIENT)
if (readMessage.contains(Configuration.MAGICADDRESSKEYWORD)) {
WifiP2pDevice p2pDevice = new WifiP2pDevice();
p2pDevice.deviceAddress = readMessage.split("___")[1];
p2pDevice.deviceName = readMessage.split("___")[2];
P2pDestinationDevice device = new P2pDestinationDevice(p2pDevice);
if (readMessage.split("___").length == 3) {
Log.d(TAG, "handleMessage, p2pDevice created with: " + p2pDevice.deviceName + ", " + p2pDevice.deviceAddress);
manageAddressMessageReception(device);
} else if (readMessage.split("___").length == 4) {
device.setDestinationIpAddress(readMessage.split("___")[3]);
//set client ip address
TabFragment.getWiFiP2pServicesFragment().setLocalDeviceIpAddress(device.getDestinationIpAddress());
Log.d(TAG, "handleMessage, p2pDevice created with: " + p2pDevice.deviceName + ", "
+ p2pDevice.deviceAddress + ", " + device.getDestinationIpAddress());
manageAddressMessageReception(device);
}
}
Copyright 2015 Stefano Cappa
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.