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 
17 package android.bluetooth.le;
18 
19 import android.annotation.Nullable;
20 import android.bluetooth.BluetoothDevice;
21 import android.os.Parcel;
22 import android.os.Parcelable;
23 
24 import java.util.Objects;
25 
26 /**
27  * ScanResult for Bluetooth LE scan.
28  */
29 public final class ScanResult implements Parcelable {
30 
31     /**
32      * For chained advertisements, inidcates tha the data contained in this
33      * scan result is complete.
34      */
35     public static final int DATA_COMPLETE = 0x00;
36 
37     /**
38      * For chained advertisements, indicates that the controller was
39      * unable to receive all chained packets and the scan result contains
40      * incomplete truncated data.
41      */
42     public static final int DATA_TRUNCATED = 0x02;
43 
44     /**
45      * Indicates that the secondary physical layer was not used.
46      */
47     public static final int PHY_UNUSED = 0x00;
48 
49     /**
50      * Advertising Set ID is not present in the packet.
51      */
52     public static final int SID_NOT_PRESENT = 0xFF;
53 
54     /**
55      * TX power is not present in the packet.
56      */
57     public static final int TX_POWER_NOT_PRESENT = 0x7F;
58 
59     /**
60      * Periodic advertising interval is not present in the packet.
61      */
62     public static final int PERIODIC_INTERVAL_NOT_PRESENT = 0x00;
63 
64     /**
65      * Mask for checking whether event type represents legacy advertisement.
66      */
67     private static final int ET_LEGACY_MASK = 0x10;
68 
69     /**
70      * Mask for checking whether event type represents connectable advertisement.
71      */
72     private static final int ET_CONNECTABLE_MASK = 0x01;
73 
74     // Remote Bluetooth device.
75     private BluetoothDevice mDevice;
76 
77     // Scan record, including advertising data and scan response data.
78     @Nullable
79     private ScanRecord mScanRecord;
80 
81     // Received signal strength.
82     private int mRssi;
83 
84     // Device timestamp when the result was last seen.
85     private long mTimestampNanos;
86 
87     private int mEventType;
88     private int mPrimaryPhy;
89     private int mSecondaryPhy;
90     private int mAdvertisingSid;
91     private int mTxPower;
92     private int mPeriodicAdvertisingInterval;
93 
94     /**
95      * Constructs a new ScanResult.
96      *
97      * @param device Remote Bluetooth device found.
98      * @param scanRecord Scan record including both advertising data and scan response data.
99      * @param rssi Received signal strength.
100      * @param timestampNanos Timestamp at which the scan result was observed.
101      * @deprecated use {@link #ScanResult(BluetoothDevice, int, int, int, int, int, int, int,
102      * ScanRecord, long)}
103      */
104     @Deprecated
ScanResult(BluetoothDevice device, ScanRecord scanRecord, int rssi, long timestampNanos)105     public ScanResult(BluetoothDevice device, ScanRecord scanRecord, int rssi,
106             long timestampNanos) {
107         mDevice = device;
108         mScanRecord = scanRecord;
109         mRssi = rssi;
110         mTimestampNanos = timestampNanos;
111         mEventType = (DATA_COMPLETE << 5) | ET_LEGACY_MASK | ET_CONNECTABLE_MASK;
112         mPrimaryPhy = BluetoothDevice.PHY_LE_1M;
113         mSecondaryPhy = PHY_UNUSED;
114         mAdvertisingSid = SID_NOT_PRESENT;
115         mTxPower = 127;
116         mPeriodicAdvertisingInterval = 0;
117     }
118 
119     /**
120      * Constructs a new ScanResult.
121      *
122      * @param device Remote Bluetooth device found.
123      * @param eventType Event type.
124      * @param primaryPhy Primary advertising phy.
125      * @param secondaryPhy Secondary advertising phy.
126      * @param advertisingSid Advertising set ID.
127      * @param txPower Transmit power.
128      * @param rssi Received signal strength.
129      * @param periodicAdvertisingInterval Periodic advertising interval.
130      * @param scanRecord Scan record including both advertising data and scan response data.
131      * @param timestampNanos Timestamp at which the scan result was observed.
132      */
ScanResult(BluetoothDevice device, int eventType, int primaryPhy, int secondaryPhy, int advertisingSid, int txPower, int rssi, int periodicAdvertisingInterval, ScanRecord scanRecord, long timestampNanos)133     public ScanResult(BluetoothDevice device, int eventType, int primaryPhy, int secondaryPhy,
134             int advertisingSid, int txPower, int rssi, int periodicAdvertisingInterval,
135             ScanRecord scanRecord, long timestampNanos) {
136         mDevice = device;
137         mEventType = eventType;
138         mPrimaryPhy = primaryPhy;
139         mSecondaryPhy = secondaryPhy;
140         mAdvertisingSid = advertisingSid;
141         mTxPower = txPower;
142         mRssi = rssi;
143         mPeriodicAdvertisingInterval = periodicAdvertisingInterval;
144         mScanRecord = scanRecord;
145         mTimestampNanos = timestampNanos;
146     }
147 
ScanResult(Parcel in)148     private ScanResult(Parcel in) {
149         readFromParcel(in);
150     }
151 
152     @Override
writeToParcel(Parcel dest, int flags)153     public void writeToParcel(Parcel dest, int flags) {
154         if (mDevice != null) {
155             dest.writeInt(1);
156             mDevice.writeToParcel(dest, flags);
157         } else {
158             dest.writeInt(0);
159         }
160         if (mScanRecord != null) {
161             dest.writeInt(1);
162             dest.writeByteArray(mScanRecord.getBytes());
163         } else {
164             dest.writeInt(0);
165         }
166         dest.writeInt(mRssi);
167         dest.writeLong(mTimestampNanos);
168         dest.writeInt(mEventType);
169         dest.writeInt(mPrimaryPhy);
170         dest.writeInt(mSecondaryPhy);
171         dest.writeInt(mAdvertisingSid);
172         dest.writeInt(mTxPower);
173         dest.writeInt(mPeriodicAdvertisingInterval);
174     }
175 
readFromParcel(Parcel in)176     private void readFromParcel(Parcel in) {
177         if (in.readInt() == 1) {
178             mDevice = BluetoothDevice.CREATOR.createFromParcel(in);
179         }
180         if (in.readInt() == 1) {
181             mScanRecord = ScanRecord.parseFromBytes(in.createByteArray());
182         }
183         mRssi = in.readInt();
184         mTimestampNanos = in.readLong();
185         mEventType = in.readInt();
186         mPrimaryPhy = in.readInt();
187         mSecondaryPhy = in.readInt();
188         mAdvertisingSid = in.readInt();
189         mTxPower = in.readInt();
190         mPeriodicAdvertisingInterval = in.readInt();
191     }
192 
193     @Override
describeContents()194     public int describeContents() {
195         return 0;
196     }
197 
198     /**
199      * Returns the remote Bluetooth device identified by the Bluetooth device address.
200      */
getDevice()201     public BluetoothDevice getDevice() {
202         return mDevice;
203     }
204 
205     /**
206      * Returns the scan record, which is a combination of advertisement and scan response.
207      */
208     @Nullable
getScanRecord()209     public ScanRecord getScanRecord() {
210         return mScanRecord;
211     }
212 
213     /**
214      * Returns the received signal strength in dBm. The valid range is [-127, 126].
215      */
getRssi()216     public int getRssi() {
217         return mRssi;
218     }
219 
220     /**
221      * Returns timestamp since boot when the scan record was observed.
222      */
getTimestampNanos()223     public long getTimestampNanos() {
224         return mTimestampNanos;
225     }
226 
227     /**
228      * Returns true if this object represents legacy scan result.
229      * Legacy scan results do not contain advanced advertising information
230      * as specified in the Bluetooth Core Specification v5.
231      */
isLegacy()232     public boolean isLegacy() {
233         return (mEventType & ET_LEGACY_MASK) != 0;
234     }
235 
236     /**
237      * Returns true if this object represents connectable scan result.
238      */
isConnectable()239     public boolean isConnectable() {
240         return (mEventType & ET_CONNECTABLE_MASK) != 0;
241     }
242 
243     /**
244      * Returns the data status.
245      * Can be one of {@link ScanResult#DATA_COMPLETE} or
246      * {@link ScanResult#DATA_TRUNCATED}.
247      */
getDataStatus()248     public int getDataStatus() {
249         // return bit 5 and 6
250         return (mEventType >> 5) & 0x03;
251     }
252 
253     /**
254      * Returns the primary Physical Layer
255      * on which this advertisment was received.
256      * Can be one of {@link BluetoothDevice#PHY_LE_1M} or
257      * {@link BluetoothDevice#PHY_LE_CODED}.
258      */
getPrimaryPhy()259     public int getPrimaryPhy() {
260         return mPrimaryPhy;
261     }
262 
263     /**
264      * Returns the secondary Physical Layer
265      * on which this advertisment was received.
266      * Can be one of {@link BluetoothDevice#PHY_LE_1M},
267      * {@link BluetoothDevice#PHY_LE_2M}, {@link BluetoothDevice#PHY_LE_CODED}
268      * or {@link ScanResult#PHY_UNUSED} - if the advertisement
269      * was not received on a secondary physical channel.
270      */
getSecondaryPhy()271     public int getSecondaryPhy() {
272         return mSecondaryPhy;
273     }
274 
275     /**
276      * Returns the advertising set id.
277      * May return {@link ScanResult#SID_NOT_PRESENT} if
278      * no set id was is present.
279      */
getAdvertisingSid()280     public int getAdvertisingSid() {
281         return mAdvertisingSid;
282     }
283 
284     /**
285      * Returns the transmit power in dBm.
286      * Valid range is [-127, 126]. A value of {@link ScanResult#TX_POWER_NOT_PRESENT}
287      * indicates that the TX power is not present.
288      */
getTxPower()289     public int getTxPower() {
290         return mTxPower;
291     }
292 
293     /**
294      * Returns the periodic advertising interval in units of 1.25ms.
295      * Valid range is 6 (7.5ms) to 65536 (81918.75ms). A value of
296      * {@link ScanResult#PERIODIC_INTERVAL_NOT_PRESENT} means periodic
297      * advertising interval is not present.
298      */
getPeriodicAdvertisingInterval()299     public int getPeriodicAdvertisingInterval() {
300         return mPeriodicAdvertisingInterval;
301     }
302 
303     @Override
hashCode()304     public int hashCode() {
305         return Objects.hash(mDevice, mRssi, mScanRecord, mTimestampNanos,
306                 mEventType, mPrimaryPhy, mSecondaryPhy,
307                 mAdvertisingSid, mTxPower,
308                 mPeriodicAdvertisingInterval);
309     }
310 
311     @Override
equals(Object obj)312     public boolean equals(Object obj) {
313         if (this == obj) {
314             return true;
315         }
316         if (obj == null || getClass() != obj.getClass()) {
317             return false;
318         }
319         ScanResult other = (ScanResult) obj;
320         return Objects.equals(mDevice, other.mDevice) && (mRssi == other.mRssi)
321                 && Objects.equals(mScanRecord, other.mScanRecord)
322                 && (mTimestampNanos == other.mTimestampNanos)
323                 && mEventType == other.mEventType
324                 && mPrimaryPhy == other.mPrimaryPhy
325                 && mSecondaryPhy == other.mSecondaryPhy
326                 && mAdvertisingSid == other.mAdvertisingSid
327                 && mTxPower == other.mTxPower
328                 && mPeriodicAdvertisingInterval == other.mPeriodicAdvertisingInterval;
329     }
330 
331     @Override
toString()332     public String toString() {
333         return "ScanResult{" + "device=" + mDevice + ", scanRecord="
334                 + Objects.toString(mScanRecord) + ", rssi=" + mRssi
335                 + ", timestampNanos=" + mTimestampNanos + ", eventType=" + mEventType
336                 + ", primaryPhy=" + mPrimaryPhy + ", secondaryPhy=" + mSecondaryPhy
337                 + ", advertisingSid=" + mAdvertisingSid + ", txPower=" + mTxPower
338                 + ", periodicAdvertisingInterval=" + mPeriodicAdvertisingInterval + '}';
339     }
340 
341     public static final @android.annotation.NonNull Parcelable.Creator<ScanResult> CREATOR = new Creator<ScanResult>() {
342         @Override
343         public ScanResult createFromParcel(Parcel source) {
344             return new ScanResult(source);
345         }
346 
347         @Override
348         public ScanResult[] newArray(int size) {
349             return new ScanResult[size];
350         }
351     };
352 
353 }
354