summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIrfan Sheriff <isheriff@google.com>2012-04-12 18:33:05 -0700
committerAndroid (Google) Code Review <android-gerrit@google.com>2012-04-12 18:33:05 -0700
commitd4fecc2567ca54427b9c86900bccbc7103ea8fde (patch)
tree194050ea8e2bca69cc4289c3e475c361b05fa21c
parentc8cbf5de769fb9396517cfcf101d5b5114012692 (diff)
parent21ba8153325e010224c6bc75a0acdc98b6ca82e8 (diff)
downloadframeworks_base-d4fecc2567ca54427b9c86900bccbc7103ea8fde.zip
frameworks_base-d4fecc2567ca54427b9c86900bccbc7103ea8fde.tar.gz
frameworks_base-d4fecc2567ca54427b9c86900bccbc7103ea8fde.tar.bz2
Merge "Pre-association service discovery support"
-rw-r--r--core/java/android/net/nsd/DnsSdTxtRecord.java22
-rw-r--r--core/jni/android_net_wifi_Wifi.cpp3
-rw-r--r--wifi/java/android/net/wifi/WifiMonitor.java56
-rw-r--r--wifi/java/android/net/wifi/WifiNative.java77
-rw-r--r--wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java56
-rw-r--r--wifi/java/android/net/wifi/p2p/WifiP2pManager.java462
-rw-r--r--wifi/java/android/net/wifi/p2p/WifiP2pService.java406
-rw-r--r--wifi/java/android/net/wifi/p2p/nsd/WifiP2pBonjourServiceInfo.java223
-rw-r--r--wifi/java/android/net/wifi/p2p/nsd/WifiP2pBonjourServiceRequest.java102
-rw-r--r--wifi/java/android/net/wifi/p2p/nsd/WifiP2pBonjourServiceResponse.java313
-rw-r--r--wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.aidl19
-rw-r--r--wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.java183
-rw-r--r--wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.aidl19
-rw-r--r--wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.java283
-rw-r--r--wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceResponse.aidl19
-rw-r--r--wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceResponse.java389
-rw-r--r--wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceInfo.java105
-rw-r--r--wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceRequest.java74
-rw-r--r--wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceResponse.java159
19 files changed, 2932 insertions, 38 deletions
diff --git a/core/java/android/net/nsd/DnsSdTxtRecord.java b/core/java/android/net/nsd/DnsSdTxtRecord.java
index 6d4342c..952e02f 100644
--- a/core/java/android/net/nsd/DnsSdTxtRecord.java
+++ b/core/java/android/net/nsd/DnsSdTxtRecord.java
@@ -24,6 +24,8 @@ package android.net.nsd;
import android.os.Parcelable;
import android.os.Parcel;
+import java.util.Arrays;
+
/**
* This class handles TXT record data for DNS based service discovery as specified at
* http://tools.ietf.org/html/draft-cheshire-dnsext-dns-sd-11
@@ -160,7 +162,7 @@ public class DnsSdTxtRecord implements Parcelable {
/* Gets the raw data in bytes */
public byte[] getRawData() {
- return mData;
+ return (byte[]) mData.clone();
}
private void insert(byte[] keyBytes, byte[] value, int index) {
@@ -279,6 +281,24 @@ public class DnsSdTxtRecord implements Parcelable {
return result != null ? result : "";
}
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof DnsSdTxtRecord)) {
+ return false;
+ }
+
+ DnsSdTxtRecord record = (DnsSdTxtRecord)o;
+ return Arrays.equals(record.mData, mData);
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(mData);
+ }
+
/** Implement the Parcelable interface */
public int describeContents() {
return 0;
diff --git a/core/jni/android_net_wifi_Wifi.cpp b/core/jni/android_net_wifi_Wifi.cpp
index c5ff16e..e7c4c23 100644
--- a/core/jni/android_net_wifi_Wifi.cpp
+++ b/core/jni/android_net_wifi_Wifi.cpp
@@ -27,6 +27,7 @@
#define WIFI_PKG_NAME "android/net/wifi/WifiNative"
#define BUF_SIZE 256
+#define EVENT_BUF_SIZE 2048
namespace android {
@@ -140,7 +141,7 @@ static void android_net_wifi_closeSupplicantConnection(JNIEnv* env, jobject, jst
static jstring android_net_wifi_waitForEvent(JNIEnv* env, jobject, jstring jIface)
{
- char buf[BUF_SIZE];
+ char buf[EVENT_BUF_SIZE];
ScopedUtfChars ifname(env, jIface);
int nread = ::wifi_wait_for_event(ifname.c_str(), buf, sizeof buf);
if (nread > 0) {
diff --git a/wifi/java/android/net/wifi/WifiMonitor.java b/wifi/java/android/net/wifi/WifiMonitor.java
index 03d5134..3bd03f5 100644
--- a/wifi/java/android/net/wifi/WifiMonitor.java
+++ b/wifi/java/android/net/wifi/WifiMonitor.java
@@ -21,6 +21,7 @@ import android.net.wifi.p2p.WifiP2pConfig;
import android.net.wifi.p2p.WifiP2pDevice;
import android.net.wifi.p2p.WifiP2pGroup;
import android.net.wifi.p2p.WifiP2pProvDiscEvent;
+import android.net.wifi.p2p.nsd.WifiP2pServiceResponse;
import android.net.wifi.StateChangeResult;
import android.os.Message;
import android.util.Log;
@@ -29,6 +30,7 @@ import android.util.Log;
import com.android.internal.util.Protocol;
import com.android.internal.util.StateMachine;
+import java.util.List;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
@@ -214,6 +216,52 @@ public class WifiMonitor {
group_capab=0x0 */
private static final String P2P_PROV_DISC_SHOW_PIN_STR = "P2P-PROV-DISC-SHOW-PIN";
+ /*
+ * Protocol format is as follows.<br>
+ * See the Table.62 in the WiFi Direct specification for the detail.
+ * ______________________________________________________________
+ * | Length(2byte) | Type(1byte) | TransId(1byte)}|
+ * ______________________________________________________________
+ * | status(1byte) | vendor specific(variable) |
+ *
+ * P2P-SERV-DISC-RESP 42:fc:89:e1:e2:27 1 0300000101
+ * length=3, service type=0(ALL Service), transaction id=1,
+ * status=1(service protocol type not available)<br>
+ *
+ * P2P-SERV-DISC-RESP 42:fc:89:e1:e2:27 1 0300020201
+ * length=3, service type=2(UPnP), transaction id=2,
+ * status=1(service protocol type not available)
+ *
+ * P2P-SERV-DISC-RESP 42:fc:89:e1:e2:27 1 990002030010757569643a3131323
+ * 2646534652d383537342d353961622d393332322d3333333435363738393034343a3
+ * a75726e3a736368656d61732d75706e702d6f72673a736572766963653a436f6e746
+ * 56e744469726563746f72793a322c757569643a36383539646564652d383537342d3
+ * 53961622d393333322d3132333435363738393031323a3a75706e703a726f6f74646
+ * 576696365
+ * length=153,type=2(UPnP),transaction id=3,status=0
+ *
+ * UPnP Protocol format is as follows.
+ * ______________________________________________________
+ * | Version (1) | USN (Variable) |
+ *
+ * version=0x10(UPnP1.0) data=usn:uuid:1122de4e-8574-59ab-9322-33345678
+ * 9044::urn:schemas-upnp-org:service:ContentDirectory:2,usn:uuid:6859d
+ * ede-8574-59ab-9332-123456789012::upnp:rootdevice
+ *
+ * P2P-SERV-DISC-RESP 58:17:0c:bc:dd:ca 21 1900010200045f6970
+ * 70c00c000c01094d795072696e746572c027
+ * length=25, type=1(Bonjour),transaction id=2,status=0
+ *
+ * Bonjour Protocol format is as follows.
+ * __________________________________________________________
+ * |DNS Name(Variable)|DNS Type(1)|Version(1)|RDATA(Variable)|
+ *
+ * DNS Name=_ipp._tcp.local.,DNS type=12(PTR), Version=1,
+ * RDATA=MyPrinter._ipp._tcp.local.
+ *
+ */
+ private static final String P2P_SERV_DISC_RESP_STR = "P2P-SERV-DISC-RESP";
+
private static final String HOST_AP_EVENT_PREFIX_STR = "AP";
/* AP-STA-CONNECTED 42:fc:89:a8:96:09 dev_addr=02:90:4c:a0:92:54 */
private static final String AP_STA_CONNECTED_STR = "AP-STA-CONNECTED";
@@ -268,6 +316,7 @@ public class WifiMonitor {
public static final int P2P_PROV_DISC_ENTER_PIN_EVENT = BASE + 35;
public static final int P2P_PROV_DISC_SHOW_PIN_EVENT = BASE + 36;
public static final int P2P_FIND_STOPPED_EVENT = BASE + 37;
+ public static final int P2P_SERV_DISC_RESP_EVENT = BASE + 38;
/* hostap events */
public static final int AP_STA_DISCONNECTED_EVENT = BASE + 41;
@@ -558,6 +607,13 @@ public class WifiMonitor {
} else if (dataString.startsWith(P2P_PROV_DISC_SHOW_PIN_STR)) {
mStateMachine.sendMessage(P2P_PROV_DISC_SHOW_PIN_EVENT,
new WifiP2pProvDiscEvent(dataString));
+ } else if (dataString.startsWith(P2P_SERV_DISC_RESP_STR)) {
+ List<WifiP2pServiceResponse> list = WifiP2pServiceResponse.newInstance(dataString);
+ if (list != null) {
+ mStateMachine.sendMessage(P2P_SERV_DISC_RESP_EVENT, list);
+ } else {
+ Log.e(TAG, "Null service resp " + dataString);
+ }
}
}
diff --git a/wifi/java/android/net/wifi/WifiNative.java b/wifi/java/android/net/wifi/WifiNative.java
index db73ea8..4ec2e02 100644
--- a/wifi/java/android/net/wifi/WifiNative.java
+++ b/wifi/java/android/net/wifi/WifiNative.java
@@ -19,8 +19,9 @@ package android.net.wifi;
import android.net.wifi.p2p.WifiP2pConfig;
import android.net.wifi.p2p.WifiP2pGroup;
import android.net.wifi.p2p.WifiP2pDevice;
-import android.os.SystemProperties;
import android.text.TextUtils;
+import android.net.wifi.p2p.nsd.WifiP2pServiceInfo;
+import android.net.wifi.p2p.nsd.WifiP2pServiceRequest;
import android.util.Log;
import java.io.InputStream;
@@ -644,4 +645,78 @@ public class WifiNative {
public String p2pPeer(String deviceAddress) {
return doStringCommand("P2P_PEER " + deviceAddress);
}
+
+ public boolean p2pServiceAdd(WifiP2pServiceInfo servInfo) {
+ /*
+ * P2P_SERVICE_ADD bonjour <query hexdump> <RDATA hexdump>
+ * P2P_SERVICE_ADD upnp <version hex> <service>
+ *
+ * e.g)
+ * [Bonjour]
+ * # IP Printing over TCP (PTR) (RDATA=MyPrinter._ipp._tcp.local.)
+ * P2P_SERVICE_ADD bonjour 045f697070c00c000c01 094d795072696e746572c027
+ * # IP Printing over TCP (TXT) (RDATA=txtvers=1,pdl=application/postscript)
+ * P2P_SERVICE_ADD bonjour 096d797072696e746572045f697070c00c001001
+ * 09747874766572733d311a70646c3d6170706c69636174696f6e2f706f7374736372797074
+ *
+ * [UPnP]
+ * P2P_SERVICE_ADD upnp 10 uuid:6859dede-8574-59ab-9332-123456789012
+ * P2P_SERVICE_ADD upnp 10 uuid:6859dede-8574-59ab-9332-123456789012::upnp:rootdevice
+ * P2P_SERVICE_ADD upnp 10 uuid:6859dede-8574-59ab-9332-123456789012::urn:schemas-upnp
+ * -org:device:InternetGatewayDevice:1
+ * P2P_SERVICE_ADD upnp 10 uuid:6859dede-8574-59ab-9322-123456789012::urn:schemas-upnp
+ * -org:service:ContentDirectory:2
+ */
+ for (String s : servInfo.getSupplicantQueryList()) {
+ String command = "P2P_SERVICE_ADD";
+ command += (" " + s);
+ if (!doBooleanCommand(command)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public boolean p2pServiceDel(WifiP2pServiceInfo servInfo) {
+ /*
+ * P2P_SERVICE_DEL bonjour <query hexdump>
+ * P2P_SERVICE_DEL upnp <version hex> <service>
+ */
+ for (String s : servInfo.getSupplicantQueryList()) {
+ String command = "P2P_SERVICE_DEL ";
+
+ String[] data = s.split(" ");
+ if (data.length < 2) {
+ return false;
+ }
+ if ("upnp".equals(data[0])) {
+ command += s;
+ } else if ("bonjour".equals(data[0])) {
+ command += data[0];
+ command += (" " + data[1]);
+ } else {
+ return false;
+ }
+ if (!doBooleanCommand(command)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public boolean p2pServiceFlush() {
+ return doBooleanCommand("P2P_SERVICE_FLUSH");
+ }
+
+ public String p2pServDiscReq(String addr, String query) {
+ String command = "P2P_SERV_DISC_REQ";
+ command += (" " + addr);
+ command += (" " + query);
+
+ return doStringCommand(command);
+ }
+
+ public boolean p2pServDiscCancelReq(String id) {
+ return doBooleanCommand("P2P_SERV_DISC_CANCEL_REQ " + id);
+ }
}
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java b/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java
index 9ce2545..3751727 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java
@@ -24,6 +24,7 @@ import android.util.Log;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
/**
* A class representing a Wi-Fi P2p device list
@@ -32,24 +33,28 @@ import java.util.Collections;
*/
public class WifiP2pDeviceList implements Parcelable {
- private Collection<WifiP2pDevice> mDevices;
+ private HashMap<String, WifiP2pDevice> mDevices;
public WifiP2pDeviceList() {
- mDevices = new ArrayList<WifiP2pDevice>();
+ mDevices = new HashMap<String, WifiP2pDevice>();
}
/** copy constructor */
public WifiP2pDeviceList(WifiP2pDeviceList source) {
if (source != null) {
- mDevices = source.getDeviceList();
+ for (WifiP2pDevice d : source.getDeviceList()) {
+ mDevices.put(d.deviceAddress, d);
+ }
}
}
/** @hide */
public WifiP2pDeviceList(ArrayList<WifiP2pDevice> devices) {
- mDevices = new ArrayList<WifiP2pDevice>();
+ mDevices = new HashMap<String, WifiP2pDevice>();
for (WifiP2pDevice device : devices) {
- mDevices.add(device);
+ if (device.deviceAddress != null) {
+ mDevices.put(device.deviceAddress, device);
+ }
}
}
@@ -62,37 +67,42 @@ public class WifiP2pDeviceList implements Parcelable {
/** @hide */
public void update(WifiP2pDevice device) {
- if (device == null) return;
- for (WifiP2pDevice d : mDevices) {
- //Found, update fields that can change
- if (d.equals(device)) {
- d.deviceName = device.deviceName;
- d.primaryDeviceType = device.primaryDeviceType;
- d.secondaryDeviceType = device.secondaryDeviceType;
- d.wpsConfigMethodsSupported = device.wpsConfigMethodsSupported;
- d.deviceCapability = device.deviceCapability;
- d.groupCapability = device.groupCapability;
- return;
- }
+ if (device == null || device.deviceAddress == null) return;
+ WifiP2pDevice d = mDevices.get(device.deviceAddress);
+ if (d != null) {
+ d.deviceName = device.deviceName;
+ d.primaryDeviceType = device.primaryDeviceType;
+ d.secondaryDeviceType = device.secondaryDeviceType;
+ d.wpsConfigMethodsSupported = device.wpsConfigMethodsSupported;
+ d.deviceCapability = device.deviceCapability;
+ d.groupCapability = device.groupCapability;
+ return;
}
//Not found, add a new one
- mDevices.add(device);
+ mDevices.put(device.deviceAddress, device);
+ }
+
+ /** @hide */
+ public WifiP2pDevice get(String deviceAddress) {
+ if (deviceAddress == null) return null;
+
+ return mDevices.get(deviceAddress);
}
/** @hide */
public boolean remove(WifiP2pDevice device) {
- if (device == null) return false;
- return mDevices.remove(device);
+ if (device == null || device.deviceAddress == null) return false;
+ return mDevices.remove(device.deviceAddress) != null;
}
/** Get the list of devices */
public Collection<WifiP2pDevice> getDeviceList() {
- return Collections.unmodifiableCollection(mDevices);
+ return Collections.unmodifiableCollection(mDevices.values());
}
public String toString() {
StringBuffer sbuf = new StringBuffer();
- for (WifiP2pDevice device : mDevices) {
+ for (WifiP2pDevice device : mDevices.values()) {
sbuf.append("\n").append(device);
}
return sbuf.toString();
@@ -106,7 +116,7 @@ public class WifiP2pDeviceList implements Parcelable {
/** Implement the Parcelable interface */
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mDevices.size());
- for(WifiP2pDevice device : mDevices) {
+ for(WifiP2pDevice device : mDevices.values()) {
dest.writeParcelable(device, flags);
}
}
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
index c7f6bf0..35f37a8 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
@@ -21,6 +21,14 @@ import android.annotation.SdkConstant.SdkConstantType;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
+import android.net.nsd.DnsSdTxtRecord;
+import android.net.wifi.p2p.nsd.WifiP2pBonjourServiceInfo;
+import android.net.wifi.p2p.nsd.WifiP2pBonjourServiceResponse;
+import android.net.wifi.p2p.nsd.WifiP2pServiceInfo;
+import android.net.wifi.p2p.nsd.WifiP2pServiceRequest;
+import android.net.wifi.p2p.nsd.WifiP2pServiceResponse;
+import android.net.wifi.p2p.nsd.WifiP2pUpnpServiceInfo;
+import android.net.wifi.p2p.nsd.WifiP2pUpnpServiceResponse;
import android.os.Binder;
import android.os.IBinder;
import android.os.Handler;
@@ -36,6 +44,7 @@ import com.android.internal.util.AsyncChannel;
import com.android.internal.util.Protocol;
import java.util.HashMap;
+import java.util.List;
/**
* This class provides the API for managing Wi-Fi peer-to-peer connectivity. This lets an
@@ -290,6 +299,61 @@ public class WifiP2pManager {
/** @hide */
public static final int RESPONSE_GROUP_INFO = BASE + 24;
+ /** @hide */
+ public static final int ADD_LOCAL_SERVICE = BASE + 28;
+ /** @hide */
+ public static final int ADD_LOCAL_SERVICE_FAILED = BASE + 29;
+ /** @hide */
+ public static final int ADD_LOCAL_SERVICE_SUCCEEDED = BASE + 30;
+
+ /** @hide */
+ public static final int REMOVE_LOCAL_SERVICE = BASE + 31;
+ /** @hide */
+ public static final int REMOVE_LOCAL_SERVICE_FAILED = BASE + 32;
+ /** @hide */
+ public static final int REMOVE_LOCAL_SERVICE_SUCCEEDED = BASE + 33;
+
+ /** @hide */
+ public static final int CLEAR_LOCAL_SERVICES = BASE + 34;
+ /** @hide */
+ public static final int CLEAR_LOCAL_SERVICES_FAILED = BASE + 35;
+ /** @hide */
+ public static final int CLEAR_LOCAL_SERVICES_SUCCEEDED = BASE + 36;
+
+ /** @hide */
+ public static final int ADD_SERVICE_REQUEST = BASE + 37;
+ /** @hide */
+ public static final int ADD_SERVICE_REQUEST_FAILED = BASE + 38;
+ /** @hide */
+ public static final int ADD_SERVICE_REQUEST_SUCCEEDED = BASE + 39;
+
+ /** @hide */
+ public static final int REMOVE_SERVICE_REQUEST = BASE + 40;
+ /** @hide */
+ public static final int REMOVE_SERVICE_REQUEST_FAILED = BASE + 41;
+ /** @hide */
+ public static final int REMOVE_SERVICE_REQUEST_SUCCEEDED = BASE + 42;
+
+ /** @hide */
+ public static final int CLEAR_SERVICE_REQUESTS = BASE + 43;
+ /** @hide */
+ public static final int CLEAR_SERVICE_REQUESTS_FAILED = BASE + 44;
+ /** @hide */
+ public static final int CLEAR_SERVICE_REQUESTS_SUCCEEDED = BASE + 45;
+
+ /** @hide */
+ public static final int DISCOVER_SERVICES = BASE + 46;
+ /** @hide */
+ public static final int DISCOVER_SERVICES_FAILED = BASE + 47;
+ /** @hide */
+ public static final int DISCOVER_SERVICES_SUCCEEDED = BASE + 48;
+
+ /** @hide */
+ public static final int PING = BASE + 49;
+
+ /** @hide */
+ public static final int RESPONSE_SERVICE = BASE + 50;
+
/**
* Create a new WifiP2pManager instance. Applications use
* {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
@@ -321,6 +385,14 @@ public class WifiP2pManager {
*/
public static final int BUSY = 2;
+ /**
+ * Passed with {@link ActionListener#onFailure}.
+ * Indicates that the {@link #discoverServices} failed because no service
+ * requests are set.
+ * @hide
+ */
+ public static final int NO_SERVICE_REQUESTS = 3;
+
/** Interface for callback invocation when framework channel is lost */
public interface ChannelListener {
/**
@@ -370,6 +442,93 @@ public class WifiP2pManager {
}
/**
+ * Interface for callback invocation when service discovery response other than
+ * UPnP or Bonjour is received
+ * @hide
+ */
+ public interface ServiceResponseListener {
+
+ /**
+ * The requested service response is available.
+ *
+ * @param serviceType service type. see the service type of
+ * {@link WifiP2pServiceInfo}
+ * @param responseData service discovery response data based on the requested
+ * service protocol type. The format depends on the service type.
+ * @param srcDevice source device.
+ */
+ public void onServiceAvailable(int serviceType,
+ byte[] responseData, WifiP2pDevice srcDevice);
+ }
+
+ /**
+ * Interface for callback invocation when Bonjour service discovery response
+ * is received
+ * @hide
+ */
+ public interface BonjourServiceResponseListener {
+
+ /**
+ * The requested Bonjour service response is available.
+ *
+ * <p>This function is invoked when the device with the specified Bonjour
+ * registration type returned the instance name.
+ * @param instanceName instance name.<br>
+ * e.g) "MyPrinter".
+ * @param registrationType <br>
+ * e.g) "_ipp._tcp.local."
+ * @param srcDevice source device.
+ */
+ public void onBonjourServiceAvailable(String instanceName,
+ String registrationType, WifiP2pDevice srcDevice);
+
+ }
+
+ /**
+ * Interface for callback invocation when Bonjour TXT record is available
+ * for a service
+ * @hide
+ */
+ public interface BonjourTxtRecordListener {
+ /**
+ * The requested Bonjour service response is available.
+ *
+ * <p>This function is invoked when the device with the specified full
+ * service domain service returned TXT record.
+ *
+ * @param fullDomainName full domain name. <br>
+ * e.g) "MyPrinter._ipp._tcp.local.".
+ * @param record txt record.
+ * @param srcDevice source device.
+ */
+ public void onBonjourTxtRecordAvailable(String fullDomainName,
+ DnsSdTxtRecord record,
+ WifiP2pDevice srcDevice);
+ }
+
+ /**
+ * Interface for callback invocation when upnp service discovery response
+ * is received
+ * @hide
+ * */
+ public interface UpnpServiceResponseListener {
+
+ /**
+ * The requested upnp service response is available.
+ *
+ * <p>This function is invoked when the specified device or service is found.
+ *
+ * @param uniqueServiceNames The list of unique service names.<br>
+ * e.g) uuid:6859dede-8574-59ab-9332-123456789012::urn:schemas-upnp-org:device:
+ * MediaServer:1
+ * @param srcDevice source device.
+ */
+ public void onUpnpServiceAvailable(List<String> uniqueServiceNames,
+ WifiP2pDevice srcDevice);
+ }
+
+
+ /**
* A channel that connects the application to the Wifi p2p framework.
* Most p2p operations require a Channel as an argument. An instance of Channel is obtained
* by doing a call on {@link #initialize}
@@ -382,6 +541,10 @@ public class WifiP2pManager {
}
private final static int INVALID_LISTENER_KEY = 0;
private ChannelListener mChannelListener;
+ private ServiceResponseListener mServRspListener;
+ private BonjourServiceResponseListener mBonjourServRspListener;
+ private BonjourTxtRecordListener mBonjourTxtListener;
+ private UpnpServiceResponseListener mUpnpServRspListener;
private HashMap<Integer, Object> mListenerMap = new HashMap<Integer, Object>();
private Object mListenerMapLock = new Object();
private int mListenerKey = 0;
@@ -406,10 +569,17 @@ public class WifiP2pManager {
/* ActionListeners grouped together */
case WifiP2pManager.DISCOVER_PEERS_FAILED:
case WifiP2pManager.STOP_DISCOVERY_FAILED:
+ case WifiP2pManager.DISCOVER_SERVICES_FAILED:
case WifiP2pManager.CONNECT_FAILED:
case WifiP2pManager.CANCEL_CONNECT_FAILED:
case WifiP2pManager.CREATE_GROUP_FAILED:
case WifiP2pManager.REMOVE_GROUP_FAILED:
+ case WifiP2pManager.ADD_LOCAL_SERVICE_FAILED:
+ case WifiP2pManager.REMOVE_LOCAL_SERVICE_FAILED:
+ case WifiP2pManager.CLEAR_LOCAL_SERVICES_FAILED:
+ case WifiP2pManager.ADD_SERVICE_REQUEST_FAILED:
+ case WifiP2pManager.REMOVE_SERVICE_REQUEST_FAILED:
+ case WifiP2pManager.CLEAR_SERVICE_REQUESTS_FAILED:
if (listener != null) {
((ActionListener) listener).onFailure(message.arg1);
}
@@ -417,10 +587,17 @@ public class WifiP2pManager {
/* ActionListeners grouped together */
case WifiP2pManager.DISCOVER_PEERS_SUCCEEDED:
case WifiP2pManager.STOP_DISCOVERY_SUCCEEDED:
+ case WifiP2pManager.DISCOVER_SERVICES_SUCCEEDED:
case WifiP2pManager.CONNECT_SUCCEEDED:
case WifiP2pManager.CANCEL_CONNECT_SUCCEEDED:
case WifiP2pManager.CREATE_GROUP_SUCCEEDED:
case WifiP2pManager.REMOVE_GROUP_SUCCEEDED:
+ case WifiP2pManager.ADD_LOCAL_SERVICE_SUCCEEDED:
+ case WifiP2pManager.REMOVE_LOCAL_SERVICE_SUCCEEDED:
+ case WifiP2pManager.CLEAR_LOCAL_SERVICES_SUCCEEDED:
+ case WifiP2pManager.ADD_SERVICE_REQUEST_SUCCEEDED:
+ case WifiP2pManager.REMOVE_SERVICE_REQUEST_SUCCEEDED:
+ case WifiP2pManager.CLEAR_SERVICE_REQUESTS_SUCCEEDED:
if (listener != null) {
((ActionListener) listener).onSuccess();
}
@@ -443,6 +620,10 @@ public class WifiP2pManager {
((GroupInfoListener) listener).onGroupInfoAvailable(group);
}
break;
+ case WifiP2pManager.RESPONSE_SERVICE:
+ WifiP2pServiceResponse resp = (WifiP2pServiceResponse) message.obj;
+ handleServiceResponse(resp);
+ break;
default:
Log.d(TAG, "Ignored " + message);
break;
@@ -450,7 +631,47 @@ public class WifiP2pManager {
}
}
- int putListener(Object listener) {
+ private void handleServiceResponse(WifiP2pServiceResponse resp) {
+ if (resp instanceof WifiP2pBonjourServiceResponse) {
+ handleBonjourServiceResponse((WifiP2pBonjourServiceResponse)resp);
+ } else if (resp instanceof WifiP2pUpnpServiceResponse) {
+ if (mUpnpServRspListener != null) {
+ handleUpnpServiceResponse((WifiP2pUpnpServiceResponse)resp);
+ }
+ } else {
+ if (mServRspListener != null) {
+ mServRspListener.onServiceAvailable(resp.getServiceType(),
+ resp.getRawData(), resp.getSrcDevice());
+ }
+ }
+ }
+
+ private void handleUpnpServiceResponse(WifiP2pUpnpServiceResponse resp) {
+ mUpnpServRspListener.onUpnpServiceAvailable(resp.getUniqueServiceNames(),
+ resp.getSrcDevice());
+ }
+
+ private void handleBonjourServiceResponse(WifiP2pBonjourServiceResponse resp) {
+ if (resp.getDnsType() == WifiP2pBonjourServiceInfo.DNS_TYPE_PTR) {
+ if (mBonjourServRspListener != null) {
+ mBonjourServRspListener.onBonjourServiceAvailable(
+ resp.getInstanceName(),
+ resp.getDnsQueryName(),
+ resp.getSrcDevice());
+ }
+ } else if (resp.getDnsType() == WifiP2pBonjourServiceInfo.DNS_TYPE_TXT) {
+ if (mBonjourTxtListener != null) {
+ mBonjourTxtListener.onBonjourTxtRecordAvailable(
+ resp.getDnsQueryName(),
+ resp.getTxtRecord(),
+ resp.getSrcDevice());
+ }
+ } else {
+ Log.e(TAG, "Unhandled resp " + resp);
+ }
+ }
+
+ private int putListener(Object listener) {
if (listener == null) return INVALID_LISTENER_KEY;
int key;
synchronized (mListenerMapLock) {
@@ -462,7 +683,7 @@ public class WifiP2pManager {
return key;
}
- Object getListener(int key) {
+ private Object getListener(int key) {
if (key == INVALID_LISTENER_KEY) return null;
synchronized (mListenerMapLock) {
return mListenerMap.remove(key);
@@ -470,6 +691,18 @@ public class WifiP2pManager {
}
}
+ private static void checkChannel(Channel c) {
+ if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
+ }
+
+ private static void checkServiceInfo(WifiP2pServiceInfo info) {
+ if (info == null) throw new IllegalArgumentException("service info is null");
+ }
+
+ private static void checkServiceRequest(WifiP2pServiceRequest req) {
+ if (req == null) throw new IllegalArgumentException("service request is null");
+ }
+
/**
* Registers the application with the Wi-Fi framework. This function
* must be the first to be called before any p2p operations are performed.
@@ -512,7 +745,7 @@ public class WifiP2pManager {
* @param listener for callbacks on success or failure. Can be null.
*/
public void discoverPeers(Channel c, ActionListener listener) {
- if (c == null) return;
+ checkChannel(c);
c.mAsyncChannel.sendMessage(DISCOVER_PEERS, 0, c.putListener(listener));
}
@@ -522,7 +755,7 @@ public class WifiP2pManager {
* @hide
*/
public void stopPeerDiscovery(Channel c, ActionListener listener) {
- if (c == null) return;
+ checkChannel(c);
c.mAsyncChannel.sendMessage(STOP_DISCOVERY, 0, c.putListener(listener));
}
@@ -549,7 +782,7 @@ public class WifiP2pManager {
* @param listener for callbacks on success or failure. Can be null.
*/
public void connect(Channel c, WifiP2pConfig config, ActionListener listener) {
- if (c == null) return;
+ checkChannel(c);
c.mAsyncChannel.sendMessage(CONNECT, 0, c.putListener(listener), config);
}
@@ -565,7 +798,7 @@ public class WifiP2pManager {
* @param listener for callbacks on success or failure. Can be null.
*/
public void cancelConnect(Channel c, ActionListener listener) {
- if (c == null) return;
+ checkChannel(c);
c.mAsyncChannel.sendMessage(CANCEL_CONNECT, 0, c.putListener(listener));
}
@@ -589,7 +822,7 @@ public class WifiP2pManager {
* @param listener for callbacks on success or failure. Can be null.
*/
public void createGroup(Channel c, ActionListener listener) {
- if (c == null) return;
+ checkChannel(c);
c.mAsyncChannel.sendMessage(CREATE_GROUP, 0, c.putListener(listener));
}
@@ -605,18 +838,225 @@ public class WifiP2pManager {
* @param listener for callbacks on success or failure. Can be null.
*/
public void removeGroup(Channel c, ActionListener listener) {
- if (c == null) return;
+ checkChannel(c);
c.mAsyncChannel.sendMessage(REMOVE_GROUP, 0, c.putListener(listener));
}
/**
+ * Register a local service of service discovery.
+ *
+ * <p> The function call immediately returns after sending a request to add a local
+ * service to the framework. The application is notified of a success or failure to
+ * add service through listener callbacks {@link ActionListener#onSuccess} or
+ * {@link ActionListener#onFailure}.
+ *
+ * <p>The service information is set through the subclass of {@link WifiP2pServiceInfo}.<br>
+ * e.g ) {@link WifiP2pUpnpServiceInfo#newInstance} or
+ * {@link WifiP2pBonjourServiceInfo#newInstance}
+ *
+ * <p>If a local service is added, the framework responds the appropriate service discovery
+ * request automatically.
+ *
+ * <p>These service information will be clear when p2p is disabled or call
+ * {@link #removeLocalService} or {@link #clearLocalServices}.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param servInfo is a local service information.
+ * @param listener for callbacks on success or failure. Can be null.
+ * @hide
+ */
+ public void addLocalService(Channel c, WifiP2pServiceInfo servInfo, ActionListener listener) {
+ checkChannel(c);
+ checkServiceInfo(servInfo);
+ c.mAsyncChannel.sendMessage(ADD_LOCAL_SERVICE, 0, c.putListener(listener), servInfo);
+ }
+
+ /**
+ * Unregister a specified local service of service discovery.
+ *
+ * <p> The function call immediately returns after sending a request to remove a
+ * local service to the framework. The application is notified of a success or failure to
+ * add service through listener callbacks {@link ActionListener#onSuccess} or
+ * {@link ActionListener#onFailure}.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param servInfo is the local service information.
+ * @param listener for callbacks on success or failure. Can be null.
+ * @hide
+ */
+ public void removeLocalService(Channel c, WifiP2pServiceInfo servInfo,
+ ActionListener listener) {
+ checkChannel(c);
+ checkServiceInfo(servInfo);
+ c.mAsyncChannel.sendMessage(REMOVE_LOCAL_SERVICE, 0, c.putListener(listener), servInfo);
+ }
+
+ /**
+ * Clear all registered local services of service discovery.
+ *
+ * <p> The function call immediately returns after sending a request to clear all
+ * local services to the framework. The application is notified of a success or failure to
+ * add service through listener callbacks {@link ActionListener#onSuccess} or
+ * {@link ActionListener#onFailure}.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param listener for callbacks on success or failure. Can be null.
+ * @hide
+ */
+ public void clearLocalServices(Channel c, ActionListener listener) {
+ checkChannel(c);
+ c.mAsyncChannel.sendMessage(CLEAR_LOCAL_SERVICES, 0, c.putListener(listener));
+ }
+
+ /**
+ * Register a callback to be invoked on receiving service discovery response.
+ *
+ * <p> see {@link #discoverServices} for the detail.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param listener for callbacks on receiving service discovery response.
+ * @hide
+ */
+ public void setServiceResponseListener(Channel c,
+ ServiceResponseListener listener) {
+ checkChannel(c);
+ c.mServRspListener = listener;
+ }
+
+ /**
+ * Register a callback to be invoked on receiving Bonjour service discovery
+ * response.
+ *
+ * <p> see {@link #discoverServices} for the detail.
+ *
+ * @param c
+ * @param servlistener is for listening to a Bonjour service response
+ * @param txtListener is for listening to a Bonjour TXT record
+ * @hide
+ */
+ public void setBonjourResponseListeners(Channel c,
+ BonjourServiceResponseListener servListener, BonjourTxtRecordListener txtListener) {
+ checkChannel(c);
+ c.mBonjourServRspListener = servListener;
+ c.mBonjourTxtListener = txtListener;
+ }
+
+ /**
+ * Register a callback to be invoked on receiving upnp service discovery
+ * response.
+ *
+ * <p> see {@link #discoverServices} for the detail.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param listener for callbacks on receiving service discovery response.
+ * @hide
+ */
+ public void setUpnpServiceResponseListener(Channel c,
+ UpnpServiceResponseListener listener) {
+ checkChannel(c);
+ c.mUpnpServRspListener = listener;
+ }
+
+ /**
+ * Initiate service discovery. A discovery process involves scanning for
+ * requested services for the purpose of establishing a connection to a peer
+ * that supports an available service.
+ *
+ * <p> The function call immediately returns after sending a request to start service
+ * discovery to the framework. The application is notified of a success or failure to initiate
+ * discovery through listener callbacks {@link ActionListener#onSuccess} or
+ * {@link ActionListener#onFailure}.
+ *
+ * <p> The services to be discovered are specified with calls to {@link #addServiceRequest}.
+ *
+ * <p>The application is notified of the response against the service discovery request
+ * through listener callbacks registered by {@link #setServiceResponseListener} or
+ * {@link #setBonjourServiceResponseListener}, or {@link #setUpnpServiceResponseListener}.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param listener for callbacks on success or failure. Can be null.
+ * @hide
+ */
+ public void discoverServices(Channel c, ActionListener listener) {
+ checkChannel(c);
+ c.mAsyncChannel.sendMessage(DISCOVER_SERVICES, 0, c.putListener(listener));
+ }
+
+ /**
+ * Add a service discovery request.
+ *
+ * <p> The function call immediately returns after sending a request to add service
+ * discovery request to the framework. The application is notified of a success or failure to
+ * add service through listener callbacks {@link ActionListener#onSuccess} or
+ * {@link ActionListener#onFailure}.
+ *
+ * <p>After service discovery request is added, you can initiate service discovery by
+ * {@link #discoverServices}.
+ *
+ * <p>These information will be clear when wifi p2p is disabled or
+ * {@link #removeServiceRequest(Channel, WifiP2pServiceRequest, ActionListener)} or
+ * {@link #clearServiceRequests(Channel, ActionListener)} is called.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param req is the service discovery request.
+ * @param listener for callbacks on success or failure. Can be null.
+ * @hide
+ */
+ public void addServiceRequest(Channel c,
+ WifiP2pServiceRequest req, ActionListener listener) {
+ checkChannel(c);
+ checkServiceRequest(req);
+ c.mAsyncChannel.sendMessage(ADD_SERVICE_REQUEST, 0,
+ c.putListener(listener), req);
+ }
+
+ /**
+ * Remove a specified service discovery request.
+ *
+ * <p> The function call immediately returns after sending a request to remove service
+ * discovery request to the framework. The application is notified of a success or failure to
+ * add service through listener callbacks {@link ActionListener#onSuccess} or
+ * {@link ActionListener#onFailure}.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param req is the service discovery request.
+ * @param listener for callbacks on success or failure. Can be null.
+ * @hide
+ */
+ public void removeServiceRequest(Channel c, WifiP2pServiceRequest req,
+ ActionListener listener) {
+ checkChannel(c);
+ checkServiceRequest(req);
+ c.mAsyncChannel.sendMessage(REMOVE_SERVICE_REQUEST, 0,
+ c.putListener(listener), req);
+ }
+
+ /**
+ * Clear all registered service discovery requests.
+ *
+ * <p> The function call immediately returns after sending a request to clear all
+ * service discovery requests to the framework. The application is notified of a success
+ * or failure to add service through listener callbacks {@link ActionListener#onSuccess} or
+ * {@link ActionListener#onFailure}.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param listener for callbacks on success or failure. Can be null.
+ * @hide
+ */
+ public void clearServiceRequests(Channel c, ActionListener listener) {
+ checkChannel(c);
+ c.mAsyncChannel.sendMessage(CLEAR_SERVICE_REQUESTS,
+ 0, c.putListener(listener));
+ }
+
+ /**
* Request the current list of peers.
*
* @param c is the channel created at {@link #initialize}
* @param listener for callback when peer list is available. Can be null.
*/
public void requestPeers(Channel c, PeerListListener listener) {
- if (c == null) return;
+ checkChannel(c);
c.mAsyncChannel.sendMessage(REQUEST_PEERS, 0, c.putListener(listener));
}
@@ -627,7 +1067,7 @@ public class WifiP2pManager {
* @param listener for callback when connection info is available. Can be null.
*/
public void requestConnectionInfo(Channel c, ConnectionInfoListener listener) {
- if (c == null) return;
+ checkChannel(c);
c.mAsyncChannel.sendMessage(REQUEST_CONNECTION_INFO, 0, c.putListener(listener));
}
@@ -638,7 +1078,7 @@ public class WifiP2pManager {
* @param listener for callback when group info is available. Can be null.
*/
public void requestGroupInfo(Channel c, GroupInfoListener listener) {
- if (c == null) return;
+ checkChannel(c);
c.mAsyncChannel.sendMessage(REQUEST_GROUP_INFO, 0, c.putListener(listener));
}
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pService.java b/wifi/java/android/net/wifi/p2p/WifiP2pService.java
index 32e1053..314e33e 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pService.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pService.java
@@ -42,6 +42,9 @@ import android.net.wifi.WifiMonitor;
import android.net.wifi.WifiNative;
import android.net.wifi.WifiStateMachine;
import android.net.wifi.WpsInfo;
+import android.net.wifi.p2p.nsd.WifiP2pServiceInfo;
+import android.net.wifi.p2p.nsd.WifiP2pServiceRequest;
+import android.net.wifi.p2p.nsd.WifiP2pServiceResponse;
import android.os.Binder;
import android.os.IBinder;
import android.os.INetworkManagementService;
@@ -55,6 +58,7 @@ import android.os.SystemProperties;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Slog;
+import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -71,7 +75,10 @@ import com.android.internal.util.StateMachine;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
/**
* WifiP2pService inclues a state machine to perform Wi-Fi p2p operations. Applications
@@ -150,6 +157,16 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
private NetworkInfo mNetworkInfo;
+ /* The transaction Id of service discovery request */
+ private byte mServiceTransactionId = 0;
+
+ /* Service discovery request ID of wpa_supplicant.
+ * null means it's not set yet. */
+ private String mServiceDiscReqId;
+
+ /* clients(application) information list. */
+ private HashMap<Messenger, ClientInfo> mClientInfoList = new HashMap<Messenger, ClientInfo>();
+
/* Is chosen as a unique range to avoid conflict with
the range defined in Tethering.java */
private static final String[] DHCP_RANGE = {"192.168.49.2", "192.168.49.254"};
@@ -313,6 +330,10 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_FAILED,
WifiP2pManager.BUSY);
break;
+ case WifiP2pManager.DISCOVER_SERVICES:
+ replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
+ WifiP2pManager.BUSY);
+ break;
case WifiP2pManager.CONNECT:
replyToMessage(message, WifiP2pManager.CONNECT_FAILED,
WifiP2pManager.BUSY);
@@ -329,6 +350,32 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
replyToMessage(message, WifiP2pManager.REMOVE_GROUP_FAILED,
WifiP2pManager.BUSY);
break;
+ case WifiP2pManager.ADD_LOCAL_SERVICE:
+ replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.REMOVE_LOCAL_SERVICE:
+ replyToMessage(message, WifiP2pManager.REMOVE_LOCAL_SERVICE_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.CLEAR_LOCAL_SERVICES:
+ replyToMessage(message, WifiP2pManager.CLEAR_LOCAL_SERVICES_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.ADD_SERVICE_REQUEST:
+ replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.REMOVE_SERVICE_REQUEST:
+ replyToMessage(message,
+ WifiP2pManager.REMOVE_SERVICE_REQUEST_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.CLEAR_SERVICE_REQUESTS:
+ replyToMessage(message,
+ WifiP2pManager.CLEAR_SERVICE_REQUESTS_FAILED,
+ WifiP2pManager.BUSY);
+ break;
case WifiP2pManager.REQUEST_PEERS:
replyToMessage(message, WifiP2pManager.RESPONSE_PEERS, mPeers);
break;
@@ -381,6 +428,10 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_FAILED,
WifiP2pManager.P2P_UNSUPPORTED);
break;
+ case WifiP2pManager.DISCOVER_SERVICES:
+ replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
case WifiP2pManager.CONNECT:
replyToMessage(message, WifiP2pManager.CONNECT_FAILED,
WifiP2pManager.P2P_UNSUPPORTED);
@@ -397,6 +448,32 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
replyToMessage(message, WifiP2pManager.REMOVE_GROUP_FAILED,
WifiP2pManager.P2P_UNSUPPORTED);
break;
+ case WifiP2pManager.ADD_LOCAL_SERVICE:
+ replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.REMOVE_LOCAL_SERVICE:
+ replyToMessage(message, WifiP2pManager.REMOVE_LOCAL_SERVICE_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.CLEAR_LOCAL_SERVICES:
+ replyToMessage(message, WifiP2pManager.CLEAR_LOCAL_SERVICES_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.ADD_SERVICE_REQUEST:
+ replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.REMOVE_SERVICE_REQUEST:
+ replyToMessage(message,
+ WifiP2pManager.REMOVE_SERVICE_REQUEST_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.CLEAR_SERVICE_REQUESTS:
+ replyToMessage(message,
+ WifiP2pManager.CLEAR_SERVICE_REQUESTS_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
default:
return NOT_HANDLED;
}
@@ -507,6 +584,8 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
transitionTo(mP2pDisablingState);
break;
case WifiP2pManager.DISCOVER_PEERS:
+ // do not send service discovery request while normal find operation.
+ clearSupplicantServiceRequest();
if (mWifiNative.p2pFind(DISCOVER_TIMEOUT_S)) {
replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_SUCCEEDED);
sendP2pDiscoveryChangedBroadcast(true);
@@ -526,6 +605,20 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
WifiP2pManager.ERROR);
}
break;
+ case WifiP2pManager.DISCOVER_SERVICES:
+ if (DBG) logd(getName() + " discover services");
+ if (!updateSupplicantServiceRequest()) {
+ replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
+ WifiP2pManager.NO_SERVICE_REQUESTS);
+ break;
+ }
+ if (mWifiNative.p2pFind(DISCOVER_TIMEOUT_S)) {
+ replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
+ WifiP2pManager.ERROR);
+ }
+ break;
case WifiMonitor.P2P_DEVICE_FOUND_EVENT:
WifiP2pDevice device = (WifiP2pDevice) message.obj;
if (mThisDevice.deviceAddress.equals(device.deviceAddress)) break;
@@ -536,7 +629,55 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
device = (WifiP2pDevice) message.obj;
if (mPeers.remove(device)) sendP2pPeersChangedBroadcast();
break;
- default:
+ case WifiP2pManager.ADD_LOCAL_SERVICE:
+ if (DBG) logd(getName() + " add service");
+ WifiP2pServiceInfo servInfo = (WifiP2pServiceInfo)message.obj;
+ if (addLocalService(message.replyTo, servInfo)) {
+ replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_FAILED);
+ }
+ break;
+ case WifiP2pManager.REMOVE_LOCAL_SERVICE:
+ if (DBG) logd(getName() + " remove service");
+ servInfo = (WifiP2pServiceInfo)message.obj;
+ removeLocalService(message.replyTo, servInfo);
+ replyToMessage(message, WifiP2pManager.REMOVE_LOCAL_SERVICE_SUCCEEDED);
+ break;
+ case WifiP2pManager.CLEAR_LOCAL_SERVICES:
+ if (DBG) logd(getName() + " clear service");
+ clearLocalServices(message.replyTo);
+ break;
+ case WifiP2pManager.ADD_SERVICE_REQUEST:
+ if (DBG) logd(getName() + " add service request");
+ if (!addServiceRequest(message.replyTo, (WifiP2pServiceRequest)message.obj)) {
+ replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_FAILED);
+ break;
+ }
+ replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_SUCCEEDED);
+ break;
+ case WifiP2pManager.REMOVE_SERVICE_REQUEST:
+ if (DBG) logd(getName() + " remove service request");
+ removeServiceRequest(message.replyTo, (WifiP2pServiceRequest)message.obj);
+ replyToMessage(message, WifiP2pManager.REMOVE_SERVICE_REQUEST_SUCCEEDED);
+ break;
+ case WifiP2pManager.CLEAR_SERVICE_REQUESTS:
+ if (DBG) logd(getName() + " clear service request");
+ clearServiceRequests(message.replyTo);
+ replyToMessage(message, WifiP2pManager.CLEAR_SERVICE_REQUESTS_SUCCEEDED);
+ break;
+ case WifiMonitor.P2P_SERV_DISC_RESP_EVENT:
+ if (DBG) logd(getName() + " receive service response");
+ List<WifiP2pServiceResponse> sdRespList =
+ (List<WifiP2pServiceResponse>) message.obj;
+ for (WifiP2pServiceResponse resp : sdRespList) {
+ WifiP2pDevice dev =
+ mPeers.get(resp.getSrcDevice().deviceAddress);
+ resp.setSrcDevice(dev);
+ sendServiceResponse(resp);
+ }
+ break;
+ default:
return NOT_HANDLED;
}
return HANDLED;
@@ -1286,6 +1427,12 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
mThisDevice.deviceAddress = mWifiNative.p2pGetDeviceAddress();
updateThisDevice(WifiP2pDevice.AVAILABLE);
if (DBG) Slog.d(TAG, "DeviceAddress: " + mThisDevice.deviceAddress);
+
+ mClientInfoList.clear();
+ mWifiNative.p2pFlush();
+ mWifiNative.p2pServiceFlush();
+ mServiceTransactionId = 0;
+ mServiceDiscReqId = null;
}
private void updateThisDevice(int status) {
@@ -1342,5 +1489,262 @@ public class WifiP2pService extends IWifiP2pManager.Stub {
Slog.e(TAG, s);
}
+ /**
+ * Update service discovery request to wpa_supplicant.
+ */
+ private boolean updateSupplicantServiceRequest() {
+ clearSupplicantServiceRequest();
+
+ StringBuffer sb = new StringBuffer();
+ for (ClientInfo c: mClientInfoList.values()) {
+ int key;
+ WifiP2pServiceRequest req;
+ for (int i=0; i < c.mReqList.size(); i++) {
+ key = c.mReqList.keyAt(i);
+ req = c.mReqList.get(key);
+ if (req != null) {
+ sb.append(req.getSupplicantQuery());
+ }
+ }
+ }
+
+ if (sb.length() == 0) {
+ return false;
+ }
+
+ mServiceDiscReqId = mWifiNative.p2pServDiscReq("00:00:00:00:00:00", sb.toString());
+ if (mServiceDiscReqId == null) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Clear service discovery request in wpa_supplicant
+ */
+ private void clearSupplicantServiceRequest() {
+ if (mServiceDiscReqId == null) return;
+
+ mWifiNative.p2pServDiscCancelReq(mServiceDiscReqId);
+ mServiceDiscReqId = null;
+ }
+
+ /* TODO: We could track individual service adds seperately and avoid
+ * having to do update all service requests on every new request
+ */
+ private boolean addServiceRequest(Messenger m, WifiP2pServiceRequest req) {
+ clearClientDeadChannels();
+ ClientInfo clientInfo = getClientInfo(m, true);
+ if (clientInfo == null) {
+ return false;
+ }
+
+ req.setTransactionId(++mServiceTransactionId);
+ clientInfo.mReqList.put(mServiceTransactionId, req);
+
+ if (mServiceDiscReqId == null) {
+ return true;
+ }
+
+ return updateSupplicantServiceRequest();
+ }
+
+ private void removeServiceRequest(Messenger m, WifiP2pServiceRequest req) {
+
+ ClientInfo clientInfo = getClientInfo(m, false);
+ if (clientInfo == null) {
+ return;
+ }
+
+ clientInfo.mReqList.remove(req.getTransactionId());
+
+ if (clientInfo.mReqList.size() == 0 && clientInfo.mServList.size() == 0) {
+ if (DBG) logd("remove client information from framework");
+ mClientInfoList.remove(clientInfo.mMessenger);
+ }
+
+ if (mServiceDiscReqId == null) {
+ return;
+ }
+
+ updateSupplicantServiceRequest();
+ }
+
+ private void clearServiceRequests(Messenger m) {
+
+ ClientInfo clientInfo = getClientInfo(m, false);
+ if (clientInfo == null) {
+ return;
+ }
+
+ if (clientInfo.mReqList.size() == 0) {
+ return;
+ }
+
+ clientInfo.mReqList.clear();
+
+ if (clientInfo.mServList.size() == 0) {
+ if (DBG) logd("remove channel information from framework");
+ mClientInfoList.remove(clientInfo.mMessenger);
+ }
+
+ if (mServiceDiscReqId == null) {
+ return;
+ }
+
+ updateSupplicantServiceRequest();
+ }
+
+ private boolean addLocalService(Messenger m, WifiP2pServiceInfo servInfo) {
+ clearClientDeadChannels();
+ ClientInfo clientInfo = getClientInfo(m, true);
+ if (clientInfo == null) {
+ return false;
+ }
+
+ if (!clientInfo.mServList.add(servInfo)) {
+ return false;
+ }
+
+ if (!mWifiNative.p2pServiceAdd(servInfo)) {
+ clientInfo.mServList.remove(servInfo);
+ return false;
+ }
+
+ return true;
+ }
+
+ private void removeLocalService(Messenger m, WifiP2pServiceInfo servInfo) {
+ ClientInfo clientInfo = getClientInfo(m, false);
+ if (clientInfo == null) {
+ return;
+ }
+
+ mWifiNative.p2pServiceDel(servInfo);
+
+ clientInfo.mServList.remove(servInfo);
+ if (clientInfo.mReqList.size() == 0 && clientInfo.mServList.size() == 0) {
+ if (DBG) logd("remove client information from framework");
+ mClientInfoList.remove(clientInfo.mMessenger);
+ }
+ }
+
+ private void clearLocalServices(Messenger m) {
+ ClientInfo clientInfo = getClientInfo(m, false);
+ if (clientInfo == null) {
+ return;
+ }
+
+ for (WifiP2pServiceInfo servInfo: clientInfo.mServList) {
+ mWifiNative.p2pServiceDel(servInfo);
+ }
+
+ clientInfo.mServList.clear();
+ if (clientInfo.mReqList.size() == 0) {
+ if (DBG) logd("remove client information from framework");
+ mClientInfoList.remove(clientInfo.mMessenger);
+ }
+ }
+
+ private void clearClientInfo(Messenger m) {
+ clearLocalServices(m);
+ clearServiceRequests(m);
+ }
+
+ /**
+ * Send the service response to the WifiP2pManager.Channel.
+ *
+ * @param resp
+ */
+ private void sendServiceResponse(WifiP2pServiceResponse resp) {
+ for (ClientInfo c : mClientInfoList.values()) {
+ WifiP2pServiceRequest req = c.mReqList.get(resp.getTransactionId());
+ if (req != null) {
+ Message msg = Message.obtain();
+ msg.what = WifiP2pManager.RESPONSE_SERVICE;
+ msg.arg1 = 0;
+ msg.arg2 = 0;
+ msg.obj = resp;
+ try {
+ c.mMessenger.send(msg);
+ } catch (RemoteException e) {
+ if (DBG) logd("detect dead channel");
+ clearClientInfo(c.mMessenger);
+ }
+ }
+ }
+ }
+
+ /**
+ * We dont get notifications of clients that have gone away.
+ * We detect this actively when services are added and throw
+ * them away.
+ *
+ * TODO: This can be done better with full async channels.
+ */
+ private void clearClientDeadChannels() {
+ for (ClientInfo c : mClientInfoList.values()) {
+ Message msg = Message.obtain();
+ msg.what = WifiP2pManager.PING;
+ msg.arg1 = 0;
+ msg.arg2 = 0;
+ msg.obj = null;
+ try {
+ c.mMessenger.send(msg);
+ } catch (RemoteException e) {
+ if (DBG) logd("detect dead channel");
+ clearClientInfo(c.mMessenger);
+ }
+ }
+ }
+
+ /**
+ * Return the specified ClientInfo.
+ * @param m Messenger
+ * @param createIfNotExist if true and the specified channel info does not exist,
+ * create new client info.
+ * @return the specified ClientInfo.
+ */
+ private ClientInfo getClientInfo(Messenger m, boolean createIfNotExist) {
+ ClientInfo clientInfo = mClientInfoList.get(m);
+
+ if (clientInfo == null && createIfNotExist) {
+ if (DBG) logd("add a new client");
+ clientInfo = new ClientInfo(m);
+ mClientInfoList.put(m, clientInfo);
+ }
+
+ return clientInfo;
+ }
+
+ }
+
+ /**
+ * Information about a particular client and we track the service discovery requests
+ * and the local services registered by the client.
+ */
+ private class ClientInfo {
+
+ /*
+ * A reference to WifiP2pManager.Channel handler.
+ * The response of this request is notified to WifiP2pManager.Channel handler
+ */
+ private Messenger mMessenger;
+
+ /*
+ * A service discovery request list.
+ */
+ private SparseArray<WifiP2pServiceRequest> mReqList;
+
+ /*
+ * A local service information list.
+ */
+ private List<WifiP2pServiceInfo> mServList;
+
+ private ClientInfo(Messenger m) {
+ mMessenger = m;
+ mReqList = new SparseArray();
+ mServList = new ArrayList<WifiP2pServiceInfo>();
+ }
}
}
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pBonjourServiceInfo.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pBonjourServiceInfo.java
new file mode 100644
index 0000000..ed278d5
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pBonjourServiceInfo.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.p2p.nsd;
+
+import android.net.nsd.DnsSdTxtRecord;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A class for Bonjour service information.
+ * @hide
+ */
+public class WifiP2pBonjourServiceInfo extends WifiP2pServiceInfo {
+
+ /**
+ * Bonjour version 1.
+ * @hide
+ */
+ public static final int VERSION_1 = 0x01;
+
+ /**
+ * Pointer record.
+ * @hide
+ */
+ public static final int DNS_TYPE_PTR = 12;
+
+ /**
+ * Text record.
+ * @hide
+ */
+ public static final int DNS_TYPE_TXT = 16;
+
+ /**
+ * virtual memory packet.
+ * see E.3 of the Wi-Fi Direct technical specification for the detail.<br>
+ * Key: domain name Value: pointer address.<br>
+ */
+ private final static Map<String, String> sVmPacket;
+
+ static {
+ sVmPacket = new HashMap<String, String>();
+ sVmPacket.put("_tcp.local.", "c00c");
+ sVmPacket.put("local.", "c011");
+ sVmPacket.put("_udp.local.", "c01c");
+ }
+
+ /**
+ * This constructor is only used in newInstance().
+ *
+ * @param queryList
+ */
+ private WifiP2pBonjourServiceInfo(List<String> queryList) {
+ super(queryList);
+ }
+
+ /**
+ * Create Bonjour service information object.
+ *
+ * @param instanceName instance name.<br>
+ * e.g) "MyPrinter"
+ * @param registrationType registration type.<br>
+ * e.g) "_ipp._tcp.local."
+ * @param txtRecord text record.
+ * @return Bonjour service information object
+ */
+ public static WifiP2pBonjourServiceInfo newInstance(String instanceName,
+ String registrationType, DnsSdTxtRecord txtRecord) {
+ if (TextUtils.isEmpty(instanceName) || TextUtils.isEmpty(registrationType)) {
+ throw new IllegalArgumentException(
+ "instance name or registration type cannot be empty");
+ }
+
+ if (txtRecord == null) {
+ txtRecord = new DnsSdTxtRecord();
+ }
+
+ ArrayList<String> queries = new ArrayList<String>();
+ queries.add(createPtrServiceQuery(instanceName, registrationType));
+ queries.add(createTxtServiceQuery(instanceName, registrationType, txtRecord));
+
+ return new WifiP2pBonjourServiceInfo(queries);
+ }
+
+ /**
+ * Create wpa_supplicant service query for PTR record.
+ *
+ * @param instanceName instance name.<br>
+ * e.g) "MyPrinter"
+ * @param registrationType registration type.<br>
+ * e.g) "_ipp._tcp.local."
+ * @return wpa_supplicant service query.
+ */
+ private static String createPtrServiceQuery(String instanceName,
+ String registrationType) {
+
+ StringBuffer sb = new StringBuffer();
+ sb.append("bonjour ");
+ sb.append(createRequest(registrationType, DNS_TYPE_PTR, VERSION_1));
+ sb.append(" ");
+
+ byte[] data = instanceName.getBytes();
+ sb.append(String.format("%02x", data.length));
+ sb.append(WifiP2pServiceInfo.bin2HexStr(data));
+ // This is the start point of this response.
+ // Therefore, it indicates the request domain name.
+ sb.append("c027");
+ return sb.toString();
+ }
+
+ /**
+ * Create wpa_supplicant service query for TXT record.
+ *
+ * @param instanceName instance name.<br>
+ * e.g) "MyPrinter"
+ * @param registrationType registration type.<br>
+ * e.g) "_ipp._tcp.local."
+ * @param txtRecord TXT record.<br>
+ * @return wpa_supplicant service query.
+ */
+ public static String createTxtServiceQuery(String instanceName,
+ String registrationType,
+ DnsSdTxtRecord txtRecord) {
+
+
+ StringBuffer sb = new StringBuffer();
+ sb.append("bonjour ");
+
+ sb.append(createRequest((instanceName + "." + registrationType),
+ DNS_TYPE_TXT, VERSION_1));
+ sb.append(" ");
+ byte[] rawData = txtRecord.getRawData();
+ if (rawData.length == 0) {
+ sb.append("00");
+ } else {
+ sb.append(bin2HexStr(rawData));
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Create bonjour service discovery request.
+ *
+ * @param dnsName dns name
+ * @param dnsType dns type
+ * @param version version number
+ * @hide
+ */
+ static String createRequest(String dnsName, int dnsType, int version) {
+ StringBuffer sb = new StringBuffer();
+
+ /*
+ * The request format is as follows.
+ * ________________________________________________
+ * | Encoded and Compressed dns name (variable) |
+ * ________________________________________________
+ * | Type (2) | Version (1) |
+ */
+ if (dnsType == WifiP2pBonjourServiceInfo.DNS_TYPE_TXT) {
+ dnsName = dnsName.toLowerCase();
+ }
+ sb.append(compressDnsName(dnsName));
+ sb.append(String.format("%04x", dnsType));
+ sb.append(String.format("%02x", version));
+
+ return sb.toString();
+ }
+
+ /**
+ * Compress DNS data.
+ *
+ * see E.3 of the Wi-Fi Direct technical specification for the detail.
+ *
+ * @param dnsName dns name
+ * @return compressed dns name
+ */
+ private static String compressDnsName(String dnsName) {
+ StringBuffer sb = new StringBuffer();
+
+ // The domain name is replaced with a pointer to a prior
+ // occurrence of the same name in virtual memory packet.
+ while (true) {
+ String data = sVmPacket.get(dnsName);
+ if (data != null) {
+ sb.append(data);
+ break;
+ }
+ int i = dnsName.indexOf('.');
+ if (i == -1) {
+ if (dnsName.length() > 0) {
+ sb.append(String.format("%02x", dnsName.length()));
+ sb.append(WifiP2pServiceInfo.bin2HexStr(dnsName.getBytes()));
+ }
+ // for a sequence of labels ending in a zero octet
+ sb.append("00");
+ break;
+ }
+
+ String name = dnsName.substring(0, i);
+ dnsName = dnsName.substring(i + 1);
+ sb.append(String.format("%02x", name.length()));
+ sb.append(WifiP2pServiceInfo.bin2HexStr(name.getBytes()));
+ }
+ return sb.toString();
+ }
+}
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pBonjourServiceRequest.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pBonjourServiceRequest.java
new file mode 100644
index 0000000..d1635f1
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pBonjourServiceRequest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.p2p.nsd;
+
+
+/**
+ * A class for a request of bonjour service discovery.
+ * @hide
+ */
+public class WifiP2pBonjourServiceRequest extends WifiP2pServiceRequest {
+
+ /**
+ * This constructor is only used in newInstance().
+ *
+ * @param query The part of service specific query.
+ * @hide
+ */
+ private WifiP2pBonjourServiceRequest(String query) {
+ super(WifiP2pServiceInfo.SERVICE_TYPE_BONJOUR, query);
+ }
+
+ /**
+ * This constructor is only used in newInstance().
+ * @hide
+ */
+ private WifiP2pBonjourServiceRequest() {
+ super(WifiP2pServiceInfo.SERVICE_TYPE_BONJOUR, null);
+ }
+
+ private WifiP2pBonjourServiceRequest(String registrationType, int dnsType, int version) {
+ super(WifiP2pServiceInfo.SERVICE_TYPE_BONJOUR, WifiP2pBonjourServiceInfo.createRequest(
+ registrationType,
+ WifiP2pBonjourServiceInfo.DNS_TYPE_PTR,
+ WifiP2pBonjourServiceInfo.VERSION_1));
+ }
+
+ /**
+ * Create a service discovery request to search all Bonjour services.
+ *
+ * @return service request for Bonjour.
+ */
+ public static WifiP2pBonjourServiceRequest newInstance() {
+ return new WifiP2pBonjourServiceRequest();
+ }
+
+ /**
+ * Create a service discovery request to resolve the instance name with the specified
+ * registration type.
+ *
+ * @param registrationType registration type. Cannot be null <br>
+ * e.g) <br>
+ * "_afpovertcp._tcp.local."(Apple File Sharing over TCP)<br>
+ * "_ipp._tcp.local." (IP Printing over TCP)<br>
+ * @return service request for Bonjour.
+ */
+ public static WifiP2pBonjourServiceRequest newInstance(String registrationType) {
+ if (registrationType == null) {
+ throw new IllegalArgumentException("registration type cannot be null");
+ }
+ return new WifiP2pBonjourServiceRequest(registrationType,
+ WifiP2pBonjourServiceInfo.DNS_TYPE_PTR,
+ WifiP2pBonjourServiceInfo.VERSION_1);
+ }
+
+ /**
+ * Create a service discovery request to get the TXT data from the specified
+ * service.
+ *
+ * @param instanceName instance name. Cannot be null. <br>
+ * "MyPrinter"
+ * @param registrationType registration type. Cannot be null. <br>
+ * e.g) <br>
+ * "_afpovertcp._tcp.local."(Apple File Sharing over TCP)<br>
+ * "_ipp._tcp.local." (IP Printing over TCP)<br>
+ * @return service request for Bonjour.
+ */
+ public static WifiP2pBonjourServiceRequest newInstance(String instanceName,
+ String registrationType) {
+ if (instanceName == null || registrationType == null) {
+ throw new IllegalArgumentException(
+ "instance name or registration type cannot be null");
+ }
+ String fullDomainName = instanceName + "." + registrationType;
+ return new WifiP2pBonjourServiceRequest(fullDomainName,
+ WifiP2pBonjourServiceInfo.DNS_TYPE_TXT,
+ WifiP2pBonjourServiceInfo.VERSION_1);
+ }
+}
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pBonjourServiceResponse.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pBonjourServiceResponse.java
new file mode 100644
index 0000000..c511569
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pBonjourServiceResponse.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.p2p.nsd;
+
+import android.net.nsd.DnsSdTxtRecord;
+import android.net.wifi.p2p.WifiP2pDevice;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A class for a response of bonjour service discovery.
+ *
+ * @hide
+ */
+public class WifiP2pBonjourServiceResponse extends WifiP2pServiceResponse {
+
+ /**
+ * DNS query name.
+ * e.g)
+ * for PTR
+ * "_ipp._tcp.local."
+ * for TXT
+ * "MyPrinter._ipp._tcp.local."
+ */
+ private String mDnsQueryName;
+
+ /**
+ * Service instance name.
+ * e.g) "MyPrinter"
+ * This field is only used when the dns type equals to
+ * {@link WifiP2pBonjourServiceInfo#DNS_TYPE_PTR}.
+ */
+ private String mInstanceName;
+
+ /**
+ * DNS Type.
+ * Should be {@link WifiP2pBonjourServiceInfo#DNS_TYPE_PTR} or
+ * {@link WifiP2pBonjourServiceInfo#DNS_TYPE_TXT}.
+ */
+ private int mDnsType;
+
+ /**
+ * Bonjour version number.
+ * Should be {@link WifiP2pBonjourServiceInfo#VERSION_1}.
+ */
+ private int mVersion;
+
+ /**
+ * Txt record.
+ * This field is only used when the dns type equals to
+ * {@link WifiP2pBonjourServiceInfo#DNS_TYPE_TXT}.
+ */
+ private DnsSdTxtRecord mTxtRecord;
+
+ /**
+ * Virtual memory packet.
+ * see E.3 of the Wi-Fi Direct technical specification for the detail.<br>
+ * The spec can be obtained from wi-fi.org
+ * Key: pointer Value: domain name.<br>
+ */
+ private final static Map<Integer, String> sVmpack;
+
+ static {
+ sVmpack = new HashMap<Integer, String>();
+ sVmpack.put(0x0c, "_tcp.local.");
+ sVmpack.put(0x11, "local.");
+ sVmpack.put(0x1c, "_udp.local.");
+ }
+
+ /**
+ * Returns query DNS name.
+ * @return DNS name.
+ */
+ public String getDnsQueryName() {
+ return mDnsQueryName;
+ }
+
+ /**
+ * Return query DNS type.
+ * @return DNS type.
+ */
+ public int getDnsType() {
+ return mDnsType;
+ }
+
+ /**
+ * Return bonjour version number.
+ * @return version number.
+ */
+ public int getVersion() {
+ return mVersion;
+ }
+
+ /**
+ * Return instance name.
+ * @return
+ */
+ public String getInstanceName() {
+ return mInstanceName;
+ }
+
+ /**
+ * Return TXT record data.
+ * @return TXT record data.
+ */
+ public DnsSdTxtRecord getTxtRecord() {
+ return mTxtRecord;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sbuf = new StringBuffer();
+ sbuf.append("serviceType:Bonjour(").append(mServiceType).append(")");
+ sbuf.append(" status:").append(Status.toString(mStatus));
+ sbuf.append(" srcAddr:").append(mDevice.deviceAddress);
+ sbuf.append(" version:").append(String.format("%02x", mVersion));
+ sbuf.append(" dnsName:").append(mDnsQueryName);
+ if (mTxtRecord != null) {
+ sbuf.append(" TxtRecord:").append(mTxtRecord);
+ }
+ if (mInstanceName != null) {
+ sbuf.append(" InsName:").append(mInstanceName);
+ }
+ return sbuf.toString();
+ }
+
+ /**
+ * This is only used in framework.
+ * @param status status code.
+ * @param dev source device.
+ * @param data RDATA.
+ * @hide
+ */
+ protected WifiP2pBonjourServiceResponse(int status,
+ int tranId, WifiP2pDevice dev, byte[] data) {
+ super(WifiP2pServiceInfo.SERVICE_TYPE_BONJOUR,
+ status, tranId, dev, data);
+ if (!parse()) {
+ throw new IllegalArgumentException("Malformed bonjour service response");
+ }
+ }
+
+ /**
+ * Parse Bonjour service discovery response.
+ *
+ * @return {@code true} if the operation succeeded
+ */
+ private boolean parse() {
+ /*
+ * The data format from Wi-Fi Direct spec is as follows.
+ * ________________________________________________
+ * | encoded and compressed dns name (variable) |
+ * ________________________________________________
+ * | dnstype(2byte) | version(1byte) |
+ * ________________________________________________
+ * | RDATA (variable) |
+ */
+ if (mData == null) {
+ // the empty is OK.
+ return true;
+ }
+
+ DataInputStream dis = new DataInputStream(new ByteArrayInputStream(mData));
+
+ mDnsQueryName = readDnsName(dis);
+ if (mDnsQueryName == null) {
+ return false;
+ }
+
+ try {
+ mDnsType = dis.readUnsignedShort();
+ mVersion = dis.readUnsignedByte();
+ } catch (IOException e) {
+ e.printStackTrace();
+ return false;
+ }
+
+ if (mDnsType == WifiP2pBonjourServiceInfo.DNS_TYPE_PTR) {
+ String rData = readDnsName(dis);
+ if (rData == null) {
+ return false;
+ }
+ if (rData.length() <= mDnsQueryName.length()) {
+ return false;
+ }
+
+ mInstanceName = rData.substring(0,
+ rData.length() - mDnsQueryName.length() -1);
+ } else if (mDnsType == WifiP2pBonjourServiceInfo.DNS_TYPE_TXT) {
+ mTxtRecord = readTxtData(dis);
+ if (mTxtRecord == null) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Read dns name.
+ *
+ * @param dis data input stream.
+ * @return dns name
+ */
+ private String readDnsName(DataInputStream dis) {
+ StringBuffer sb = new StringBuffer();
+
+ // copy virtual memory packet.
+ HashMap<Integer, String> vmpack = new HashMap<Integer, String>(sVmpack);
+ if (mDnsQueryName != null) {
+ vmpack.put(0x27, mDnsQueryName);
+ }
+ try {
+ while (true) {
+ int i = dis.readUnsignedByte();
+ if (i == 0x00) {
+ return sb.toString();
+ } else if (i == 0xc0) {
+ // refer to pointer.
+ String ref = vmpack.get(dis.readUnsignedByte());
+ if (ref == null) {
+ //invalid.
+ return null;
+ }
+ sb.append(ref);
+ return sb.toString();
+ } else {
+ byte[] data = new byte[i];
+ dis.readFully(data);
+ sb.append(new String(data));
+ sb.append(".");
+ }
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * Read TXT record data.
+ *
+ * @param dis
+ * @return TXT record data
+ */
+ private DnsSdTxtRecord readTxtData(DataInputStream dis) {
+ DnsSdTxtRecord txtRecord = new DnsSdTxtRecord();
+ try {
+ while (dis.available() > 0) {
+ int len = dis.readUnsignedByte();
+ if (len == 0) {
+ break;
+ }
+ byte[] data = new byte[len];
+ dis.readFully(data);
+ String[] keyVal = new String(data).split("=");
+ if (keyVal.length != 2) {
+ return null;
+ }
+ txtRecord.set(keyVal[0], keyVal[1]);
+ }
+ return txtRecord;
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * Creates Bonjour service response.
+ * This is only called from WifiP2pServiceResponse
+ *
+ * @param status status code.
+ * @param dev source device.
+ * @param data Bonjour response data.
+ * @return Bonjour service response data.
+ * @hide
+ */
+ static WifiP2pBonjourServiceResponse newInstance(int status,
+ int transId, WifiP2pDevice dev, byte[] data) {
+ if (status != WifiP2pServiceResponse.Status.SUCCESS) {
+ return new WifiP2pBonjourServiceResponse(status,
+ transId, dev, null);
+ }
+ try {
+ return new WifiP2pBonjourServiceResponse(status,
+ transId, dev, data);
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+}
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.aidl b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.aidl
new file mode 100644
index 0000000..cf2cb4a
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.p2p.servicediscovery;
+
+parcelable WifiP2pServiceInfo;
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.java
new file mode 100644
index 0000000..aed5616
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.p2p.nsd;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The class for service information.
+ *
+ * <p>Currently UPnP and Bonjour are only supported.
+ *
+ * @see WifiP2pUpnpServiceInfo
+ * @see WifiP2pBonjourServiceInfo
+ * @hide
+ */
+public class WifiP2pServiceInfo implements Parcelable {
+
+ /**
+ * All service protocol types.
+ */
+ public static final int SERVICE_TYPE_ALL = 0;
+
+ /**
+ * Bonjour protocol.
+ */
+ public static final int SERVICE_TYPE_BONJOUR = 1;
+
+ /**
+ * UPnP protocol.
+ */
+ public static final int SERVICE_TYPE_UPNP = 2;
+
+ /**
+ * WS-Discovery protocol
+ */
+ public static final int SERVICE_TYPE_WS_DISCOVERY = 3;
+
+ /**
+ * Vendor Specific protocol
+ */
+ public static final int SERVICE_TYPE_VENDOR_SPECIFIC = 255;
+
+ /**
+ * the list of query string for wpa_supplicant
+ *
+ * e.g)
+ * # IP Printing over TCP (PTR) (RDATA=MyPrinter._ipp._tcp.local.)
+ * {"bonjour", "045f697070c00c000c01", "094d795072696e746572c027"
+ *
+ * # IP Printing over TCP (TXT) (RDATA=txtvers=1,pdl=application/postscript)
+ * {"bonjour", "096d797072696e746572045f697070c00c001001",
+ * "09747874766572733d311a70646c3d6170706c69636174696f6e2f706f7374736372797074"}
+ *
+ * [UPnP]
+ * # UPnP uuid
+ * {"upnp", "10", "uuid:6859dede-8574-59ab-9332-123456789012"}
+ *
+ * # UPnP rootdevice
+ * {"upnp", "10", "uuid:6859dede-8574-59ab-9332-123456789012::upnp:rootdevice"}
+ *
+ * # UPnP device
+ * {"upnp", "10", "uuid:6859dede-8574-59ab-9332-123456789012::urn:schemas-upnp
+ * -org:device:InternetGatewayDevice:1"}
+ *
+ * # UPnP service
+ * {"upnp", "10", "uuid:6859dede-8574-59ab-9322-123456789012::urn:schemas-upnp
+ * -org:service:ContentDirectory:2"}
+ */
+ private List<String> mQueryList;
+
+ /**
+ * This is only used in subclass.
+ *
+ * @param queryList query string for wpa_supplicant
+ * @hide
+ */
+ protected WifiP2pServiceInfo(List<String> queryList) {
+ if (queryList == null) {
+ throw new IllegalArgumentException("query list cannot be null");
+ }
+ mQueryList = queryList;
+ }
+
+ /**
+ * Return the list of the query string for wpa_supplicant.
+ *
+ * @return the list of the query string for wpa_supplicant.
+ * @hide
+ */
+ public List<String> getSupplicantQueryList() {
+ return mQueryList;
+ }
+
+ /**
+ * Converts byte array to hex string.
+ *
+ * @param data
+ * @return hex string.
+ * @hide
+ */
+ static String bin2HexStr(byte[] data) {
+ StringBuffer sb = new StringBuffer();
+
+ for (byte b: data) {
+ String s = null;
+ try {
+ s = Integer.toHexString(b & 0xff);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ //add 0 padding
+ if (s.length() == 1) {
+ sb.append('0');
+ }
+ sb.append(s);
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof WifiP2pServiceInfo)) {
+ return false;
+ }
+
+ WifiP2pServiceInfo servInfo = (WifiP2pServiceInfo)o;
+ return mQueryList.equals(servInfo.mQueryList);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + (mQueryList == null ? 0 : mQueryList.hashCode());
+ return result;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStringList(mQueryList);
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public static final Creator<WifiP2pServiceInfo> CREATOR =
+ new Creator<WifiP2pServiceInfo>() {
+ public WifiP2pServiceInfo createFromParcel(Parcel in) {
+
+ List<String> data = new ArrayList<String>();
+ in.readStringList(data);
+ return new WifiP2pServiceInfo(data);
+ }
+
+ public WifiP2pServiceInfo[] newArray(int size) {
+ return new WifiP2pServiceInfo[size];
+ }
+ };
+}
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.aidl b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.aidl
new file mode 100644
index 0000000..d5a1e8f
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.p2p.servicediscovery;
+
+parcelable WifiP2pServiceRequest;
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.java
new file mode 100644
index 0000000..e41d9aa
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.p2p.nsd;
+
+import android.net.wifi.p2p.WifiP2pManager;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class for a request of service discovery.
+ *
+ * <p>This class is used when you create customized service discovery request.
+ * e.g) vendor specific request/ws discovery etc.
+ *
+ * <p>If you want to create UPnP or Bonjour service request, then you had better
+ * use {@link WifiP2pUpnpServiceRequest} or {@link WifiP2pBonjourServiceRequest}.
+ *
+ * @see WifiP2pUpnpServiceRequest
+ * @see WifiP2pBonjourServiceRequest
+ * @hide
+ */
+public class WifiP2pServiceRequest implements Parcelable {
+
+ /**
+ * Service type. It's defined in table63 in Wi-Fi Direct specification.
+ */
+ private int mServiceType;
+
+ /**
+ * The length of the service request TLV.
+ * The value is equal to 2 plus the number of octets in the
+ * query data field.
+ */
+ private int mLength;
+
+ /**
+ * Service transaction ID.
+ * This is a nonzero value used to match the service request/response TLVs.
+ */
+ private int mTransId;
+
+ /**
+ * The hex dump string of query data for the requested service information.
+ *
+ * e.g) Bonjour apple file sharing over tcp (dns name=_afpovertcp._tcp.local.)
+ * 0b5f6166706f766572746370c00c000c01
+ */
+ private String mQuery;
+
+ /**
+ * This constructor is only used in newInstance().
+ *
+ * @param serviceType service discovery type.
+ * @param query The part of service specific query.
+ * @hide
+ */
+ protected WifiP2pServiceRequest(int serviceType, String query) {
+ validateQuery(query);
+
+ mServiceType = serviceType;
+ mQuery = query;
+ if (query != null) {
+ mLength = query.length()/2 + 2;
+ } else {
+ mLength = 2;
+ }
+ }
+
+ /**
+ * This constructor is only used in Parcelable.
+ *
+ * @param serviceType service discovery type.
+ * @param length the length of service discovery packet.
+ * @param transId the transaction id
+ * @param query The part of service specific query.
+ */
+ private WifiP2pServiceRequest(int serviceType, int length,
+ int transId, String query) {
+ mServiceType = serviceType;
+ mLength = length;
+ mTransId = transId;
+ mQuery = query;
+ }
+
+ /**
+ * Return transaction id.
+ *
+ * @return transaction id
+ * @hide
+ */
+ public int getTransactionId() {
+ return mTransId;
+ }
+
+ /**
+ * Set transaction id.
+ *
+ * @param id
+ * @hide
+ */
+ public void setTransactionId(int id) {
+ mTransId = id;
+ }
+
+ /**
+ * Return wpa_supplicant request string.
+ *
+ * The format is the hex dump of the following frame.
+ * <pre>
+ * _______________________________________________________________
+ * | Length (2) | Type (1) | Transaction ID (1) |
+ * | Query Data (variable) |
+ * </pre>
+ *
+ * @return wpa_supplicant request string.
+ * @hide
+ */
+ public String getSupplicantQuery() {
+ StringBuffer sb = new StringBuffer();
+ // length is retained as little endian format.
+ sb.append(String.format("%02x", (mLength) & 0xff));
+ sb.append(String.format("%02x", (mLength >> 8) & 0xff));
+ sb.append(String.format("%02x", mServiceType));
+ sb.append(String.format("%02x", mTransId));
+ if (mQuery != null) {
+ sb.append(mQuery);
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Validate query.
+ *
+ * <p>If invalid, throw IllegalArgumentException.
+ * @param query The part of service specific query.
+ */
+ private void validateQuery(String query) {
+ if (query == null) {
+ return;
+ }
+
+ int UNSIGNED_SHORT_MAX = 0xffff;
+ if (query.length()%2 == 1) {
+ throw new IllegalArgumentException(
+ "query size is invalid. query=" + query);
+ }
+ if (query.length()/2 > UNSIGNED_SHORT_MAX) {
+ throw new IllegalArgumentException(
+ "query size is too large. len=" + query.length());
+ }
+
+ // check whether query is hex string.
+ query = query.toLowerCase();
+ char[] chars = query.toCharArray();
+ for (char c: chars) {
+ if (!((c >= '0' && c <= '9') ||
+ (c >= 'a' && c <= 'f'))){
+ throw new IllegalArgumentException(
+ "query should be hex string. query=" + query);
+ }
+ }
+ }
+
+ /**
+ * Create service discovery request.
+ *
+ * <p>The created instance is set to framework by
+ * {@link WifiP2pManager#addLocalService}.
+ *
+ * @param serviceType service type.<br>
+ * e.g) {@link WifiP2pServiceInfo#SERVICE_TYPE_ALL},
+ * {@link WifiP2pServiceInfo#SERVICE_TYPE_WS_DISCOVERY},
+ * {@link WifiP2pServiceInfo#SERVICE_TYPE_VENDOR_SPECIFIC}.
+ * If you want to use UPnP or Bonjour, you create the request by
+ * {@link WifiP2pUpnpServiceRequest} or {@link WifiP2pBonjourServiceRequest}
+ *
+ * @param query hex string. if null, all specified services are requested.
+ * @return service discovery request.
+ */
+ public static WifiP2pServiceRequest newInstance(int serviceType, String query) {
+ return new WifiP2pServiceRequest(serviceType, query);
+ }
+
+ /**
+ * Create all service discovery request.
+ *
+ * <p>The created instance is set to framework by
+ * {@link WifiP2pManager#addLocalService}.
+ *
+ * @param serviceType service type.<br>
+ * e.g) {@link WifiP2pServiceInfo#SERVICE_TYPE_ALL},
+ * {@link WifiP2pServiceInfo#SERVICE_TYPE_WS_DISCOVERY},
+ * {@link WifiP2pServiceInfo#SERVICE_TYPE_VENDOR_SPECIFIC}.
+ * If you want to use UPnP or Bonjour, you create the request by
+ * {@link WifiP2pUpnpServiceRequest} or {@link WifiP2pBonjourServiceRequest}
+ *
+ * @return service discovery request.
+ */
+ public static WifiP2pServiceRequest newInstance(int serviceType) {
+ return new WifiP2pServiceRequest(serviceType, null);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof WifiP2pServiceRequest)) {
+ return false;
+ }
+
+ WifiP2pServiceRequest req = (WifiP2pServiceRequest)o;
+
+ /*
+ * Not compare transaction id.
+ * Transaction id may be changed on each service discovery operation.
+ */
+ if ((req.mServiceType != mServiceType) ||
+ (req.mLength != mLength)) {
+ return false;
+ }
+
+ if (req.mQuery == null && mQuery == null) {
+ return true;
+ } else if (req.mQuery != null) {
+ return req.mQuery.equals(mQuery);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + mServiceType;
+ result = 31 * result + mLength;
+ result = 31 * result + (mQuery == null ? 0 : mQuery.hashCode());
+ return result;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mServiceType);
+ dest.writeInt(mLength);
+ dest.writeInt(mTransId);
+ dest.writeString(mQuery);
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public static final Creator<WifiP2pServiceRequest> CREATOR =
+ new Creator<WifiP2pServiceRequest>() {
+ public WifiP2pServiceRequest createFromParcel(Parcel in) {
+ int servType = in.readInt();
+ int length = in.readInt();
+ int transId = in.readInt();
+ String query = in.readString();
+ return new WifiP2pServiceRequest(servType, length, transId, query);
+ }
+
+ public WifiP2pServiceRequest[] newArray(int size) {
+ return new WifiP2pServiceRequest[size];
+ }
+ };
+}
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceResponse.aidl b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceResponse.aidl
new file mode 100644
index 0000000..c81d1f9
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceResponse.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.p2p.servicediscovery;
+
+parcelable WifiP2pServiceResponse;
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceResponse.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceResponse.java
new file mode 100644
index 0000000..0855eae
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceResponse.java
@@ -0,0 +1,389 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.p2p.nsd;
+
+import android.net.wifi.p2p.WifiP2pDevice;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * The class for a response of service discovery.
+ *
+ * @hide
+ */
+public class WifiP2pServiceResponse implements Parcelable {
+
+ private static int MAX_BUF_SIZE = 1024;
+
+ /**
+ * Service type. It's defined in table63 in Wi-Fi Direct specification.
+ */
+ protected int mServiceType;
+
+ /**
+ * Status code of service discovery response.
+ * It's defined in table65 in Wi-Fi Direct specification.
+ * @see Status
+ */
+ protected int mStatus;
+
+ /**
+ * Service transaction ID.
+ * This is a nonzero value used to match the service request/response TLVs.
+ */
+ protected int mTransId;
+
+ /**
+ * Source device.
+ */
+ protected WifiP2pDevice mDevice;
+
+ /**
+ * Service discovery response data based on the requested on
+ * the service protocol type. The protocol format depends on the service type.
+ */
+ protected byte[] mData;
+
+
+ /**
+ * The status code of service discovery response.
+ * Currently 4 status codes are defined and the status codes from 4 to 255
+ * are reserved.
+ *
+ * See Wi-Fi Direct specification for the detail.
+ */
+ public static class Status {
+ /** success */
+ public static final int SUCCESS = 0;
+
+ /** the service protocol type is not available */
+ public static final int SERVICE_PROTOCOL_NOT_AVAILABLE = 1;
+
+ /** the requested information is not available */
+ public static final int REQUESTED_INFORMATION_NOT_AVAILABLE = 2;
+
+ /** bad request */
+ public static final int BAD_REQUEST = 3;
+
+ /** @hide */
+ public static String toString(int status) {
+ switch(status) {
+ case SUCCESS:
+ return "SUCCESS";
+ case SERVICE_PROTOCOL_NOT_AVAILABLE:
+ return "SERVICE_PROTOCOL_NOT_AVAILABLE";
+ case REQUESTED_INFORMATION_NOT_AVAILABLE:
+ return "REQUESTED_INFORMATION_NOT_AVAILABLE";
+ case BAD_REQUEST:
+ return "BAD_REQUEST";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ /** not used */
+ private Status() {}
+ }
+
+ /**
+ * Hidden constructor. This is only used in framework.
+ *
+ * @param serviceType service discovery type.
+ * @param status status code.
+ * @param transId transaction id.
+ * @param device source device.
+ * @param data query data.
+ */
+ protected WifiP2pServiceResponse(int serviceType, int status, int transId,
+ WifiP2pDevice device, byte[] data) {
+ mServiceType = serviceType;
+ mStatus = status;
+ mTransId = transId;
+ mDevice = device;
+ mData = data;
+ }
+
+ /**
+ * Return the service type of service discovery response.
+ *
+ * @return service discovery type.<br>
+ * e.g) {@link WifiP2pServiceInfo#SERVICE_TYPE_BONJOUR}
+ */
+ public int getServiceType() {
+ return mServiceType;
+ }
+
+ /**
+ * Return the status code of service discovery response.
+ *
+ * @return status code.
+ * @see Status
+ */
+ public int getStatus() {
+ return mStatus;
+ }
+
+ /**
+ * Return the transaction id of service discovery response.
+ *
+ * @return transaction id.
+ * @hide
+ */
+ public int getTransactionId() {
+ return mTransId;
+ }
+
+ /**
+ * Return response data.
+ *
+ * <pre>Data format depends on service type
+ *
+ * @return a query or response data.
+ */
+ public byte[] getRawData() {
+ return mData;
+ }
+
+ /**
+ * Returns the source device of service discovery response.
+ *
+ * <pre>This is valid only when service discovery response.
+ *
+ * @return the source device of service discovery response.
+ */
+ public WifiP2pDevice getSrcDevice() {
+ return mDevice;
+ }
+
+ /** @hide */
+ public void setSrcDevice(WifiP2pDevice dev) {
+ if (dev == null) return;
+ this.mDevice = dev;
+ }
+
+
+ /**
+ * Create the list of WifiP2pServiceResponse instance from supplicant event.
+ *
+ * <pre>The format is as follows.
+ * P2P-SERV-DISC-RESP &lt;address&gt; &lt;update indicator&gt; &lt;response data&gt;
+ * e.g) P2P-SERV-DISC-RESP 02:03:7f:11:62:da 1 0300000101
+ *
+ * @param supplicantEvent wpa_supplicant event string.
+ * @return if parse failed, return null
+ * @hide
+ */
+ public static List<WifiP2pServiceResponse> newInstance(String supplicantEvent) {
+
+ List<WifiP2pServiceResponse> respList = new ArrayList<WifiP2pServiceResponse>();
+ String[] args = supplicantEvent.split(" ");
+ if (args.length != 4) {
+ return null;
+ }
+ WifiP2pDevice dev = new WifiP2pDevice();
+ String srcAddr = args[1];
+ dev.deviceAddress = srcAddr;
+ //String updateIndicator = args[2];//not used.
+ byte[] bin = hexStr2Bin(args[3]);
+ if (bin == null) {
+ return null;
+ }
+
+ DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bin));
+ try {
+ while (dis.available() > 0) {
+ /*
+ * Service discovery header is as follows.
+ * ______________________________________________________________
+ * | Length(2byte) | Type(1byte) | TransId(1byte)}|
+ * ______________________________________________________________
+ * | status(1byte) | vendor specific(variable) |
+ */
+ // The length equals to 3 plus the number of octets in the vendor
+ // specific content field. And this is little endian.
+ int length = ((dis.readByte() & 0xff) +
+ ((dis.readByte() & 0xff) << 8)) - 3;
+ int type = dis.readUnsignedByte();
+ byte transId = dis.readByte();
+ int status = dis.readUnsignedByte();
+ if (length < 0) {
+ return null;
+ }
+ if (length == 0) {
+ if (status == Status.SUCCESS) {
+ respList.add(new WifiP2pServiceResponse(type, status,
+ transId, dev, null));
+ }
+ continue;
+ }
+ if (length > MAX_BUF_SIZE) {
+ dis.skip(length);
+ continue;
+ }
+ byte[] data = new byte[length];
+ dis.readFully(data);
+
+ WifiP2pServiceResponse resp;
+ if (type == WifiP2pServiceInfo.SERVICE_TYPE_BONJOUR) {
+ resp = WifiP2pBonjourServiceResponse.newInstance(status,
+ transId, dev, data);
+ } else if (type == WifiP2pServiceInfo.SERVICE_TYPE_UPNP) {
+ resp = WifiP2pUpnpServiceResponse.newInstance(status,
+ transId, dev, data);
+ } else {
+ resp = new WifiP2pServiceResponse(type, status, transId, dev, data);
+ }
+ if (resp != null && resp.getStatus() == Status.SUCCESS) {
+ respList.add(resp);
+ }
+ }
+ return respList;
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ if (respList.size() > 0) {
+ return respList;
+ }
+ return null;
+ }
+
+ /**
+ * Converts hex string to byte array.
+ *
+ * @param hex hex string. if invalid, return null.
+ * @return binary data.
+ */
+ private static byte[] hexStr2Bin(String hex) {
+ int sz = hex.length()/2;
+ byte[] b = new byte[hex.length()/2];
+
+ for (int i=0;i<sz;i++) {
+ try {
+ b[i] = (byte)Integer.parseInt(hex.substring(i*2, i*2+2), 16);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+ return b;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sbuf = new StringBuffer();
+ sbuf.append("serviceType:").append(mServiceType);
+ sbuf.append(" status:").append(Status.toString(mStatus));
+ sbuf.append(" srcAddr:").append(mDevice.deviceAddress);
+ sbuf.append(" data:").append(mData);
+ return sbuf.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof WifiP2pServiceResponse)) {
+ return false;
+ }
+
+ WifiP2pServiceResponse req = (WifiP2pServiceResponse)o;
+
+ return (req.mServiceType == mServiceType) &&
+ (req.mStatus == mStatus) &&
+ equals(req.mDevice.deviceAddress, mDevice.deviceAddress) &&
+ Arrays.equals(req.mData, mData);
+ }
+
+ private boolean equals(Object a, Object b) {
+ if (a == null && b == null) {
+ return true;
+ } else if (a != null) {
+ return a.equals(b);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + mServiceType;
+ result = 31 * result + mStatus;
+ result = 31 * result + mTransId;
+ result = 31 * result + (mDevice.deviceAddress == null ?
+ 0 : mDevice.deviceAddress.hashCode());
+ result = 31 * result + (mData == null ? 0 : mData.hashCode());
+ return result;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mServiceType);
+ dest.writeInt(mStatus);
+ dest.writeInt(mTransId);
+ dest.writeParcelable(mDevice, flags);
+ if (mData == null || mData.length == 0) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(mData.length);
+ dest.writeByteArray(mData);
+ }
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public static final Creator<WifiP2pServiceResponse> CREATOR =
+ new Creator<WifiP2pServiceResponse>() {
+ public WifiP2pServiceResponse createFromParcel(Parcel in) {
+
+ int type = in.readInt();
+ int status = in.readInt();
+ int transId = in.readInt();
+ WifiP2pDevice dev = (WifiP2pDevice)in.readParcelable(null);
+ int len = in.readInt();
+ byte[] data = null;
+ if (len > 0) {
+ data = new byte[len];
+ in.readByteArray(data);
+ }
+ if (type == WifiP2pServiceInfo.SERVICE_TYPE_BONJOUR) {
+ return WifiP2pBonjourServiceResponse.newInstance(status,
+ transId, dev, data);
+ } else if (type == WifiP2pServiceInfo.SERVICE_TYPE_UPNP) {
+ return WifiP2pUpnpServiceResponse.newInstance(status,
+ transId, dev, data);
+ }
+ return new WifiP2pServiceResponse(type, status, transId, dev, data);
+ }
+
+ public WifiP2pServiceResponse[] newArray(int size) {
+ return new WifiP2pServiceResponse[size];
+ }
+ };
+}
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceInfo.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceInfo.java
new file mode 100644
index 0000000..4d40e81
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceInfo.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.p2p.nsd;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * The class for UPnP service information.
+ * @hide
+ */
+public class WifiP2pUpnpServiceInfo extends WifiP2pServiceInfo {
+
+ /**
+ * UPnP version 1.0.
+ *
+ * <pre>Query Version should always be set to 0x10 if the query values are
+ * compatible with UPnP Device Architecture 1.0.
+ * @hide
+ */
+ public static final int VERSION_1_0 = 0x10;
+
+ /**
+ * This constructor is only used in newInstance().
+ *
+ * @param queryList
+ */
+ private WifiP2pUpnpServiceInfo(List<String> queryList) {
+ super(queryList);
+ }
+
+ /**
+ * Create UPnP service information object.
+ *
+ * @param uuid a string representation of this UUID in the following format,
+ * as per <a href="http://www.ietf.org/rfc/rfc4122.txt">RFC 4122</a>.<br>
+ * e.g) 6859dede-8574-59ab-9332-123456789012
+ * @param device a string representation of this device in the following format,
+ * as per
+ * <a href="http://www.upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf">
+ * UPnP Device Architecture1.1</a><br>
+ * e.g) urn:schemas-upnp-org:device:MediaServer:1
+ * @param services a string representation of this service in the following format,
+ * as per
+ * <a href="http://www.upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf">
+ * UPnP Device Architecture1.1</a><br>
+ * e.g) urn:schemas-upnp-org:service:ContentDirectory:1
+ * @return UPnP service information object.
+ */
+ public static WifiP2pUpnpServiceInfo newInstance(String uuid,
+ String device, List<String> services) {
+ if (uuid == null || device == null) {
+ throw new IllegalArgumentException("uuid or device cannnot be null");
+ }
+ UUID.fromString(uuid);
+
+ ArrayList<String> info = new ArrayList<String>();
+
+ info.add(createSupplicantQuery(uuid, null));
+ info.add(createSupplicantQuery(uuid, "upnp:rootdevice"));
+ info.add(createSupplicantQuery(uuid, device));
+ if (services != null) {
+ for (String service:services) {
+ info.add(createSupplicantQuery(uuid, service));
+ }
+ }
+
+ return new WifiP2pUpnpServiceInfo(info);
+ }
+
+ /**
+ * Create wpa_supplicant service query for upnp.
+ *
+ * @param uuid
+ * @param data
+ * @return wpa_supplicant service query for upnp
+ */
+ private static String createSupplicantQuery(String uuid, String data) {
+ StringBuffer sb = new StringBuffer();
+ sb.append("upnp ");
+ sb.append(String.format("%02x ", VERSION_1_0));
+ sb.append("uuid:");
+ sb.append(uuid);
+ if (data != null) {
+ sb.append("::");
+ sb.append(data);
+ }
+ return sb.toString();
+ }
+}
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceRequest.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceRequest.java
new file mode 100644
index 0000000..b97637a
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceRequest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.p2p.nsd;
+
+/**
+ * The class for a request of upnp service discovery.
+ * @hide
+ */
+public class WifiP2pUpnpServiceRequest extends WifiP2pServiceRequest {
+
+ /**
+ * This constructor is only used in newInstance().
+ *
+ * @param query The part of service specific query.
+ * @hide
+ */
+ protected WifiP2pUpnpServiceRequest(String query) {
+ super(WifiP2pServiceInfo.SERVICE_TYPE_UPNP, query);
+ }
+
+ /**
+ * This constructor is only used in newInstance().
+ * @hide
+ */
+ protected WifiP2pUpnpServiceRequest() {
+ super(WifiP2pServiceInfo.SERVICE_TYPE_UPNP, null);
+ }
+
+ /**
+ * Create a service discovery request to search all UPnP services.
+ *
+ * @return service request for UPnP.
+ */
+ public static WifiP2pUpnpServiceRequest newInstance() {
+ return new WifiP2pUpnpServiceRequest();
+ }
+ /**
+ * Create a service discovery request to search specified UPnP services.
+ *
+ * @param st ssdp search target. Cannot be null.<br>
+ * e.g ) <br>
+ * <ul>
+ * <li>"ssdp:all"
+ * <li>"upnp:rootdevice"
+ * <li>"urn:schemas-upnp-org:device:MediaServer:2"
+ * <li>"urn:schemas-upnp-org:service:ContentDirectory:2"
+ * <li>"uuid:6859dede-8574-59ab-9332-123456789012"
+ * </ul>
+ * @return service request for UPnP.
+ */
+ public static WifiP2pUpnpServiceRequest newInstance(String st) {
+ if (st == null) {
+ throw new IllegalArgumentException("search target cannot be null");
+ }
+ StringBuffer sb = new StringBuffer();
+ sb.append(String.format("%02x", WifiP2pUpnpServiceInfo.VERSION_1_0));
+ sb.append(WifiP2pServiceInfo.bin2HexStr(st.getBytes()));
+ return new WifiP2pUpnpServiceRequest(sb.toString());
+ }
+}
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceResponse.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceResponse.java
new file mode 100644
index 0000000..ab95af6
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceResponse.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.p2p.nsd;
+
+import android.net.wifi.p2p.WifiP2pDevice;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A class for a response of upnp service discovery.
+ *
+ * @hide
+ */
+public class WifiP2pUpnpServiceResponse extends WifiP2pServiceResponse {
+
+ /**
+ * UPnP version. should be {@link WifiP2pUpnpServiceInfo#VERSION_1_0}
+ */
+ private int mVersion;
+
+ /**
+ * The list of Unique Service Name.
+ * e.g)
+ *{"uuid:6859dede-8574-59ab-9332-123456789012",
+ *"uuid:6859dede-8574-59ab-9332-123456789012::upnp:rootdevice"}
+ */
+ private List<String> mUniqueServiceNames;
+
+ /**
+ * Return UPnP version number.
+ *
+ * @return version number.
+ * @see WifiP2pUpnpServiceInfo#VERSION_1_0
+ */
+ public int getVersion() {
+ return mVersion;
+ }
+
+ /**
+ * Return Unique Service Name strings.
+ *
+ * @return Unique Service Name.<br>
+ * e.g ) <br>
+ * <ul>
+ * <li>"uuid:6859dede-8574-59ab-9332-123456789012"
+ * <li>"uuid:6859dede-8574-59ab-9332-123456789012::upnp:rootdevice"
+ * <li>"uuid:6859dede-8574-59ab-9332-123456789012::urn:schemas-upnp-org:device:
+ * MediaServer:2"
+ * <li>"uuid:6859dede-8574-59ab-9332-123456789012::urn:schemas-upnp-org:service:
+ * ContentDirectory:2"
+ * </ul>
+ */
+ public List<String> getUniqueServiceNames() {
+ return mUniqueServiceNames;
+ }
+
+ /**
+ * hidden constructor.
+ *
+ * @param status status code
+ * @param transId transaction id
+ * @param dev source device
+ * @param data UPnP response data.
+ */
+ protected WifiP2pUpnpServiceResponse(int status,
+ int transId, WifiP2pDevice dev, byte[] data) {
+ super(WifiP2pServiceInfo.SERVICE_TYPE_UPNP,
+ status, transId, dev, data);
+ if (!parse()) {
+ throw new IllegalArgumentException("Malformed upnp service response");
+ }
+ }
+
+ /**
+ * Parse UPnP service discovery response
+ *
+ * @return {@code true} if the operation succeeded
+ */
+ private boolean parse() {
+ /*
+ * The data format is as follows.
+ *
+ * ______________________________________________________
+ * | Version (1) | USN (Variable) |
+ */
+ if (mData == null) {
+ // the empty is OK.
+ return true;
+ }
+
+ if (mData.length < 1) {
+ return false;
+ }
+
+ mVersion = mData[0] & 0xff;
+ String[] names = new String(mData, 1, mData.length-1).split(",");
+ mUniqueServiceNames = new ArrayList<String>();
+ for (String name : names) {
+ mUniqueServiceNames.add(name);
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sbuf = new StringBuffer();
+ sbuf.append("serviceType:UPnP(").append(mServiceType).append(")");
+ sbuf.append(" status:").append(Status.toString(mStatus));
+ sbuf.append(" srcAddr:").append(mDevice.deviceAddress);
+ sbuf.append(" version:").append(String.format("%02x", mVersion));
+ if (mUniqueServiceNames != null) {
+ for (String name : mUniqueServiceNames) {
+ sbuf.append(" usn:").append(name);
+ }
+ }
+ return sbuf.toString();
+ }
+
+ /**
+ * Create upnp service response.
+ *
+ * <pre>This is only used in{@link WifiP2pServiceResponse}
+ *
+ * @param status status code.
+ * @param transId transaction id.
+ * @param device source device.
+ * @param data UPnP response data.
+ * @return UPnP service response data.
+ * @hide
+ */
+ static WifiP2pUpnpServiceResponse newInstance(int status,
+ int transId, WifiP2pDevice device, byte[] data) {
+ if (status != WifiP2pServiceResponse.Status.SUCCESS) {
+ return new WifiP2pUpnpServiceResponse(status, transId, device, null);
+ }
+
+ try {
+ return new WifiP2pUpnpServiceResponse(status, transId, device, data);
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+}