/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.bluetooth.le; import android.annotation.NonNull; import android.annotation.Nullable; import android.bluetooth.Attributable; import android.bluetooth.BluetoothDevice; import android.content.AttributionSource; import android.os.Parcel; import android.os.Parcelable; import java.util.Objects; /** ScanResult for Bluetooth LE scan. */ public final class ScanResult implements Parcelable, Attributable { /** * For chained advertisements, indicates that the data contained in this scan result is * complete. */ public static final int DATA_COMPLETE = 0x00; /** * For chained advertisements, indicates that the controller was unable to receive all chained * packets and the scan result contains incomplete truncated data. */ public static final int DATA_TRUNCATED = 0x02; /** Indicates that the secondary physical layer was not used. */ public static final int PHY_UNUSED = 0x00; /** Advertising Set ID is not present in the packet. */ public static final int SID_NOT_PRESENT = 0xFF; /** TX power is not present in the packet. */ public static final int TX_POWER_NOT_PRESENT = 0x7F; /** Periodic advertising interval is not present in the packet. */ public static final int PERIODIC_INTERVAL_NOT_PRESENT = 0x00; /** Mask for checking whether event type represents legacy advertisement. */ private static final int ET_LEGACY_MASK = 0x10; /** Mask for checking whether event type represents connectable advertisement. */ private static final int ET_CONNECTABLE_MASK = 0x01; // Remote Bluetooth device. private BluetoothDevice mDevice; // Scan record, including advertising data and scan response data. @Nullable private ScanRecord mScanRecord; // Received signal strength. private int mRssi; // Device timestamp when the result was last seen. private long mTimestampNanos; private int mEventType; private int mPrimaryPhy; private int mSecondaryPhy; private int mAdvertisingSid; private int mTxPower; private int mPeriodicAdvertisingInterval; /** * Constructs a new ScanResult. * * @param device Remote Bluetooth device found. * @param scanRecord Scan record including both advertising data and scan response data. * @param rssi Received signal strength. * @param timestampNanos Timestamp at which the scan result was observed. * @deprecated use {@link #ScanResult(BluetoothDevice, int, int, int, int, int, int, int, * ScanRecord, long)} */ @Deprecated public ScanResult( BluetoothDevice device, ScanRecord scanRecord, int rssi, long timestampNanos) { mDevice = device; mScanRecord = scanRecord; mRssi = rssi; mTimestampNanos = timestampNanos; mEventType = (DATA_COMPLETE << 5) | ET_LEGACY_MASK | ET_CONNECTABLE_MASK; mPrimaryPhy = BluetoothDevice.PHY_LE_1M; mSecondaryPhy = PHY_UNUSED; mAdvertisingSid = SID_NOT_PRESENT; mTxPower = 127; mPeriodicAdvertisingInterval = 0; } /** * Constructs a new ScanResult. * * @param device Remote Bluetooth device found. * @param eventType Event type. * @param primaryPhy Primary advertising phy. * @param secondaryPhy Secondary advertising phy. * @param advertisingSid Advertising set ID. * @param txPower Transmit power. * @param rssi Received signal strength. * @param periodicAdvertisingInterval Periodic advertising interval. * @param scanRecord Scan record including both advertising data and scan response data. * @param timestampNanos Timestamp at which the scan result was observed. */ public ScanResult( BluetoothDevice device, int eventType, int primaryPhy, int secondaryPhy, int advertisingSid, int txPower, int rssi, int periodicAdvertisingInterval, ScanRecord scanRecord, long timestampNanos) { mDevice = device; mEventType = eventType; mPrimaryPhy = primaryPhy; mSecondaryPhy = secondaryPhy; mAdvertisingSid = advertisingSid; mTxPower = txPower; mRssi = rssi; mPeriodicAdvertisingInterval = periodicAdvertisingInterval; mScanRecord = scanRecord; mTimestampNanos = timestampNanos; } private ScanResult(Parcel in) { readFromParcel(in); } @Override public void writeToParcel(Parcel dest, int flags) { if (mDevice != null) { dest.writeInt(1); mDevice.writeToParcel(dest, flags); } else { dest.writeInt(0); } if (mScanRecord != null) { dest.writeInt(1); dest.writeByteArray(mScanRecord.getBytes()); } else { dest.writeInt(0); } dest.writeInt(mRssi); dest.writeLong(mTimestampNanos); dest.writeInt(mEventType); dest.writeInt(mPrimaryPhy); dest.writeInt(mSecondaryPhy); dest.writeInt(mAdvertisingSid); dest.writeInt(mTxPower); dest.writeInt(mPeriodicAdvertisingInterval); } private void readFromParcel(Parcel in) { if (in.readInt() == 1) { mDevice = BluetoothDevice.CREATOR.createFromParcel(in); } if (in.readInt() == 1) { mScanRecord = ScanRecord.parseFromBytes(in.createByteArray()); } mRssi = in.readInt(); mTimestampNanos = in.readLong(); mEventType = in.readInt(); mPrimaryPhy = in.readInt(); mSecondaryPhy = in.readInt(); mAdvertisingSid = in.readInt(); mTxPower = in.readInt(); mPeriodicAdvertisingInterval = in.readInt(); } @Override public int describeContents() { return 0; } /** @hide */ public void setAttributionSource(@NonNull AttributionSource attributionSource) { Attributable.setAttributionSource(mDevice, attributionSource); } /** * Returns the remote Bluetooth device identified by the Bluetooth device address. If the device * is bonded, calling {@link BluetoothDevice#getAddress} on the object returned by this method * will return the address that was originally bonded with (either identity address or random * address). */ public BluetoothDevice getDevice() { return mDevice; } /** Returns the scan record, which is a combination of advertisement and scan response. */ @Nullable public ScanRecord getScanRecord() { return mScanRecord; } /** Returns the received signal strength in dBm. The valid range is [-127, 126]. */ public int getRssi() { return mRssi; } /** Returns timestamp since boot when the scan record was observed. */ public long getTimestampNanos() { return mTimestampNanos; } /** * Returns true if this object represents legacy scan result. Legacy scan results do not contain * advanced advertising information as specified in the Bluetooth Core Specification v5. */ public boolean isLegacy() { return (mEventType & ET_LEGACY_MASK) != 0; } /** Returns true if this object represents connectable scan result. */ public boolean isConnectable() { return (mEventType & ET_CONNECTABLE_MASK) != 0; } /** * Returns the data status. Can be one of {@link ScanResult#DATA_COMPLETE} or {@link * ScanResult#DATA_TRUNCATED}. */ public int getDataStatus() { // return bit 5 and 6 return (mEventType >> 5) & 0x03; } /** * Returns the primary Physical Layer on which this advertisement was received. Can be one of * {@link BluetoothDevice#PHY_LE_1M} or {@link BluetoothDevice#PHY_LE_CODED}. */ public int getPrimaryPhy() { return mPrimaryPhy; } /** * Returns the secondary Physical Layer on which this advertisement was received. Can be one of * {@link BluetoothDevice#PHY_LE_1M}, {@link BluetoothDevice#PHY_LE_2M}, {@link * BluetoothDevice#PHY_LE_CODED} or {@link ScanResult#PHY_UNUSED} - if the advertisement was not * received on a secondary physical channel. */ public int getSecondaryPhy() { return mSecondaryPhy; } /** * Returns the advertising set id. May return {@link ScanResult#SID_NOT_PRESENT} if no set id * was is present. */ public int getAdvertisingSid() { return mAdvertisingSid; } /** * Returns the transmit power in dBm. Valid range is [-127, 126]. A value of {@link * ScanResult#TX_POWER_NOT_PRESENT} indicates that the TX power is not present. */ public int getTxPower() { return mTxPower; } /** * Returns the periodic advertising interval in units of 1.25ms. Valid range is 6 (7.5ms) to * 65536 (81918.75ms). A value of {@link ScanResult#PERIODIC_INTERVAL_NOT_PRESENT} means * periodic advertising interval is not present. */ public int getPeriodicAdvertisingInterval() { return mPeriodicAdvertisingInterval; } @Override public int hashCode() { return Objects.hash( mDevice, mRssi, mScanRecord, mTimestampNanos, mEventType, mPrimaryPhy, mSecondaryPhy, mAdvertisingSid, mTxPower, mPeriodicAdvertisingInterval); } @Override public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } ScanResult other = (ScanResult) obj; return Objects.equals(mDevice, other.mDevice) && (mRssi == other.mRssi) && Objects.equals(mScanRecord, other.mScanRecord) && (mTimestampNanos == other.mTimestampNanos) && mEventType == other.mEventType && mPrimaryPhy == other.mPrimaryPhy && mSecondaryPhy == other.mSecondaryPhy && mAdvertisingSid == other.mAdvertisingSid && mTxPower == other.mTxPower && mPeriodicAdvertisingInterval == other.mPeriodicAdvertisingInterval; } @Override public String toString() { return "ScanResult{" + "device=" + mDevice + ", scanRecord=" + Objects.toString(mScanRecord) + ", rssi=" + mRssi + ", timestampNanos=" + mTimestampNanos + ", eventType=" + mEventType + ", primaryPhy=" + mPrimaryPhy + ", secondaryPhy=" + mSecondaryPhy + ", advertisingSid=" + mAdvertisingSid + ", txPower=" + mTxPower + ", periodicAdvertisingInterval=" + mPeriodicAdvertisingInterval + '}'; } public static final @android.annotation.NonNull Parcelable.Creator CREATOR = new Creator() { @Override public ScanResult createFromParcel(Parcel source) { return new ScanResult(source); } @Override public ScanResult[] newArray(int size) { return new ScanResult[size]; } }; }