From 969c243f17a785dbe697e2cbe09c320136f0d82a Mon Sep 17 00:00:00 2001 From: Alexander Lucas Date: Wed, 13 Jun 2012 10:57:22 -0700 Subject: [PATCH] Initial commit of wireless connectivity/discovery class. Change-Id: Ifbcbb8f5021a749ddb131d678ee1117835168598 --- docs/html/shareables/training/nsdchat.zip | Bin 0 -> 46385 bytes .../connect-devices-wirelessly/index.jd | 62 +++ .../nsd-wifi-direct.jd | 252 ++++++++++++ .../connect-devices-wirelessly/nsd.jd | 373 ++++++++++++++++++ .../connect-devices-wirelessly/wifi-direct.jd | 372 +++++++++++++++++ docs/html/training/training_toc.cs | 21 + 6 files changed, 1080 insertions(+) create mode 100644 docs/html/shareables/training/nsdchat.zip create mode 100644 docs/html/training/connect-devices-wirelessly/index.jd create mode 100644 docs/html/training/connect-devices-wirelessly/nsd-wifi-direct.jd create mode 100644 docs/html/training/connect-devices-wirelessly/nsd.jd create mode 100644 docs/html/training/connect-devices-wirelessly/wifi-direct.jd diff --git a/docs/html/shareables/training/nsdchat.zip b/docs/html/shareables/training/nsdchat.zip new file mode 100644 index 0000000000000000000000000000000000000000..a10697570f9c3138bae10e6376e135b31fb8dee5 GIT binary patch literal 46385 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 psr{|A8+?~(uj literal 0 HcmV?d00001 diff --git a/docs/html/training/connect-devices-wirelessly/index.jd b/docs/html/training/connect-devices-wirelessly/index.jd new file mode 100644 index 0000000000000..37cf633a4538d --- /dev/null +++ b/docs/html/training/connect-devices-wirelessly/index.jd @@ -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 + + +
+
+ +

Dependencies and prerequisites

+
    +
  • Android 4.1 or higher
  • +
+ +

You should also read

+ + + +
+
+ + +

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.

+

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™ 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. +

+

Lessons

+ +
+
Using Network Service Discovery
+
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.
+
Connecting with Wi-Fi Direct
+
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.
+
Using Wi-Fi Direct for Service + Discovery
+
Learn how to discover services published by nearby devices without being + on the same network, using Wi-Fi Direct.
+
+ diff --git a/docs/html/training/connect-devices-wirelessly/nsd-wifi-direct.jd b/docs/html/training/connect-devices-wirelessly/nsd-wifi-direct.jd new file mode 100644 index 0000000000000..5e276ded54918 --- /dev/null +++ b/docs/html/training/connect-devices-wirelessly/nsd-wifi-direct.jd @@ -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 + +
+
+

This lesson teaches you to

+
    +
  1. Set Up the Manifest
  2. +
  3. Add a Local Service
  4. +
  5. Discover Nearby Services
  6. +
+ +
+
+ +

The first lesson in this class, Using Network Service + Discovery, 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.

+

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™. The lesson assumes that you're already familiar with the +Wi-Fi Direct API.

+ + +

Set Up the Manifest

+

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.

+ +
+<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"/>
+    ...
+
+ +

Add a Local Service

+

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.

+ +

To create a local service:

+ +
    +
  1. Create a +{@link android.net.wifi.p2p.nsd.WifiP2pServiceInfo} object.
  2. +
  3. Populate it with information about your service.
  4. +
  5. Call {@link +android.net.wifi.p2p.WifiP2pManager#addLocalService(WifiP2pManager.Channel, +WifiP2pServiceInfo, WifiP2pManager.ActionListener) addLocalService()} to register the local +service for service discovery.
  6. +
+ +
+     private void startRegistration() {
+        //  Create a string map containing information about your service.
+        Map record = new HashMap();
+        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() {
+            @Override
+            public void onSuccess() {
+                // Command successful! Code isn't necessarily needed here,
+                // Unless you want to update the UI or add logging statements.
+            }
+
+            @Override
+            public void onFailure(int arg0) {
+                // Command failed.  Check for P2P_UNSUPPORTED, ERROR, or BUSY
+            }
+        });
+    }
+
+ +

Discover Nearby Services

+

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.

+ +
+final HashMap<String, String> buddies = new HashMap<String, String>();
+...
+private void discoverService() {
+    DnsSdTxtRecordListener txtListener = new DnsSdTxtRecordListener() {
+        @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 record, WifiP2pDevice device) {
+                Log.d(TAG, "DnsSdTxtRecord available -" + record.toString());
+                buddies.put(device.deviceAddress, record.get("buddyname"));
+            }
+        };
+    ...
+}
+
+ +

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.

+ +
+private void discoverService() {
+...
+
+    DnsSdServiceResponseListener servListener = new DnsSdServiceResponseListener() {
+        @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);
+    ...
+}
+
+ +

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.

+ +
+        serviceRequest = WifiP2pDnsSdServiceRequest.newInstance();
+        mManager.addServiceRequest(channel,
+                serviceRequest,
+                new ActionListener() {
+                    @Override
+                    public void onSuccess() {
+                        // Success!
+                    }
+
+                    @Override
+                    public void onFailure(int code) {
+                        // Command failed.  Check for P2P_UNSUPPORTED, ERROR, or BUSY
+                    }
+                });
+
+ +

Finally, make the call to {@link +android.net.wifi.p2p.WifiP2pManager#discoverServices(WifiP2pManager.Channel, +WifiP2pManager.ActionListener) discoverServices()}.

+ +
+        mManager.discoverServices(channel, new ActionListener() {
+
+            @Override
+            public void onSuccess() {
+                // Success!
+            }
+
+            @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(...)
+                    ...
+            }
+        });
+
+ +

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

+
+
{@link android.net.wifi.p2p.WifiP2pManager#P2P_UNSUPPORTED}
+
Wi-Fi Direct isn't supported on the device running the app.
+
{@link android.net.wifi.p2p.WifiP2pManager#BUSY}
+
The system is to busy to process the request.
+
{@link android.net.wifi.p2p.WifiP2pManager#ERROR}
+
The operation failed due to an internal error.
+
diff --git a/docs/html/training/connect-devices-wirelessly/nsd.jd b/docs/html/training/connect-devices-wirelessly/nsd.jd new file mode 100644 index 0000000000000..d2b01a177a9f3 --- /dev/null +++ b/docs/html/training/connect-devices-wirelessly/nsd.jd @@ -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 + + + +

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.

+ +

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.

+ +

Register Your Service on the Network

+ +

Note: 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, Discover Services on the Network.

+ +

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.

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

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)".

+ +

The second parameter sets the service type, specifies which protocol and transport +layer the application uses. The syntax is +"_<protocol>._<transportlayer>". 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".

+ +

Note: 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 the +IANA list of service names and port numbers. +If you intend to use a new service type, you should reserve it by filling out +the IANA Ports and Service + registration form.

+ +

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.

+ +

If you're working with sockets, here's how you can initialize a socket to any +available port simply by setting it to 0.

+ +
+public void initializeServerSocket() {
+    // Initialize a server socket on the next available port.
+    mServerSocket = new ServerSocket(0);
+
+    // Store the chosen port.
+    mLocalPort =  mServerSocket.getLocalPort();
+    ...
+}
+
+ +

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. +

+
+public void initializeRegistrationListener() {
+    mRegistrationListener = new NsdManager.RegistrationListener() {
+
+        @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();
+        }
+
+        @Override
+        public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
+            // Registration failed!  Put debugging code here to determine why.
+        }
+
+        @Override
+        public void onServiceUnregistered(NsdServiceInfo arg0) {
+            // Service has been unregistered.  This only happens when you call
+            // NsdManager.unregisterService() and pass in this listener.
+        }
+
+        @Override
+        public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
+            // Unregistration failed.  Put debugging code here to determine why.
+        }
+    };
+}
+
+ +

Now you have all the pieces to register your service. Call the method +{@link android.net.nsd.NsdManager#registerService registerService()}. +

+ +

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.

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

Discover Services on the 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. 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.

+ +

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

+ +

First, instantiate an anonymous class that implements {@link +android.net.nsd.NsdManager.DiscoveryListener}. The following snippet shows a +simple example:

+ +
+public void initializeDiscoveryListener() {
+
+    // Instantiate a new DiscoveryListener
+    mDiscoveryListener = new NsdManager.DiscoveryListener() {
+
+        //  Called as soon as service discovery begins.
+        @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 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);
+            }
+        }
+
+        @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);
+        }
+    };
+}
+
+ +

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.

+
    +
  1. 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).
  2. +
  3. The service type is checked, to verify it's a type of service your +application can connect to.
  4. +
  5. The service name is checked to verify connection to the correct +application.
  6. +
+ +

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".

+ +

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.

+ +
+    mNsdManager.discoverServices(
+        SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);
+
+ + +

Connect to Services on the Network

+

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.

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

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.

+ + +

Unregister Your Service on Application Close

+

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.

+ +
+//In your application's Activity
+
+    @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);
+    }
+
+ diff --git a/docs/html/training/connect-devices-wirelessly/wifi-direct.jd b/docs/html/training/connect-devices-wirelessly/wifi-direct.jd new file mode 100644 index 0000000000000..99bb243879a03 --- /dev/null +++ b/docs/html/training/connect-devices-wirelessly/wifi-direct.jd @@ -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 + + + +

The Wi-Fi Direct™ 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. +

+

+This lesson shows you how to find and connect to nearby devices using Wi-Fi Direct. +

+

Set Up Application Permissions

+

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.

+ +
+<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"/>
+    ...
+
+ +

Set Up a Broadcast Receiver and Peer-to-Peer Manager

+

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:

+
+
{@link android.net.wifi.p2p.WifiP2pManager#WIFI_P2P_STATE_CHANGED_ACTION}
+
Indicates whether Wi-Fi Peer-To-Peer (P2P) is enabled
+
{@link android.net.wifi.p2p.WifiP2pManager#WIFI_P2P_PEERS_CHANGED_ACTION}
+
Indicates that the available peer list has changed.
+
{@link android.net.wifi.p2p.WifiP2pManager#WIFI_P2P_CONNECTION_CHANGED_ACTION}
+
Indicates the state of Wi-Fi P2P connectivity has changed.
+
{@link android.net.wifi.p2p.WifiP2pManager#WIFI_P2P_THIS_DEVICE_CHANGED_ACTION}
+
Indicates this device's configuration details have changed.
+
+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 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);
+
+    ...
+}
+
+ +

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.

+ +
+@Override
+
+Channel mChannel;
+
+public void onCreate(Bundle savedInstanceState) {
+    ....
+    mManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
+    mChannel = mManager.initialize(this, getMainLooper(), null);
+}
+
+

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.

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

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. + +

+    /** 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);
+    }
+
+ + +

Initiate Peer Discovery

+

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:

+
    +
  • The {@link android.net.wifi.p2p.WifiP2pManager.Channel} you + received back when you initialized the peer-to-peer mManager
  • +
  • An implementation of {@link android.net.wifi.p2p.WifiP2pManager.ActionListener} with methods + the system invokes for successful and unsuccessful discovery.
  • +
+ +
+mManager.discoverPeers(mChannel, new WifiP2pManager.ActionListener() {
+
+        @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.
+        }
+
+        @Override
+        public void onFailure(int reasonCode) {
+            // Code for when the discovery initiation fails goes here.
+            // Alert the user that something went wrong.
+        }
+});
+
+ +

Keep in mind that this only initiates 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.

+ +

Fetch the List of Peers

+

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.

+ +
+    private List peers = new ArrayList();
+    ...
+
+    private PeerListListener peerListListener = new PeerListListener() {
+        @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;
+            }
+        }
+    }
+
+ +

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. +

+ +
+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");
+    }...
+}
+
+ +

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.

+ +

Connect to a Peer

+

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.

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

The {@link android.net.wifi.p2p.WifiP2pManager.ActionListener} implemented in +this snippet only notifies you when the initiation succeeds or fails. +To listen for changes 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".

+ +
+    @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.
+        }
+    }
+
+ +

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. + +

+        ...
+        } 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);
+            }
+            ...
+
diff --git a/docs/html/training/training_toc.cs b/docs/html/training/training_toc.cs index 37b69d4c8c704..b70ba3f4828c7 100644 --- a/docs/html/training/training_toc.cs +++ b/docs/html/training/training_toc.cs @@ -673,6 +673,27 @@ + + +