/*********************************************************************************
* TotalCross Software Development Kit *
* Copyright (C) 2000-2012 SuperWaba Ltda. *
* All Rights Reserved *
* *
* This library and virtual machine is distributed in the hope that it will *
* be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
* *
* This file is covered by the GNU LESSER GENERAL PUBLIC LICENSE VERSION 3.0 *
* A copy of this license is located in file license.txt at the root of this *
* SDK or can be downloaded here: *
* http://www.gnu.org/licenses/lgpl-3.0.txt *
* *
*********************************************************************************/
package totalcross.io.device.bluetooth;
import totalcross.io.IOException;
import totalcross.sys.Convert;
import totalcross.sys.Vm;
import totalcross.util.*;
/**
* The <code>DiscoveryAgent</code> class provides methods to perform device and service discovery. A local device must
* have only one <code>DiscoveryAgent</code> object. This object must be retrieved by a call to
* <code>getDiscoveryAgent()</code> on the <code>LocalDevice</code> object.
*
* @since TotalCross 1.15
*/
public class DiscoveryAgent
{
/**
* Used with the <code>retrieveDevices()</code> method to return those devices that were found via a previous
* inquiry. If no inquiries have been started, this will cause the method to return <code>null</code>.
* <p>
* The value of <code>CACHED</code> is 0x00 (0).
* </p>
*/
public static final int CACHED = 0x00;
/**
* The inquiry access code for General/Unlimited Inquiry Access Code (GIAC). This is used to specify the type of
* inquiry to complete or respond to.
* <p>
* The value of <code>GIAC</code> is 0x9E8B33 (10390323). This value is defined in the Bluetooth Assigned Numbers
* document.
* </p>
*/
public static final int GIAC = 0x9E8B33;
/**
* The inquiry access code for Limited Dedicated Inquiry Access Code (LIAC). This is used to specify the type of
* inquiry to complete or respond to.
* <p>
* The value of <code>LIAC</code> is 0x9E8B00 (10390272). This value is defined in the Bluetooth Assigned Numbers
* document.
* </p>
*/
public static final int LIAC = 0x9E8B00;
/**
* Takes the device out of discoverable mode.
* <p>
* The value of <code>NOT_DISCOVERABLE</code> is 0x00 (0).
* </p>
*/
public static final int NOT_DISCOVERABLE = 0x00;
/**
* Used with the <code>retrieveDevices()</code> method to return those devices that are defined to be pre-known
* devices. Pre-known devices are specified in the BCC. These are devices that are specified by the user as devices
* with which the local device will frequently communicate.
* <p>
* The value of <code>PREKNOWN</code> is 0x01 (1).
* </p>
*/
public static final int PREKNOWN = 0x01;
/** Max attribute value */
private static int maxAttrValue = (2 << 16) - 1;
DiscoveryAgent()
{
}
/**
* Removes the device from inquiry mode.
* <p>
* An <code>inquiryCompleted()</code> event will occur with a type of <code>INQUIRY_TERMINATED</code> as a result of
* calling this method. After receiving this event, no further <code>deviceDiscovered()</code> events will occur as a
* result of this inquiry.
* </p>
* <p>
* This method will only cancel the inquiry if the <code>listener</code> provided is the listener that started the
* inquiry.
* </p>
* Not implemented on Android.
*
* @param listener
* the listener that is receiving inquiry events
* @return <code>true</code> if the inquiry was canceled; otherwise <code>false</code> if the inquiry was not
* canceled or if the inquiry was not started using <code>listener</code>
* @throws NullPointerException
* if <code>listener</code> is <code>null</code>
* @since TotalCross 1.2
*/
public boolean cancelInquiry(DiscoveryListener listener)
{
if (listener == null)
throw new NullPointerException();
return false; // // not supported on JDK, always returns false.
}
/**
* Cancels the service search transaction that has the specified transaction ID. The ID was assigned to the
* transaction by the method <code>searchServices()</code>. A <code>serviceSearchCompleted()</code> event with a
* discovery type of <code>SERVICE_SEARCH_TERMINATED</code> will occur when this method is called. After receiving
* this event, no further <code>servicesDiscovered()</code> events will occur as a result of this search.
*
* Not implemented on Android and Windows CE.
*
* @param transID
* the ID of the service search transaction to cancel; returned by <code>searchServices()</code>
* @return <code>true</code> if the service search transaction is terminated, else <code>false</code> if
* <code>transID</code> does not represent an active service search transaction
* @since TotalCross 1.2
*/
public boolean cancelServiceSearch(int transID)
{
return true; // not supported on JDK, always returns false.
}
/**
* Returns an array of Bluetooth devices that have either been found by the local device during previous inquiry
* requests or been specified as a pre-known device depending on the argument. The list of previously found devices
* is maintained by the implementation of this API. (In other words, maintenance of the list of previously found
* devices is an implementation detail.) A device can be set as a pre-known device in the Bluetooth Control Center.
*
* Works on Android. Pass CACHED to list the unpaired devices, and PREKNOWN to list the paired devices. Note that
* paired devices may not be at reach at the moment.
*
* @param option
* <code>option</code> - <code>CACHED</code> if previously found devices should be returned;
* <code>PREKNOWN</code> if pre-known devices should be returned
* @return an array containing the Bluetooth devices that were previously found if <code>option</code> is
* <code>CACHED</code>; an array of devices that are pre-known devices if <code>option</code> is
* <code>PREKNOWN</code>; <code>null</code> if no devices meet the criteria
* @throws IllegalArgumentException
* if <code>option</code> is not <code>CACHED</code> or <code>PREKNOWN</code>
*/
public RemoteDevice[] retrieveDevices(int option)
{
if (option != CACHED && option != PREKNOWN)
throw new IllegalArgumentException();
return null; // not supported on JDK, always returns null.
}
/**
* Searches for services on a remote Bluetooth device that have all the UUIDs specified in <code>uuidSet</code>. Once
* the service is found, the attributes specified in <code>attrSet</code> and the default attributes are retrieved.
* The default attributes are ServiceRecordHandle (0x0000), ServiceClassIDList (0x0001), ServiceRecordState (0x0002),
* ServiceID (0x0003), and ProtocolDescriptorList (0x0004). If <code>attrSet</code> is <code>null</code> then only the
* default attributes will be retrieved. <code>attrSet</code> does not have to be sorted in increasing order, but
* must only contain values in the range [0 - (2<sup>16</sup>-1)].
*
* Not implemented on Android.
*
* @param attrSet
* indicates the attributes whose values will be retrieved on services which have the UUIDs specified in
* <code>uuidSet</code>
* @param uuidSet
* the set of UUIDs that are being searched for; all services returned will contain all the UUIDs specified
* here
* @param btDev
* the remote Bluetooth device to search for services on
* @param discListener
* the object that will receive events when services are discovered
* @return the transaction ID of the service search, which is a positive number.
* @throws NullPointerException
* if <code>uuidSet</code>, <code>btDev</code>, or <code>discListener</code> is <code>null</code>; if an
* element in <code>uuidSet</code> array is <code>null</code>
* @throws IllegalArgumentException
* if <code>attrSet</code> has an illegal service attribute ID or exceeds the property
* <code>bluetooth.sd.attr.retrievable.max</code> defined in the class <code>LocalDevice</code>; if
* <code>attrSet</code> or <code>uuidSet</code> is of length 0; if <code>attrSet</code> or
* <code>uuidSet</code> contains duplicates
* @throws IOException
* if the number of concurrent service search transactions exceeds the limit specified by the
* <code>bluetooth.sd.trans.max</code> property obtained from the class <code>LocalDevice</code> or the
* system is unable to start one due to current conditions
*/
public int searchServices(int[] attrSet, UUID[] uuidSet, RemoteDevice btDev, DiscoveryListener discListener) throws IOException
{
if (uuidSet == null || btDev == null || discListener == null)
throw new NullPointerException();
// arrays cannot be empty
int attrSetLen = attrSet == null ? 0 : attrSet.length;
int uuidSetLen = uuidSet.length;
if ((attrSet != null && attrSetLen == 0) || uuidSetLen == 0)
throw new IllegalArgumentException();
if (attrSet == null)
attrSet = new int[] { 0, 1, 2, 3, 4 };
else
{
IntVector attrSetVector = new IntVector(attrSet);
attrSetVector.qsort();
if (attrSetVector.items[0] < 0 || attrSetVector.items[0] > maxAttrValue)
throw new IllegalArgumentException("attrSet values must be in the range [0 - (2^16 - 1)]");
for (int i = attrSetLen - 1; i > 0; i--)
{
// attrSet cannot have duplicated values
if (attrSetVector.items[i] == attrSetVector.items[i - 1])
throw new IllegalArgumentException("Duplicated value in attrSet");
// values must be in range
if (attrSetVector.items[i] < 0 || attrSetVector.items[i] > maxAttrValue)
throw new IllegalArgumentException("attrSet values must be in the range [0 - (2^16 - 1)]");
}
// not pretty, but this way we avoid some extra method calls and a short loop.
if (attrSetVector.items[0] != 0)
attrSetVector.addElement(0);
if (attrSetVector.indexOf(1, 0) == -1)
attrSetVector.addElement(1);
if (attrSetVector.indexOf(2, 0) == -1)
attrSetVector.addElement(2);
if (attrSetVector.indexOf(3, 0) == -1)
attrSetVector.addElement(3);
if (attrSetVector.indexOf(4, 0) == -1)
attrSetVector.addElement(4);
attrSetVector.qsort();
attrSet = attrSetVector.toIntArray(); // final array, after adding the default attributes and sorting.
}
// uuidSet cannot have duplicated values
UUID[] uuidSet2 = new UUID[uuidSetLen];
Vm.arrayCopy(uuidSet, 0, uuidSet2, 0, uuidSetLen);
Convert.qsort(uuidSet2, 0, uuidSetLen - 1, Convert.SORT_OBJECT, true); // we don't have to check uuidSet for null values, qsort already does that;
for (int i = uuidSetLen - 1; i > 0; i--)
if (uuidSet2[i].equals(uuidSet2[i - 1]))
throw new IllegalArgumentException();
throw new IOException("Bluetooth API is not supported on JDK"); // not supported on JDK, always throws IOException.
}
/**
* Attempts to locate a service that contains <code>uuid</code> in the ServiceClassIDList of its service record. This
* method will return a string that may be used in <code>Connector.open()</code> to establish a connection to the
* service. How the service is selected if there are multiple services with <code>uuid</code> and which devices to
* search is implementation dependent.
*
* Not implemented on Android.
*
* @param uuid
* the UUID to search for in the ServiceClassIDList
* @param security
* specifies the security requirements for a connection to this service; must be one of
* <code>ServiceRecord.NOAUTHENTICATE_NOENCRYPT</code>, <code>ServiceRecord.AUTHENTICATE_NOENCRYPT</code>,
* or <code>ServiceRecord.AUTHENTICATE_ENCRYPT</code>
* @param master
* determines if this client must be the master of the connection; <code>true</code> if the client must be
* the master; <code>false</code> if the client can be the master or the slave
* @return the connection string used to connect to the service with a UUID of <code>uuid</code>; or
* <code>null</code> if no service could be found with a UUID of <code>uuid</code> in the ServiceClassIDList
* @throws NullPointerException
* if <code>uuid</code> is <code>null</code>
* @throws IllegalArgumentException
* if <code>security</code> is not <code>ServiceRecord.NOAUTHENTICATE_NOENCRYPT</code>,
* <code>ServiceRecord.AUTHENTICATE_NOENCRYPT</code>, or <code>ServiceRecord.AUTHENTICATE_ENCRYPT</code>
* @throws IOException
* if the Bluetooth system cannot start the request due to the current state of the Bluetooth system
* @since TotalCross 1.2
*/
public String selectService(UUID uuid, int security, boolean master) throws IOException
{
if (uuid == null)
throw new NullPointerException();
if (security != ServiceRecord.NOAUTHENTICATE_NOENCRYPT && security != ServiceRecord.AUTHENTICATE_NOENCRYPT
&& security != ServiceRecord.AUTHENTICATE_ENCRYPT)
throw new IllegalArgumentException();
throw new IOException("Bluetooth API is not supported on JDK"); // not supported on JDK, always throws IOException.
}
/**
* Places the device into inquiry mode. The length of the inquiry is implementation dependent. This method will
* search for devices with the specified inquiry access code. Devices that responded to the inquiry are returned to
* the application via the method <code>deviceDiscovered()</code> of the interface <code>DiscoveryListener</code>.
* The <code>cancelInquiry()</code> method is called to stop the inquiry.
*
* Not implemented on Android.
*
* @param accessCode
* the type of inquiry to complete
* @param listener
* the event listener that will receive device discovery events
* @return <code>true</code> if the inquiry was started; <code>false</code> if the inquiry was not started because
* the <code>accessCode</code> is not supported
* @throws IllegalArgumentException
* if the access code provided is not <code>LIAC</code>, <code>GIAC</code>, or in the range 0x9E8B00 to
* 0x9E8B3F
* @throws NullPointerException
* if <code>listener</code> is <code>null</code>
* @throws IOException
* if the Bluetooth device does not allow an inquiry to be started due to other operations that are being
* performed by the device
* @since TotalCross 1.2
*/
public boolean startInquiry(int accessCode, DiscoveryListener listener) throws IOException
{
if (accessCode != LIAC || accessCode != GIAC || accessCode < 0x9E8B00 || accessCode > 0x9E8B3F)
throw new IllegalArgumentException();
if (listener == null)
throw new NullPointerException();
throw new IOException("Bluetooth API is not supported on JDK"); // not supported on JDK, always throws IOException.
}
}