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