/* * 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.le; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Parcel; import android.os.ParcelUuid; import android.os.Parcelable; import android.util.ArrayMap; import android.util.SparseArray; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; /** * Advertise data packet container for Bluetooth LE advertising. This represents the data to be * advertised as well as the scan response data for active scans. * *

Use {@link AdvertiseData.Builder} to create an instance of {@link AdvertiseData} to be * advertised. * * @see BluetoothLeAdvertiser * @see ScanRecord */ public final class AdvertiseData implements Parcelable { @Nullable private final List mServiceUuids; @NonNull private final List mServiceSolicitationUuids; @Nullable private final List mTransportDiscoveryData; private final SparseArray mManufacturerSpecificData; private final Map mServiceData; private final boolean mIncludeTxPowerLevel; private final boolean mIncludeDeviceName; private AdvertiseData( List serviceUuids, List serviceSolicitationUuids, List transportDiscoveryData, SparseArray manufacturerData, Map serviceData, boolean includeTxPowerLevel, boolean includeDeviceName) { mServiceUuids = serviceUuids; mServiceSolicitationUuids = serviceSolicitationUuids; mTransportDiscoveryData = transportDiscoveryData; mManufacturerSpecificData = manufacturerData; mServiceData = serviceData; mIncludeTxPowerLevel = includeTxPowerLevel; mIncludeDeviceName = includeDeviceName; } /** * Returns a list of service UUIDs within the advertisement that are used to identify the * Bluetooth GATT services. */ public List getServiceUuids() { return mServiceUuids; } /** * Returns a list of service solicitation UUIDs within the advertisement that we invite to * connect. */ @NonNull public List getServiceSolicitationUuids() { return mServiceSolicitationUuids; } /** Returns a list of {@link TransportDiscoveryData} within the advertisement. */ @NonNull public List getTransportDiscoveryData() { if (mTransportDiscoveryData == null) { return Collections.emptyList(); } return mTransportDiscoveryData; } /** * Returns an array of manufacturer Id and the corresponding manufacturer specific data. The * manufacturer id is a non-negative number assigned by Bluetooth SIG. */ public SparseArray getManufacturerSpecificData() { return mManufacturerSpecificData; } /** Returns a map of 16-bit UUID and its corresponding service data. */ public Map getServiceData() { return mServiceData; } /** Whether the transmission power level will be included in the advertisement packet. */ public boolean getIncludeTxPowerLevel() { return mIncludeTxPowerLevel; } /** Whether the device name will be included in the advertisement packet. */ public boolean getIncludeDeviceName() { return mIncludeDeviceName; } /** @hide */ @Override public int hashCode() { return Objects.hash( mServiceUuids, mServiceSolicitationUuids, mTransportDiscoveryData, mManufacturerSpecificData, mServiceData, mIncludeDeviceName, mIncludeTxPowerLevel); } /** @hide */ @Override public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } AdvertiseData other = (AdvertiseData) obj; return Objects.equals(mServiceUuids, other.mServiceUuids) && Objects.equals(mServiceSolicitationUuids, other.mServiceSolicitationUuids) && Objects.equals(mTransportDiscoveryData, other.mTransportDiscoveryData) && BluetoothLeUtils.equals( mManufacturerSpecificData, other.mManufacturerSpecificData) && BluetoothLeUtils.equals(mServiceData, other.mServiceData) && mIncludeDeviceName == other.mIncludeDeviceName && mIncludeTxPowerLevel == other.mIncludeTxPowerLevel; } @Override public String toString() { return "AdvertiseData [mServiceUuids=" + mServiceUuids + ", mServiceSolicitationUuids=" + mServiceSolicitationUuids + ", mTransportDiscoveryData=" + mTransportDiscoveryData + ", mManufacturerSpecificData=" + BluetoothLeUtils.toString(mManufacturerSpecificData) + ", mServiceData=" + BluetoothLeUtils.toString(mServiceData) + ", mIncludeTxPowerLevel=" + mIncludeTxPowerLevel + ", mIncludeDeviceName=" + mIncludeDeviceName + "]"; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeTypedArray(mServiceUuids.toArray(new ParcelUuid[mServiceUuids.size()]), flags); dest.writeTypedArray( mServiceSolicitationUuids.toArray(new ParcelUuid[mServiceSolicitationUuids.size()]), flags); dest.writeTypedList(mTransportDiscoveryData); // mManufacturerSpecificData could not be null. dest.writeInt(mManufacturerSpecificData.size()); for (int i = 0; i < mManufacturerSpecificData.size(); ++i) { dest.writeInt(mManufacturerSpecificData.keyAt(i)); dest.writeByteArray(mManufacturerSpecificData.valueAt(i)); } dest.writeInt(mServiceData.size()); for (ParcelUuid uuid : mServiceData.keySet()) { dest.writeTypedObject(uuid, flags); dest.writeByteArray(mServiceData.get(uuid)); } dest.writeByte((byte) (getIncludeTxPowerLevel() ? 1 : 0)); dest.writeByte((byte) (getIncludeDeviceName() ? 1 : 0)); } public static final @android.annotation.NonNull Parcelable.Creator CREATOR = new Creator() { @Override public AdvertiseData[] newArray(int size) { return new AdvertiseData[size]; } @Override public AdvertiseData createFromParcel(Parcel in) { Builder builder = new Builder(); ArrayList uuids = in.createTypedArrayList(ParcelUuid.CREATOR); for (ParcelUuid uuid : uuids) { builder.addServiceUuid(uuid); } ArrayList solicitationUuids = in.createTypedArrayList(ParcelUuid.CREATOR); for (ParcelUuid uuid : solicitationUuids) { builder.addServiceSolicitationUuid(uuid); } List transportDiscoveryData = in.createTypedArrayList(TransportDiscoveryData.CREATOR); for (TransportDiscoveryData tdd : transportDiscoveryData) { builder.addTransportDiscoveryData(tdd); } int manufacturerSize = in.readInt(); for (int i = 0; i < manufacturerSize; ++i) { int manufacturerId = in.readInt(); byte[] manufacturerData = in.createByteArray(); builder.addManufacturerData(manufacturerId, manufacturerData); } int serviceDataSize = in.readInt(); for (int i = 0; i < serviceDataSize; ++i) { ParcelUuid serviceDataUuid = in.readTypedObject(ParcelUuid.CREATOR); byte[] serviceData = in.createByteArray(); builder.addServiceData(serviceDataUuid, serviceData); } builder.setIncludeTxPowerLevel(in.readByte() == 1); builder.setIncludeDeviceName(in.readByte() == 1); return builder.build(); } }; /** Builder for {@link AdvertiseData}. */ public static final class Builder { @Nullable private List mServiceUuids = new ArrayList(); @NonNull private List mServiceSolicitationUuids = new ArrayList(); @Nullable private List mTransportDiscoveryData = new ArrayList(); private SparseArray mManufacturerSpecificData = new SparseArray(); private Map mServiceData = new ArrayMap(); private boolean mIncludeTxPowerLevel; private boolean mIncludeDeviceName; /** * Add a service UUID to advertise data. * * @param serviceUuid A service UUID to be advertised. * @throws IllegalArgumentException If the {@code serviceUuid} is null. */ public Builder addServiceUuid(ParcelUuid serviceUuid) { if (serviceUuid == null) { throw new IllegalArgumentException("serviceUuid is null"); } mServiceUuids.add(serviceUuid); return this; } /** * Add a service solicitation UUID to advertise data. * * @param serviceSolicitationUuid A service solicitation UUID to be advertised. * @throws IllegalArgumentException If the {@code serviceSolicitationUuid} is null. */ @NonNull public Builder addServiceSolicitationUuid(@NonNull ParcelUuid serviceSolicitationUuid) { if (serviceSolicitationUuid == null) { throw new IllegalArgumentException("serviceSolicitationUuid is null"); } mServiceSolicitationUuids.add(serviceSolicitationUuid); return this; } /** * Add service data to advertise data. * * @param serviceDataUuid 16-bit UUID of the service the data is associated with * @param serviceData Service data * @throws IllegalArgumentException If the {@code serviceDataUuid} or {@code serviceData} is * empty. */ public Builder addServiceData(ParcelUuid serviceDataUuid, byte[] serviceData) { if (serviceDataUuid == null || serviceData == null) { throw new IllegalArgumentException("serviceDataUuid or serviceDataUuid is null"); } mServiceData.put(serviceDataUuid, serviceData); return this; } /** * Add Transport Discovery Data to advertise data. * * @param transportDiscoveryData Transport Discovery Data, consisting of one or more * Transport Blocks. Transport Discovery Data AD Type Code is already included. * @throws IllegalArgumentException If the {@code transportDiscoveryData} is empty */ @NonNull public Builder addTransportDiscoveryData( @NonNull TransportDiscoveryData transportDiscoveryData) { if (transportDiscoveryData == null) { throw new IllegalArgumentException("transportDiscoveryData is null"); } mTransportDiscoveryData.add(transportDiscoveryData); return this; } /** * Add manufacturer specific data. * *

Please refer to the Bluetooth Assigned Numbers document provided by the Bluetooth SIG for a list of existing company * identifiers. * * @param manufacturerId Manufacturer ID assigned by Bluetooth SIG. * @param manufacturerSpecificData Manufacturer specific data * @throws IllegalArgumentException If the {@code manufacturerId} is negative or {@code * manufacturerSpecificData} is null. */ public Builder addManufacturerData(int manufacturerId, byte[] manufacturerSpecificData) { if (manufacturerId < 0) { throw new IllegalArgumentException("invalid manufacturerId - " + manufacturerId); } if (manufacturerSpecificData == null) { throw new IllegalArgumentException("manufacturerSpecificData is null"); } mManufacturerSpecificData.put(manufacturerId, manufacturerSpecificData); return this; } /** * Whether the transmission power level should be included in the advertise packet. Tx power * level field takes 3 bytes in advertise packet. */ public Builder setIncludeTxPowerLevel(boolean includeTxPowerLevel) { mIncludeTxPowerLevel = includeTxPowerLevel; return this; } /** Set whether the device name should be included in advertise packet. */ public Builder setIncludeDeviceName(boolean includeDeviceName) { mIncludeDeviceName = includeDeviceName; return this; } /** Build the {@link AdvertiseData}. */ public AdvertiseData build() { return new AdvertiseData( mServiceUuids, mServiceSolicitationUuids, mTransportDiscoveryData, mManufacturerSpecificData, mServiceData, mIncludeTxPowerLevel, mIncludeDeviceName); } } }