1/*
2 * Copyright (C) 2017 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.bluetooth.BluetoothAdapter;
20import android.bluetooth.BluetoothDevice;
21import android.os.Parcel;
22import android.os.Parcelable;
23
24/**
25 * The {@link AdvertisingSetParameters} provide a way to adjust advertising
26 * preferences for each
27 * Bluetooth LE advertising set. Use {@link AdvertisingSetParameters.Builder} to
28 * create an
29 * instance of this class.
30 */
31public final class AdvertisingSetParameters implements Parcelable {
32
33    /**
34    * Advertise on low frequency, around every 1000ms. This is the default and
35    * preferred advertising mode as it consumes the least power.
36    */
37    public static final int INTERVAL_HIGH = 1600;
38
39    /**
40     * Advertise on medium frequency, around every 250ms. This is balanced
41     * between advertising frequency and power consumption.
42     */
43    public static final int INTERVAL_MEDIUM = 400;
44
45    /**
46     * Perform high frequency, low latency advertising, around every 100ms. This
47     * has the highest power consumption and should not be used for continuous
48     * background advertising.
49     */
50    public static final int INTERVAL_LOW = 160;
51
52    /**
53     * Minimum value for advertising interval.
54     */
55    public static final int INTERVAL_MIN = 160;
56
57    /**
58     * Maximum value for advertising interval.
59     */
60    public static final int INTERVAL_MAX = 16777215;
61
62    /**
63     * Advertise using the lowest transmission (TX) power level. Low transmission
64     * power can be used to restrict the visibility range of advertising packets.
65     */
66    public static final int TX_POWER_ULTRA_LOW = -21;
67
68    /**
69     * Advertise using low TX power level.
70     */
71    public static final int TX_POWER_LOW = -15;
72
73    /**
74     * Advertise using medium TX power level.
75     */
76    public static final int TX_POWER_MEDIUM = -7;
77
78    /**
79     * Advertise using high TX power level. This corresponds to largest visibility
80     * range of the advertising packet.
81     */
82    public static final int TX_POWER_HIGH = 1;
83
84    /**
85     * Minimum value for TX power.
86     */
87    public static final int TX_POWER_MIN = -127;
88
89    /**
90     * Maximum value for TX power.
91     */
92    public static final int TX_POWER_MAX = 1;
93
94    /**
95     * The maximum limited advertisement duration as specified by the Bluetooth
96     * SIG
97     */
98    private static final int LIMITED_ADVERTISING_MAX_MILLIS = 180 * 1000;
99
100    private final boolean isLegacy;
101    private final boolean isAnonymous;
102    private final boolean includeTxPower;
103    private final int primaryPhy;
104    private final int secondaryPhy;
105    private final boolean connectable;
106    private final boolean scannable;
107    private final int interval;
108    private final int txPowerLevel;
109
110    private AdvertisingSetParameters(boolean connectable, boolean scannable, boolean isLegacy,
111                                     boolean isAnonymous, boolean includeTxPower,
112                                     int primaryPhy, int secondaryPhy,
113                                     int interval, int txPowerLevel) {
114        this.connectable = connectable;
115        this.scannable = scannable;
116        this.isLegacy = isLegacy;
117        this.isAnonymous = isAnonymous;
118        this.includeTxPower = includeTxPower;
119        this.primaryPhy = primaryPhy;
120        this.secondaryPhy = secondaryPhy;
121        this.interval = interval;
122        this.txPowerLevel = txPowerLevel;
123    }
124
125    private AdvertisingSetParameters(Parcel in) {
126        connectable = in.readInt() != 0 ? true : false;
127        scannable = in.readInt() != 0 ? true : false;
128        isLegacy = in.readInt() != 0 ? true : false;
129        isAnonymous = in.readInt() != 0 ? true : false;
130        includeTxPower = in.readInt() != 0 ? true : false;
131        primaryPhy = in.readInt();
132        secondaryPhy = in.readInt();
133        interval = in.readInt();
134        txPowerLevel = in.readInt();
135    }
136
137    /**
138     * Returns whether the advertisement will be connectable.
139     */
140    public boolean isConnectable() { return connectable; }
141
142    /**
143     * Returns whether the advertisement will be scannable.
144     */
145    public boolean isScannable() { return scannable; }
146
147    /**
148     * Returns whether the legacy advertisement will be used.
149     */
150    public boolean isLegacy() { return isLegacy; }
151
152    /**
153     * Returns whether the advertisement will be anonymous.
154     */
155    public boolean isAnonymous() { return isAnonymous; }
156
157    /**
158     * Returns whether the TX Power will be included.
159     */
160    public boolean includeTxPower() { return includeTxPower; }
161
162    /**
163     * Returns the primary advertising phy.
164     */
165    public int getPrimaryPhy() { return primaryPhy; }
166
167    /**
168     * Returns the secondary advertising phy.
169     */
170    public int getSecondaryPhy() { return secondaryPhy; }
171
172    /**
173     * Returns the advertising interval.
174     */
175    public int getInterval() { return interval; }
176
177    /**
178     * Returns the TX power level for advertising.
179     */
180    public int getTxPowerLevel() { return txPowerLevel; }
181
182    @Override
183    public String toString() {
184        return "AdvertisingSetParameters [connectable=" + connectable
185             + ", isLegacy=" + isLegacy
186             + ", isAnonymous=" + isAnonymous
187             + ", includeTxPower=" + includeTxPower
188             + ", primaryPhy=" + primaryPhy
189             + ", secondaryPhy=" + secondaryPhy
190             + ", interval=" + interval
191             + ", txPowerLevel=" + txPowerLevel + "]";
192    }
193
194    @Override
195    public int describeContents() {
196       return 0;
197    }
198
199    @Override
200    public void writeToParcel(Parcel dest, int flags) {
201        dest.writeInt(connectable ? 1 : 0);
202        dest.writeInt(scannable ? 1 : 0);
203        dest.writeInt(isLegacy ? 1 : 0);
204        dest.writeInt(isAnonymous ? 1 : 0);
205        dest.writeInt(includeTxPower ? 1 : 0);
206        dest.writeInt(primaryPhy);
207        dest.writeInt(secondaryPhy);
208        dest.writeInt(interval);
209        dest.writeInt(txPowerLevel);
210    }
211
212    public static final Parcelable.Creator<AdvertisingSetParameters> CREATOR =
213        new Creator<AdvertisingSetParameters>() {
214          @Override
215          public AdvertisingSetParameters[] newArray(int size) {
216            return new AdvertisingSetParameters[size];
217          }
218
219          @Override
220          public AdvertisingSetParameters createFromParcel(Parcel in) {
221            return new AdvertisingSetParameters(in);
222          }
223        };
224
225    /**
226     * Builder class for {@link AdvertisingSetParameters}.
227     */
228    public static final class Builder {
229
230        private boolean connectable = false;
231        private boolean scannable = false;
232        private boolean isLegacy = false;
233        private boolean isAnonymous = false;
234        private boolean includeTxPower = false;
235        private int primaryPhy = BluetoothDevice.PHY_LE_1M;
236        private int secondaryPhy = BluetoothDevice.PHY_LE_1M;
237        private int interval = INTERVAL_LOW;
238        private int txPowerLevel = TX_POWER_MEDIUM;
239
240        /**
241         * Set whether the advertisement type should be connectable or
242         * non-connectable.
243         * Legacy advertisements can be both connectable and scannable. Non-legacy
244         * advertisements can be only scannable or only connectable.
245         * @param connectable Controls whether the advertisement type will be
246         * connectable (true) or non-connectable (false).
247         */
248        public Builder setConnectable(boolean connectable) {
249            this.connectable = connectable;
250            return this;
251        }
252
253        /**
254         * Set whether the advertisement type should be scannable.
255         * Legacy advertisements can be both connectable and scannable. Non-legacy
256         * advertisements can be only scannable or only connectable.
257         * @param scannable Controls whether the advertisement type will be
258         * scannable (true) or non-scannable (false).
259         */
260        public Builder setScannable(boolean scannable) {
261            this.scannable = scannable;
262            return this;
263        }
264
265        /**
266         * When set to true, advertising set will advertise 4.x Spec compliant
267         * advertisements.
268         *
269         * @param isLegacy whether legacy advertising mode should be used.
270         */
271        public Builder setLegacyMode(boolean isLegacy) {
272            this.isLegacy = isLegacy;
273            return this;
274        }
275
276        /**
277         * Set whether advertiser address should be ommited from all packets. If this
278         * mode is used, periodic advertising can't be enabled for this set.
279         *
280         * This is used only if legacy mode is not used.
281         *
282         * @param isAnonymous whether anonymous advertising should be used.
283         */
284        public Builder setAnonymous(boolean isAnonymous) {
285            this.isAnonymous = isAnonymous;
286            return this;
287        }
288
289        /**
290         * Set whether TX power should be included in the extended header.
291         *
292         * This is used only if legacy mode is not used.
293         *
294         * @param includeTxPower whether TX power should be included in extended
295         *            header
296         */
297        public Builder setIncludeTxPower(boolean includeTxPower) {
298            this.includeTxPower = includeTxPower;
299            return this;
300        }
301
302        /**
303         * Set the primary physical channel used for this advertising set.
304         *
305         * This is used only if legacy mode is not used.
306         *
307         * Use {@link BluetoothAdapter#isLeCodedPhySupported} to determine if LE Coded PHY is
308         * supported on this device.
309         * @param primaryPhy Primary advertising physical channel, can only be
310         *            {@link BluetoothDevice#PHY_LE_1M} or
311         *            {@link BluetoothDevice#PHY_LE_CODED}.
312         * @throws IllegalArgumentException If the primaryPhy is invalid.
313         */
314        public Builder setPrimaryPhy(int primaryPhy) {
315            if (primaryPhy != BluetoothDevice.PHY_LE_1M &&
316                primaryPhy != BluetoothDevice.PHY_LE_CODED) {
317               throw new IllegalArgumentException("bad primaryPhy " + primaryPhy);
318            }
319            this.primaryPhy = primaryPhy;
320            return this;
321        }
322
323        /**
324         * Set the secondary physical channel used for this advertising set.
325         *
326         * This is used only if legacy mode is not used.
327         *
328         * Use {@link BluetoothAdapter#isLeCodedPhySupported} and
329         * {@link BluetoothAdapter#isLe2MPhySupported} to determine if LE Coded PHY or 2M PHY is
330         * supported on this device.
331         *
332         * @param secondaryPhy Secondary advertising physical channel, can only be
333         *            one of {@link BluetoothDevice#PHY_LE_1M},
334         *            {@link BluetoothDevice#PHY_LE_2M} or
335         *            {@link BluetoothDevice#PHY_LE_CODED}.
336         * @throws IllegalArgumentException If the secondaryPhy is invalid.
337         */
338        public Builder setSecondaryPhy(int secondaryPhy) {
339            if (secondaryPhy != BluetoothDevice.PHY_LE_1M &&
340                secondaryPhy != BluetoothDevice.PHY_LE_2M &&
341                secondaryPhy != BluetoothDevice.PHY_LE_CODED) {
342               throw new IllegalArgumentException("bad secondaryPhy " + secondaryPhy);
343            }
344            this.secondaryPhy = secondaryPhy;
345            return this;
346        }
347
348        /**
349         * Set advertising interval.
350         *
351         * @param interval Bluetooth LE Advertising interval, in 0.625ms unit. Valid
352         *            range is from 160 (100ms) to 16777215 (10,485.759375 s).
353         *            Recommended values are:
354         *            {@link AdvertisingSetParameters#INTERVAL_LOW},
355         *            {@link AdvertisingSetParameters#INTERVAL_MEDIUM}, or
356         *            {@link AdvertisingSetParameters#INTERVAL_HIGH}.
357         * @throws IllegalArgumentException If the interval is invalid.
358         */
359        public Builder setInterval(int interval) {
360            if (interval < INTERVAL_MIN || interval > INTERVAL_MAX) {
361               throw new IllegalArgumentException("unknown interval " + interval);
362            }
363            this.interval = interval;
364            return this;
365        }
366
367        /**
368         * Set the transmission power level for the advertising.
369         * @param txPowerLevel Transmission power of Bluetooth LE Advertising, in
370         *             dBm. The valid range is [-127, 1] Recommended values are:
371         *             {@link AdvertisingSetParameters#TX_POWER_ULTRA_LOW},
372         *             {@link AdvertisingSetParameters#TX_POWER_LOW},
373         *             {@link AdvertisingSetParameters#TX_POWER_MEDIUM}, or
374         *             {@link AdvertisingSetParameters#TX_POWER_HIGH}.
375         *
376         * @throws IllegalArgumentException If the {@code txPowerLevel} is invalid.
377         */
378        public Builder setTxPowerLevel(int txPowerLevel) {
379            if (txPowerLevel < TX_POWER_MIN || txPowerLevel > TX_POWER_MAX) {
380                throw new IllegalArgumentException("unknown txPowerLevel " +
381                                                   txPowerLevel);
382            }
383            this.txPowerLevel = txPowerLevel;
384            return this;
385        }
386
387        /**
388         * Build the {@link AdvertisingSetParameters} object.
389         * @throws IllegalStateException if invalid combination of parameters is used.
390         */
391        public AdvertisingSetParameters build() {
392            if (isLegacy) {
393                if (isAnonymous) {
394                    throw new IllegalArgumentException("Legacy advertising can't be anonymous");
395                }
396
397                if (connectable == true && scannable == false) {
398                    throw new IllegalStateException(
399                        "Legacy advertisement can't be connectable and non-scannable");
400                }
401
402                if (includeTxPower) {
403                    throw new IllegalStateException(
404                        "Legacy advertising can't include TX power level in header");
405                }
406            } else {
407                if (connectable && scannable) {
408                    throw new IllegalStateException(
409                        "Advertising can't be both connectable and scannable");
410                }
411
412                if (isAnonymous && connectable) {
413                    throw new IllegalStateException(
414                        "Advertising can't be both connectable and anonymous");
415                }
416            }
417
418            return new AdvertisingSetParameters(connectable, scannable, isLegacy, isAnonymous,
419                                                includeTxPower, primaryPhy,
420                                                secondaryPhy, interval, txPowerLevel);
421        }
422    }
423}