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