summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
authorWei Wang <weiwa@google.com>2014-04-16 18:49:18 -0700
committerWei Wang <weiwa@google.com>2014-05-19 19:40:49 -0700
commit62d5bc745a4fd7f305a47b628b1fe902ee9d29f8 (patch)
treede267354e1e446114a8333462dbb9ac6def4d8fa /core/java
parent51800847d9a9965a5d00a7960abb50983651d4a2 (diff)
downloadframeworks_base-62d5bc745a4fd7f305a47b628b1fe902ee9d29f8.zip
frameworks_base-62d5bc745a4fd7f305a47b628b1fe902ee9d29f8.tar.gz
frameworks_base-62d5bc745a4fd7f305a47b628b1fe902ee9d29f8.tar.bz2
APIs for BLE scan, scan filter, batch scan, onFound/onLost and multiple
advertising. Change-Id: I1655eb9cffa890b6fe38108bf51078662e90bc03
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/bluetooth/BluetoothAdapter.java32
-rw-r--r--core/java/android/bluetooth/BluetoothGatt.java10
-rw-r--r--core/java/android/bluetooth/BluetoothLeAdvertiseScanData.aidl19
-rw-r--r--core/java/android/bluetooth/BluetoothLeAdvertiseScanData.java649
-rw-r--r--core/java/android/bluetooth/BluetoothLeAdvertiser.aidl19
-rw-r--r--core/java/android/bluetooth/BluetoothLeAdvertiser.java600
-rw-r--r--core/java/android/bluetooth/BluetoothLeScanFilter.aidl19
-rw-r--r--core/java/android/bluetooth/BluetoothLeScanFilter.java577
-rw-r--r--core/java/android/bluetooth/BluetoothLeScanner.aidl20
-rw-r--r--core/java/android/bluetooth/BluetoothLeScanner.java759
-rw-r--r--core/java/android/bluetooth/IBluetoothGatt.aidl11
-rw-r--r--core/java/android/bluetooth/IBluetoothGattCallback.aidl1
12 files changed, 2714 insertions, 2 deletions
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index e79deec..9e1c995 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -498,6 +498,34 @@ public final class BluetoothAdapter {
}
/**
+ * Returns a {@link BluetoothLeAdvertiser} object for Bluetooth LE Advertising operations.
+ */
+ public BluetoothLeAdvertiser getBluetoothLeAdvertiser() {
+ // TODO: Return null if this feature is not supported by hardware.
+ try {
+ IBluetoothGatt iGatt = mManagerService.getBluetoothGatt();
+ return new BluetoothLeAdvertiser(iGatt);
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to get BluetoothLeAdvertiser, error: " + e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns a {@link BluetoothLeScanner} object for Bluetooth LE scan operations.
+ */
+ public BluetoothLeScanner getBluetoothLeScanner() {
+ // TODO: Return null if BLE scan is not supported by hardware.
+ try {
+ IBluetoothGatt iGatt = mManagerService.getBluetoothGatt();
+ return new BluetoothLeScanner(iGatt);
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to get BluetoothLeScanner, error: " + e);
+ return null;
+ }
+ }
+
+ /**
* Interface for BLE advertising callback.
*
* @hide
@@ -2024,6 +2052,10 @@ public final class BluetoothAdapter {
}
}
+ @Override
+ public void onMultiAdvertiseCallback(int status) {
+ // no op
+ }
/**
* Callback reporting LE ATT MTU.
* @hide
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index 601d9ee..c9df9c0 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -581,7 +581,15 @@ public final class BluetoothGatt implements BluetoothProfile {
public void onAdvertiseStateChange(int state, int status) {
if (DBG) Log.d(TAG, "onAdvertiseStateChange() - state = "
+ state + " status=" + status);
- }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void onMultiAdvertiseCallback(int status) {
+ // no op.
+ }
/**
* Callback invoked when the MTU for a given connection changes
diff --git a/core/java/android/bluetooth/BluetoothLeAdvertiseScanData.aidl b/core/java/android/bluetooth/BluetoothLeAdvertiseScanData.aidl
new file mode 100644
index 0000000..4aa8881
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothLeAdvertiseScanData.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+parcelable BluetoothLeAdvertiseScanData.AdvertisementData; \ No newline at end of file
diff --git a/core/java/android/bluetooth/BluetoothLeAdvertiseScanData.java b/core/java/android/bluetooth/BluetoothLeAdvertiseScanData.java
new file mode 100644
index 0000000..3d85810
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothLeAdvertiseScanData.java
@@ -0,0 +1,649 @@
+/*
+ * 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} or
+ * {@link BluetoothLeAdvertiseScanData#SCAN_RESPONSE_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;
+ }
+ }
+ }
+
+}
diff --git a/core/java/android/bluetooth/BluetoothLeAdvertiser.aidl b/core/java/android/bluetooth/BluetoothLeAdvertiser.aidl
new file mode 100644
index 0000000..3108610
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothLeAdvertiser.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+parcelable BluetoothLeAdvertiser.Settings; \ No newline at end of file
diff --git a/core/java/android/bluetooth/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/BluetoothLeAdvertiser.java
new file mode 100644
index 0000000..2a8aa23
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothLeAdvertiser.java
@@ -0,0 +1,600 @@
+/*
+ * 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.bluetooth.BluetoothLeAdvertiseScanData.AdvertisementData;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.ParcelUuid;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * This class provides a way to perform Bluetooth LE advertise operations, such as start and stop
+ * advertising. An advertiser can broadcast up to 31 bytes of advertisement data represented by
+ * {@link BluetoothLeAdvertiseScanData.AdvertisementData}.
+ * <p>
+ * To get an instance of {@link BluetoothLeAdvertiser}, call the
+ * {@link BluetoothAdapter#getBluetoothLeAdvertiser()} method.
+ * <p>
+ * Note most of the methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+ * permission.
+ *
+ * @see BluetoothLeAdvertiseScanData.AdvertisementData
+ */
+public class BluetoothLeAdvertiser {
+
+ private static final String TAG = "BluetoothLeAdvertiser";
+
+ /**
+ * The {@link Settings} provide a way to adjust advertising preferences for each individual
+ * advertisement. Use {@link Settings.Builder} to create a {@link Settings} instance.
+ */
+ public static final class Settings implements Parcelable {
+ /**
+ * Perform Bluetooth LE advertising in low power mode. This is the default and preferred
+ * advertising mode as it consumes the least power.
+ */
+ public static final int ADVERTISE_MODE_LOW_POWER = 0;
+ /**
+ * Perform Bluetooth LE advertising in balanced power mode. This is balanced between
+ * advertising frequency and power consumption.
+ */
+ public static final int ADVERTISE_MODE_BALANCED = 1;
+ /**
+ * Perform Bluetooth LE advertising in low latency, high power mode. This has the highest
+ * power consumption and should not be used for background continuous advertising.
+ */
+ public static final int ADVERTISE_MODE_LOW_LATENCY = 2;
+
+ /**
+ * Advertise using the lowest transmission(tx) power level. An app can use low transmission
+ * power to restrict the visibility range of its advertising packet.
+ */
+ public static final int ADVERTISE_TX_POWER_ULTRA_LOW = 0;
+ /**
+ * Advertise using low tx power level.
+ */
+ public static final int ADVERTISE_TX_POWER_LOW = 1;
+ /**
+ * Advertise using medium tx power level.
+ */
+ public static final int ADVERTISE_TX_POWER_MEDIUM = 2;
+ /**
+ * Advertise using high tx power level. This is corresponding to largest visibility range of
+ * the advertising packet.
+ */
+ public static final int ADVERTISE_TX_POWER_HIGH = 3;
+
+ /**
+ * Non-connectable undirected advertising event, as defined in Bluetooth Specification V4.0
+ * vol6, part B, section 4.4.2 - Advertising state.
+ */
+ public static final int ADVERTISE_TYPE_NON_CONNECTABLE = 0;
+ /**
+ * Scannable undirected advertise type, as defined in same spec mentioned above. This event
+ * type allows a scanner to send a scan request asking additional information about the
+ * advertiser.
+ */
+ public static final int ADVERTISE_TYPE_SCANNABLE = 1;
+ /**
+ * Connectable undirected advertising type, as defined in same spec mentioned above. This
+ * event type allows a scanner to send scan request asking additional information about the
+ * advertiser. It also allows an initiator to send a connect request for connection.
+ */
+ public static final int ADVERTISE_TYPE_CONNECTABLE = 2;
+
+ private final int mAdvertiseMode;
+ private final int mAdvertiseTxPowerLevel;
+ private final int mAdvertiseEventType;
+
+ private Settings(int advertiseMode, int advertiseTxPowerLevel,
+ int advertiseEventType) {
+ mAdvertiseMode = advertiseMode;
+ mAdvertiseTxPowerLevel = advertiseTxPowerLevel;
+ mAdvertiseEventType = advertiseEventType;
+ }
+
+ private Settings(Parcel in) {
+ mAdvertiseMode = in.readInt();
+ mAdvertiseTxPowerLevel = in.readInt();
+ mAdvertiseEventType = in.readInt();
+ }
+
+ /**
+ * Creates a {@link Builder} to construct a {@link Settings} object.
+ */
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ /**
+ * Returns the advertise mode.
+ */
+ public int getMode() {
+ return mAdvertiseMode;
+ }
+
+ /**
+ * Returns the tx power level for advertising.
+ */
+ public int getTxPowerLevel() {
+ return mAdvertiseTxPowerLevel;
+ }
+
+ /**
+ * Returns the advertise event type.
+ */
+ public int getType() {
+ return mAdvertiseEventType;
+ }
+
+ @Override
+ public String toString() {
+ return "Settings [mAdvertiseMode=" + mAdvertiseMode + ", mAdvertiseTxPowerLevel="
+ + mAdvertiseTxPowerLevel + ", mAdvertiseEventType=" + mAdvertiseEventType + "]";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mAdvertiseMode);
+ dest.writeInt(mAdvertiseTxPowerLevel);
+ dest.writeInt(mAdvertiseEventType);
+ }
+
+ public static final Parcelable.Creator<Settings> CREATOR =
+ new Creator<BluetoothLeAdvertiser.Settings>() {
+ @Override
+ public Settings[] newArray(int size) {
+ return new Settings[size];
+ }
+
+ @Override
+ public Settings createFromParcel(Parcel in) {
+ return new Settings(in);
+ }
+ };
+
+ /**
+ * Builder class for {@link BluetoothLeAdvertiser.Settings}. Caller should use
+ * {@link Settings#newBuilder()} to get an instance of the builder.
+ */
+ public static final class Builder {
+ private int mMode = ADVERTISE_MODE_LOW_POWER;
+ private int mTxPowerLevel = ADVERTISE_TX_POWER_MEDIUM;
+ private int mType = ADVERTISE_TYPE_NON_CONNECTABLE;
+
+ // Private constructor, use Settings.newBuilder() get an instance of BUILDER.
+ private Builder() {
+ }
+
+ /**
+ * Set advertise mode to control the advertising power and latency.
+ *
+ * @param advertiseMode Bluetooth LE Advertising mode, can only be one of
+ * {@link Settings#ADVERTISE_MODE_LOW_POWER},
+ * {@link Settings#ADVERTISE_MODE_BALANCED}, or
+ * {@link Settings#ADVERTISE_MODE_LOW_LATENCY}.
+ * @throws IllegalArgumentException If the advertiseMode is invalid.
+ */
+ public Builder advertiseMode(int advertiseMode) {
+ if (advertiseMode < ADVERTISE_MODE_LOW_POWER
+ || advertiseMode > ADVERTISE_MODE_LOW_LATENCY) {
+ throw new IllegalArgumentException("unknown mode " + advertiseMode);
+ }
+ mMode = advertiseMode;
+ return this;
+ }
+
+ /**
+ * Set advertise tx power level to control the transmission power level for the
+ * advertising.
+ *
+ * @param txPowerLevel Transmission power of Bluetooth LE Advertising, can only be one
+ * of {@link Settings#ADVERTISE_TX_POWER_ULTRA_LOW},
+ * {@link Settings#ADVERTISE_TX_POWER_LOW},
+ * {@link Settings#ADVERTISE_TX_POWER_MEDIUM} or
+ * {@link Settings#ADVERTISE_TX_POWER_HIGH}.
+ * @throws IllegalArgumentException If the {@code txPowerLevel} is invalid.
+ */
+ public Builder txPowerLevel(int txPowerLevel) {
+ if (txPowerLevel < ADVERTISE_TX_POWER_ULTRA_LOW
+ || txPowerLevel > ADVERTISE_TX_POWER_HIGH) {
+ throw new IllegalArgumentException("unknown tx power level " + txPowerLevel);
+ }
+ mTxPowerLevel = txPowerLevel;
+ return this;
+ }
+
+ /**
+ * Set advertise type to control the event type of advertising.
+ *
+ * @param type Bluetooth LE Advertising type, can be either
+ * {@link Settings#ADVERTISE_TYPE_NON_CONNECTABLE},
+ * {@link Settings#ADVERTISE_TYPE_SCANNABLE} or
+ * {@link Settings#ADVERTISE_TYPE_CONNECTABLE}.
+ * @throws IllegalArgumentException If the {@code type} is invalid.
+ */
+ public Builder type(int type) {
+ if (type < ADVERTISE_TYPE_NON_CONNECTABLE
+ || type > ADVERTISE_TYPE_CONNECTABLE) {
+ throw new IllegalArgumentException("unknown advertise type " + type);
+ }
+ mType = type;
+ return this;
+ }
+
+ /**
+ * Build the {@link Settings} object.
+ */
+ public Settings build() {
+ return new Settings(mMode, mTxPowerLevel, mType);
+ }
+ }
+ }
+
+ /**
+ * Callback of Bluetooth LE advertising, which is used to deliver operation status for start and
+ * stop advertising.
+ */
+ public interface AdvertiseCallback {
+
+ /**
+ * The operation is success.
+ *
+ * @hide
+ */
+ public static final int SUCCESS = 0;
+ /**
+ * Fails to start advertising as the advertisement data contains services that are not added
+ * to the local bluetooth Gatt server.
+ */
+ public static final int ADVERTISING_SERVICE_UNKNOWN = 1;
+ /**
+ * Fails to start advertising as system runs out of quota for advertisers.
+ */
+ public static final int TOO_MANY_ADVERTISERS = 2;
+
+ /**
+ * Fails to start advertising as the advertising is already started.
+ */
+ public static final int ADVERTISING_ALREADY_STARTED = 3;
+ /**
+ * Fails to stop advertising as the advertising is not started.
+ */
+ public static final int ADVERISING_NOT_STARTED = 4;
+
+ /**
+ * Operation fails due to bluetooth controller failure.
+ */
+ public static final int CONTROLLER_FAILURE = 5;
+
+ /**
+ * Callback when advertising operation succeeds.
+ *
+ * @param settingsInEffect The actual settings used for advertising, which may be different
+ * from what the app asks.
+ */
+ public void onSuccess(Settings settingsInEffect);
+
+ /**
+ * Callback when advertising operation fails.
+ *
+ * @param errorCode Error code for failures.
+ */
+ public void onFailure(int errorCode);
+ }
+
+ private final IBluetoothGatt mBluetoothGatt;
+ private final Handler mHandler;
+ private final Map<Settings, AdvertiseCallbackWrapper>
+ mLeAdvertisers = new HashMap<Settings, AdvertiseCallbackWrapper>();
+
+ // Package private constructor, use BluetoothAdapter.getLeAdvertiser() instead.
+ BluetoothLeAdvertiser(IBluetoothGatt bluetoothGatt) {
+ mBluetoothGatt = bluetoothGatt;
+ mHandler = new Handler(Looper.getMainLooper());
+ }
+
+ /**
+ * Bluetooth GATT interface callbacks for advertising.
+ */
+ private static class AdvertiseCallbackWrapper extends IBluetoothGattCallback.Stub {
+ private static final int LE_CALLBACK_TIMEOUT_MILLIS = 2000;
+ private final AdvertiseCallback mAdvertiseCallback;
+ private final AdvertisementData mAdvertisement;
+ private final Settings mSettings;
+ private final IBluetoothGatt mBluetoothGatt;
+
+ // mLeHandle 0: not registered
+ // -1: scan stopped
+ // >0: registered and scan started
+ private int mLeHandle;
+ private boolean isAdvertising = false;
+
+ public AdvertiseCallbackWrapper(AdvertiseCallback advertiseCallback,
+ AdvertisementData advertiseData, Settings settings,
+ IBluetoothGatt bluetoothGatt) {
+ mAdvertiseCallback = advertiseCallback;
+ mAdvertisement = advertiseData;
+ mSettings = settings;
+ mBluetoothGatt = bluetoothGatt;
+ mLeHandle = 0;
+ }
+
+ public boolean advertiseStarted() {
+ boolean started = false;
+ synchronized (this) {
+ if (mLeHandle == -1) {
+ return false;
+ }
+ try {
+ wait(LE_CALLBACK_TIMEOUT_MILLIS);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Callback reg wait interrupted: " + e);
+ }
+ started = (mLeHandle > 0 && isAdvertising);
+ }
+ return started;
+ }
+
+ public boolean advertiseStopped() {
+ synchronized (this) {
+ try {
+ wait(LE_CALLBACK_TIMEOUT_MILLIS);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Callback reg wait interrupted: " + e);
+ }
+ return !isAdvertising;
+ }
+ }
+
+ /**
+ * Application interface registered - app is ready to go
+ */
+ @Override
+ public void onClientRegistered(int status, int clientIf) {
+ Log.d(TAG, "onClientRegistered() - status=" + status + " clientIf=" + clientIf);
+ synchronized (this) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ mLeHandle = clientIf;
+ try {
+ mBluetoothGatt.startMultiAdvertising(mLeHandle, mAdvertisement, mSettings);
+ } catch (RemoteException e) {
+ Log.e(TAG, "fail to start le advertise: " + e);
+ mLeHandle = -1;
+ notifyAll();
+ } catch (Exception e) {
+ Log.e(TAG, "fail to start advertise: " + e.getStackTrace());
+ }
+ } else {
+ // registration failed
+ mLeHandle = -1;
+ notifyAll();
+ }
+ }
+ }
+
+ @Override
+ public void onClientConnectionState(int status, int clientIf,
+ boolean connected, String address) {
+ // no op
+ }
+
+ @Override
+ public void onScanResult(String address, int rssi, byte[] advData) {
+ // no op
+ }
+
+ @Override
+ public void onGetService(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid) {
+ // no op
+ }
+
+ @Override
+ public void onGetIncludedService(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int inclSrvcType, int inclSrvcInstId,
+ ParcelUuid inclSrvcUuid) {
+ // no op
+ }
+
+ @Override
+ public void onGetCharacteristic(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ int charProps) {
+ // no op
+ }
+
+ @Override
+ public void onGetDescriptor(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ int descInstId, ParcelUuid descUuid) {
+ // no op
+ }
+
+ @Override
+ public void onSearchComplete(String address, int status) {
+ // no op
+ }
+
+ @Override
+ public void onCharacteristicRead(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid, byte[] value) {
+ // no op
+ }
+
+ @Override
+ public void onCharacteristicWrite(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid) {
+ // no op
+ }
+
+ @Override
+ public void onNotify(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ byte[] value) {
+ // no op
+ }
+
+ @Override
+ public void onDescriptorRead(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ int descInstId, ParcelUuid descrUuid, byte[] value) {
+ // no op
+ }
+
+ @Override
+ public void onDescriptorWrite(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ int descInstId, ParcelUuid descrUuid) {
+ // no op
+ }
+
+ @Override
+ public void onExecuteWrite(String address, int status) {
+ // no op
+ }
+
+ @Override
+ public void onReadRemoteRssi(String address, int rssi, int status) {
+ // no op
+ }
+
+ @Override
+ public void onAdvertiseStateChange(int advertiseState, int status) {
+ // no op
+ }
+
+ @Override
+ public void onMultiAdvertiseCallback(int status) {
+ synchronized (this) {
+ if (status == 0) {
+ isAdvertising = !isAdvertising;
+ if (!isAdvertising) {
+ try {
+ mBluetoothGatt.unregisterClient(mLeHandle);
+ mLeHandle = -1;
+ } catch (RemoteException e) {
+ Log.e(TAG, "remote exception when unregistering", e);
+ }
+ }
+ mAdvertiseCallback.onSuccess(null);
+ } else {
+ mAdvertiseCallback.onFailure(status);
+ }
+ notifyAll();
+ }
+
+ }
+
+ /**
+ * Callback reporting LE ATT MTU.
+ *
+ * @hide
+ */
+ public void onConfigureMTU(String address, int mtu, int status) {
+ // no op
+ }
+ }
+
+ /**
+ * Start Bluetooth LE Advertising.
+ *
+ * @param settings {@link Settings} for Bluetooth LE advertising.
+ * @param advertiseData {@link AdvertisementData} to be advertised.
+ * @param callback {@link AdvertiseCallback} for advertising status.
+ */
+ public void startAdvertising(Settings settings,
+ AdvertisementData advertiseData, final AdvertiseCallback callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("callback cannot be null");
+ }
+ if (mLeAdvertisers.containsKey(settings)) {
+ postCallbackFailure(callback, AdvertiseCallback.ADVERTISING_ALREADY_STARTED);
+ return;
+ }
+ AdvertiseCallbackWrapper wrapper = new AdvertiseCallbackWrapper(callback, advertiseData,
+ settings,
+ mBluetoothGatt);
+ UUID uuid = UUID.randomUUID();
+ try {
+ mBluetoothGatt.registerClient(new ParcelUuid(uuid), wrapper);
+ if (wrapper.advertiseStarted()) {
+ mLeAdvertisers.put(settings, wrapper);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to stop advertising", e);
+ }
+ }
+
+ /**
+ * Stop Bluetooth LE advertising. Returns immediately, the operation status will be delivered
+ * through the {@code callback}.
+ * <p>
+ * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+ *
+ * @param settings {@link Settings} used to start Bluetooth LE advertising.
+ * @param callback {@link AdvertiseCallback} for delivering stopping advertising status.
+ */
+ public void stopAdvertising(final Settings settings, final AdvertiseCallback callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("callback cannot be null");
+ }
+ AdvertiseCallbackWrapper wrapper = mLeAdvertisers.get(settings);
+ if (wrapper == null) {
+ postCallbackFailure(callback, AdvertiseCallback.ADVERISING_NOT_STARTED);
+ return;
+ }
+ try {
+ mBluetoothGatt.stopMultiAdvertising(wrapper.mLeHandle);
+ if (wrapper.advertiseStopped()) {
+ mLeAdvertisers.remove(settings);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to stop advertising", e);
+ }
+ }
+
+ private void postCallbackFailure(final AdvertiseCallback callback, final int error) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onFailure(error);
+ }
+ });
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothLeScanFilter.aidl b/core/java/android/bluetooth/BluetoothLeScanFilter.aidl
new file mode 100644
index 0000000..86ee06d
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothLeScanFilter.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+parcelable BluetoothLeScanFilter;
diff --git a/core/java/android/bluetooth/BluetoothLeScanFilter.java b/core/java/android/bluetooth/BluetoothLeScanFilter.java
new file mode 100644
index 0000000..2ed85ba
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothLeScanFilter.java
@@ -0,0 +1,577 @@
+/*
+ * 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.ScanRecord;
+import android.bluetooth.BluetoothLeScanner.ScanResult;
+import android.os.Parcel;
+import android.os.ParcelUuid;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ * {@link BluetoothLeScanFilter} abstracts different scan filters across Bluetooth Advertisement
+ * packet fields.
+ * <p>
+ * Current filtering on the following fields are supported:
+ * <li>Service UUIDs which identify the bluetooth gatt services running on the device.
+ * <li>Name of remote Bluetooth LE device.
+ * <li>Mac address of the remote device.
+ * <li>Rssi which indicates the received 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 BluetoothLeAdvertiseScanData.ScanRecord
+ * @see BluetoothLeScanner
+ */
+public final class BluetoothLeScanFilter implements Parcelable {
+
+ @Nullable
+ private final String mLocalName;
+
+ @Nullable
+ private final String mMacAddress;
+
+ @Nullable
+ private final ParcelUuid mServiceUuid;
+ @Nullable
+ private final ParcelUuid mServiceUuidMask;
+
+ @Nullable
+ private final byte[] mServiceData;
+ @Nullable
+ private final byte[] mServiceDataMask;
+
+ private final int mManufacturerId;
+ @Nullable
+ private final byte[] mManufacturerData;
+ @Nullable
+ private final byte[] mManufacturerDataMask;
+
+ private final int mMinRssi;
+ private final int mMaxRssi;
+
+ private BluetoothLeScanFilter(String name, String macAddress, ParcelUuid uuid,
+ ParcelUuid uuidMask, byte[] serviceData, byte[] serviceDataMask,
+ int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask,
+ int minRssi, int maxRssi) {
+ mLocalName = name;
+ mServiceUuid = uuid;
+ mServiceUuidMask = uuidMask;
+ mMacAddress = macAddress;
+ mServiceData = serviceData;
+ mServiceDataMask = serviceDataMask;
+ mManufacturerId = manufacturerId;
+ mManufacturerData = manufacturerData;
+ mManufacturerDataMask = manufacturerDataMask;
+ mMinRssi = minRssi;
+ mMaxRssi = maxRssi;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mLocalName == null ? 0 : 1);
+ if (mLocalName != null) {
+ dest.writeString(mLocalName);
+ }
+ dest.writeInt(mMacAddress == null ? 0 : 1);
+ if (mMacAddress != null) {
+ dest.writeString(mMacAddress);
+ }
+ dest.writeInt(mServiceUuid == null ? 0 : 1);
+ if (mServiceUuid != null) {
+ dest.writeParcelable(mServiceUuid, flags);
+ }
+ dest.writeInt(mServiceUuidMask == null ? 0 : 1);
+ if (mServiceUuidMask != null) {
+ dest.writeParcelable(mServiceUuidMask, flags);
+ }
+ dest.writeInt(mServiceData == null ? 0 : mServiceData.length);
+ if (mServiceData != null) {
+ dest.writeByteArray(mServiceData);
+ }
+ dest.writeInt(mServiceDataMask == null ? 0 : mServiceDataMask.length);
+ if (mServiceDataMask != null) {
+ dest.writeByteArray(mServiceDataMask);
+ }
+ dest.writeInt(mManufacturerId);
+ dest.writeInt(mManufacturerData == null ? 0 : mManufacturerData.length);
+ if (mManufacturerData != null) {
+ dest.writeByteArray(mManufacturerData);
+ }
+ dest.writeInt(mManufacturerDataMask == null ? 0 : mManufacturerDataMask.length);
+ if (mManufacturerDataMask != null) {
+ dest.writeByteArray(mManufacturerDataMask);
+ }
+ dest.writeInt(mMinRssi);
+ dest.writeInt(mMaxRssi);
+ }
+
+ /**
+ * A {@link android.os.Parcelable.Creator} to create {@link BluetoothLeScanFilter} form parcel.
+ */
+ public static final Creator<BluetoothLeScanFilter>
+ CREATOR = new Creator<BluetoothLeScanFilter>() {
+
+ @Override
+ public BluetoothLeScanFilter[] newArray(int size) {
+ return new BluetoothLeScanFilter[size];
+ }
+
+ @Override
+ public BluetoothLeScanFilter createFromParcel(Parcel in) {
+ Builder builder = newBuilder();
+ if (in.readInt() == 1) {
+ builder.name(in.readString());
+ }
+ if (in.readInt() == 1) {
+ builder.macAddress(in.readString());
+ }
+ if (in.readInt() == 1) {
+ ParcelUuid uuid = in.readParcelable(ParcelUuid.class.getClassLoader());
+ builder.serviceUuid(uuid);
+ }
+ if (in.readInt() == 1) {
+ ParcelUuid uuidMask = in.readParcelable(ParcelUuid.class.getClassLoader());
+ builder.serviceUuidMask(uuidMask);
+ }
+ int serviceDataLength = in.readInt();
+ if (serviceDataLength > 0) {
+ byte[] serviceData = new byte[serviceDataLength];
+ in.readByteArray(serviceData);
+ builder.serviceData(serviceData);
+ }
+ int serviceDataMaskLength = in.readInt();
+ if (serviceDataMaskLength > 0) {
+ byte[] serviceDataMask = new byte[serviceDataMaskLength];
+ in.readByteArray(serviceDataMask);
+ builder.serviceDataMask(serviceDataMask);
+ }
+ int manufacturerId = in.readInt();
+ int manufacturerDataLength = in.readInt();
+ if (manufacturerDataLength > 0) {
+ byte[] manufacturerData = new byte[manufacturerDataLength];
+ in.readByteArray(manufacturerData);
+ builder.manufacturerData(manufacturerId, manufacturerData);
+ }
+ int manufacturerDataMaskLength = in.readInt();
+ if (manufacturerDataMaskLength > 0) {
+ byte[] manufacturerDataMask = new byte[manufacturerDataMaskLength];
+ in.readByteArray(manufacturerDataMask);
+ builder.manufacturerDataMask(manufacturerDataMask);
+ }
+ int minRssi = in.readInt();
+ int maxRssi = in.readInt();
+ builder.rssiRange(minRssi, maxRssi);
+ return builder.build();
+ }
+ };
+
+ /**
+ * Returns the filter set the local name field of Bluetooth advertisement data.
+ */
+ @Nullable
+ public String getLocalName() {
+ return mLocalName;
+ }
+
+ @Nullable /**
+ * Returns the filter set on the service uuid.
+ */
+ public ParcelUuid getServiceUuid() {
+ return mServiceUuid;
+ }
+
+ @Nullable
+ public ParcelUuid getServiceUuidMask() {
+ return mServiceUuidMask;
+ }
+
+ @Nullable
+ public String getDeviceAddress() {
+ return mMacAddress;
+ }
+
+ @Nullable
+ public byte[] getServiceData() {
+ return mServiceData;
+ }
+
+ @Nullable
+ public byte[] getServiceDataMask() {
+ return mServiceDataMask;
+ }
+
+ /**
+ * Returns the manufacturer id. -1 if the manufacturer filter is not set.
+ */
+ public int getManufacturerId() {
+ return mManufacturerId;
+ }
+
+ @Nullable
+ public byte[] getManufacturerData() {
+ return mManufacturerData;
+ }
+
+ @Nullable
+ public byte[] getManufacturerDataMask() {
+ return mManufacturerDataMask;
+ }
+
+ /**
+ * Returns minimum value of rssi for the scan filter. {@link Integer#MIN_VALUE} if not set.
+ */
+ public int getMinRssi() {
+ return mMinRssi;
+ }
+
+ /**
+ * Returns maximum value of the rssi for the scan filter. {@link Integer#MAX_VALUE} if not set.
+ */
+ public int getMaxRssi() {
+ return mMaxRssi;
+ }
+
+ /**
+ * Check if the scan filter matches a {@code scanResult}. A scan result is considered as a match
+ * if it matches all the field filters.
+ */
+ public boolean matches(ScanResult scanResult) {
+ if (scanResult == null) {
+ return false;
+ }
+ BluetoothDevice device = scanResult.getDevice();
+ // Device match.
+ if (mMacAddress != null && (device == null || !mMacAddress.equals(device.getAddress()))) {
+ return false;
+ }
+
+ int rssi = scanResult.getRssi();
+ if (rssi < mMinRssi || rssi > mMaxRssi) {
+ return false;
+ }
+
+ byte[] scanRecordBytes = scanResult.getScanRecord();
+ ScanRecord scanRecord = ScanRecord.getParser().parseFromScanRecord(scanRecordBytes);
+
+ // Scan record is null but there exist filters on it.
+ if (scanRecord == null
+ && (mLocalName != null || mServiceUuid != null || mManufacturerData != null
+ || mServiceData != null)) {
+ return false;
+ }
+
+ // Local name match.
+ if (mLocalName != null && !mLocalName.equals(scanRecord.getLocalName())) {
+ return false;
+ }
+
+ // UUID match.
+ if (mServiceUuid != null && !matchesServiceUuids(mServiceUuid, mServiceUuidMask,
+ scanRecord.getServiceUuids())) {
+ return false;
+ }
+
+ // Service data match
+ if (mServiceData != null &&
+ !matchesPartialData(mServiceData, mServiceDataMask, scanRecord.getServiceData())) {
+ return false;
+ }
+
+ // Manufacturer data match.
+ if (mManufacturerData != null && !matchesPartialData(mManufacturerData,
+ mManufacturerDataMask, scanRecord.getManufacturerSpecificData())) {
+ return false;
+ }
+ // All filters match.
+ return true;
+ }
+
+ // Check if the uuid pattern is contained in a list of parcel uuids.
+ private boolean matchesServiceUuids(ParcelUuid uuid, ParcelUuid parcelUuidMask,
+ List<ParcelUuid> uuids) {
+ if (uuid == null) {
+ return true;
+ }
+ if (uuids == null) {
+ return false;
+ }
+
+ for (ParcelUuid parcelUuid : uuids) {
+ UUID uuidMask = parcelUuidMask == null ? null : parcelUuidMask.getUuid();
+ if (matchesServiceUuid(uuid.getUuid(), uuidMask, parcelUuid.getUuid())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Check if the uuid pattern matches the particular service uuid.
+ private boolean matchesServiceUuid(UUID uuid, UUID mask, UUID data) {
+ if (mask == null) {
+ return uuid.equals(data);
+ }
+ if ((uuid.getLeastSignificantBits() & mask.getLeastSignificantBits()) !=
+ (data.getLeastSignificantBits() & mask.getLeastSignificantBits())) {
+ return false;
+ }
+ return ((uuid.getMostSignificantBits() & mask.getMostSignificantBits()) ==
+ (data.getMostSignificantBits() & mask.getMostSignificantBits()));
+ }
+
+ // Check whether the data pattern matches the parsed data.
+ private boolean matchesPartialData(byte[] data, byte[] dataMask, byte[] parsedData) {
+ if (dataMask == null) {
+ return Arrays.equals(data, parsedData);
+ }
+ if (parsedData == null) {
+ return false;
+ }
+ for (int i = 0; i < data.length; ++i) {
+ if ((dataMask[i] & parsedData[i]) != (dataMask[i] & data[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "BluetoothLeScanFilter [mLocalName=" + mLocalName + ", mMacAddress=" + mMacAddress
+ + ", mUuid=" + mServiceUuid + ", mUuidMask=" + mServiceUuidMask + ", mServiceData="
+ + Arrays.toString(mServiceData) + ", mServiceDataMask="
+ + Arrays.toString(mServiceDataMask) + ", mManufacturerId=" + mManufacturerId
+ + ", mManufacturerData=" + Arrays.toString(mManufacturerData)
+ + ", mManufacturerDataMask=" + Arrays.toString(mManufacturerDataMask)
+ + ", mMinRssi=" + mMinRssi + ", mMaxRssi=" + mMaxRssi + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mLocalName, mMacAddress, mManufacturerId, mManufacturerData,
+ mManufacturerDataMask, mMaxRssi, mMinRssi, mServiceData, mServiceDataMask,
+ mServiceUuid, mServiceUuidMask);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ BluetoothLeScanFilter other = (BluetoothLeScanFilter) obj;
+ return Objects.equals(mLocalName, other.mLocalName) &&
+ Objects.equals(mMacAddress, other.mMacAddress) &&
+ mManufacturerId == other.mManufacturerId &&
+ Objects.deepEquals(mManufacturerData, other.mManufacturerData) &&
+ Objects.deepEquals(mManufacturerDataMask, other.mManufacturerDataMask) &&
+ mMinRssi == other.mMinRssi && mMaxRssi == other.mMaxRssi &&
+ Objects.deepEquals(mServiceData, other.mServiceData) &&
+ Objects.deepEquals(mServiceDataMask, other.mServiceDataMask) &&
+ Objects.equals(mServiceUuid, other.mServiceUuid) &&
+ Objects.equals(mServiceUuidMask, other.mServiceUuidMask);
+ }
+
+ /**
+ * Returns the {@link Builder} for {@link BluetoothLeScanFilter}.
+ */
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder class for {@link BluetoothLeScanFilter}. Use
+ * {@link BluetoothLeScanFilter#newBuilder()} to get an instance of the {@link Builder}.
+ */
+ public static class Builder {
+
+ private String mLocalName;
+ private String mMacAddress;
+
+ private ParcelUuid mServiceUuid;
+ private ParcelUuid mUuidMask;
+
+ private byte[] mServiceData;
+ private byte[] mServiceDataMask;
+
+ private int mManufacturerId = -1;
+ private byte[] mManufacturerData;
+ private byte[] mManufacturerDataMask;
+
+ private int mMinRssi = Integer.MIN_VALUE;
+ private int mMaxRssi = Integer.MAX_VALUE;
+
+ // Private constructor, use BluetoothLeScanFilter.newBuilder instead.
+ private Builder() {
+ }
+
+ /**
+ * Set filtering on local name.
+ */
+ public Builder name(String localName) {
+ mLocalName = localName;
+ return this;
+ }
+
+ /**
+ * Set filtering on device mac address.
+ *
+ * @param macAddress The device mac address for the filter. It needs to be in the format of
+ * "01:02:03:AB:CD:EF". The mac address can be validated using
+ * {@link BluetoothAdapter#checkBluetoothAddress}.
+ * @throws IllegalArgumentException If the {@code macAddress} is invalid.
+ */
+ public Builder macAddress(String macAddress) {
+ if (macAddress != null && !BluetoothAdapter.checkBluetoothAddress(macAddress)) {
+ throw new IllegalArgumentException("invalid mac address " + macAddress);
+ }
+ mMacAddress = macAddress;
+ return this;
+ }
+
+ /**
+ * Set filtering on service uuid.
+ */
+ public Builder serviceUuid(ParcelUuid serviceUuid) {
+ mServiceUuid = serviceUuid;
+ return this;
+ }
+
+ /**
+ * Set partial uuid filter. The {@code uuidMask} is the bit mask for the {@code uuid} set
+ * through {@link #serviceUuid(ParcelUuid)} method. Set any bit in the mask to 1 to indicate
+ * a match is needed for the bit in {@code serviceUuid}, and 0 to ignore that bit.
+ * <p>
+ * The length of {@code uuidMask} must be the same as {@code serviceUuid}.
+ */
+ public Builder serviceUuidMask(ParcelUuid uuidMask) {
+ mUuidMask = uuidMask;
+ return this;
+ }
+
+ /**
+ * Set service data filter.
+ */
+ public Builder serviceData(byte[] serviceData) {
+ mServiceData = serviceData;
+ return this;
+ }
+
+ /**
+ * Set partial service data filter bit mask. For any bit in the mask, set it to 1 if it
+ * needs to match the one in service data, otherwise set it to 0 to ignore that bit.
+ * <p>
+ * The {@code serviceDataMask} must have the same length of the {@code serviceData} set
+ * through {@link #serviceData(byte[])}.
+ */
+ public Builder serviceDataMask(byte[] serviceDataMask) {
+ mServiceDataMask = serviceDataMask;
+ return this;
+ }
+
+ /**
+ * Set manufacturerId and manufacturerData. A negative manufacturerId is considered as
+ * invalid id.
+ * <p>
+ * Note the first two bytes of the {@code manufacturerData} is the manufacturerId.
+ */
+ public Builder manufacturerData(int manufacturerId, byte[] manufacturerData) {
+ if (manufacturerData != null && manufacturerId < 0) {
+ throw new IllegalArgumentException("invalid manufacture id");
+ }
+ mManufacturerId = manufacturerId;
+ mManufacturerData = manufacturerData;
+ return this;
+ }
+
+ /**
+ * Set partial manufacture data filter bit mask. For any bit in the mask, set it the 1 if it
+ * needs to match the one in manufacturer data, otherwise set it to 0.
+ * <p>
+ * The {@code manufacturerDataMask} must have the same length of {@code manufacturerData}
+ * set through {@link #manufacturerData(int, byte[])}.
+ */
+ public Builder manufacturerDataMask(byte[] manufacturerDataMask) {
+ mManufacturerDataMask = manufacturerDataMask;
+ return this;
+ }
+
+ /**
+ * Set the desired rssi range for the filter. A scan result with rssi in the range of
+ * [minRssi, maxRssi] will be consider as a match.
+ */
+ public Builder rssiRange(int minRssi, int maxRssi) {
+ mMinRssi = minRssi;
+ mMaxRssi = maxRssi;
+ return this;
+ }
+
+ /**
+ * Build {@link BluetoothLeScanFilter}.
+ *
+ * @throws IllegalArgumentException If the filter cannot be built.
+ */
+ public BluetoothLeScanFilter build() {
+ if (mUuidMask != null && mServiceUuid == null) {
+ throw new IllegalArgumentException("uuid is null while uuidMask is not null!");
+ }
+
+ if (mServiceDataMask != null) {
+ if (mServiceData == null) {
+ throw new IllegalArgumentException(
+ "serviceData is null while serviceDataMask is not null");
+ }
+ // Since the mServiceDataMask is a bit mask for mServiceData, the lengths of the two
+ // byte array need to be the same.
+ if (mServiceData.length != mServiceDataMask.length) {
+ throw new IllegalArgumentException(
+ "size mismatch for service data and service data mask");
+ }
+ }
+
+ if (mManufacturerDataMask != null) {
+ if (mManufacturerData == null) {
+ throw new IllegalArgumentException(
+ "manufacturerData is null while manufacturerDataMask is not null");
+ }
+ // Since the mManufacturerDataMask is a bit mask for mManufacturerData, the lengths
+ // of the two byte array need to be the same.
+ if (mManufacturerData.length != mManufacturerDataMask.length) {
+ throw new IllegalArgumentException(
+ "size mismatch for manufacturerData and manufacturerDataMask");
+ }
+ }
+ return new BluetoothLeScanFilter(mLocalName, mMacAddress,
+ mServiceUuid, mUuidMask,
+ mServiceData, mServiceDataMask,
+ mManufacturerId, mManufacturerData, mManufacturerDataMask, mMinRssi, mMaxRssi);
+ }
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothLeScanner.aidl b/core/java/android/bluetooth/BluetoothLeScanner.aidl
new file mode 100644
index 0000000..8cecdd7
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothLeScanner.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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;
+
+parcelable BluetoothLeScanner.ScanResult;
+parcelable BluetoothLeScanner.Settings;
diff --git a/core/java/android/bluetooth/BluetoothLeScanner.java b/core/java/android/bluetooth/BluetoothLeScanner.java
new file mode 100644
index 0000000..ed3188b
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothLeScanner.java
@@ -0,0 +1,759 @@
+/*
+ * 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.os.Handler;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.ParcelUuid;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This class provides methods to perform scan related operations for Bluetooth LE devices. An
+ * application can scan for a particular type of BLE devices using {@link BluetoothLeScanFilter}. It
+ * can also request different types of callbacks for delivering the result.
+ * <p>
+ * Use {@link BluetoothAdapter#getBluetoothLeScanner()} to get an instance of
+ * {@link BluetoothLeScanner}.
+ * <p>
+ * Note most of the scan methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+ * permission.
+ *
+ * @see BluetoothLeScanFilter
+ */
+public class BluetoothLeScanner {
+
+ private static final String TAG = "BluetoothLeScanner";
+ private static final boolean DBG = true;
+
+ /**
+ * Settings for Bluetooth LE scan.
+ */
+ public static final class Settings implements Parcelable {
+ /**
+ * Perform Bluetooth LE scan in low power mode. This is the default scan mode as it consumes
+ * the least power.
+ */
+ public static final int SCAN_MODE_LOW_POWER = 0;
+ /**
+ * Perform Bluetooth LE scan in balanced power mode.
+ */
+ public static final int SCAN_MODE_BALANCED = 1;
+ /**
+ * Scan using highest duty cycle. It's recommended only using this mode when the application
+ * is running in foreground.
+ */
+ public static final int SCAN_MODE_LOW_LATENCY = 2;
+
+ /**
+ * Callback each time when a bluetooth advertisement is found.
+ */
+ public static final int CALLBACK_TYPE_ON_UPDATE = 0;
+ /**
+ * Callback when a bluetooth advertisement is found for the first time.
+ */
+ public static final int CALLBACK_TYPE_ON_FOUND = 1;
+ /**
+ * Callback when a bluetooth advertisement is found for the first time, then lost.
+ */
+ public static final int CALLBACK_TYPE_ON_LOST = 2;
+
+ /**
+ * Full scan result which contains device mac address, rssi, advertising and scan response
+ * and scan timestamp.
+ */
+ public static final int SCAN_RESULT_TYPE_FULL = 0;
+ /**
+ * Truncated scan result which contains device mac address, rssi and scan timestamp. Note
+ * it's possible for an app to get more scan results that it asks if there are multiple apps
+ * using this type. TODO: decide whether we could unhide this setting.
+ *
+ * @hide
+ */
+ public static final int SCAN_RESULT_TYPE_TRUNCATED = 1;
+
+ // Bluetooth LE scan mode.
+ private int mScanMode;
+
+ // Bluetooth LE scan callback type
+ private int mCallbackType;
+
+ // Bluetooth LE scan result type
+ private int mScanResultType;
+
+ // Time of delay for reporting the scan result
+ private long mReportDelayMicros;
+
+ public int getScanMode() {
+ return mScanMode;
+ }
+
+ public int getCallbackType() {
+ return mCallbackType;
+ }
+
+ public int getScanResultType() {
+ return mScanResultType;
+ }
+
+ /**
+ * Returns report delay timestamp based on the device clock.
+ */
+ public long getReportDelayMicros() {
+ return mReportDelayMicros;
+ }
+
+ /**
+ * Creates a new {@link Builder} to build {@link Settings} object.
+ */
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ private Settings(int scanMode, int callbackType, int scanResultType,
+ long reportDelayMicros) {
+ mScanMode = scanMode;
+ mCallbackType = callbackType;
+ mScanResultType = scanResultType;
+ mReportDelayMicros = reportDelayMicros;
+ }
+
+ private Settings(Parcel in) {
+ mScanMode = in.readInt();
+ mCallbackType = in.readInt();
+ mScanResultType = in.readInt();
+ mReportDelayMicros = in.readLong();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mScanMode);
+ dest.writeInt(mCallbackType);
+ dest.writeInt(mScanResultType);
+ dest.writeLong(mReportDelayMicros);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Parcelable.Creator<Settings> CREATOR = new Creator<Settings>() {
+ @Override
+ public Settings[] newArray(int size) {
+ return new Settings[size];
+ }
+
+ @Override
+ public Settings createFromParcel(Parcel in) {
+ return new Settings(in);
+ }
+ };
+
+ /**
+ * Builder for {@link BluetoothLeScanner.Settings}.
+ */
+ public static class Builder {
+ private int mScanMode = SCAN_MODE_LOW_POWER;
+ private int mCallbackType = CALLBACK_TYPE_ON_UPDATE;
+ private int mScanResultType = SCAN_RESULT_TYPE_FULL;
+ private long mReportDelayMicros = 0;
+
+ // Hidden constructor.
+ private Builder() {
+ }
+
+ /**
+ * Set scan mode for Bluetooth LE scan.
+ *
+ * @param scanMode The scan mode can be one of {@link Settings#SCAN_MODE_LOW_POWER},
+ * {@link Settings#SCAN_MODE_BALANCED} or
+ * {@link Settings#SCAN_MODE_LOW_LATENCY}.
+ * @throws IllegalArgumentException If the {@code scanMode} is invalid.
+ */
+ public Builder scanMode(int scanMode) {
+ if (scanMode < SCAN_MODE_LOW_POWER || scanMode > SCAN_MODE_LOW_LATENCY) {
+ throw new IllegalArgumentException("invalid scan mode " + scanMode);
+ }
+ mScanMode = scanMode;
+ return this;
+ }
+
+ /**
+ * Set callback type for Bluetooth LE scan.
+ *
+ * @param callbackType The callback type for the scan. Can be either one of
+ * {@link Settings#CALLBACK_TYPE_ON_UPDATE},
+ * {@link Settings#CALLBACK_TYPE_ON_FOUND} or
+ * {@link Settings#CALLBACK_TYPE_ON_LOST}.
+ * @throws IllegalArgumentException If the {@code callbackType} is invalid.
+ */
+ public Builder callbackType(int callbackType) {
+ if (callbackType < CALLBACK_TYPE_ON_UPDATE
+ || callbackType > CALLBACK_TYPE_ON_LOST) {
+ throw new IllegalArgumentException("invalid callback type - " + callbackType);
+ }
+ mCallbackType = callbackType;
+ return this;
+ }
+
+ /**
+ * Set scan result type for Bluetooth LE scan.
+ *
+ * @param scanResultType Type for scan result, could be either
+ * {@link Settings#SCAN_RESULT_TYPE_FULL} or
+ * {@link Settings#SCAN_RESULT_TYPE_TRUNCATED}.
+ * @throws IllegalArgumentException If the {@code scanResultType} is invalid.
+ * @hide
+ */
+ public Builder scanResultType(int scanResultType) {
+ if (scanResultType < SCAN_RESULT_TYPE_FULL
+ || scanResultType > SCAN_RESULT_TYPE_TRUNCATED) {
+ throw new IllegalArgumentException(
+ "invalid scanResultType - " + scanResultType);
+ }
+ mScanResultType = scanResultType;
+ return this;
+ }
+
+ /**
+ * Set report delay timestamp for Bluetooth LE scan.
+ */
+ public Builder reportDelayMicros(long reportDelayMicros) {
+ mReportDelayMicros = reportDelayMicros;
+ return this;
+ }
+
+ /**
+ * Build {@link Settings}.
+ */
+ public Settings build() {
+ return new Settings(mScanMode, mCallbackType, mScanResultType, mReportDelayMicros);
+ }
+ }
+ }
+
+ /**
+ * ScanResult for Bluetooth LE scan.
+ */
+ public static final class ScanResult implements Parcelable {
+ // Remote bluetooth device.
+ private BluetoothDevice mDevice;
+
+ // Scan record, including advertising data and scan response data.
+ private byte[] mScanRecord;
+
+ // Received signal strength.
+ private int mRssi;
+
+ // Device timestamp when the result was last seen.
+ private long mTimestampMicros;
+
+ // Constructor of scan result.
+ public ScanResult(BluetoothDevice device, byte[] scanRecord, int rssi, long timestampMicros) {
+ mDevice = device;
+ mScanRecord = scanRecord;
+ mRssi = rssi;
+ mTimestampMicros = timestampMicros;
+ }
+
+ private ScanResult(Parcel in) {
+ readFromParcel(in);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (mDevice != null) {
+ dest.writeInt(1);
+ mDevice.writeToParcel(dest, flags);
+ } else {
+ dest.writeInt(0);
+ }
+ if (mScanRecord != null) {
+ dest.writeInt(1);
+ dest.writeByteArray(mScanRecord);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeInt(mRssi);
+ dest.writeLong(mTimestampMicros);
+ }
+
+ private void readFromParcel(Parcel in) {
+ if (in.readInt() == 1) {
+ mDevice = BluetoothDevice.CREATOR.createFromParcel(in);
+ }
+ if (in.readInt() == 1) {
+ mScanRecord = in.createByteArray();
+ }
+ mRssi = in.readInt();
+ mTimestampMicros = in.readLong();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Returns the remote bluetooth device identified by the bluetooth device address.
+ */
+ @Nullable
+ public BluetoothDevice getDevice() {
+ return mDevice;
+ }
+
+ @Nullable /**
+ * Returns the scan record, which can be a combination of advertisement and scan response.
+ */
+ public byte[] getScanRecord() {
+ return mScanRecord;
+ }
+
+ /**
+ * Returns the received signal strength in dBm. The valid range is [-127, 127].
+ */
+ public int getRssi() {
+ return mRssi;
+ }
+
+ /**
+ * Returns timestamp since boot when the scan record was observed.
+ */
+ public long getTimestampMicros() {
+ return mTimestampMicros;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mDevice, mRssi, mScanRecord, mTimestampMicros);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ ScanResult other = (ScanResult) obj;
+ return Objects.equals(mDevice, other.mDevice) && (mRssi == other.mRssi) &&
+ Objects.deepEquals(mScanRecord, other.mScanRecord)
+ && (mTimestampMicros == other.mTimestampMicros);
+ }
+
+ @Override
+ public String toString() {
+ return "ScanResult{" + "mDevice=" + mDevice + ", mScanRecord="
+ + Arrays.toString(mScanRecord) + ", mRssi=" + mRssi + ", mTimestampMicros="
+ + mTimestampMicros + '}';
+ }
+
+ public static final Parcelable.Creator<ScanResult> CREATOR = new Creator<ScanResult>() {
+ @Override
+ public ScanResult createFromParcel(Parcel source) {
+ return new ScanResult(source);
+ }
+
+ @Override
+ public ScanResult[] newArray(int size) {
+ return new ScanResult[size];
+ }
+ };
+
+ }
+
+ /**
+ * Callback of Bluetooth LE scans. The results of the scans will be delivered through the
+ * callbacks.
+ */
+ public interface ScanCallback {
+ /**
+ * Callback when any BLE beacon is found.
+ *
+ * @param result A Bluetooth LE scan result.
+ */
+ public void onDeviceUpdate(ScanResult result);
+
+ /**
+ * Callback when the BLE beacon is found for the first time.
+ *
+ * @param result The Bluetooth LE scan result when the onFound event is triggered.
+ */
+ public void onDeviceFound(ScanResult result);
+
+ /**
+ * Callback when the BLE device was lost. Note a device has to be "found" before it's lost.
+ *
+ * @param device The Bluetooth device that is lost.
+ */
+ public void onDeviceLost(BluetoothDevice device);
+
+ /**
+ * Callback when batch results are delivered.
+ *
+ * @param results List of scan results that are previously scanned.
+ */
+ public void onBatchScanResults(List<ScanResult> results);
+
+ /**
+ * Fails to start scan as BLE scan with the same settings is already started by the app.
+ */
+ public static final int SCAN_ALREADY_STARTED = 1;
+ /**
+ * Fails to start scan as app cannot be registered.
+ */
+ public static final int APPLICATION_REGISTRATION_FAILED = 2;
+ /**
+ * Fails to start scan due to gatt service failure.
+ */
+ public static final int GATT_SERVICE_FAILURE = 3;
+ /**
+ * Fails to start scan due to controller failure.
+ */
+ public static final int CONTROLLER_FAILURE = 4;
+
+ /**
+ * Callback when scan failed.
+ */
+ public void onScanFailed(int errorCode);
+ }
+
+ private final IBluetoothGatt mBluetoothGatt;
+ private final Handler mHandler;
+ private final Map<Settings, BleScanCallbackWrapper> mLeScanClients;
+
+ BluetoothLeScanner(IBluetoothGatt bluetoothGatt) {
+ mBluetoothGatt = bluetoothGatt;
+ mHandler = new Handler(Looper.getMainLooper());
+ mLeScanClients = new HashMap<Settings, BleScanCallbackWrapper>();
+ }
+
+ /**
+ * Bluetooth GATT interface callbacks
+ */
+ private static class BleScanCallbackWrapper extends IBluetoothGattCallback.Stub {
+ private static final int REGISTRATION_CALLBACK_TIMEOUT_SECONDS = 5;
+
+ private final ScanCallback mScanCallback;
+ private final List<BluetoothLeScanFilter> mFilters;
+ private Settings mSettings;
+ private IBluetoothGatt mBluetoothGatt;
+
+ // mLeHandle 0: not registered
+ // -1: scan stopped
+ // > 0: registered and scan started
+ private int mLeHandle;
+
+ public BleScanCallbackWrapper(IBluetoothGatt bluetoothGatt,
+ List<BluetoothLeScanFilter> filters, Settings settings, ScanCallback scanCallback) {
+ mBluetoothGatt = bluetoothGatt;
+ mFilters = filters;
+ mSettings = settings;
+ mScanCallback = scanCallback;
+ mLeHandle = 0;
+ }
+
+ public boolean scanStarted() {
+ synchronized (this) {
+ if (mLeHandle == -1) {
+ return false;
+ }
+ try {
+ wait(REGISTRATION_CALLBACK_TIMEOUT_SECONDS);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Callback reg wait interrupted: " + e);
+ }
+ }
+ return mLeHandle > 0;
+ }
+
+ public void stopLeScan() {
+ synchronized (this) {
+ if (mLeHandle <= 0) {
+ Log.e(TAG, "Error state, mLeHandle: " + mLeHandle);
+ return;
+ }
+ try {
+ mBluetoothGatt.stopScan(mLeHandle, false);
+ mBluetoothGatt.unregisterClient(mLeHandle);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to stop scan and unregister" + e);
+ }
+ mLeHandle = -1;
+ notifyAll();
+ }
+ }
+
+ /**
+ * Application interface registered - app is ready to go
+ */
+ @Override
+ public void onClientRegistered(int status, int clientIf) {
+ Log.d(TAG, "onClientRegistered() - status=" + status +
+ " clientIf=" + clientIf);
+
+ synchronized (this) {
+ if (mLeHandle == -1) {
+ if (DBG)
+ Log.d(TAG, "onClientRegistered LE scan canceled");
+ }
+
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ mLeHandle = clientIf;
+ try {
+ mBluetoothGatt.startScanWithFilters(mLeHandle, false, mSettings, mFilters);
+ } catch (RemoteException e) {
+ Log.e(TAG, "fail to start le scan: " + e);
+ mLeHandle = -1;
+ }
+ } else {
+ // registration failed
+ mLeHandle = -1;
+ }
+ notifyAll();
+ }
+ }
+
+ @Override
+ public void onClientConnectionState(int status, int clientIf,
+ boolean connected, String address) {
+ // no op
+ }
+
+ /**
+ * Callback reporting an LE scan result.
+ *
+ * @hide
+ */
+ @Override
+ public void onScanResult(String address, int rssi, byte[] advData) {
+ if (DBG)
+ Log.d(TAG, "onScanResult() - Device=" + address + " RSSI=" + rssi);
+
+ // Check null in case the scan has been stopped
+ synchronized (this) {
+ if (mLeHandle <= 0)
+ return;
+ }
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(
+ address);
+ long scanMicros = TimeUnit.NANOSECONDS.toMicros(SystemClock.elapsedRealtimeNanos());
+ ScanResult result = new ScanResult(device, advData, rssi,
+ scanMicros);
+ mScanCallback.onDeviceUpdate(result);
+ }
+
+ @Override
+ public void onGetService(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid) {
+ // no op
+ }
+
+ @Override
+ public void onGetIncludedService(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int inclSrvcType, int inclSrvcInstId,
+ ParcelUuid inclSrvcUuid) {
+ // no op
+ }
+
+ @Override
+ public void onGetCharacteristic(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ int charProps) {
+ // no op
+ }
+
+ @Override
+ public void onGetDescriptor(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ int descInstId, ParcelUuid descUuid) {
+ // no op
+ }
+
+ @Override
+ public void onSearchComplete(String address, int status) {
+ // no op
+ }
+
+ @Override
+ public void onCharacteristicRead(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid, byte[] value) {
+ // no op
+ }
+
+ @Override
+ public void onCharacteristicWrite(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid) {
+ // no op
+ }
+
+ @Override
+ public void onNotify(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ byte[] value) {
+ // no op
+ }
+
+ @Override
+ public void onDescriptorRead(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ int descInstId, ParcelUuid descrUuid, byte[] value) {
+ // no op
+ }
+
+ @Override
+ public void onDescriptorWrite(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ int descInstId, ParcelUuid descrUuid) {
+ // no op
+ }
+
+ @Override
+ public void onExecuteWrite(String address, int status) {
+ // no op
+ }
+
+ @Override
+ public void onReadRemoteRssi(String address, int rssi, int status) {
+ // no op
+ }
+
+ @Override
+ public void onAdvertiseStateChange(int advertiseState, int status) {
+ // no op
+ }
+
+ @Override
+ public void onMultiAdvertiseCallback(int status) {
+ // no op
+ }
+
+ @Override
+ public void onConfigureMTU(String address, int mtu, int status) {
+ // no op
+ }
+ }
+
+ /**
+ * Scan Bluetooth LE scan. The scan results will be delivered through {@code callback}.
+ *
+ * @param filters {@link BluetoothLeScanFilter}s for finding exact BLE devices.
+ * @param settings Settings for ble scan.
+ * @param callback Callback when scan results are delivered.
+ * @throws IllegalArgumentException If {@code settings} or {@code callback} is null.
+ */
+ public void startScan(List<BluetoothLeScanFilter> filters, Settings settings,
+ final ScanCallback callback) {
+ if (settings == null || callback == null) {
+ throw new IllegalArgumentException("settings or callback is null");
+ }
+ synchronized (mLeScanClients) {
+ if (mLeScanClients.get(settings) != null) {
+ postCallbackError(callback, ScanCallback.SCAN_ALREADY_STARTED);
+ return;
+ }
+ BleScanCallbackWrapper wrapper = new BleScanCallbackWrapper(mBluetoothGatt, filters,
+ settings, callback);
+ try {
+ UUID uuid = UUID.randomUUID();
+ mBluetoothGatt.registerClient(new ParcelUuid(uuid), wrapper);
+ if (wrapper.scanStarted()) {
+ mLeScanClients.put(settings, wrapper);
+ } else {
+ postCallbackError(callback, ScanCallback.APPLICATION_REGISTRATION_FAILED);
+ return;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "GATT service exception when starting scan", e);
+ postCallbackError(callback, ScanCallback.GATT_SERVICE_FAILURE);
+ }
+ }
+ }
+
+ private void postCallbackError(final ScanCallback callback, final int errorCode) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onScanFailed(errorCode);
+ }
+ });
+ }
+
+ /**
+ * Stop Bluetooth LE scan.
+ *
+ * @param settings The same settings as used in {@link #startScan}, which is used to identify
+ * the BLE scan.
+ */
+ public void stopScan(Settings settings) {
+ synchronized (mLeScanClients) {
+ BleScanCallbackWrapper wrapper = mLeScanClients.remove(settings);
+ if (wrapper == null) {
+ return;
+ }
+ wrapper.stopLeScan();
+ }
+ }
+
+ /**
+ * Returns available storage size for batch scan results. It's recommended not to use batch scan
+ * if available storage size is small (less than 1k bytes, for instance).
+ *
+ * @hide TODO: unhide when batching is supported in stack.
+ */
+ public int getAvailableBatchStorageSizeBytes() {
+ throw new UnsupportedOperationException("not impelemented");
+ }
+
+ /**
+ * Poll scan results from bluetooth controller. This will return Bluetooth LE scan results
+ * batched on bluetooth controller.
+ *
+ * @param callback Callback of the Bluetooth LE Scan, it has to be the same instance as the one
+ * used to start scan.
+ * @param flush Whether to flush the batch scan buffer. Note the other batch scan clients will
+ * get batch scan callback if the batch scan buffer is flushed.
+ * @return Batch Scan results.
+ * @hide TODO: unhide when batching is supported in stack.
+ */
+ public List<ScanResult> getBatchScanResults(ScanCallback callback, boolean flush) {
+ throw new UnsupportedOperationException("not impelemented");
+ }
+
+}
diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl
index 3dd7094..091b806 100644
--- a/core/java/android/bluetooth/IBluetoothGatt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGatt.aidl
@@ -17,6 +17,10 @@
package android.bluetooth;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeAdvertiseScanData;
+import android.bluetooth.BluetoothLeAdvertiser;
+import android.bluetooth.BluetoothLeScanFilter;
+import android.bluetooth.BluetoothLeScanner;
import android.os.ParcelUuid;
import android.bluetooth.IBluetoothGattCallback;
@@ -33,8 +37,13 @@ interface IBluetoothGatt {
void startScanWithUuids(in int appIf, in boolean isServer, in ParcelUuid[] ids);
void startScanWithUuidsScanParam(in int appIf, in boolean isServer,
in ParcelUuid[] ids, int scanWindow, int scanInterval);
+ void startScanWithFilters(in int appIf, in boolean isServer,
+ in BluetoothLeScanner.Settings settings,
+ in List<BluetoothLeScanFilter> filters);
void stopScan(in int appIf, in boolean isServer);
-
+ void startMultiAdvertising(in int appIf, in BluetoothLeAdvertiseScanData.AdvertisementData data,
+ in BluetoothLeAdvertiser.Settings settings);
+ void stopMultiAdvertising(in int appIf);
void registerClient(in ParcelUuid appId, in IBluetoothGattCallback callback);
void unregisterClient(in int clientIf);
void clientConnect(in int clientIf, in String address, in boolean isDirect, in int transport);
diff --git a/core/java/android/bluetooth/IBluetoothGattCallback.aidl b/core/java/android/bluetooth/IBluetoothGattCallback.aidl
index a78c29b..bf9e0a7 100644
--- a/core/java/android/bluetooth/IBluetoothGattCallback.aidl
+++ b/core/java/android/bluetooth/IBluetoothGattCallback.aidl
@@ -64,5 +64,6 @@ interface IBluetoothGattCallback {
in byte[] value);
void onReadRemoteRssi(in String address, in int rssi, in int status);
oneway void onAdvertiseStateChange(in int advertiseState, in int status);
+ oneway void onMultiAdvertiseCallback(in int status);
void onConfigureMTU(in String address, in int mtu, in int status);
}