blob: d12ac6ccefb22b209683550a3c688c0689cda4e5 [file] [log] [blame]
/*
* Copyright (C) 2014 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.bluetooth;
import android.annotation.Nullable;
import android.bluetooth.BluetoothLeAdvertiseScanData.AdvertisementData;
import android.os.Parcel;
import android.os.ParcelUuid;
import android.os.Parcelable;
import android.util.Log;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Represents Bluetooth LE advertise and scan response data. This could be either the advertisement
* data to be advertised, or the scan record obtained from BLE scans.
* <p>
* The exact bluetooth advertising and scan response data fields and types are defined in Bluetooth
* 4.0 specification, Volume 3, Part C, Section 11 and 18, as well as Supplement to the Bluetooth
* Core Specification Version 4. Currently the following fields are allowed to be set:
* <li>Service UUIDs which identify the bluetooth gatt services running on the device.
* <li>Tx power level which is the transmission power level.
* <li>Service data which is the data associated with a service.
* <li>Manufacturer specific data which is the data associated with a particular manufacturer.
*
* @see BluetoothLeAdvertiser
*/
public final class BluetoothLeAdvertiseScanData {
private static final String TAG = "BluetoothLeAdvertiseScanData";
/**
* Bluetooth LE Advertising Data type, the data will be placed in AdvData field of advertising
* packet.
*/
public static final int ADVERTISING_DATA = 0;
/**
* Bluetooth LE scan response data, the data will be placed in ScanRspData field of advertising
* packet.
* <p>
* TODO: unhide when stack supports setting scan response data.
*
* @hide
*/
public static final int SCAN_RESPONSE_DATA = 1;
/**
* Scan record parsed from Bluetooth LE scans. The content can contain a concatenation of
* advertising data and scan response data.
*/
public static final int PARSED_SCAN_RECORD = 2;
/**
* Base data type which contains the common fields for {@link AdvertisementData} and
* {@link ScanRecord}.
*/
public abstract static class AdvertiseBaseData {
private final int mDataType;
@Nullable
private final List<ParcelUuid> mServiceUuids;
private final int mManufacturerId;
@Nullable
private final byte[] mManufacturerSpecificData;
@Nullable
private final ParcelUuid mServiceDataUuid;
@Nullable
private final byte[] mServiceData;
private AdvertiseBaseData(int dataType,
List<ParcelUuid> serviceUuids,
ParcelUuid serviceDataUuid, byte[] serviceData,
int manufacturerId,
byte[] manufacturerSpecificData) {
mDataType = dataType;
mServiceUuids = serviceUuids;
mManufacturerId = manufacturerId;
mManufacturerSpecificData = manufacturerSpecificData;
mServiceDataUuid = serviceDataUuid;
mServiceData = serviceData;
}
/**
* Returns the type of data, indicating whether the data is advertising data, scan response
* data or scan record.
*/
public int getDataType() {
return mDataType;
}
/**
* Returns a list of service uuids within the advertisement that are used to identify the
* bluetooth gatt services.
*/
public List<ParcelUuid> getServiceUuids() {
return mServiceUuids;
}
/**
* Returns the manufacturer identifier, which is a non-negative number assigned by Bluetooth
* SIG.
*/
public int getManufacturerId() {
return mManufacturerId;
}
/**
* Returns the manufacturer specific data which is the content of manufacturer specific data
* field. The first 2 bytes of the data contain the company id.
*/
public byte[] getManufacturerSpecificData() {
return mManufacturerSpecificData;
}
/**
* Returns a 16 bit uuid of the service that the service data is associated with.
*/
public ParcelUuid getServiceDataUuid() {
return mServiceDataUuid;
}
/**
* Returns service data. The first two bytes should be a 16 bit service uuid associated with
* the service data.
*/
public byte[] getServiceData() {
return mServiceData;
}
@Override
public String toString() {
return "AdvertiseBaseData [mDataType=" + mDataType + ", mServiceUuids=" + mServiceUuids
+ ", mManufacturerId=" + mManufacturerId + ", mManufacturerSpecificData="
+ Arrays.toString(mManufacturerSpecificData) + ", mServiceDataUuid="
+ mServiceDataUuid + ", mServiceData=" + Arrays.toString(mServiceData) + "]";
}
}
/**
* Advertisement data packet for Bluetooth LE advertising. This represents the data to be
* broadcasted in Bluetooth LE advertising.
* <p>
* Use {@link AdvertisementData.Builder} to create an instance of {@link AdvertisementData} to
* be advertised.
*
* @see BluetoothLeAdvertiser
*/
public static final class AdvertisementData extends AdvertiseBaseData implements Parcelable {
private boolean mIncludeTxPowerLevel;
/**
* Whether the transmission power level will be included in the advertisement packet.
*/
public boolean getIncludeTxPowerLevel() {
return mIncludeTxPowerLevel;
}
/**
* Returns a {@link Builder} to build {@link AdvertisementData}.
*/
public static Builder newBuilder() {
return new Builder();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(getDataType());
List<ParcelUuid> uuids = getServiceUuids();
if (uuids == null) {
dest.writeInt(0);
} else {
dest.writeInt(uuids.size());
dest.writeList(uuids);
}
dest.writeInt(getManufacturerId());
byte[] manufacturerData = getManufacturerSpecificData();
if (manufacturerData == null) {
dest.writeInt(0);
} else {
dest.writeInt(manufacturerData.length);
dest.writeByteArray(manufacturerData);
}
ParcelUuid serviceDataUuid = getServiceDataUuid();
if (serviceDataUuid == null) {
dest.writeInt(0);
} else {
dest.writeInt(1);
dest.writeParcelable(serviceDataUuid, flags);
byte[] serviceData = getServiceData();
if (serviceData == null) {
dest.writeInt(0);
} else {
dest.writeInt(serviceData.length);
dest.writeByteArray(serviceData);
}
}
dest.writeByte((byte) (getIncludeTxPowerLevel() ? 1 : 0));
}
private AdvertisementData(int dataType,
List<ParcelUuid> serviceUuids,
ParcelUuid serviceDataUuid, byte[] serviceData,
int manufacturerId,
byte[] manufacturerSpecificData, boolean mIncludeTxPowerLevel) {
super(dataType, serviceUuids, serviceDataUuid, serviceData, manufacturerId,
manufacturerSpecificData);
this.mIncludeTxPowerLevel = mIncludeTxPowerLevel;
}
public static final Parcelable.Creator<AdvertisementData> CREATOR =
new Creator<AdvertisementData>() {
@Override
public AdvertisementData[] newArray(int size) {
return new AdvertisementData[size];
}
@Override
public AdvertisementData createFromParcel(Parcel in) {
Builder builder = newBuilder();
int dataType = in.readInt();
builder.dataType(dataType);
if (in.readInt() > 0) {
List<ParcelUuid> uuids = new ArrayList<ParcelUuid>();
in.readList(uuids, ParcelUuid.class.getClassLoader());
builder.serviceUuids(uuids);
}
int manufacturerId = in.readInt();
int manufacturerDataLength = in.readInt();
if (manufacturerDataLength > 0) {
byte[] manufacturerData = new byte[manufacturerDataLength];
in.readByteArray(manufacturerData);
builder.manufacturerData(manufacturerId, manufacturerData);
}
if (in.readInt() == 1) {
ParcelUuid serviceDataUuid = in.readParcelable(
ParcelUuid.class.getClassLoader());
int serviceDataLength = in.readInt();
if (serviceDataLength > 0) {
byte[] serviceData = new byte[serviceDataLength];
in.readByteArray(serviceData);
builder.serviceData(serviceDataUuid, serviceData);
}
}
builder.includeTxPowerLevel(in.readByte() == 1);
return builder.build();
}
};
/**
* Builder for {@link BluetoothLeAdvertiseScanData.AdvertisementData}. Use
* {@link AdvertisementData#newBuilder()} to get an instance of the Builder.
*/
public static final class Builder {
private static final int MAX_ADVERTISING_DATA_BYTES = 31;
// Each fields need one byte for field length and another byte for field type.
private static final int OVERHEAD_BYTES_PER_FIELD = 2;
// Flags field will be set by system.
private static final int FLAGS_FIELD_BYTES = 3;
private int mDataType;
@Nullable
private List<ParcelUuid> mServiceUuids;
private boolean mIncludeTxPowerLevel;
private int mManufacturerId;
@Nullable
private byte[] mManufacturerSpecificData;
@Nullable
private ParcelUuid mServiceDataUuid;
@Nullable
private byte[] mServiceData;
/**
* Set data type.
*
* @param dataType Data type, could only be
* {@link BluetoothLeAdvertiseScanData#ADVERTISING_DATA}
* @throws IllegalArgumentException If the {@code dataType} is invalid.
*/
public Builder dataType(int dataType) {
if (mDataType != ADVERTISING_DATA && mDataType != SCAN_RESPONSE_DATA) {
throw new IllegalArgumentException("invalid data type - " + dataType);
}
mDataType = dataType;
return this;
}
/**
* Set the service uuids. Note the corresponding bluetooth Gatt services need to be
* already added on the device before start BLE advertising.
*
* @param serviceUuids Service uuids to be advertised, could be 16-bit, 32-bit or
* 128-bit uuids.
* @throws IllegalArgumentException If the {@code serviceUuids} are null.
*/
public Builder serviceUuids(List<ParcelUuid> serviceUuids) {
if (serviceUuids == null) {
throw new IllegalArgumentException("serivceUuids are null");
}
mServiceUuids = serviceUuids;
return this;
}
/**
* Add service data to advertisement.
*
* @param serviceDataUuid A 16 bit uuid of the service data
* @param serviceData Service data - the first two bytes of the service data are the
* service data uuid.
* @throws IllegalArgumentException If the {@code serviceDataUuid} or
* {@code serviceData} is empty.
*/
public Builder serviceData(ParcelUuid serviceDataUuid, byte[] serviceData) {
if (serviceDataUuid == null || serviceData == null) {
throw new IllegalArgumentException(
"serviceDataUuid or serviceDataUuid is null");
}
mServiceDataUuid = serviceDataUuid;
mServiceData = serviceData;
return this;
}
/**
* Set manufacturer id and data. See <a
* href="https://www.bluetooth.org/en-us/specification/assigned-numbers/company-identifiers">assigned
* manufacturer identifies</a> for the existing company identifiers.
*
* @param manufacturerId Manufacturer id assigned by Bluetooth SIG.
* @param manufacturerSpecificData Manufacturer specific data - the first two bytes of
* the manufacturer specific data are the manufacturer id.
* @throws IllegalArgumentException If the {@code manufacturerId} is negative or
* {@code manufacturerSpecificData} is null.
*/
public Builder manufacturerData(int manufacturerId, byte[] manufacturerSpecificData) {
if (manufacturerId < 0) {
throw new IllegalArgumentException(
"invalid manufacturerId - " + manufacturerId);
}
if (manufacturerSpecificData == null) {
throw new IllegalArgumentException("manufacturerSpecificData is null");
}
mManufacturerId = manufacturerId;
mManufacturerSpecificData = manufacturerSpecificData;
return this;
}
/**
* Whether the transmission power level should be included in the advertising packet.
*/
public Builder includeTxPowerLevel(boolean includeTxPowerLevel) {
mIncludeTxPowerLevel = includeTxPowerLevel;
return this;
}
/**
* Build the {@link BluetoothLeAdvertiseScanData}.
*
* @throws IllegalArgumentException If the data size is larger than 31 bytes.
*/
public AdvertisementData build() {
if (totalBytes() > MAX_ADVERTISING_DATA_BYTES) {
throw new IllegalArgumentException(
"advertisement data size is larger than 31 bytes");
}
return new AdvertisementData(mDataType,
mServiceUuids,
mServiceDataUuid,
mServiceData, mManufacturerId, mManufacturerSpecificData,
mIncludeTxPowerLevel);
}
// Compute the size of the advertisement data.
private int totalBytes() {
int size = FLAGS_FIELD_BYTES; // flags field is always set.
if (mServiceUuids != null) {
int num16BitUuids = 0;
int num32BitUuids = 0;
int num128BitUuids = 0;
for (ParcelUuid uuid : mServiceUuids) {
if (BluetoothUuid.is16BitUuid(uuid)) {
++num16BitUuids;
} else if (BluetoothUuid.is32BitUuid(uuid)) {
++num32BitUuids;
} else {
++num128BitUuids;
}
}
// 16 bit service uuids are grouped into one field when doing advertising.
if (num16BitUuids != 0) {
size += OVERHEAD_BYTES_PER_FIELD +
num16BitUuids * BluetoothUuid.UUID_BYTES_16_BIT;
}
// 32 bit service uuids are grouped into one field when doing advertising.
if (num32BitUuids != 0) {
size += OVERHEAD_BYTES_PER_FIELD +
num32BitUuids * BluetoothUuid.UUID_BYTES_32_BIT;
}
// 128 bit service uuids are grouped into one field when doing advertising.
if (num128BitUuids != 0) {
size += OVERHEAD_BYTES_PER_FIELD +
num128BitUuids * BluetoothUuid.UUID_BYTES_128_BIT;
}
}
if (mServiceData != null) {
size += OVERHEAD_BYTES_PER_FIELD + mServiceData.length;
}
if (mManufacturerSpecificData != null) {
size += OVERHEAD_BYTES_PER_FIELD + mManufacturerSpecificData.length;
}
if (mIncludeTxPowerLevel) {
size += OVERHEAD_BYTES_PER_FIELD + 1; // tx power level value is one byte.
}
return size;
}
}
}
/**
* Represents a scan record from Bluetooth LE scan.
*/
public static final class ScanRecord extends AdvertiseBaseData {
// Flags of the advertising data.
private final int mAdvertiseFlags;
// Transmission power level(in dB).
private final int mTxPowerLevel;
// Local name of the Bluetooth LE device.
private final String mLocalName;
/**
* Returns the advertising flags indicating the discoverable mode and capability of the
* device. Returns -1 if the flag field is not set.
*/
public int getAdvertiseFlags() {
return mAdvertiseFlags;
}
/**
* Returns the transmission power level of the packet in dBm. Returns
* {@link Integer#MIN_VALUE} if the field is not set. This value can be used to calculate
* the path loss of a received packet using the following equation:
* <p>
* <code>pathloss = txPowerLevel - rssi</code>
*/
public int getTxPowerLevel() {
return mTxPowerLevel;
}
/**
* Returns the local name of the BLE device. The is a UTF-8 encoded string.
*/
@Nullable
public String getLocalName() {
return mLocalName;
}
ScanRecord(int dataType,
List<ParcelUuid> serviceUuids,
ParcelUuid serviceDataUuid, byte[] serviceData,
int manufacturerId,
byte[] manufacturerSpecificData, int advertiseFlags, int txPowerLevel,
String localName) {
super(dataType, serviceUuids, serviceDataUuid, serviceData, manufacturerId,
manufacturerSpecificData);
mLocalName = localName;
mAdvertiseFlags = advertiseFlags;
mTxPowerLevel = txPowerLevel;
}
/**
* Get a {@link Parser} to parse the scan record byte array into {@link ScanRecord}.
*/
public static Parser getParser() {
return new Parser();
}
/**
* A parser class used to parse a Bluetooth LE scan record to
* {@link BluetoothLeAdvertiseScanData}. Note not all field types would be parsed.
*/
public static final class Parser {
private static final String PARSER_TAG = "BluetoothLeAdvertiseDataParser";
// The following data type values are assigned by Bluetooth SIG.
// For more details refer to Bluetooth 4.0 specification, Volume 3, Part C, Section 18.
private static final int DATA_TYPE_FLAGS = 0x01;
private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL = 0x02;
private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE = 0x03;
private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL = 0x04;
private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE = 0x05;
private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL = 0x06;
private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE = 0x07;
private static final int DATA_TYPE_LOCAL_NAME_SHORT = 0x08;
private static final int DATA_TYPE_LOCAL_NAME_COMPLETE = 0x09;
private static final int DATA_TYPE_TX_POWER_LEVEL = 0x0A;
private static final int DATA_TYPE_SERVICE_DATA = 0x16;
private static final int DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF;
// Helper method to extract bytes from byte array.
private static byte[] extractBytes(byte[] scanRecord, int start, int length) {
byte[] bytes = new byte[length];
System.arraycopy(scanRecord, start, bytes, 0, length);
return bytes;
}
/**
* Parse scan record to {@link BluetoothLeAdvertiseScanData.ScanRecord}.
* <p>
* The format is defined in Bluetooth 4.0 specification, Volume 3, Part C, Section 11
* and 18.
* <p>
* All numerical multi-byte entities and values shall use little-endian
* <strong>byte</strong> order.
*
* @param scanRecord The scan record of Bluetooth LE advertisement and/or scan response.
*/
public ScanRecord parseFromScanRecord(byte[] scanRecord) {
if (scanRecord == null) {
return null;
}
int currentPos = 0;
int advertiseFlag = -1;
List<ParcelUuid> serviceUuids = new ArrayList<ParcelUuid>();
String localName = null;
int txPowerLevel = Integer.MIN_VALUE;
ParcelUuid serviceDataUuid = null;
byte[] serviceData = null;
int manufacturerId = -1;
byte[] manufacturerSpecificData = null;
try {
while (currentPos < scanRecord.length) {
// length is unsigned int.
int length = scanRecord[currentPos++] & 0xFF;
if (length == 0) {
break;
}
// Note the length includes the length of the field type itself.
int dataLength = length - 1;
// fieldType is unsigned int.
int fieldType = scanRecord[currentPos++] & 0xFF;
switch (fieldType) {
case DATA_TYPE_FLAGS:
advertiseFlag = scanRecord[currentPos] & 0xFF;
break;
case DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL:
case DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE:
parseServiceUuid(scanRecord, currentPos,
dataLength, BluetoothUuid.UUID_BYTES_16_BIT, serviceUuids);
break;
case DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL:
case DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE:
parseServiceUuid(scanRecord, currentPos, dataLength,
BluetoothUuid.UUID_BYTES_32_BIT, serviceUuids);
break;
case DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL:
case DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE:
parseServiceUuid(scanRecord, currentPos, dataLength,
BluetoothUuid.UUID_BYTES_128_BIT, serviceUuids);
break;
case DATA_TYPE_LOCAL_NAME_SHORT:
case DATA_TYPE_LOCAL_NAME_COMPLETE:
localName = new String(
extractBytes(scanRecord, currentPos, dataLength));
break;
case DATA_TYPE_TX_POWER_LEVEL:
txPowerLevel = scanRecord[currentPos];
break;
case DATA_TYPE_SERVICE_DATA:
serviceData = extractBytes(scanRecord, currentPos, dataLength);
// The first two bytes of the service data are service data uuid.
int serviceUuidLength = BluetoothUuid.UUID_BYTES_16_BIT;
byte[] serviceDataUuidBytes = extractBytes(scanRecord, currentPos,
serviceUuidLength);
serviceDataUuid = BluetoothUuid.parseUuidFrom(serviceDataUuidBytes);
break;
case DATA_TYPE_MANUFACTURER_SPECIFIC_DATA:
manufacturerSpecificData = extractBytes(scanRecord, currentPos,
dataLength);
// The first two bytes of the manufacturer specific data are
// manufacturer ids in little endian.
manufacturerId = ((manufacturerSpecificData[1] & 0xFF) << 8) +
(manufacturerSpecificData[0] & 0xFF);
break;
default:
// Just ignore, we don't handle such data type.
break;
}
currentPos += dataLength;
}
if (serviceUuids.isEmpty()) {
serviceUuids = null;
}
return new ScanRecord(PARSED_SCAN_RECORD,
serviceUuids, serviceDataUuid, serviceData,
manufacturerId, manufacturerSpecificData, advertiseFlag, txPowerLevel,
localName);
} catch (IndexOutOfBoundsException e) {
Log.e(PARSER_TAG,
"unable to parse scan record: " + Arrays.toString(scanRecord));
return null;
}
}
// Parse service uuids.
private int parseServiceUuid(byte[] scanRecord, int currentPos, int dataLength,
int uuidLength, List<ParcelUuid> serviceUuids) {
while (dataLength > 0) {
byte[] uuidBytes = extractBytes(scanRecord, currentPos,
uuidLength);
serviceUuids.add(BluetoothUuid.parseUuidFrom(uuidBytes));
dataLength -= uuidLength;
currentPos += uuidLength;
}
return currentPos;
}
}
}
}