Android Connectivity
Juan E. Vargas
https://goo.gl/ddLM9h
Apologies to Antartica
Interior Maps
Android devices can be connected to
Android devices can be passive clients or active servers running their own networking services.
The broad spectrum of possibilities makes the topic of Android connectivity subtle and quite complex.
NSD
Android has under its .net api a set of libraries for NSD (Network Service Discovery). These libraries are used to find devices and to connect to some of the local devices found.
Once such devices are found, connectivity can be established using USB, Bluetooth and/or Wi-Fi.
We will explore the following topics:
NSD
Network Service Discovery (NSD) allows apps to discover and identify other devices on the local network that support services that our apps may need or request.
This is useful for apps that require P2P communication for file sharing, multi-player gaming, etc.
Developers can use the NSD API to broadcast names and connection information to the local network and to get information from other applications doing the same.
Once a connection is established, communicating with the same app running on a different device is possible.
NSD
These are the steps needed:
In most cases code development for these steps consist of overriding methods from the android.net API classes.
This involves (1) code reuse and (2) following best practices examples. Developers need not ask what they can do for Android connectivity but instead ask what Android connectivity can do for them … ;-)
Developers who understand the mechanics of method overriding can become experts quickly as long as they follow the patterns embedded in the API
Standards and Best Practices
IANA is an international org that manages a centralized and authoritative list of service types used by service discovery protocols such as NSD.
You may need to get the list from IANA for service names and port numbers. If you are creating a new service type, you may need to reserve it by filling out the IANA Ports and Service Registration form.
When setting a port number for a service in your app, DO NOT HARDCODE the number. There is no need for the port number to be known by other apps at compile-time because this practice will likely create conflicts with other applications and also become a honey trap (not to be confused with a honeypot) for viral infections.
Instead use the device's next available port. This information will be provided at run-time to other apps wishing to connect as a service broadcast. The apps can get this information from your service broadcast, right before connecting to your service.
http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml
This step is necessary only if the app is a service that needs to broadcast over the local network.
To create and register a service on the local network, a NsdServiceInfo object is needed. This object provides the information that other devices on the network will use to connect to your service.
public void registerService(int port) {
NsdServiceInfo serviceInfo = new NsdServiceInfo();
// The name is subject to change based on conflicts
// with other services advertised on the same network.
serviceInfo.setServiceName("NsdChat");
serviceInfo.setServiceType("_http._tcp.");
serviceInfo.setPort(port);
....
}
� �
"NsdChat" becomes a service visible to any device on the network that is using NSD to search for local services.
Name must be unique on the network. Android automatically handles conflict resolution, e.g., a second service of same name becomes "NsdChat (1)".
_protocol._trasportlayer
See also:
https://developer.android.com/reference/android/net/nsd/NsdManager.html
End Points
A network socket is an endpoint of a P2P communication across a computer network.
Most often the Internet Protocol (IP) is used.
Therefore you will work mostly with TCP/IP Sockets.
Initializing Sockets
public void initializeServerSocket() {
// Initialize a server socket on the next available port.
mServerSocket = new ServerSocket(0);
// Store the chosen port.
mLocalPort = mServerSocket.getLocalPort();
...
}
Entering (0) initializes the socket to the the “next available port”
now you know your port number
2. Registering the NSD Object
The NsdServiceInfo object needs to be registered. This is done with a RegistrationListener interface, which contains callbacks to alert your app of the success/failure of service registration/unregistration.
public void initializeRegistrationListener() {
mRegistrationListener = new NsdManager.RegistrationListener() {
@Override
public void onServiceRegistered(NsdServiceInfo NsdServiceInfo) {
// App needs to get serviceName because Android may have changed it to resolve conflicts.
mServiceName = NsdServiceInfo.getServiceName();
}
@Override
public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
// Registration failed! Enter code to determine why.
}
@Override
public void onServiceUnregistered(NsdServiceInfo arg0) {
// Service has been unregistered. This happens when app calls NsdManager.unregisterService() and passes in this listener.
}
@Override
public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
// Unregistration failed. Enter code to determine why.
}
};
}
DO SOMETHING!
2. Registering the Service
Once the NSDInfo object and the sockets are ready the service needs to be registered via a registerService() method, such as the one shown below, which is asynchronous.
This means that any code that needs to run after the service has been registered must go in the onServiceRegistered() event listener method, which is a method that the service developer needs to specialize
public void registerService(int port) {
NsdServiceInfo serviceInfo = new NsdServiceInfo();
serviceInfo.setServiceName("NsdChat");
serviceInfo.setServiceType("_http._tcp.");
serviceInfo.setPort(port);
mNsdManager = Context.getSystemService(Context.NSD_SERVICE);
mNsdManager.registerService(
serviceInfo, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);
}
Not shown here
At this point we have...
SERVER
CLIENT
3. Discovering Services on Network
“ … the network is teeming with life, from the beastly network printers to the docile network webcams, to the brutal, fiery battles of nearby tic-tac-toe players…”
Client apps need to listen to service broadcasts on the network to determine what services are available and are worth connecting to.
Service discovery involves (1) setting up a discovery listener with its relevant callbacks, and (2) making a single asynchronous API call to discoverServices().
Discovery Listener
public void initializeDiscoveryListener() {
mDiscoveryListener = new NsdManager.DiscoveryListener() {
@Override
public void onDiscoveryStarted(String regType) {
Log.d(TAG, "Service discovery started");
}
@Override
public void onServiceFound(NsdServiceInfo service) {
// A service was found! Do something with it.
Log.d(TAG, "Service discovery success" + service);
if (!service.getServiceType().equals(SERVICE_TYPE)) {
// Service type is the string containing the protocol and transport layer for this service.
Log.d(TAG, "Unknown Service Type: " + service.getServiceType());
} else if (service.getServiceName().equals(mServiceName)) {
// The name of the service tells users what they'd be connecting to.
Log.d(TAG, "Same machine: " + mServiceName);
} else if (service.getServiceName().contains("NsdChat")){
mNsdManager.resolveService(service, mResolveListener);
}
}
Creates anonymous method as a closure
instantiate nsd DiscoveryListener
called as soon as svc begins
Discovery Listener
public void initializeDiscoveryListener() {
@Override
public void onServiceLost(NsdServiceInfo service) {
// When the network service is no longer available Internal bookkeeping code goes here.
Log.e(TAG, "service lost" + service);
}
@Override
public void onDiscoveryStopped(String serviceType) {
Log.i(TAG, "Discovery stopped: " + serviceType);
}
@Override
public void onStartDiscoveryFailed(String serviceType, int errorCode) {
Log.e(TAG, "Discovery failed: Error code:" + errorCode);
mNsdManager.stopServiceDiscovery(this);
}
@Override
public void onStopDiscoveryFailed(String serviceType, int errorCode) {
Log.e(TAG, "Discovery failed: Error code:" + errorCode);
mNsdManager.stopServiceDiscovery(this);
}
These methods need to be implemented with the specific details for the app in mind
4. Connecting to Services
Once a worth-connecting service is found, an explicit connection needs to be initiated at the client app.
To do that the app needs to examine the connection information for that service via resolveService().
The app needs to implement a NsdManager.ResolveListener() and use it to get the connection information.
4. Connecting to Services
public void initializeResolveListener() {
mResolveListener = new NsdManager.ResolveListener() {
@Override
public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
// Called when the resolve fails. Use the error code to debug.
Log.e(TAG, "Resolve failed" + errorCode);
}
@Override
public void onServiceResolved(NsdServiceInfo serviceInfo) {
Log.e(TAG, "Resolve Succeeded. " + serviceInfo);
if (serviceInfo.getServiceName().equals(mServiceName)) {
Log.d(TAG, "Same IP.");
return;
}
mService = serviceInfo;
int port = mService.getPort();
InetAddress host = mService.getHost();
}
}; // end of listener object
} // end of method
where are the mThings kept?
At this point we have...
SERVER
CLIENT
5. Unregister a Service on Application Close
It is important to enable and disable NSD as needed during the app lifecycle. Unregistering the service when the app server closes prevents other apps from attempting to connect to it.
Service discovery is an expensive operation that should be stopped when the parent activity is paused and re-enabled when the activity is resumed.
This is done by overriding the lifecycle methods of the main activity and inserting code to start and stop service broadcast and discovery as appropriate.
@Override
protected void onPause() {
if (mNsdHelper != null) {
mNsdHelper.tearDown();
}
super.onPause();
}
@Override
protected void onResume() {
super.onResume();
if (mNsdHelper != null) {
mNsdHelper.registerService(mConnection.getLocalPort());
mNsdHelper.discoverServices();
}
}
@Override
protected void onDestroy() {
mNsdHelper.tearDown();
mConnection.tearDown();
super.onDestroy();
}
// NsdHelper's tearDown method
public void tearDown() {
mNsdManager.unregisterService(mRegistrationListener);
mNsdManager.stopServiceDiscovery(mDiscoveryListener);
}
This code goes in app’s Activity
where are the mThings kept?
At this point we have...
SERVER
CLIENT
android.net
There are several android-specific classes under this base-class that provide services above and beyond what you have seen in the java.net.* APIs
http://developer.android.com/reference/android/net/package-summary.html
P2P Connections with Wi-Fi
The android.net.wifi.p2p is an Android P2P API that provides apps the ability to connect to a network or a hotspot via Wi-Fi.
WiFi P2P allows apps to interact with nearby devices over a range beyond Bluetooth.
As before, there are several areas in the app code that need to be touched to make this happen.
… much more
SetUp Application Permissions
The following permissions need to be set in the app manifest:
CHANGE_WIFI_STATE ACCESS_WIFI_STATE and INTERNET
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.nsdchat"
...
<uses-permission
android:required="true"
android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission
android:required="true"
android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission
android:required="true"
android:name="android.permission.INTERNET"/>
...
Receiving Messages
In Android, broadcast messages take the form of intents that notify apps the occurrence of certain events.
Events in Android are detected via listeners.
The app needs to set up two listeners: IntentFilter and Brodcastreceiver.
IntentFilter
WIFI_P2P_STATE_CHANGED_ACTION | Is Wi-Fi P2P enabled ? |
WIFI_P2P_PEERS_CHANGED_ACTION | Has the available peer list changed? |
WIFI_P2P_CONNECTION_CHANGED_ACTION | Has the state of Wi-Fi P2P connectivity changed? |
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION | Has device configuration details changed? |
Overriding IntentFilter Methods
private final IntentFilter intentFilter = new IntentFilter();
...
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Indicates a change in the Wi-Fi P2P status.
intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
// Indicates a change in the list of available peers.
intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
// Indicates the state of Wi-Fi P2P connectivity has changed.
intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
// Indicates this device's details have changed.
intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
...
}
Besides the overriding mechanics, we may envision listeners as an extension of the
try/catch structure...
…. on steroids...
WiFiP2pManager
At the end of the activity onCreate( ) an instance of WifiP2pManager needs to be created and immediately after a call to its initialize() method must be issued.
The channel object returned is the one that will be used by the app to connect to the Wi-Fi P2P framework.
Channel mChannel;
public void onCreate(Bundle savedInstanceState) {
.... // do stuff...
mManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
mChannel = mManager.initialize(this, getMainLooper(), null);
}
BroadcastReceiver Object
The next step is to create a new object BroadcastReceiver that will listen for changes to the System's Wi-Fi P2P state.
This object will have an onReceive() method, and code/conditions to handle each of the four P2P actions (state changes) listed before.
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
// Determine if Wifi P2P mode is enabled or not, alert the Activity.
int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1);
if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) {
activity.setIsWifiP2pEnabled(true);
} else {
activity.setIsWifiP2pEnabled(false);
}
} else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
// Peer list has changed! Do something about that.
} else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {
// Connection state changed! Do something about that.
} else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {
DeviceListFragment fragment = (DeviceListFragment) activity.getFragmentManager()
.findFragmentById(R.id.frag_list);
fragment.updateThisDevice((WifiP2pDevice) intent.getParcelableExtra(
WifiP2pManager.EXTRA_WIFI_P2P_DEVICE));
}
}
Where is this method created?????
Register Filter and Receiver
Next step is to register the intent filter and the broadcast receiver when the main activity is active.
Also, these objects need to get unregistered when the activity is paused. The best place to do the registration and the unregistration is the onResume() and onPause() methods.
OnResume and OnPause
/** register the BroadcastReceiver with the intent values to be matched */
@Override
public void onResume() {
super.onResume();
receiver = new WiFiDirectBroadcastReceiver(mManager, mChannel, this);
registerReceiver(receiver, intentFilter);
}
@Override
public void onPause() {
super.onPause();
unregisterReceiver(receiver);
}
Where are these method created?????
Peer Discovery
Apps search for nearby devices with Wi-Fi P2P using discoverPeers(). This method takes two arguments:
mManager.discoverPeers(mChannel, new WifiP2pManager.ActionListener() {
@Override
public void onSuccess() {
// Code to process successful discovery initiation.
// No services are actually been discovered yet, so this method
// can often be left blank. Code for peer discovery goes in the
// onReceive method, described later on another slide.
}
@Override
public void onFailure(int reasonCode) {
// Code template to process discovery initiation failures.
// Alert the user that something went wrong.
}
});
Remember that this only initiates the peer discovery process. The method just gets the discovery process launched and immediately returns. The system notifies the app via the action listeners.
Note also that discovery will remain active until a connection is initiated or a P2P group is formed.
List of Peers
Likely there will be more than one response from peers. The next step is to get the list of peers, process the list, and select the peer the app will connect to.
This is easier done than said, by implementing a WifiP2pManager.PeerListListener interface, which provides information about the peers detected by the Wi-Fi P2P
private List peers = new ArrayList();
...
private PeerListListener peerListListener = new PeerListListener() {
@Override
public void onPeersAvailable(WifiP2pDeviceList peerList) {
// Clear and refresh list
peers.clear();
peers.addAll(peerList.getDeviceList());
// If an AdapterView is backed by this data, notify it about the change.
// For instance, if you have a ListView of available peers, trigger an update.
((WiFiPeerListAdapter) getListAdapter()).notifyDataSetChanged();
if (peers.size() == 0) {
Log.d(WiFiDirectActivity.TAG, "No devices found");
return;
}
}
}
Where is this interface created?????
OnReceive
The broadcast receiver's onReceive() method calls requestPeers() when an intent with the action WIFI_P2P_PEERS_CHANGED_ACTION is received.
You must pass this listener into the receiver, e.g., sending it as an argument to the broadcast receiver's constructor.
public void onReceive(Context context, Intent intent) {
...
else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
// Request available peers from the wifi p2p manager. This call is asynchronous call.
// The calling activity is notified with a callback on PeerListListener.onPeersAvailable()
if (mManager != null) {
mManager.requestPeers(mChannel, peerListListener);
}
Log.d(WiFiDirectActivity.TAG, "P2P peers changed");
}...
}
Connecting to a Peer
Connections to peers are established through WifiP2pConfig objects. �
First an object of the class is constructed, then parameters are passed, and then the connect () method is called.
Overriding Connect ()
@Override
public void connect() {
WifiP2pDevice device = peers.get(0); // get first device found on network.
WifiP2pConfig config = new WifiP2pConfig();
config.deviceAddress = device.deviceAddress;
config.wps.setup = WpsInfo.PBC;
mManager.connect ( mChannel, config, new ActionListener() {
@Override
public void onSuccess() {
// WiFiDirectBroadcastReceiver will notify us. Ignore for now.
}
@Override
public void onFailure(int reason) {
Toast.makeText(WiFiDirectActivity.this, "Connect failed. Retry.",
Toast.LENGTH_SHORT).show();
}
} ) ;
}
Listen for changes in Connection State
The WifiP2pManager.ActionListener above only notifies when the initiation succeeds or fails.
To listen for changes in connection state, an implementation of a WifiP2pManager.ConnectionInfoListener interface is needed.
Its onConnectionInfoAvailable() callback will notify when the state of the connection changes.
In cases where multiple devices are going to be connected to a single device one device will be designated as the "group owner".
@Override
public void onConnectionInfoAvailable(final WifiP2pInfo info) {
// InetAddress from WifiP2pInfo struct.
InetAddress groupOwnerAddress = info.groupOwnerAddress.getHostAddress());
// After group negotiation, determine group owner.
if (info.groupFormed && info.isGroupOwner) {
// Do whatever tasks are specific to the group owner.
// One common case is creating a server thread to accept incoming connections.
} else if (info.groupFormed) {
// The other device acts as the client. In this case, create a client
// thread to connect to the group owner.
}
}
Addition to OnReceive
The code in next slide needs to be added to the broadcastreceiver onReceive() method to listen for the intent
WIFI_P2P_CONNECTION_CHANGED_ACTION
When the intent is received, the code calls requestConnectionInfo().
This is an asynchronous call; results will be received by the connection info listener provided as a parameter.
...
} else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {
if (mManager == null) {
return;
}
NetworkInfo networkInfo = (NetworkInfo) intent
.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);
if (networkInfo.isConnected()) {
// App is connected with the other device(s), request connection
// info to find group owner IP
mManager.requestConnectionInfo(mChannel, connectionListener);
}
...
Using Wi-Fi P2P for Service Discovery
We have seen how NSD can be used to discover services connected to a local network.
Wi-Fi P2P Service Discovery allows apps to discover nearby services directly without being connected to a network.
Also, services (on your device) can be advertised
BlueTooth Code
BlueTooth Chat
Three Java Classes
4 Layouts
BluetoothChat Java Class
BluetoothChat Service Java Class
Sets up and manages the BT connections with other devices.
This class defines a thread class and uses three threads: one listens incoming connections, another connects to other devices, and the third is used for data transmission
Update Android Studio
If your code fails after updating the Android Studio and you get some error message suggesting that something is wrong with Gradle, try this:
minifyEnabled false
instead of
runProguard false
… NsdChat
NsdChat Code
Google Cloud Messaging (GCM)
GCM is a free service designed for sending messages to Android devices. GCM messaging can enhance users experience because apps can stay updated without wasting battery power on waking up the radio and polling the server when there are no updates.
GCM has generous allocations. It gives developers permission to attach up to 1K recipients to a message, this enables developers to contact large user base when needed without affecting the work load on their servers.
This topic is to be discussed on a different file, named Android GCM
Client / Server Interactions
SERVER
CLIENT
Synergy KVM