Merge "BLE: Add service solicitation uuid feature in scan filter" am: 13d6540956 am: 253403081a
am: 76bb514c78
Change-Id: I303fbf653865158f31de98b7b0d640c92ba896f6
diff --git a/api/current.txt b/api/current.txt
index 3a523f6..4d692ee 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8713,6 +8713,8 @@
method public byte[] getServiceData();
method public byte[] getServiceDataMask();
method public android.os.ParcelUuid getServiceDataUuid();
+ method public android.os.ParcelUuid getServiceSolicitationUuid();
+ method public android.os.ParcelUuid getServiceSolicitationUuidMask();
method public android.os.ParcelUuid getServiceUuid();
method public android.os.ParcelUuid getServiceUuidMask();
method public boolean matches(android.bluetooth.le.ScanResult);
@@ -8729,6 +8731,8 @@
method public android.bluetooth.le.ScanFilter.Builder setManufacturerData(int, byte[], byte[]);
method public android.bluetooth.le.ScanFilter.Builder setServiceData(android.os.ParcelUuid, byte[]);
method public android.bluetooth.le.ScanFilter.Builder setServiceData(android.os.ParcelUuid, byte[], byte[]);
+ method public android.bluetooth.le.ScanFilter.Builder setServiceSolicitationUuid(android.os.ParcelUuid);
+ method public android.bluetooth.le.ScanFilter.Builder setServiceSolicitationUuid(android.os.ParcelUuid, android.os.ParcelUuid);
method public android.bluetooth.le.ScanFilter.Builder setServiceUuid(android.os.ParcelUuid);
method public android.bluetooth.le.ScanFilter.Builder setServiceUuid(android.os.ParcelUuid, android.os.ParcelUuid);
}
@@ -8741,6 +8745,7 @@
method public byte[] getManufacturerSpecificData(int);
method public java.util.Map<android.os.ParcelUuid, byte[]> getServiceData();
method public byte[] getServiceData(android.os.ParcelUuid);
+ method public java.util.List<android.os.ParcelUuid> getServiceSolicitationUuids();
method public java.util.List<android.os.ParcelUuid> getServiceUuids();
method public int getTxPowerLevel();
}
diff --git a/core/java/android/bluetooth/le/ScanFilter.java b/core/java/android/bluetooth/le/ScanFilter.java
index c3fae7d..c5d435b 100644
--- a/core/java/android/bluetooth/le/ScanFilter.java
+++ b/core/java/android/bluetooth/le/ScanFilter.java
@@ -58,6 +58,11 @@
private final ParcelUuid mServiceUuidMask;
@Nullable
+ private final ParcelUuid mServiceSolicitationUuid;
+ @Nullable
+ private final ParcelUuid mServiceSolicitationUuidMask;
+
+ @Nullable
private final ParcelUuid mServiceDataUuid;
@Nullable
private final byte[] mServiceData;
@@ -75,12 +80,15 @@
private ScanFilter(String name, String deviceAddress, ParcelUuid uuid,
- ParcelUuid uuidMask, ParcelUuid serviceDataUuid,
+ ParcelUuid uuidMask, ParcelUuid solicitationUuid,
+ ParcelUuid solicitationUuidMask, ParcelUuid serviceDataUuid,
byte[] serviceData, byte[] serviceDataMask,
int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask) {
mDeviceName = name;
mServiceUuid = uuid;
mServiceUuidMask = uuidMask;
+ mServiceSolicitationUuid = solicitationUuid;
+ mServiceSolicitationUuidMask = solicitationUuidMask;
mDeviceAddress = deviceAddress;
mServiceDataUuid = serviceDataUuid;
mServiceData = serviceData;
@@ -113,6 +121,14 @@
dest.writeParcelable(mServiceUuidMask, flags);
}
}
+ dest.writeInt(mServiceSolicitationUuid == null ? 0 : 1);
+ if (mServiceSolicitationUuid != null) {
+ dest.writeParcelable(mServiceSolicitationUuid, flags);
+ dest.writeInt(mServiceSolicitationUuidMask == null ? 0 : 1);
+ if (mServiceSolicitationUuidMask != null) {
+ dest.writeParcelable(mServiceSolicitationUuidMask, flags);
+ }
+ }
dest.writeInt(mServiceDataUuid == null ? 0 : 1);
if (mServiceDataUuid != null) {
dest.writeParcelable(mServiceDataUuid, flags);
@@ -172,6 +188,17 @@
}
}
if (in.readInt() == 1) {
+ ParcelUuid solicitationUuid = in.readParcelable(
+ ParcelUuid.class.getClassLoader());
+ builder.setServiceSolicitationUuid(solicitationUuid);
+ if (in.readInt() == 1) {
+ ParcelUuid solicitationUuidMask = in.readParcelable(
+ ParcelUuid.class.getClassLoader());
+ builder.setServiceSolicitationUuid(solicitationUuid,
+ solicitationUuidMask);
+ }
+ }
+ if (in.readInt() == 1) {
ParcelUuid servcieDataUuid =
in.readParcelable(ParcelUuid.class.getClassLoader());
if (in.readInt() == 1) {
@@ -231,6 +258,22 @@
return mServiceUuidMask;
}
+ /**
+ * Returns the filter set on the service Solicitation uuid.
+ */
+ @Nullable
+ public ParcelUuid getServiceSolicitationUuid() {
+ return mServiceSolicitationUuid;
+ }
+
+ /**
+ * Returns the filter set on the service Solicitation uuid mask.
+ */
+ @Nullable
+ public ParcelUuid getServiceSolicitationUuidMask() {
+ return mServiceSolicitationUuidMask;
+ }
+
@Nullable
public String getDeviceAddress() {
return mDeviceAddress;
@@ -288,7 +331,7 @@
// Scan record is null but there exist filters on it.
if (scanRecord == null
&& (mDeviceName != null || mServiceUuid != null || mManufacturerData != null
- || mServiceData != null)) {
+ || mServiceData != null || mServiceSolicitationUuid != null)) {
return false;
}
@@ -303,6 +346,13 @@
return false;
}
+ // solicitation UUID match.
+ if (mServiceSolicitationUuid != null && !matchesServiceSolicitationUuids(
+ mServiceSolicitationUuid, mServiceSolicitationUuidMask,
+ scanRecord.getServiceSolicitationUuids())) {
+ return false;
+ }
+
// Service data match
if (mServiceDataUuid != null) {
if (!matchesPartialData(mServiceData, mServiceDataMask,
@@ -350,6 +400,36 @@
return BitUtils.maskedEquals(data, uuid, mask);
}
+ /**
+ * Check if the solicitation uuid pattern is contained in a list of parcel uuids.
+ *
+ */
+ private static boolean matchesServiceSolicitationUuids(ParcelUuid solicitationUuid,
+ ParcelUuid parcelSolicitationUuidMask, List<ParcelUuid> solicitationUuids) {
+ if (solicitationUuid == null) {
+ return true;
+ }
+ if (solicitationUuids == null) {
+ return false;
+ }
+
+ for (ParcelUuid parcelSolicitationUuid : solicitationUuids) {
+ UUID solicitationUuidMask = parcelSolicitationUuidMask == null
+ ? null : parcelSolicitationUuidMask.getUuid();
+ if (matchesServiceUuid(solicitationUuid.getUuid(), solicitationUuidMask,
+ parcelSolicitationUuid.getUuid())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Check if the solicitation uuid pattern matches the particular service solicitation uuid.
+ private static boolean matchesServiceSolicitationUuid(UUID solicitationUuid,
+ UUID solicitationUuidMask, UUID data) {
+ return BitUtils.maskedEquals(data, solicitationUuid, solicitationUuidMask);
+ }
+
// Check whether the data pattern matches the parsed data.
private boolean matchesPartialData(byte[] data, byte[] dataMask, byte[] parsedData) {
if (parsedData == null || parsedData.length < data.length) {
@@ -376,6 +456,8 @@
return "BluetoothLeScanFilter [mDeviceName=" + mDeviceName + ", mDeviceAddress="
+ mDeviceAddress
+ ", mUuid=" + mServiceUuid + ", mUuidMask=" + mServiceUuidMask
+ + ", mServiceSolicitationUuid=" + mServiceSolicitationUuid
+ + ", mServiceSolicitationUuidMask=" + mServiceSolicitationUuidMask
+ ", mServiceDataUuid=" + Objects.toString(mServiceDataUuid) + ", mServiceData="
+ Arrays.toString(mServiceData) + ", mServiceDataMask="
+ Arrays.toString(mServiceDataMask) + ", mManufacturerId=" + mManufacturerId
@@ -391,7 +473,8 @@
mServiceDataUuid,
Arrays.hashCode(mServiceData),
Arrays.hashCode(mServiceDataMask),
- mServiceUuid, mServiceUuidMask);
+ mServiceUuid, mServiceUuidMask,
+ mServiceSolicitationUuid, mServiceSolicitationUuidMask);
}
@Override
@@ -412,7 +495,10 @@
&& Objects.deepEquals(mServiceData, other.mServiceData)
&& Objects.deepEquals(mServiceDataMask, other.mServiceDataMask)
&& Objects.equals(mServiceUuid, other.mServiceUuid)
- && Objects.equals(mServiceUuidMask, other.mServiceUuidMask);
+ && Objects.equals(mServiceUuidMask, other.mServiceUuidMask)
+ && Objects.equals(mServiceSolicitationUuid, other.mServiceSolicitationUuid)
+ && Objects.equals(mServiceSolicitationUuidMask,
+ other.mServiceSolicitationUuidMask);
}
/**
@@ -435,6 +521,9 @@
private ParcelUuid mServiceUuid;
private ParcelUuid mUuidMask;
+ private ParcelUuid mServiceSolicitationUuid;
+ private ParcelUuid mServiceSolicitationUuidMask;
+
private ParcelUuid mServiceDataUuid;
private byte[] mServiceData;
private byte[] mServiceDataMask;
@@ -493,6 +582,36 @@
return this;
}
+
+ /**
+ * Set filter on service solicitation uuid.
+ */
+ public Builder setServiceSolicitationUuid(ParcelUuid serviceSolicitationUuid) {
+ mServiceSolicitationUuid = serviceSolicitationUuid;
+ return this;
+ }
+
+
+ /**
+ * Set filter on partial service Solicitation uuid. The {@code SolicitationUuidMask} is the
+ * bit mask for the {@code serviceSolicitationUuid}. Set any bit in the mask to 1 to
+ * indicate a match is needed for the bit in {@code serviceSolicitationUuid}, and 0 to
+ * ignore that bit.
+ *
+ * @throws IllegalArgumentException If {@code serviceSolicitationUuid} is {@code null} but
+ * {@code serviceSolicitationUuidMask} is not {@code null}.
+ */
+ public Builder setServiceSolicitationUuid(ParcelUuid serviceSolicitationUuid,
+ ParcelUuid solicitationUuidMask) {
+ if (mServiceSolicitationUuidMask != null && mServiceSolicitationUuid == null) {
+ throw new IllegalArgumentException(
+ "SolicitationUuid is null while SolicitationUuidMask is not null!");
+ }
+ mServiceSolicitationUuid = serviceSolicitationUuid;
+ mServiceSolicitationUuidMask = solicitationUuidMask;
+ return this;
+ }
+
/**
* Set filtering on service data.
*
@@ -598,7 +717,8 @@
*/
public ScanFilter build() {
return new ScanFilter(mDeviceName, mDeviceAddress,
- mServiceUuid, mUuidMask,
+ mServiceUuid, mUuidMask, mServiceSolicitationUuid,
+ mServiceSolicitationUuidMask,
mServiceDataUuid, mServiceData, mServiceDataMask,
mManufacturerId, mManufacturerData, mManufacturerDataMask);
}
diff --git a/core/java/android/bluetooth/le/ScanRecord.java b/core/java/android/bluetooth/le/ScanRecord.java
index 07ed18d..7988008 100644
--- a/core/java/android/bluetooth/le/ScanRecord.java
+++ b/core/java/android/bluetooth/le/ScanRecord.java
@@ -51,6 +51,9 @@
private static final int DATA_TYPE_SERVICE_DATA_16_BIT = 0x16;
private static final int DATA_TYPE_SERVICE_DATA_32_BIT = 0x20;
private static final int DATA_TYPE_SERVICE_DATA_128_BIT = 0x21;
+ private static final int DATA_TYPE_SERVICE_SOLICITATION_UUIDS_16_BIT = 0x14;
+ private static final int DATA_TYPE_SERVICE_SOLICITATION_UUIDS_32_BIT = 0x1F;
+ private static final int DATA_TYPE_SERVICE_SOLICITATION_UUIDS_128_BIT = 0x15;
private static final int DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF;
// Flags of the advertising data.
@@ -58,6 +61,8 @@
@Nullable
private final List<ParcelUuid> mServiceUuids;
+ @Nullable
+ private final List<ParcelUuid> mServiceSolicitationUuids;
private final SparseArray<byte[]> mManufacturerSpecificData;
@@ -89,6 +94,15 @@
}
/**
+ * Returns a list of service solicitation UUIDs within the advertisement that are used to
+ * identify the Bluetooth GATT services.
+ */
+ @Nullable
+ public List<ParcelUuid> getServiceSolicitationUuids() {
+ return mServiceSolicitationUuids;
+ }
+
+ /**
* Returns a sparse array of manufacturer identifier and its corresponding manufacturer specific
* data.
*/
@@ -151,10 +165,12 @@
}
private ScanRecord(List<ParcelUuid> serviceUuids,
+ List<ParcelUuid> serviceSolicitationUuids,
SparseArray<byte[]> manufacturerData,
Map<ParcelUuid, byte[]> serviceData,
int advertiseFlags, int txPowerLevel,
String localName, byte[] bytes) {
+ mServiceSolicitationUuids = serviceSolicitationUuids;
mServiceUuids = serviceUuids;
mManufacturerSpecificData = manufacturerData;
mServiceData = serviceData;
@@ -184,6 +200,7 @@
int currentPos = 0;
int advertiseFlag = -1;
List<ParcelUuid> serviceUuids = new ArrayList<ParcelUuid>();
+ List<ParcelUuid> serviceSolicitationUuids = new ArrayList<ParcelUuid>();
String localName = null;
int txPowerLevel = Integer.MIN_VALUE;
@@ -220,6 +237,18 @@
parseServiceUuid(scanRecord, currentPos, dataLength,
BluetoothUuid.UUID_BYTES_128_BIT, serviceUuids);
break;
+ case DATA_TYPE_SERVICE_SOLICITATION_UUIDS_16_BIT:
+ parseServiceSolicitationUuid(scanRecord, currentPos, dataLength,
+ BluetoothUuid.UUID_BYTES_16_BIT, serviceSolicitationUuids);
+ break;
+ case DATA_TYPE_SERVICE_SOLICITATION_UUIDS_32_BIT:
+ parseServiceSolicitationUuid(scanRecord, currentPos, dataLength,
+ BluetoothUuid.UUID_BYTES_32_BIT, serviceSolicitationUuids);
+ break;
+ case DATA_TYPE_SERVICE_SOLICITATION_UUIDS_128_BIT:
+ parseServiceSolicitationUuid(scanRecord, currentPos, dataLength,
+ BluetoothUuid.UUID_BYTES_128_BIT, serviceSolicitationUuids);
+ break;
case DATA_TYPE_LOCAL_NAME_SHORT:
case DATA_TYPE_LOCAL_NAME_COMPLETE:
localName = new String(
@@ -265,19 +294,23 @@
if (serviceUuids.isEmpty()) {
serviceUuids = null;
}
- return new ScanRecord(serviceUuids, manufacturerData, serviceData,
- advertiseFlag, txPowerLevel, localName, scanRecord);
+ if (serviceSolicitationUuids.isEmpty()) {
+ serviceSolicitationUuids = null;
+ }
+ return new ScanRecord(serviceUuids, serviceSolicitationUuids, manufacturerData,
+ serviceData, advertiseFlag, txPowerLevel, localName, scanRecord);
} catch (Exception e) {
Log.e(TAG, "unable to parse scan record: " + Arrays.toString(scanRecord));
// As the record is invalid, ignore all the parsed results for this packet
// and return an empty record with raw scanRecord bytes in results
- return new ScanRecord(null, null, null, -1, Integer.MIN_VALUE, null, scanRecord);
+ return new ScanRecord(null, null, null, null, -1, Integer.MIN_VALUE, null, scanRecord);
}
}
@Override
public String toString() {
return "ScanRecord [mAdvertiseFlags=" + mAdvertiseFlags + ", mServiceUuids=" + mServiceUuids
+ + ", mServiceSolicitationUuids=" + mServiceSolicitationUuids
+ ", mManufacturerSpecificData=" + BluetoothLeUtils.toString(
mManufacturerSpecificData)
+ ", mServiceData=" + BluetoothLeUtils.toString(mServiceData)
@@ -297,6 +330,20 @@
return currentPos;
}
+ /**
+ * Parse service Solicitation UUIDs.
+ */
+ private static int parseServiceSolicitationUuid(byte[] scanRecord, int currentPos,
+ int dataLength, int uuidLength, List<ParcelUuid> serviceSolicitationUuids) {
+ while (dataLength > 0) {
+ byte[] uuidBytes = extractBytes(scanRecord, currentPos, uuidLength);
+ serviceSolicitationUuids.add(BluetoothUuid.parseUuidFrom(uuidBytes));
+ dataLength -= uuidLength;
+ currentPos += uuidLength;
+ }
+ return currentPos;
+ }
+
// Helper method to extract bytes from byte array.
private static byte[] extractBytes(byte[] scanRecord, int start, int length) {
byte[] bytes = new byte[length];