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,
123                    other.mManufacturerSpecificData)
124                && BluetoothLeUtils.equals(mServiceData, other.mServiceData)
125                && mIncludeDeviceName == other.mIncludeDeviceName
126                && mIncludeTxPowerLevel == other.mIncludeTxPowerLevel;
127    }
128
129    @Override
130    public String toString() {
131        return "AdvertiseData [mServiceUuids=" + mServiceUuids + ", mManufacturerSpecificData="
132                + BluetoothLeUtils.toString(mManufacturerSpecificData) + ", mServiceData="
133                + BluetoothLeUtils.toString(mServiceData)
134                + ", mIncludeTxPowerLevel=" + mIncludeTxPowerLevel + ", mIncludeDeviceName="
135                + mIncludeDeviceName + "]";
136    }
137
138    @Override
139    public int describeContents() {
140        return 0;
141    }
142
143    @Override
144    public void writeToParcel(Parcel dest, int flags) {
145        dest.writeTypedArray(mServiceUuids.toArray(new ParcelUuid[mServiceUuids.size()]), flags);
146
147        // mManufacturerSpecificData could not be null.
148        dest.writeInt(mManufacturerSpecificData.size());
149        for (int i = 0; i < mManufacturerSpecificData.size(); ++i) {
150            dest.writeInt(mManufacturerSpecificData.keyAt(i));
151            dest.writeByteArray(mManufacturerSpecificData.valueAt(i));
152        }
153        dest.writeInt(mServiceData.size());
154        for (ParcelUuid uuid : mServiceData.keySet()) {
155            dest.writeTypedObject(uuid, flags);
156            dest.writeByteArray(mServiceData.get(uuid));
157        }
158        dest.writeByte((byte) (getIncludeTxPowerLevel() ? 1 : 0));
159        dest.writeByte((byte) (getIncludeDeviceName() ? 1 : 0));
160    }
161
162    public static final Parcelable.Creator<AdvertiseData> CREATOR =
163            new Creator<AdvertiseData>() {
164                @Override
165                public AdvertiseData[] newArray(int size) {
166                    return new AdvertiseData[size];
167                }
168
169                @Override
170                public AdvertiseData createFromParcel(Parcel in) {
171                    Builder builder = new Builder();
172                    ArrayList<ParcelUuid> uuids = in.createTypedArrayList(ParcelUuid.CREATOR);
173                    for (ParcelUuid uuid : uuids) {
174                        builder.addServiceUuid(uuid);
175                    }
176
177                    int manufacturerSize = in.readInt();
178                    for (int i = 0; i < manufacturerSize; ++i) {
179                        int manufacturerId = in.readInt();
180                        byte[] manufacturerData = in.createByteArray();
181                        builder.addManufacturerData(manufacturerId, manufacturerData);
182                    }
183                    int serviceDataSize = in.readInt();
184                    for (int i = 0; i < serviceDataSize; ++i) {
185                        ParcelUuid serviceDataUuid = in.readTypedObject(ParcelUuid.CREATOR);
186                        byte[] serviceData = in.createByteArray();
187                        builder.addServiceData(serviceDataUuid, serviceData);
188                    }
189                    builder.setIncludeTxPowerLevel(in.readByte() == 1);
190                    builder.setIncludeDeviceName(in.readByte() == 1);
191                    return builder.build();
192                }
193            };
194
195    /**
196     * Builder for {@link AdvertiseData}.
197     */
198    public static final class Builder {
199        @Nullable
200        private List<ParcelUuid> mServiceUuids = new ArrayList<ParcelUuid>();
201        private SparseArray<byte[]> mManufacturerSpecificData = new SparseArray<byte[]>();
202        private Map<ParcelUuid, byte[]> mServiceData = new ArrayMap<ParcelUuid, byte[]>();
203        private boolean mIncludeTxPowerLevel;
204        private boolean mIncludeDeviceName;
205
206        /**
207         * Add a service UUID to advertise data.
208         *
209         * @param serviceUuid A service UUID to be advertised.
210         * @throws IllegalArgumentException If the {@code serviceUuids} are null.
211         */
212        public Builder addServiceUuid(ParcelUuid serviceUuid) {
213            if (serviceUuid == null) {
214                throw new IllegalArgumentException("serivceUuids are null");
215            }
216            mServiceUuids.add(serviceUuid);
217            return this;
218        }
219
220        /**
221         * Add service data to advertise data.
222         *
223         * @param serviceDataUuid 16-bit UUID of the service the data is associated with
224         * @param serviceData Service data
225         * @throws IllegalArgumentException If the {@code serviceDataUuid} or {@code serviceData} is
226         * empty.
227         */
228        public Builder addServiceData(ParcelUuid serviceDataUuid, byte[] serviceData) {
229            if (serviceDataUuid == null || serviceData == null) {
230                throw new IllegalArgumentException(
231                        "serviceDataUuid or serviceDataUuid is null");
232            }
233            mServiceData.put(serviceDataUuid, serviceData);
234            return this;
235        }
236
237        /**
238         * Add manufacturer specific data.
239         * <p>
240         * Please refer to the Bluetooth Assigned Numbers document provided by the <a
241         * href="https://www.bluetooth.org">Bluetooth SIG</a> for a list of existing company
242         * identifiers.
243         *
244         * @param manufacturerId Manufacturer ID assigned by Bluetooth SIG.
245         * @param manufacturerSpecificData Manufacturer specific data
246         * @throws IllegalArgumentException If the {@code manufacturerId} is negative or {@code
247         * manufacturerSpecificData} is null.
248         */
249        public Builder addManufacturerData(int manufacturerId, byte[] manufacturerSpecificData) {
250            if (manufacturerId < 0) {
251                throw new IllegalArgumentException(
252                        "invalid manufacturerId - " + manufacturerId);
253            }
254            if (manufacturerSpecificData == null) {
255                throw new IllegalArgumentException("manufacturerSpecificData is null");
256            }
257            mManufacturerSpecificData.put(manufacturerId, manufacturerSpecificData);
258            return this;
259        }
260
261        /**
262         * Whether the transmission power level should be included in the advertise packet. Tx power
263         * level field takes 3 bytes in advertise packet.
264         */
265        public Builder setIncludeTxPowerLevel(boolean includeTxPowerLevel) {
266            mIncludeTxPowerLevel = includeTxPowerLevel;
267            return this;
268        }
269
270        /**
271         * Set whether the device name should be included in advertise packet.
272         */
273        public Builder setIncludeDeviceName(boolean includeDeviceName) {
274            mIncludeDeviceName = includeDeviceName;
275            return this;
276        }
277
278        /**
279         * Build the {@link AdvertiseData}.
280         */
281        public AdvertiseData build() {
282            return new AdvertiseData(mServiceUuids, mManufacturerSpecificData, mServiceData,
283                    mIncludeTxPowerLevel, mIncludeDeviceName);
284        }
285    }
286}
287