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 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 */ 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 148 private ScanResult(Parcel in) { 149 readFromParcel(in); 150 } 151 152 @Override 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 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 194 public int describeContents() { 195 return 0; 196 } 197 198 /** 199 * Returns the remote Bluetooth device identified by the Bluetooth device address. 200 */ 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 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 */ 216 public int getRssi() { 217 return mRssi; 218 } 219 220 /** 221 * Returns timestamp since boot when the scan record was observed. 222 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 299 public int getPeriodicAdvertisingInterval() { 300 return mPeriodicAdvertisingInterval; 301 } 302 303 @Override 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 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 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 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