Merge "BLE: Add service solicitation uuid feature in scan filter"

This commit is contained in:
Jakub Pawlowski
2018-08-29 11:26:51 +00:00
committed by Gerrit Code Review
3 changed files with 180 additions and 8 deletions

View File

@@ -8689,6 +8689,8 @@ package android.bluetooth.le {
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);
@@ -8705,6 +8707,8 @@ package android.bluetooth.le {
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);
}
@@ -8717,6 +8721,7 @@ package android.bluetooth.le {
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();
}

View File

@@ -57,6 +57,11 @@ public final class ScanFilter implements Parcelable {
@Nullable
private final ParcelUuid mServiceUuidMask;
@Nullable
private final ParcelUuid mServiceSolicitationUuid;
@Nullable
private final ParcelUuid mServiceSolicitationUuidMask;
@Nullable
private final ParcelUuid mServiceDataUuid;
@Nullable
@@ -75,12 +80,15 @@ public final class ScanFilter implements Parcelable {
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 @@ public final class ScanFilter implements Parcelable {
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);
@@ -171,6 +187,17 @@ public final class ScanFilter implements Parcelable {
builder.setServiceUuid(uuid, uuidMask);
}
}
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());
@@ -231,6 +258,22 @@ public final class ScanFilter implements Parcelable {
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 @@ public final class ScanFilter implements Parcelable {
// 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 @@ public final class ScanFilter implements Parcelable {
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 @@ public final class ScanFilter implements Parcelable {
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 @@ public final class ScanFilter implements Parcelable {
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 @@ public final class ScanFilter implements Parcelable {
mServiceDataUuid,
Arrays.hashCode(mServiceData),
Arrays.hashCode(mServiceDataMask),
mServiceUuid, mServiceUuidMask);
mServiceUuid, mServiceUuidMask,
mServiceSolicitationUuid, mServiceSolicitationUuidMask);
}
@Override
@@ -412,7 +495,10 @@ public final class ScanFilter implements Parcelable {
&& 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 @@ public final class ScanFilter implements Parcelable {
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 @@ public final class ScanFilter implements Parcelable {
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 final class ScanFilter implements Parcelable {
*/
public ScanFilter build() {
return new ScanFilter(mDeviceName, mDeviceAddress,
mServiceUuid, mUuidMask,
mServiceUuid, mUuidMask, mServiceSolicitationUuid,
mServiceSolicitationUuidMask,
mServiceDataUuid, mServiceData, mServiceDataMask,
mManufacturerId, mManufacturerData, mManufacturerDataMask);
}

View File

@@ -51,6 +51,9 @@ public final class ScanRecord {
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 @@ public final class ScanRecord {
@Nullable
private final List<ParcelUuid> mServiceUuids;
@Nullable
private final List<ParcelUuid> mServiceSolicitationUuids;
private final SparseArray<byte[]> mManufacturerSpecificData;
@@ -88,6 +93,15 @@ public final class ScanRecord {
return mServiceUuids;
}
/**
* 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 @@ public final class ScanRecord {
}
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 @@ public final class ScanRecord {
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 @@ public final class ScanRecord {
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 @@ public final class ScanRecord {
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 @@ public final class ScanRecord {
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];