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, ScanRecord, long)} 102 */ ScanResult(BluetoothDevice device, ScanRecord scanRecord, int rssi, long timestampNanos)103 public ScanResult(BluetoothDevice device, ScanRecord scanRecord, int rssi, 104 long timestampNanos) { 105 mDevice = device; 106 mScanRecord = scanRecord; 107 mRssi = rssi; 108 mTimestampNanos = timestampNanos; 109 mEventType = (DATA_COMPLETE << 5) | ET_LEGACY_MASK | ET_CONNECTABLE_MASK; 110 mPrimaryPhy = BluetoothDevice.PHY_LE_1M; 111 mSecondaryPhy = PHY_UNUSED; 112 mAdvertisingSid = SID_NOT_PRESENT; 113 mTxPower = 127; 114 mPeriodicAdvertisingInterval = 0; 115 } 116 117 /** 118 * Constructs a new ScanResult. 119 * 120 * @param device Remote Bluetooth device found. 121 * @param eventType Event type. 122 * @param primaryPhy Primary advertising phy. 123 * @param secondaryPhy Secondary advertising phy. 124 * @param advertisingSid Advertising set ID. 125 * @param txPower Transmit power. 126 * @param rssi Received signal strength. 127 * @param periodicAdvertisingInterval Periodic advertising interval. 128 * @param scanRecord Scan record including both advertising data and scan response data. 129 * @param timestampNanos Timestamp at which the scan result was observed. 130 */ ScanResult(BluetoothDevice device, int eventType, int primaryPhy, int secondaryPhy, int advertisingSid, int txPower, int rssi, int periodicAdvertisingInterval, ScanRecord scanRecord, long timestampNanos)131 public ScanResult(BluetoothDevice device, int eventType, int primaryPhy, int secondaryPhy, 132 int advertisingSid, int txPower, int rssi, int periodicAdvertisingInterval, 133 ScanRecord scanRecord, long timestampNanos) { 134 mDevice = device; 135 mEventType = eventType; 136 mPrimaryPhy = primaryPhy; 137 mSecondaryPhy = secondaryPhy; 138 mAdvertisingSid = advertisingSid; 139 mTxPower = txPower; 140 mRssi = rssi; 141 mPeriodicAdvertisingInterval = periodicAdvertisingInterval; 142 mScanRecord = scanRecord; 143 mTimestampNanos = timestampNanos; 144 } 145 ScanResult(Parcel in)146 private ScanResult(Parcel in) { 147 readFromParcel(in); 148 } 149 150 @Override writeToParcel(Parcel dest, int flags)151 public void writeToParcel(Parcel dest, int flags) { 152 if (mDevice != null) { 153 dest.writeInt(1); 154 mDevice.writeToParcel(dest, flags); 155 } else { 156 dest.writeInt(0); 157 } 158 if (mScanRecord != null) { 159 dest.writeInt(1); 160 dest.writeByteArray(mScanRecord.getBytes()); 161 } else { 162 dest.writeInt(0); 163 } 164 dest.writeInt(mRssi); 165 dest.writeLong(mTimestampNanos); 166 dest.writeInt(mEventType); 167 dest.writeInt(mPrimaryPhy); 168 dest.writeInt(mSecondaryPhy); 169 dest.writeInt(mAdvertisingSid); 170 dest.writeInt(mTxPower); 171 dest.writeInt(mPeriodicAdvertisingInterval); 172 } 173 readFromParcel(Parcel in)174 private void readFromParcel(Parcel in) { 175 if (in.readInt() == 1) { 176 mDevice = BluetoothDevice.CREATOR.createFromParcel(in); 177 } 178 if (in.readInt() == 1) { 179 mScanRecord = ScanRecord.parseFromBytes(in.createByteArray()); 180 } 181 mRssi = in.readInt(); 182 mTimestampNanos = in.readLong(); 183 mEventType = in.readInt(); 184 mPrimaryPhy = in.readInt(); 185 mSecondaryPhy = in.readInt(); 186 mAdvertisingSid = in.readInt(); 187 mTxPower = in.readInt(); 188 mPeriodicAdvertisingInterval = in.readInt(); 189 } 190 191 @Override describeContents()192 public int describeContents() { 193 return 0; 194 } 195 196 /** 197 * Returns the remote Bluetooth device identified by the Bluetooth device address. 198 */ getDevice()199 public BluetoothDevice getDevice() { 200 return mDevice; 201 } 202 203 /** 204 * Returns the scan record, which is a combination of advertisement and scan response. 205 */ 206 @Nullable getScanRecord()207 public ScanRecord getScanRecord() { 208 return mScanRecord; 209 } 210 211 /** 212 * Returns the received signal strength in dBm. The valid range is [-127, 126]. 213 */ getRssi()214 public int getRssi() { 215 return mRssi; 216 } 217 218 /** 219 * Returns timestamp since boot when the scan record was observed. 220 */ getTimestampNanos()221 public long getTimestampNanos() { 222 return mTimestampNanos; 223 } 224 225 /** 226 * Returns true if this object represents legacy scan result. 227 * Legacy scan results do not contain advanced advertising information 228 * as specified in the Bluetooth Core Specification v5. 229 */ isLegacy()230 public boolean isLegacy() { 231 return (mEventType & ET_LEGACY_MASK) != 0; 232 } 233 234 /** 235 * Returns true if this object represents connectable scan result. 236 */ isConnectable()237 public boolean isConnectable() { 238 return (mEventType & ET_CONNECTABLE_MASK) != 0; 239 } 240 241 /** 242 * Returns the data status. 243 * Can be one of {@link ScanResult#DATA_COMPLETE} or 244 * {@link ScanResult#DATA_TRUNCATED}. 245 */ getDataStatus()246 public int getDataStatus() { 247 // return bit 5 and 6 248 return (mEventType >> 5) & 0x03; 249 } 250 251 /** 252 * Returns the primary Physical Layer 253 * on which this advertisment was received. 254 * Can be one of {@link BluetoothDevice#PHY_LE_1M} or 255 * {@link BluetoothDevice#PHY_LE_CODED}. 256 */ getPrimaryPhy()257 public int getPrimaryPhy() { return mPrimaryPhy; } 258 259 /** 260 * Returns the secondary Physical Layer 261 * on which this advertisment was received. 262 * Can be one of {@link BluetoothDevice#PHY_LE_1M}, 263 * {@link BluetoothDevice#PHY_LE_2M}, {@link BluetoothDevice#PHY_LE_CODED} 264 * or {@link ScanResult#PHY_UNUSED} - if the advertisement 265 * was not received on a secondary physical channel. 266 */ getSecondaryPhy()267 public int getSecondaryPhy() { return mSecondaryPhy; } 268 269 /** 270 * Returns the advertising set id. 271 * May return {@link ScanResult#SID_NOT_PRESENT} if 272 * no set id was is present. 273 */ getAdvertisingSid()274 public int getAdvertisingSid() { return mAdvertisingSid; } 275 276 /** 277 * Returns the transmit power in dBm. 278 * Valid range is [-127, 126]. A value of {@link ScanResult#TX_POWER_NOT_PRESENT} 279 * indicates that the TX power is not present. 280 */ getTxPower()281 public int getTxPower() { return mTxPower; } 282 283 /** 284 * Returns the periodic advertising interval in units of 1.25ms. 285 * Valid range is 6 (7.5ms) to 65536 (81918.75ms). A value of 286 * {@link ScanResult#PERIODIC_INTERVAL_NOT_PRESENT} means periodic 287 * advertising interval is not present. 288 */ getPeriodicAdvertisingInterval()289 public int getPeriodicAdvertisingInterval() { 290 return mPeriodicAdvertisingInterval; 291 } 292 293 @Override hashCode()294 public int hashCode() { 295 return Objects.hash(mDevice, mRssi, mScanRecord, mTimestampNanos, 296 mEventType, mPrimaryPhy, mSecondaryPhy, 297 mAdvertisingSid, mTxPower, 298 mPeriodicAdvertisingInterval); 299 } 300 301 @Override equals(Object obj)302 public boolean equals(Object obj) { 303 if (this == obj) { 304 return true; 305 } 306 if (obj == null || getClass() != obj.getClass()) { 307 return false; 308 } 309 ScanResult other = (ScanResult) obj; 310 return Objects.equals(mDevice, other.mDevice) && (mRssi == other.mRssi) && 311 Objects.equals(mScanRecord, other.mScanRecord) && 312 (mTimestampNanos == other.mTimestampNanos) && 313 mEventType == other.mEventType && 314 mPrimaryPhy == other.mPrimaryPhy && 315 mSecondaryPhy == other.mSecondaryPhy && 316 mAdvertisingSid == other.mAdvertisingSid && 317 mTxPower == other.mTxPower && 318 mPeriodicAdvertisingInterval == other.mPeriodicAdvertisingInterval; 319 } 320 321 @Override toString()322 public String toString() { 323 return "ScanResult{" + "device=" + mDevice + ", scanRecord=" + 324 Objects.toString(mScanRecord) + ", rssi=" + mRssi + 325 ", timestampNanos=" + mTimestampNanos + ", eventType=" + mEventType + 326 ", primaryPhy=" + mPrimaryPhy + ", secondaryPhy=" + mSecondaryPhy + 327 ", advertisingSid=" + mAdvertisingSid + ", txPower=" + mTxPower + 328 ", periodicAdvertisingInterval=" + mPeriodicAdvertisingInterval + '}'; 329 } 330 331 public static final Parcelable.Creator<ScanResult> CREATOR = new Creator<ScanResult>() { 332 @Override 333 public ScanResult createFromParcel(Parcel source) { 334 return new ScanResult(source); 335 } 336 337 @Override 338 public ScanResult[] newArray(int size) { 339 return new ScanResult[size]; 340 } 341 }; 342 343 } 344