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, ScanRecord, long)}
102      */
ScanResult(BluetoothDevice device, ScanRecord scanRecord, int rssi, long timestampNanos)103     public ScanResult(BluetoothDevice device, ScanRecord scanRecord, int rssi,
104             long timestampNanos) {
105         mDevice = device;
106         mScanRecord = scanRecord;
107         mRssi = rssi;
108         mTimestampNanos = timestampNanos;
109         mEventType = (DATA_COMPLETE << 5) | ET_LEGACY_MASK | ET_CONNECTABLE_MASK;
110         mPrimaryPhy = BluetoothDevice.PHY_LE_1M;
111         mSecondaryPhy = PHY_UNUSED;
112         mAdvertisingSid = SID_NOT_PRESENT;
113         mTxPower = 127;
114         mPeriodicAdvertisingInterval = 0;
115     }
116 
117     /**
118      * Constructs a new ScanResult.
119      *
120      * @param device Remote Bluetooth device found.
121      * @param eventType Event type.
122      * @param primaryPhy Primary advertising phy.
123      * @param secondaryPhy Secondary advertising phy.
124      * @param advertisingSid Advertising set ID.
125      * @param txPower Transmit power.
126      * @param rssi Received signal strength.
127      * @param periodicAdvertisingInterval Periodic advertising interval.
128      * @param scanRecord Scan record including both advertising data and scan response data.
129      * @param timestampNanos Timestamp at which the scan result was observed.
130      */
ScanResult(BluetoothDevice device, int eventType, int primaryPhy, int secondaryPhy, int advertisingSid, int txPower, int rssi, int periodicAdvertisingInterval, ScanRecord scanRecord, long timestampNanos)131     public ScanResult(BluetoothDevice device, int eventType, int primaryPhy, int secondaryPhy,
132                       int advertisingSid, int txPower, int rssi, int periodicAdvertisingInterval,
133                       ScanRecord scanRecord, long timestampNanos) {
134         mDevice = device;
135         mEventType = eventType;
136         mPrimaryPhy = primaryPhy;
137         mSecondaryPhy = secondaryPhy;
138         mAdvertisingSid = advertisingSid;
139         mTxPower = txPower;
140         mRssi = rssi;
141         mPeriodicAdvertisingInterval = periodicAdvertisingInterval;
142         mScanRecord = scanRecord;
143         mTimestampNanos = timestampNanos;
144     }
145 
ScanResult(Parcel in)146     private ScanResult(Parcel in) {
147         readFromParcel(in);
148     }
149 
150     @Override
writeToParcel(Parcel dest, int flags)151     public void writeToParcel(Parcel dest, int flags) {
152         if (mDevice != null) {
153             dest.writeInt(1);
154             mDevice.writeToParcel(dest, flags);
155         } else {
156             dest.writeInt(0);
157         }
158         if (mScanRecord != null) {
159             dest.writeInt(1);
160             dest.writeByteArray(mScanRecord.getBytes());
161         } else {
162             dest.writeInt(0);
163         }
164         dest.writeInt(mRssi);
165         dest.writeLong(mTimestampNanos);
166         dest.writeInt(mEventType);
167         dest.writeInt(mPrimaryPhy);
168         dest.writeInt(mSecondaryPhy);
169         dest.writeInt(mAdvertisingSid);
170         dest.writeInt(mTxPower);
171         dest.writeInt(mPeriodicAdvertisingInterval);
172     }
173 
readFromParcel(Parcel in)174     private void readFromParcel(Parcel in) {
175         if (in.readInt() == 1) {
176             mDevice = BluetoothDevice.CREATOR.createFromParcel(in);
177         }
178         if (in.readInt() == 1) {
179             mScanRecord = ScanRecord.parseFromBytes(in.createByteArray());
180         }
181         mRssi = in.readInt();
182         mTimestampNanos = in.readLong();
183         mEventType = in.readInt();
184         mPrimaryPhy = in.readInt();
185         mSecondaryPhy = in.readInt();
186         mAdvertisingSid = in.readInt();
187         mTxPower = in.readInt();
188         mPeriodicAdvertisingInterval = in.readInt();
189     }
190 
191     @Override
describeContents()192     public int describeContents() {
193         return 0;
194     }
195 
196     /**
197      * Returns the remote Bluetooth device identified by the Bluetooth device address.
198      */
getDevice()199     public BluetoothDevice getDevice() {
200         return mDevice;
201     }
202 
203     /**
204      * Returns the scan record, which is a combination of advertisement and scan response.
205      */
206     @Nullable
getScanRecord()207     public ScanRecord getScanRecord() {
208         return mScanRecord;
209     }
210 
211     /**
212      * Returns the received signal strength in dBm. The valid range is [-127, 126].
213      */
getRssi()214     public int getRssi() {
215         return mRssi;
216     }
217 
218     /**
219      * Returns timestamp since boot when the scan record was observed.
220      */
getTimestampNanos()221     public long getTimestampNanos() {
222         return mTimestampNanos;
223     }
224 
225     /**
226      * Returns true if this object represents legacy scan result.
227      * Legacy scan results do not contain advanced advertising information
228      * as specified in the Bluetooth Core Specification v5.
229      */
isLegacy()230     public boolean isLegacy() {
231         return (mEventType & ET_LEGACY_MASK) != 0;
232     }
233 
234     /**
235      * Returns true if this object represents connectable scan result.
236      */
isConnectable()237     public boolean isConnectable() {
238         return (mEventType & ET_CONNECTABLE_MASK) != 0;
239     }
240 
241     /**
242      * Returns the data status.
243      * Can be one of {@link ScanResult#DATA_COMPLETE} or
244      * {@link ScanResult#DATA_TRUNCATED}.
245      */
getDataStatus()246     public int getDataStatus() {
247         // return bit 5 and 6
248         return (mEventType >> 5) & 0x03;
249     }
250 
251     /**
252      * Returns the primary Physical Layer
253      * on which this advertisment was received.
254      * Can be one of {@link BluetoothDevice#PHY_LE_1M} or
255      * {@link BluetoothDevice#PHY_LE_CODED}.
256      */
getPrimaryPhy()257     public int getPrimaryPhy() { return mPrimaryPhy; }
258 
259     /**
260      * Returns the secondary Physical Layer
261      * on which this advertisment was received.
262      * Can be one of {@link BluetoothDevice#PHY_LE_1M},
263      * {@link BluetoothDevice#PHY_LE_2M}, {@link BluetoothDevice#PHY_LE_CODED}
264      * or {@link ScanResult#PHY_UNUSED} - if the advertisement
265      * was not received on a secondary physical channel.
266      */
getSecondaryPhy()267     public int getSecondaryPhy() { return mSecondaryPhy; }
268 
269     /**
270      * Returns the advertising set id.
271      * May return {@link ScanResult#SID_NOT_PRESENT} if
272      * no set id was is present.
273      */
getAdvertisingSid()274     public int getAdvertisingSid() { return mAdvertisingSid; }
275 
276     /**
277      * Returns the transmit power in dBm.
278      * Valid range is [-127, 126]. A value of {@link ScanResult#TX_POWER_NOT_PRESENT}
279      * indicates that the TX power is not present.
280      */
getTxPower()281     public int getTxPower() { return mTxPower; }
282 
283     /**
284      * Returns the periodic advertising interval in units of 1.25ms.
285      * Valid range is 6 (7.5ms) to 65536 (81918.75ms). A value of
286      * {@link ScanResult#PERIODIC_INTERVAL_NOT_PRESENT} means periodic
287      * advertising interval is not present.
288      */
getPeriodicAdvertisingInterval()289     public int getPeriodicAdvertisingInterval() {
290         return mPeriodicAdvertisingInterval;
291     }
292 
293     @Override
hashCode()294     public int hashCode() {
295         return Objects.hash(mDevice, mRssi, mScanRecord, mTimestampNanos,
296                             mEventType, mPrimaryPhy, mSecondaryPhy,
297                             mAdvertisingSid, mTxPower,
298                             mPeriodicAdvertisingInterval);
299     }
300 
301     @Override
equals(Object obj)302     public boolean equals(Object obj) {
303         if (this == obj) {
304             return true;
305         }
306         if (obj == null || getClass() != obj.getClass()) {
307             return false;
308         }
309         ScanResult other = (ScanResult) obj;
310         return Objects.equals(mDevice, other.mDevice) && (mRssi == other.mRssi) &&
311             Objects.equals(mScanRecord, other.mScanRecord) &&
312             (mTimestampNanos == other.mTimestampNanos) &&
313             mEventType == other.mEventType &&
314             mPrimaryPhy == other.mPrimaryPhy &&
315             mSecondaryPhy == other.mSecondaryPhy &&
316             mAdvertisingSid == other.mAdvertisingSid &&
317             mTxPower == other.mTxPower &&
318             mPeriodicAdvertisingInterval == other.mPeriodicAdvertisingInterval;
319     }
320 
321     @Override
toString()322     public String toString() {
323       return "ScanResult{" + "device=" + mDevice + ", scanRecord=" +
324           Objects.toString(mScanRecord) + ", rssi=" + mRssi +
325           ", timestampNanos=" + mTimestampNanos + ", eventType=" + mEventType +
326           ", primaryPhy=" + mPrimaryPhy + ", secondaryPhy=" + mSecondaryPhy +
327           ", advertisingSid=" + mAdvertisingSid + ", txPower=" + mTxPower +
328           ", periodicAdvertisingInterval=" + mPeriodicAdvertisingInterval + '}';
329     }
330 
331     public static final Parcelable.Creator<ScanResult> CREATOR = new Creator<ScanResult>() {
332             @Override
333         public ScanResult createFromParcel(Parcel source) {
334             return new ScanResult(source);
335         }
336 
337             @Override
338         public ScanResult[] newArray(int size) {
339             return new ScanResult[size];
340         }
341     };
342 
343 }
344