1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.bluetooth.le;
18
19import android.annotation.Nullable;
20import android.os.Parcel;
21import android.os.ParcelUuid;
22import android.os.Parcelable;
23import android.util.ArrayMap;
24import android.util.SparseArray;
25
26import java.util.ArrayList;
27import java.util.List;
28import java.util.Map;
29import java.util.Objects;
30
31/**
32 * Advertise data packet container for Bluetooth LE advertising. This represents the data to be
33 * advertised as well as the scan response data for active scans.
34 * <p>
35 * Use {@link AdvertiseData.Builder} to create an instance of {@link AdvertiseData} to be
36 * advertised.
37 *
38 * @see BluetoothLeAdvertiser
39 * @see ScanRecord
40 */
41public final class AdvertiseData implements Parcelable {
42
43    @Nullable
44    private final List<ParcelUuid> mServiceUuids;
45
46    private final SparseArray<byte[]> mManufacturerSpecificData;
47    private final Map<ParcelUuid, byte[]> mServiceData;
48    private final boolean mIncludeTxPowerLevel;
49    private final boolean mIncludeDeviceName;
50
51    private AdvertiseData(List<ParcelUuid> serviceUuids,
52            SparseArray<byte[]> manufacturerData,
53            Map<ParcelUuid, byte[]> serviceData,
54            boolean includeTxPowerLevel,
55            boolean includeDeviceName) {
56        mServiceUuids = serviceUuids;
57        mManufacturerSpecificData = manufacturerData;
58        mServiceData = serviceData;
59        mIncludeTxPowerLevel = includeTxPowerLevel;
60        mIncludeDeviceName = includeDeviceName;
61    }
62
63    /**
64     * Returns a list of service UUIDs within the advertisement that are used to identify the
65     * Bluetooth GATT services.
66     */
67    public List<ParcelUuid> getServiceUuids() {
68        return mServiceUuids;
69    }
70
71    /**
72     * Returns an array of manufacturer Id and the corresponding manufacturer specific data. The
73     * manufacturer id is a non-negative number assigned by Bluetooth SIG.
74     */
75    public SparseArray<byte[]> getManufacturerSpecificData() {
76        return mManufacturerSpecificData;
77    }
78
79    /**
80     * Returns a map of 16-bit UUID and its corresponding service data.
81     */
82    public Map<ParcelUuid, byte[]> getServiceData() {
83        return mServiceData;
84    }
85
86    /**
87     * Whether the transmission power level will be included in the advertisement packet.
88     */
89    public boolean getIncludeTxPowerLevel() {
90        return mIncludeTxPowerLevel;
91    }
92
93    /**
94     * Whether the device name will be included in the advertisement packet.
95     */
96    public boolean getIncludeDeviceName() {
97        return mIncludeDeviceName;
98    }
99
100    /**
101     * @hide
102     */
103    @Override
104    public int hashCode() {
105        return Objects.hash(mServiceUuids, mManufacturerSpecificData, mServiceData,
106                mIncludeDeviceName, mIncludeTxPowerLevel);
107    }
108
109    /**
110     * @hide
111     */
112    @Override
113    public boolean equals(Object obj) {
114        if (this == obj) {
115            return true;
116        }
117        if (obj == null || getClass() != obj.getClass()) {
118            return false;
119        }
120        AdvertiseData other = (AdvertiseData) obj;
121        return Objects.equals(mServiceUuids, other.mServiceUuids) &&
122                BluetoothLeUtils.equals(mManufacturerSpecificData, other.mManufacturerSpecificData) &&
123                BluetoothLeUtils.equals(mServiceData, other.mServiceData) &&
124                        mIncludeDeviceName == other.mIncludeDeviceName &&
125                        mIncludeTxPowerLevel == other.mIncludeTxPowerLevel;
126    }
127
128    @Override
129    public String toString() {
130        return "AdvertiseData [mServiceUuids=" + mServiceUuids + ", mManufacturerSpecificData="
131                + BluetoothLeUtils.toString(mManufacturerSpecificData) + ", mServiceData="
132                + BluetoothLeUtils.toString(mServiceData)
133                + ", mIncludeTxPowerLevel=" + mIncludeTxPowerLevel + ", mIncludeDeviceName="
134                + mIncludeDeviceName + "]";
135    }
136
137    @Override
138    public int describeContents() {
139        return 0;
140    }
141
142    @Override
143    public void writeToParcel(Parcel dest, int flags) {
144        dest.writeList(mServiceUuids);
145
146        // mManufacturerSpecificData could not be null.
147        dest.writeInt(mManufacturerSpecificData.size());
148        for (int i = 0; i < mManufacturerSpecificData.size(); ++i) {
149            dest.writeInt(mManufacturerSpecificData.keyAt(i));
150            byte[] data = mManufacturerSpecificData.valueAt(i);
151            if (data == null) {
152                dest.writeInt(0);
153            } else {
154                dest.writeInt(1);
155                dest.writeInt(data.length);
156                dest.writeByteArray(data);
157            }
158        }
159        dest.writeInt(mServiceData.size());
160        for (ParcelUuid uuid : mServiceData.keySet()) {
161            dest.writeParcelable(uuid, flags);
162            byte[] data = mServiceData.get(uuid);
163            if (data == null) {
164                dest.writeInt(0);
165            } else {
166                dest.writeInt(1);
167                dest.writeInt(data.length);
168                dest.writeByteArray(data);
169            }
170        }
171        dest.writeByte((byte) (getIncludeTxPowerLevel() ? 1 : 0));
172        dest.writeByte((byte) (getIncludeDeviceName() ? 1 : 0));
173    }
174
175    public static final Parcelable.Creator<AdvertiseData> CREATOR =
176            new Creator<AdvertiseData>() {
177            @Override
178                public AdvertiseData[] newArray(int size) {
179                    return new AdvertiseData[size];
180                }
181
182            @Override
183                public AdvertiseData createFromParcel(Parcel in) {
184                    Builder builder = new Builder();
185                    @SuppressWarnings("unchecked")
186                    List<ParcelUuid> uuids = in.readArrayList(ParcelUuid.class.getClassLoader());
187                    if (uuids != null) {
188                        for (ParcelUuid uuid : uuids) {
189                            builder.addServiceUuid(uuid);
190                        }
191                    }
192                    int manufacturerSize = in.readInt();
193                    for (int i = 0; i < manufacturerSize; ++i) {
194                        int manufacturerId = in.readInt();
195                        if (in.readInt() == 1) {
196                            int manufacturerDataLength = in.readInt();
197                            byte[] manufacturerData = new byte[manufacturerDataLength];
198                            in.readByteArray(manufacturerData);
199                            builder.addManufacturerData(manufacturerId, manufacturerData);
200                        }
201                    }
202                    int serviceDataSize = in.readInt();
203                    for (int i = 0; i < serviceDataSize; ++i) {
204                        ParcelUuid serviceDataUuid = in.readParcelable(
205                                ParcelUuid.class.getClassLoader());
206                        if (in.readInt() == 1) {
207                            int serviceDataLength = in.readInt();
208                            byte[] serviceData = new byte[serviceDataLength];
209                            in.readByteArray(serviceData);
210                            builder.addServiceData(serviceDataUuid, serviceData);
211                        }
212                    }
213                    builder.setIncludeTxPowerLevel(in.readByte() == 1);
214                    builder.setIncludeDeviceName(in.readByte() == 1);
215                    return builder.build();
216                }
217            };
218
219    /**
220     * Builder for {@link AdvertiseData}.
221     */
222    public static final class Builder {
223        @Nullable
224        private List<ParcelUuid> mServiceUuids = new ArrayList<ParcelUuid>();
225        private SparseArray<byte[]> mManufacturerSpecificData = new SparseArray<byte[]>();
226        private Map<ParcelUuid, byte[]> mServiceData = new ArrayMap<ParcelUuid, byte[]>();
227        private boolean mIncludeTxPowerLevel;
228        private boolean mIncludeDeviceName;
229
230        /**
231         * Add a service UUID to advertise data.
232         *
233         * @param serviceUuid A service UUID to be advertised.
234         * @throws IllegalArgumentException If the {@code serviceUuids} are null.
235         */
236        public Builder addServiceUuid(ParcelUuid serviceUuid) {
237            if (serviceUuid == null) {
238                throw new IllegalArgumentException("serivceUuids are null");
239            }
240            mServiceUuids.add(serviceUuid);
241            return this;
242        }
243
244        /**
245         * Add service data to advertise data.
246         *
247         * @param serviceDataUuid 16-bit UUID of the service the data is associated with
248         * @param serviceData Service data
249         * @throws IllegalArgumentException If the {@code serviceDataUuid} or {@code serviceData} is
250         *             empty.
251         */
252        public Builder addServiceData(ParcelUuid serviceDataUuid, byte[] serviceData) {
253            if (serviceDataUuid == null || serviceData == null) {
254                throw new IllegalArgumentException(
255                        "serviceDataUuid or serviceDataUuid is null");
256            }
257            mServiceData.put(serviceDataUuid, serviceData);
258            return this;
259        }
260
261        /**
262         * Add manufacturer specific data.
263         * <p>
264         * Please refer to the Bluetooth Assigned Numbers document provided by the <a
265         * href="https://www.bluetooth.org">Bluetooth SIG</a> for a list of existing company
266         * identifiers.
267         *
268         * @param manufacturerId Manufacturer ID assigned by Bluetooth SIG.
269         * @param manufacturerSpecificData Manufacturer specific data
270         * @throws IllegalArgumentException If the {@code manufacturerId} is negative or
271         *             {@code manufacturerSpecificData} is null.
272         */
273        public Builder addManufacturerData(int manufacturerId, byte[] manufacturerSpecificData) {
274            if (manufacturerId < 0) {
275                throw new IllegalArgumentException(
276                        "invalid manufacturerId - " + manufacturerId);
277            }
278            if (manufacturerSpecificData == null) {
279                throw new IllegalArgumentException("manufacturerSpecificData is null");
280            }
281            mManufacturerSpecificData.put(manufacturerId, manufacturerSpecificData);
282            return this;
283        }
284
285        /**
286         * Whether the transmission power level should be included in the advertise packet. Tx power
287         * level field takes 3 bytes in advertise packet.
288         */
289        public Builder setIncludeTxPowerLevel(boolean includeTxPowerLevel) {
290            mIncludeTxPowerLevel = includeTxPowerLevel;
291            return this;
292        }
293
294        /**
295         * Set whether the device name should be included in advertise packet.
296         */
297        public Builder setIncludeDeviceName(boolean includeDeviceName) {
298            mIncludeDeviceName = includeDeviceName;
299            return this;
300        }
301
302        /**
303         * Build the {@link AdvertiseData}.
304         */
305        public AdvertiseData build() {
306            return new AdvertiseData(mServiceUuids, mManufacturerSpecificData, mServiceData,
307                    mIncludeTxPowerLevel, mIncludeDeviceName);
308        }
309    }
310}
311