diff options
author | Irfan Sheriff <isheriff@google.com> | 2012-04-04 16:22:21 -0700 |
---|---|---|
committer | Irfan Sheriff <isheriff@google.com> | 2012-04-12 17:50:29 -0700 |
commit | 21ba8153325e010224c6bc75a0acdc98b6ca82e8 (patch) | |
tree | 9d1e982ef4f02a31be76a8c1220b0ce7d3f0b541 /wifi/java/android/net | |
parent | 63c115c4aa2158ea18338a9fb2c2619755958ddc (diff) | |
download | frameworks_base-21ba8153325e010224c6bc75a0acdc98b6ca82e8.zip frameworks_base-21ba8153325e010224c6bc75a0acdc98b6ca82e8.tar.gz frameworks_base-21ba8153325e010224c6bc75a0acdc98b6ca82e8.tar.bz2 |
Pre-association service discovery support
Add UPnp, Bonjour and vendor specific support for discovering services on
Wi-Fi direct before establishing a connection.
Change-Id: I1c1f3427180abdc80a4e682e713adc7f0326c5ef
Signed-off-by: Yoshihiko Ikenaga <yoshihiko.ikenaga@jp.sony.com>
Diffstat (limited to 'wifi/java/android/net')
17 files changed, 2909 insertions, 36 deletions
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 c11d082..ca593c1 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; @@ -602,4 +603,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 3d3a746..d3caf90 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 @@ -147,6 +154,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"}; @@ -310,6 +327,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); @@ -326,6 +347,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; @@ -367,6 +414,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); @@ -383,6 +434,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; } @@ -492,6 +569,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); @@ -511,6 +590,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; @@ -521,7 +614,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; @@ -1262,6 +1403,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) { @@ -1310,5 +1457,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 <address> <update indicator> <response data> + * 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; + } +} |