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