am 320b3ec2: Merge "Initial commit of wireless connectivity/discovery class." into jb-dev

* commit '320b3ec21eff971827a9032f1fb1ee7c866e4303':
  Initial commit of wireless connectivity/discovery class.
This commit is contained in:
Alexander Lucas
2012-07-31 11:42:59 -07:00
committed by Android Git Automerger
6 changed files with 1080 additions and 0 deletions

Binary file not shown.

View File

@@ -0,0 +1,62 @@
page.title=Connecting Devices Wirelessly
trainingnavtop=true
startpage=true
next.title=Using Network Service Discovery
next.link=nsd.html
@jd:body
<div id="tb-wrapper">
<div id="tb">
<h2>Dependencies and prerequisites</h2>
<ul>
<li>Android 4.1 or higher</li>
</ul>
<h2>You should also read</h2>
<ul>
<li><a href="{@docRoot}guide/topics/connectivity/wifip2p.html">Wi-Fi Direct</a></li>
</ul>
</div>
</div>
<p>Besides enabling communication with the cloud, Android's wireless APIs also
enable communication with other devices on the same local network, and even
devices which are not on a network, but are physically nearby. The addition of
Network Service Discovery (NSD) takes this further by allowing an application to
seek out a nearby device running services with which it can communicate.
Integrating this functionality into your application helps you provide a wide range
of features, such as playing games with users in the same room, pulling
images from a networked NSD-enabled webcam, or remotely logging into
other machines on the same network.</p>
<p>This class describes the key APIs for finding and
connecting to other devices from your application. Specifically, it
describes the NSD API for discovering available services and the Wi-Fi
Direct&trade; API for doing peer-to-peer wireless connections. This class also
shows you how to use NSD and Wi-Fi Direct in
combination to detect the services offered by a device and connect to the
device when neither device is connected to a network.
</p>
<h2>Lessons</h2>
<dl>
<dt><strong><a href="nsd.html">Using Network Service Discovery</a></strong></dt>
<dd>Learn how to broadcast services offered by your own application, discover
services offered on the local network, and use NSD to determine the connection
details for the service you wish to connect to.</dd>
<dt><strong><a href="wifi-direct.html">Connecting with Wi-Fi Direct</a></strong></dt>
<dd>Learn how to fetch a list of nearby peer devices, create an access point
for legacy devices, and connect to other devices capable of Wi-Fi Direct
connections.</dd>
<dt><strong><a href="nsd-wifi-direct.html">Using Wi-Fi Direct for Service
Discovery</a></strong></dt>
<dd>Learn how to discover services published by nearby devices without being
on the same network, using Wi-Fi Direct.</dd>
</dl>

View File

@@ -0,0 +1,252 @@
page.title=Using Wi-Fi Direct for Service Discovery
parent.title=Connecting Devices Wirelessly
parent.link=index.html
trainingnavtop=true
@jd:body
<div id="tb-wrapper">
<div id="tb">
<h2>This lesson teaches you to</h2>
<ol>
<li><a href="#manifest">Set Up the Manifest</a></li>
<li><a href="#register">Add a Local Service</a></li>
<li><a href="#discover">Discover Nearby Services</a></li>
</ol>
<!--
<h2>You should also read</h2>
<ul>
<li><a href="#"></a></li>
</ul>
-->
</div>
</div>
<p>The first lesson in this class, <a href="nsd.html">Using Network Service
Discovery</a>, showed you
how to discover services that are connected to a local network. However, using
Wi-Fi Direct&trad; Service Discovery allows you to discover the services of nearby devices directly,
without being connected to a network. You can also advertise the services
running on your device. These capabilities help you communicate between apps,
even when no local network or hotspot is available.</p>
<p>While this set of APIs is similar in purpose to the Network Service Discovery
APIs outlined in a previous lesson, implementing them in code is very different.
This lesson shows you how to discover services available from other devices,
using Wi-Fi Direct&trade;. The lesson assumes that you're already familiar with the
<a href="{@docRoot}guide/topics/connectivity/wifip2p.html">Wi-Fi Direct</a> API.</p>
<h2 id="manifest">Set Up the Manifest</h2>
<p>In order to use Wi-Fi Direct, add the {@link
android.Manifest.permission#CHANGE_WIFI_STATE}, {@link
android.Manifest.permission#ACCESS_WIFI_STATE},
and {@link android.Manifest.permission#INTERNET}
permissions to your manifest. Even though Wi-Fi Direct doesn't require an
Internet connection, it uses standard Java sockets, and using these in Android
requires the requested permissions.</p>
<pre>
&lt;manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.nsdchat"
...
&lt;uses-permission
android:required="true"
android:name="android.permission.ACCESS_WIFI_STATE"/&gt;
&lt;uses-permission
android:required="true"
android:name="android.permission.CHANGE_WIFI_STATE"/&gt;
&lt;uses-permission
android:required="true"
android:name="android.permission.INTERNET"/&gt;
...
</pre>
<h2 id="register">Add a Local Service</h2>
<p>If you're providing a local service, you need to register it for
service discovery. Once your local service is registered, the framework
automatically responds to service discovery requests from peers.</p>
<p>To create a local service:</p>
<ol>
<li>Create a
{@link android.net.wifi.p2p.nsd.WifiP2pServiceInfo} object.</li>
<li>Populate it with information about your service.</li>
<li>Call {@link
android.net.wifi.p2p.WifiP2pManager#addLocalService(WifiP2pManager.Channel,
WifiP2pServiceInfo, WifiP2pManager.ActionListener) addLocalService()} to register the local
service for service discovery.</li>
</ol>
<pre>
private void startRegistration() {
// Create a string map containing information about your service.
Map<String,String> record = new HashMap<String,String>();
record.put("listenport", String.valueOf(SERVER_PORT));
record.put("buddyname", "John Doe" + (int) (Math.random() * 1000));
record.put("available", "visible");
// Service information. Pass it an instance name, service type
// _protocol._transportlayer , and the map containing
// information other devices will want once they connect to this one.
WifiP2pDnsSdServiceInfo serviceInfo =
WifiP2pDnsSdServiceInfo.newInstance("_test", "_presence._tcp", record);
// Add the local service, sending the service info, network channel,
// and listener that will be used to indicate success or failure of
// the request.
mManager.addLocalService(channel, serviceInfo, new ActionListener() {
&#64;Override
public void onSuccess() {
// Command successful! Code isn't necessarily needed here,
// Unless you want to update the UI or add logging statements.
}
&#64;Override
public void onFailure(int arg0) {
// Command failed. Check for P2P_UNSUPPORTED, ERROR, or BUSY
}
});
}
</pre>
<h2 id="discover">Discover Nearby Services</h2>
<p>Android uses callback methods to notify your application of available services, so
the first thing to do is set those up. Create a {@link
android.net.wifi.p2p.WifiP2pManager.DnsSdTxtRecordListener} to listen for
incoming records. This record can optionally be broadcast by other
devices. When one comes in, copy the device address and any other
relevant information you want into a data structure external to the current
method, so you can access it later. The following example assumes that the
record contains a "buddyname" field, populated with the user's identity.</p>
<pre>
final HashMap&lt;String, String&gt; buddies = new HashMap&lt;String, String&gt;();
...
private void discoverService() {
DnsSdTxtRecordListener txtListener = new DnsSdTxtRecordListener() {
&#64;Override
/* Callback includes:
* fullDomain: full domain name: e.g "printer._ipp._tcp.local."
* record: TXT record dta as a map of key/value pairs.
* device: The device running the advertised service.
*/
public void onDnsSdTxtRecordAvailable(
String fullDomain, Map<String,String> record, WifiP2pDevice device) {
Log.d(TAG, "DnsSdTxtRecord available -" + record.toString());
buddies.put(device.deviceAddress, record.get("buddyname"));
}
};
...
}
</pre>
<p>To get the service information, create a {@link
android.net.wifi.p2p.WifiP2pManager.DnsSdServiceResponseListener}. This
receives the actual description and connection information. The previous code
snippet implemented a {@link java.util.Map} object to pair a device address with the buddy
name. The service response listener uses this to link the DNS record with the
corresponding service information. Once both
listeners are implemented, add them to the {@link
android.net.wifi.p2p.WifiP2pManager} using the {@link
android.net.wifi.p2p.WifiP2pManager#setDnsSdResponseListeners(WifiP2pManager.Channel,
WifiP2pManager.DnsSdServiceResponseListener,
WifiP2pManager.DnsSdTxtRecordListener) setDnsSdResponseListeners()} method.</p>
<pre>
private void discoverService() {
...
DnsSdServiceResponseListener servListener = new DnsSdServiceResponseListener() {
&#64;Override
public void onDnsSdServiceAvailable(String instanceName, String registrationType,
WifiP2pDevice resourceType) {
// Update the device name with the human-friendly version from
// the DnsTxtRecord, assuming one arrived.
resourceType.deviceName = buddies
.containsKey(resourceType.deviceAddress) ? buddies
.get(resourceType.deviceAddress) : resourceType.deviceName;
// Add to the custom adapter defined specifically for showing
// wifi devices.
WiFiDirectServicesList fragment = (WiFiDirectServicesList) getFragmentManager()
.findFragmentById(R.id.frag_peerlist);
WiFiDevicesAdapter adapter = ((WiFiDevicesAdapter) fragment
.getListAdapter());
adapter.add(resourceType);
adapter.notifyDataSetChanged();
Log.d(TAG, "onBonjourServiceAvailable " + instanceName);
}
};
mManager.setDnsSdResponseListeners(channel, servListener, txtListener);
...
}
</pre>
<p>Now create a service request and call {@link
android.net.wifi.p2p.WifiP2pManager#addServiceRequest(WifiP2pManager.Channel,
WifiP2pServiceRequest, WifiP2pManager.ActionListener) addServiceRequest()}.
This method also takes a listener to report success or failure.</p>
<pre>
serviceRequest = WifiP2pDnsSdServiceRequest.newInstance();
mManager.addServiceRequest(channel,
serviceRequest,
new ActionListener() {
&#64;Override
public void onSuccess() {
// Success!
}
&#64;Override
public void onFailure(int code) {
// Command failed. Check for P2P_UNSUPPORTED, ERROR, or BUSY
}
});
</pre>
<p>Finally, make the call to {@link
android.net.wifi.p2p.WifiP2pManager#discoverServices(WifiP2pManager.Channel,
WifiP2pManager.ActionListener) discoverServices()}.</p>
<pre>
mManager.discoverServices(channel, new ActionListener() {
&#64;Override
public void onSuccess() {
// Success!
}
&#64;Override
public void onFailure(int code) {
// Command failed. Check for P2P_UNSUPPORTED, ERROR, or BUSY
if (code == WifiP2pManager.P2P_UNSUPPORTED) {
Log.d(TAG, "P2P isn't supported on this device.");
else if(...)
...
}
});
</pre>
<p>If all goes well, hooray, you're done! If you encounter problems, remember
that the asynchronous calls you've made take an
{@link android.net.wifi.p2p.WifiP2pManager.ActionListener} as an argument, and
this provides you with callbacks indicating success or failure. To diagnose
problems, put debugging code in {@link
android.net.wifi.p2p.WifiP2pManager.ActionListener#onFailure(int) onFailure()}. The error code
provided by the method hints at the problem. Here are the possible error values
and what they mean</p>
<dl>
<dt> {@link android.net.wifi.p2p.WifiP2pManager#P2P_UNSUPPORTED}</dt>
<dd> Wi-Fi Direct isn't supported on the device running the app.</dd>
<dt> {@link android.net.wifi.p2p.WifiP2pManager#BUSY}</dt>
<dd> The system is to busy to process the request.</dd>
<dt> {@link android.net.wifi.p2p.WifiP2pManager#ERROR}</dt>
<dd> The operation failed due to an internal error.</dd>
</dl>

View File

@@ -0,0 +1,373 @@
page.title=Using Network Service Discovery
parent.title=Connecting Devices Wirelessly
parent.link=index.html
trainingnavtop=true
next.title=Connecting with Wi-Fi Direct
next.link=wifi-direct.html
@jd:body
<div id="tb-wrapper">
<div id="tb">
<!-- table of contents -->
<h2>This lesson teaches you how to</h2>
<ol>
<li><a href="#register">Register Your Service on the Network</a></li>
<li><a href="#discover">Discover Services on the Network</a></li>
<li><a href="#connect">Connect to Services on the Network</a></li>
<li><a href="#teardown">Unregister Your Service on Application Close</a></li>
</ol>
<!--
<h2>You should also read</h2>
<ul>
</ul>
-->
<h2>Try it out</h2>
<div class="download-box">
<a href="{@docRoot}shareables/training/nsdchat.zip" class="button">Download
the sample app</a>
<p class="filename">nsdchat.zip</p>
</div>
</p>
</div>
</div>
<p>Adding Network Service Discovery (NSD) to your app allows your users to
identify other devices on the local network that support the services your app
requests. This is useful for a variety of peer-to-peer applications such as file
sharing or multi-player gaming. Android's NSD APIs simplify the effort required
for you to implement such features.</p>
<p>This lesson shows you how to build an application that can broadcast its
name and connection information to the local network and scan for information
from other applications doing the same. Finally, this lesson shows you how
to connect to the same application running on another device.</p>
<h2 id="register">Register Your Service on the Network</h2>
<p class="note"><strong>Note: </strong>This step is optional. If
you don't care about broadcasting your app's services over the local network,
you can skip forward to the
next section, <a href="#discover">Discover Services on the Network</a>.</p>
<p>To register your service on the local network, first create a {@link
android.net.nsd.NsdServiceInfo} object. This object provides the information
that other devices on the network use when they're deciding whether to connect to your
service. </p>
<pre>
public void registerService(int port) {
// Create the NsdServiceInfo object, and populate it.
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);
....
}
</pre>
<p>This code snippet sets the service name to "NsdChat".
The name is visible to any device on the network that is using NSD to look for
local services. Keep in mind that the name must be unique for any service on the
network, and Android automatically handles conflict resolution. If
two devices on the network both have the NsdChat application installed, one of
them changes the service name automatically, to something like "NsdChat
(1)".</p>
<p>The second parameter sets the service type, specifies which protocol and transport
layer the application uses. The syntax is
"_&lt;protocol&gt;._&lt;transportlayer&gt;". In the
code snippet, the service uses HTTP protocol running over TCP. An application
offering a printer service (for instance, a network printer) would set the
service type to "_ipp._tcp".</p>
<p class="note"><strong>Note: </strong> The International Assigned Numbers
Authority (IANA) manages a centralized,
authoritative list of service types used by service discovery protocols such as NSD and Bonjour.
You can download the list from <a
href="http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml">the
IANA list of service names and port numbers</a>.
If you intend to use a new service type, you should reserve it by filling out
the <a
href="http://www.iana.org/form/ports-services">IANA Ports and Service
registration form</a>.</p>
<p>When setting the port for your service, avoid hardcoding it as this
conflicts with other applications. For instance, assuming
that your application always uses port 1337 puts it in potential conflict with
other installed applications that use the same port. Instead, use the device's
next available port. Because this information is provided to other apps by a
service broadcast, there's no need for the port your application uses to be
known by other applications at compile-time. Instead, the applications can get
this information from your service broadcast, right before connecting to your
service.</p>
<p>If you're working with sockets, here's how you can initialize a socket to any
available port simply by setting it to 0.</p>
<pre>
public void initializeServerSocket() {
// Initialize a server socket on the next available port.
mServerSocket = new ServerSocket(0);
// Store the chosen port.
mLocalPort = mServerSocket.getLocalPort();
...
}
</pre>
<p>Now that you've defined the {@link android.net.nsd.NsdServiceInfo
NsdServiceInfo} object, you need to implement the {@link
android.net.nsd.NsdManager.RegistrationListener RegistrationListener} interface. This
interface contains callbacks used by Android to alert your application of the
success or failure of service registration and unregistration.
</p>
<pre>
public void initializeRegistrationListener() {
mRegistrationListener = new NsdManager.RegistrationListener() {
&#64;Override
public void onServiceRegistered(NsdServiceInfo NsdServiceInfo) {
// Save the service name. Android may have changed it in order to
// resolve a conflict, so update the name you initially requested
// with the name Android actually used.
mServiceName = NsdServiceInfo.getServiceName();
}
&#64;Override
public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
// Registration failed! Put debugging code here to determine why.
}
&#64;Override
public void onServiceUnregistered(NsdServiceInfo arg0) {
// Service has been unregistered. This only happens when you call
// NsdManager.unregisterService() and pass in this listener.
}
&#64;Override
public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
// Unregistration failed. Put debugging code here to determine why.
}
};
}
</pre>
<p>Now you have all the pieces to register your service. Call the method
{@link android.net.nsd.NsdManager#registerService registerService()}.
</p>
<p>Note that this method is asynchronous, so any code that needs to run
after the service has been registered must go in the {@link
android.net.nsd.NsdManager.RegistrationListener#onServiceRegistered(NsdServiceInfo)
onServiceRegistered()} method.</p>
<pre>
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);
}
</pre>
<h2 id="discover">Discover Services on the Network</h2>
<p>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. The key to letting your application see this vibrant ecosystem of
functionality is service discovery. Your application needs to listen to service
broadcasts on the network to see what services are available, and filter out
anything the application can't work with.</p>
<p>Service discovery, like service registration, has two steps:
setting up a discovery listener with the relevant callbacks, and making a single asynchronous
API call to {@link android.net.nsd.NsdManager#discoverServices(String
, int , NsdManager.DiscoveryListener) discoverServices()}.</p>
<p>First, instantiate an anonymous class that implements {@link
android.net.nsd.NsdManager.DiscoveryListener}. The following snippet shows a
simple example:</p>
<pre>
public void initializeDiscoveryListener() {
// Instantiate a new DiscoveryListener
mDiscoveryListener = new NsdManager.DiscoveryListener() {
// Called as soon as service discovery begins.
&#64;Override
public void onDiscoveryStarted(String regType) {
Log.d(TAG, "Service discovery started");
}
&#64;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 the user what they'd be
// connecting to. It could be "Bob's Chat App".
Log.d(TAG, "Same machine: " + mServiceName);
} else if (service.getServiceName().contains("NsdChat")){
mNsdManager.resolveService(service, mResolveListener);
}
}
&#64;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);
}
&#64;Override
public void onDiscoveryStopped(String serviceType) {
Log.i(TAG, "Discovery stopped: " + serviceType);
}
&#64;Override
public void onStartDiscoveryFailed(String serviceType, int errorCode) {
Log.e(TAG, "Discovery failed: Error code:" + errorCode);
mNsdManager.stopServiceDiscovery(this);
}
&#64;Override
public void onStopDiscoveryFailed(String serviceType, int errorCode) {
Log.e(TAG, "Discovery failed: Error code:" + errorCode);
mNsdManager.stopServiceDiscovery(this);
}
};
}
</pre>
<p>The NSD API uses the methods in this interface to inform your application when discovery
is started, when it fails, and when services are found and lost (lost means "is
no longer available"). Notice that this snippet does several checks
when a service is found.</p>
<ol>
<li>The service name of the found service is compared to the service
name of the local service to determine if the device just picked up its own
broadcast (which is valid).</li>
<li>The service type is checked, to verify it's a type of service your
application can connect to.</li>
<li>The service name is checked to verify connection to the correct
application.</li>
</ol>
<p>Checking the service name isn't always necessary, and is only relevant if you
want to connect to a specific application. For instance, the application might
only want to connect to instances of itself running on other devices. However, if the
application wants to connect to a network printer, it's enough to see that the service type
is "_ipp._tcp".</p>
<p>After setting up the listener, call {@link android.net.nsd.NsdManager#discoverServices(String, int,
NsdManager.DiscoveryListener) discoverServices()}, passing in the service type
your application should look for, the discovery protocol to use, and the
listener you just created.</p>
<pre>
mNsdManager.discoverServices(
SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);
</pre>
<h2 id="connect">Connect to Services on the Network</h2>
<p>When your application finds a service on the network to connect to, it
must first determine the connection information for that service, using the
{@link android.net.nsd.NsdManager#resolveService resolveService()} method.
Implement a {@link android.net.nsd.NsdManager.ResolveListener} to pass into this
method, and use it to get a {@link android.net.nsd.NsdServiceInfo} containing
the connection information.</p>
<pre>
public void initializeResolveListener() {
mResolveListener = new NsdManager.ResolveListener() {
&#64;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);
}
&#64;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();
}
};
}
</pre>
<p>Once the service is resolved, your application receives detailed
service information including an IP address and port number. This is everything
you need to create your own network connection to the service.</p>
<h2 id="teardown">Unregister Your Service on Application Close</h2>
<p>It's important to enable and disable NSD
functionality as appropriate during the application's
lifecycle. Unregistering your application when it closes down helps prevent
other applications from thinking it's still active and attempting to connect to
it. Also, service discovery is an expensive operation, and should be stopped
when the parent Activity is paused, and re-enabled when the Activity is
resumed. Override the lifecycle methods of your main Activity and insert code
to start and stop service broadcast and discovery as appropriate.</p>
<pre>
//In your application's Activity
&#64;Override
protected void onPause() {
if (mNsdHelper != null) {
mNsdHelper.tearDown();
}
super.onPause();
}
&#64;Override
protected void onResume() {
super.onResume();
if (mNsdHelper != null) {
mNsdHelper.registerService(mConnection.getLocalPort());
mNsdHelper.discoverServices();
}
}
&#64;Override
protected void onDestroy() {
mNsdHelper.tearDown();
mConnection.tearDown();
super.onDestroy();
}
// NsdHelper's tearDown method
public void tearDown() {
mNsdManager.unregisterService(mRegistrationListener);
mNsdManager.stopServiceDiscovery(mDiscoveryListener);
}
</pre>

View File

@@ -0,0 +1,372 @@
page.title=Connecting with Wi-Fi Direct
parent.title=Connecting Devices Wirelessly
parent.link=index.html
trainingnavtop=true
previous.title=Using Network Service Discovery
previous.link=nsd.html
next.title=Service Discovery with Wi-Fi Direct
next.link=nsd-wifi-direct.html
@jd:body
<div id="tb-wrapper">
<div id="tb">
<h2>This lesson teaches you how to</h2>
<ol>
<li><a href="#permissions">Set Up Application Permissions</a></li>
<li><a href="#receiver">Set Up the Broadcast Receiver and Peer-to-Peer
Manager</a></li>
<li><a href="#discover">Initiate Peer Discovery</a></li>
<li><a href="#fetch">Fetch the List of Peers</a></li>
<li><a href="#connect">Connect to a Peer</a></li>
</ol>
</div>
</div>
<p>The Wi-Fi Direct&trade; APIs allow applications to connect to nearby devices without
needing to connect to a network or hotspot. This allows your application to quickly
find and interact with nearby devices, at a range beyond the capabilities of Bluetooth.
</p>
<p>
This lesson shows you how to find and connect to nearby devices using Wi-Fi Direct.
</p>
<h2 id="permissions">Set Up Application Permissions</h2>
<p>In order to use Wi-Fi Direct, add the {@link
android.Manifest.permission#CHANGE_WIFI_STATE}, {@link
android.Manifest.permission#ACCESS_WIFI_STATE},
and {@link android.Manifest.permission#INTERNET}
permissions to your manifest. Wi-Fi Direct doesn't require an internet connection,
but it does use standard Java sockets, which require the {@link
android.Manifest.permission#INTERNET} permission.
So you need the following permissions to use Wi-Fi Direct.</p>
<pre>
&lt;manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.nsdchat"
...
&lt;uses-permission
android:required="true"
android:name="android.permission.ACCESS_WIFI_STATE"/&gt;
&lt;uses-permission
android:required="true"
android:name="android.permission.CHANGE_WIFI_STATE"/&gt;
&lt;uses-permission
android:required="true"
android:name="android.permission.INTERNET"/&gt;
...
</pre>
<h2 id="receiver">Set Up a Broadcast Receiver and Peer-to-Peer Manager</h2>
<p>To use Wi-Fi Direct, you need to listen for broadcast intents that tell your
application when certain events have occurred. In your application, instantiate
an {@link
android.content.IntentFilter} and set it to listen for the following:</p>
<dl>
<dt>{@link android.net.wifi.p2p.WifiP2pManager#WIFI_P2P_STATE_CHANGED_ACTION}</dt>
<dd>Indicates whether Wi-Fi Peer-To-Peer (P2P) is enabled</dd>
<dt>{@link android.net.wifi.p2p.WifiP2pManager#WIFI_P2P_PEERS_CHANGED_ACTION}</dt>
<dd>Indicates that the available peer list has changed.</dd>
<dt>{@link android.net.wifi.p2p.WifiP2pManager#WIFI_P2P_CONNECTION_CHANGED_ACTION}</dt>
<dd>Indicates the state of Wi-Fi P2P connectivity has changed.</dd>
<dt>{@link android.net.wifi.p2p.WifiP2pManager#WIFI_P2P_THIS_DEVICE_CHANGED_ACTION}</dt>
<dd>Indicates this device's configuration details have changed.</dd>
<pre>
private final IntentFilter intentFilter = new IntentFilter();
...
&#64;Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Indicates a change in the Wi-Fi Peer-to-Peer 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);
...
}
</pre>
<p>At the end of the {@link android.app.Activity#onCreate onCreate()} method, get an instance of the {@link
android.net.wifi.p2p.WifiP2pManager}, and call its {@link
android.net.wifi.p2p.WifiP2pManager#initialize(Context, Looper, WifiP2pManager.ChannelListener) initialize()}
method. This method returns a {@link
android.net.wifi.p2p.WifiP2pManager.Channel} object, which you'll use later to
connect your app to the Wi-Fi Direct Framework.</p>
<pre>
&#64;Override
Channel mChannel;
public void onCreate(Bundle savedInstanceState) {
....
mManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
mChannel = mManager.initialize(this, getMainLooper(), null);
}
</pre>
<p>Now create a new {@link
android.content.BroadcastReceiver} class that you'll use to listen for changes
to the System's Wi-Fi P2P state. In the {@link
android.content.BroadcastReceiver#onReceive(Context, Intent) onReceive()}
method, add a condition to handle each P2P state change listed above.</p>
<pre>
&#64;Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
// Determine if Wifi Direct 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)) {
// The peer list has changed! We should probably do something about
// that.
} else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {
// Connection state changed! We should probably 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));
}
}
</pre>
<p>Finally, add code to register the intent filter and broadcast receiver when
your main activity is active, and unregister them when the activity is paused.
The best place to do this is the {@link android.app.Activity#onResume()} and
{@link android.app.Activity#onPause()} methods.
<pre>
/** register the BroadcastReceiver with the intent values to be matched */
&#64;Override
public void onResume() {
super.onResume();
receiver = new WiFiDirectBroadcastReceiver(mManager, mChannel, this);
registerReceiver(receiver, intentFilter);
}
&#64;Override
public void onPause() {
super.onPause();
unregisterReceiver(receiver);
}
</pre>
<h2 id="discover">Initiate Peer Discovery</h2>
<p>To start searching for nearby devices with Wi-Fi Direct, call {@link
android.net.wifi.p2p.WifiP2pManager#discoverPeers(WifiP2pManager.Channel,
WifiP2pManager.ActionListener) discoverPeers()}. This method takes the
following arguments:</p>
<ul>
<li>The {@link android.net.wifi.p2p.WifiP2pManager.Channel} you
received back when you initialized the peer-to-peer mManager</li>
<li>An implementation of {@link android.net.wifi.p2p.WifiP2pManager.ActionListener} with methods
the system invokes for successful and unsuccessful discovery.</li>
</ul>
<pre>
mManager.discoverPeers(mChannel, new WifiP2pManager.ActionListener() {
&#64;Override
public void onSuccess() {
// Code for when the discovery initiation is successful goes here.
// No services have actually been discovered yet, so this method
// can often be left blank. Code for peer discovery goes in the
// onReceive method, detailed below.
}
&#64;Override
public void onFailure(int reasonCode) {
// Code for when the discovery initiation fails goes here.
// Alert the user that something went wrong.
}
});
</pre>
<p>Keep in mind that this only <em>initiates</em> peer discovery. The
{@link android.net.wifi.p2p.WifiP2pManager#discoverPeers(WifiP2pManager.Channel,
WifiP2pManager.ActionListener) discoverPeers()} method starts the discovery process and then
immediately returns. The system notifies you if the peer discovery process is
successfully initiated by calling methods in the provided action listener.
Also, discovery will remain active until a connection is initiated or a P2P group is
formed.</p>
<h2 id="fetch">Fetch the List of Peers</h2>
<p>Now write the code that fetches and processes the list of peers. First
implement the {@link android.net.wifi.p2p.WifiP2pManager.PeerListListener}
interface, which provides information about the peers that Wi-Fi Direct has
detected. The following code snippet illustrates this.</p>
<pre>
private List<WifiP2pDevice> peers = new ArrayList<WifiP2pDevice>();
...
private PeerListListener peerListListener = new PeerListListener() {
&#64;Override
public void onPeersAvailable(WifiP2pDeviceList peerList) {
// Out with the old, in with the new.
peers.clear();
peers.addAll(peerList.getDeviceList());
// If an AdapterView is backed by this data, notify it
// of 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;
}
}
}
</pre>
<p>Now modify your broadcast receiver's {@link
android.content.BroadcastReceiver#onReceive(Context, Intent) onReceive()}
method to call {@link android.net.wifi.p2p.WifiP2pManager#requestPeers
requestPeers()} when an intent with the action {@link
android.net.wifi.p2p.WifiP2pManager#WIFI_P2P_PEERS_CHANGED_ACTION} is received. You
need to pass this listener into the receiver somehow. One way is to send it
as an argument to the broadcast receiver's constructor.
</p>
<pre>
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 is an
// asynchronous call and the calling activity is notified with a
// callback on PeerListListener.onPeersAvailable()
if (mManager != null) {
mManager.requestPeers(mChannel, peerListener);
}
Log.d(WiFiDirectActivity.TAG, "P2P peers changed");
}...
}
</pre>
<p>Now, an intent with the action {@link
android.net.wifi.p2p.WifiP2pManager#WIFI_P2P_PEERS_CHANGED_ACTION} intent will
trigger a request for an updated peer list. </p>
<h2 id="connect">Connect to a Peer</h2>
<p>In order to connect to a peer, create a new {@link
android.net.wifi.p2p.WifiP2pConfig} object, and copy data into it from the
{@link android.net.wifi.p2p.WifiP2pDevice} representing the device you want to
connect to. Then call the {@link
android.net.wifi.p2p.WifiP2pManager#connect(WifiP2pManager.Channel,
WifiP2pConfig, WifiP2pManager.ActionListener) connect()}
method.</p>
<pre>
&#64;Override
public void connect() {
// Picking the first device found on the network.
WifiP2pDevice device = peers.get(0);
WifiP2pConfig config = new WifiP2pConfig();
config.deviceAddress = device.deviceAddress;
config.wps.setup = WpsInfo.PBC;
mManager.connect(mChannel, config, new ActionListener() {
&#64;Override
public void onSuccess() {
// WiFiDirectBroadcastReceiver will notify us. Ignore for now.
}
&#64;Override
public void onFailure(int reason) {
Toast.makeText(WiFiDirectActivity.this, "Connect failed. Retry.",
Toast.LENGTH_SHORT).show();
}
});
}
</pre>
<p>The {@link android.net.wifi.p2p.WifiP2pManager.ActionListener} implemented in
this snippet only notifies you when the <em>initiation</em> succeeds or fails.
To listen for <em>changes</em> in connection state, implement the {@link
android.net.wifi.p2p.WifiP2pManager.ConnectionInfoListener} interface. Its {@link
android.net.wifi.p2p.WifiP2pManager.ConnectionInfoListener#onConnectionInfoAvailable(WifiP2pInfo)
onConnectionInfoAvailable()}
callback will notify you when the state of the connection changes. In cases
where multiple devices are going to be connected to a single device (like a game with
3 or more players, or a chat app), one device will be designated the "group
owner".</p>
<pre>
&#64;Override
public void onConnectionInfoAvailable(final WifiP2pInfo info) {
// InetAddress from WifiP2pInfo struct.
InetAddress groupOwnerAddress = info.groupOwnerAddress.getHostAddress());
// After the group negotiation, we can determine the group owner.
if (info.groupFormed && info.isGroupOwner) {
// Do whatever tasks are specific to the group owner.
// One common case is creating a server thread and accepting
// incoming connections.
} else if (info.groupFormed) {
// The other device acts as the client. In this case,
// you'll want to create a client thread that connects to the group
// owner.
}
}
</pre>
<p>Now go back to the {@link
android.content.BroadcastReceiver#onReceive(Context, Intent) onReceive()} method of the broadcast receiver, and modify the section
that listens for a {@link
android.net.wifi.p2p.WifiP2pManager#WIFI_P2P_CONNECTION_CHANGED_ACTION} intent.
When this intent is received, call {@link
android.net.wifi.p2p.WifiP2pManager#requestConnectionInfo(WifiP2pManager.Channel,
WifiP2pManager.ConnectionInfoListener) requestConnectionInfo()}. This is an
asynchronous call, so results will be received by the connection info listener
you provide as a parameter.
<pre>
...
} 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()) {
// We are connected with the other device, request connection
// info to find group owner IP
mManager.requestConnectionInfo(mChannel, connectionListener);
}
...
</pre>

View File

@@ -673,6 +673,27 @@
</li>
<li class="nav-section">
<div class="nav-section-header"><a href="<?cs var:toroot ?>training/connect-devices-wirelessly/index.html">
<span class="en">Connecting Devices Wirelessly</span>
</a></div>
<ul>
<li><a href="<?cs var:toroot ?>training/connect-devices-wirelessly/nsd.html">
<span class="en">Using Network Service Discovery</span>
</a>
</li>
<li><a href="<?cs var:toroot ?>training/connect-devices-wirelessly/wifi-direct.html">
<span class="en">Connecting with Wi-Fi Direct</span>
</a>
</li>
<li><a href="<?cs var:toroot ?>training/connect-devices-wirelessly/nsd-wifi-direct.html">
<span class="en">Using Wi-Fi Direct for Service Discovery</span>
</a>
</li>
</ul>
</li>
</ul>
</li>
</ul><!-- nav -->