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