ScanResult.kt
/*
* Copyright 2023 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 androidx.bluetooth
import android.bluetooth.le.ScanResult as FwkScanResult
import android.os.Build
import android.os.ParcelUuid
import androidx.annotation.DoNotInline
import androidx.annotation.RequiresApi
import androidx.annotation.RestrictTo
import java.util.UUID
/**
* Represents a scan result for Bluetooth LE scan.
*
* The ScanResult class is used by Bluetooth LE applications to scan for and discover Bluetooth LE
* devices. When a Bluetooth LE application scans for devices, it will receive a list of
* [ScanResult] objects that contain information about the scanned devices. The application can
* then use this information to determine which devices it wants to connect to.
*
* @property device Remote device found
* @property deviceAddress Bluetooth address for the remote device found
* @property timestampNanos Device timestamp when the result was last seen
* @property serviceUuids A list of service UUIDs within advertisement that are used to identify the
* bluetooth GATT services.
*
*/
class ScanResult @RestrictTo(RestrictTo.Scope.LIBRARY) constructor(
private val fwkScanResult: FwkScanResult
) {
companion object {
/**
* Periodic advertising interval is not present in the packet.
*/
const val PERIODIC_INTERVAL_NOT_PRESENT: Int = FwkScanResult.PERIODIC_INTERVAL_NOT_PRESENT
}
@RequiresApi(29)
private object ScanResultApi29Impl {
@JvmStatic
@DoNotInline
fun serviceSolicitationUuids(fwkScanResult: FwkScanResult): List<ParcelUuid> =
fwkScanResult.scanRecord?.serviceSolicitationUuids.orEmpty()
}
@RequiresApi(26)
private object ScanResultApi26Impl {
@JvmStatic
@DoNotInline
fun isConnectable(fwkScanResult: FwkScanResult): Boolean =
fwkScanResult.isConnectable
@JvmStatic
@DoNotInline
fun periodicAdvertisingInterval(fwkScanResult: FwkScanResult): Long =
(fwkScanResult.periodicAdvertisingInterval * 1.25).toLong()
}
/** Remote Bluetooth device found. */
val device: BluetoothDevice = BluetoothDevice(fwkScanResult.device)
// TODO(kihongs) Find a way to get address type from framework scan result
/** Bluetooth address for the remote device found. */
val deviceAddress: BluetoothAddress = BluetoothAddress(
fwkScanResult.device.address,
BluetoothAddress.ADDRESS_TYPE_UNKNOWN
)
/** Device timestamp when the advertisement was last seen. */
val timestampNanos: Long
get() = fwkScanResult.timestampNanos
/**
* Returns the manufacturer specific data associated with the manufacturer id.
*
* @param manufacturerId The manufacturer id of the scanned device
* @return the manufacturer specific data associated with the manufacturer id, or @{code null}
* if the manufacturer specific data is not present
*/
fun getManufacturerSpecificData(manufacturerId: Int): ByteArray? {
return fwkScanResult.scanRecord?.getManufacturerSpecificData(manufacturerId)
}
/**
* A list of service UUIDs within advertisement that are used to identify the bluetooth GATT
* services.
*/
val serviceUuids: List<UUID>
get() = fwkScanResult.scanRecord?.serviceUuids?.map { it.uuid }.orEmpty()
/**
* Returns a list of service solicitation UUIDs within the advertisement that are used to
* identify the Bluetooth GATT services.
*
* Please note that this will return an `emptyList()` on versions
* before [android.os.Build.VERSION_CODES.Q].
*/
val serviceSolicitationUuids: List<ParcelUuid>
get() = if (Build.VERSION.SDK_INT >= 29) {
ScanResultApi29Impl.serviceSolicitationUuids(fwkScanResult)
} else {
emptyList()
}
/**
* Returns a map of service UUID and its corresponding service data.
*/
val serviceData: Map<ParcelUuid, ByteArray>
get() = fwkScanResult.scanRecord?.serviceData.orEmpty()
/**
* Returns the service data associated with the service UUID.
*
* @param serviceUuid The service UUID of the service data
* @return the service data associated with the specified service UUID, or `null`
* if the service UUID is not found
*/
fun getServiceData(serviceUuid: UUID): ByteArray? {
return fwkScanResult.scanRecord?.getServiceData(ParcelUuid(serviceUuid))
}
/**
* Checks if this object represents a connectable scan result.
*
* @return {@code true} if the scanned device is connectable.
*
* Please note that this will return {@code true} on versions
* before [android.os.Build.VERSION_CODES.Q].
*/
fun isConnectable(): Boolean {
return if (Build.VERSION.SDK_INT >= 26) {
ScanResultApi26Impl.isConnectable(fwkScanResult)
} else {
true
}
}
/** Returns the received signal strength in dBm. The valid range is [-127, 126]. */
val rssi: Int
get() = fwkScanResult.rssi
/**
* Returns the periodic advertising interval in milliseconds ranging from 7.5ms to 81918.75ms
* A value of [PERIODIC_INTERVAL_NOT_PRESENT] means periodic advertising interval is not present.
*
* Please note that this will return [PERIODIC_INTERVAL_NOT_PRESENT] on versions
* before [android.os.Build.VERSION_CODES.Q].
*/
val periodicAdvertisingInterval: Long
get() = if (Build.VERSION.SDK_INT >= 26) {
// Framework returns interval in units of 1.25ms.
ScanResultApi26Impl.periodicAdvertisingInterval(fwkScanResult)
} else {
PERIODIC_INTERVAL_NOT_PRESENT.toLong()
}
}