Revision control
Copy as Markdown
// Copyright (C) 2016 Lauri Laanmets (Proekspert AS) <lauri.laanmets@eesti.ee>
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:critical reason:data-parser
#include "android/devicediscoverybroadcastreceiver_p.h"
#include <QCoreApplication>
#include <QtCore/QtEndian>
#include <QtCore/QLoggingCategory>
#include <QtBluetooth/QBluetoothAddress>
#include <QtBluetooth/QBluetoothDeviceInfo>
#include <QtBluetooth/QBluetoothUuid>
#include "android/jni_android_p.h"
#include <QtCore/QHash>
#include <QtCore/qbitarray.h>
#include <algorithm>
QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(QT_BT_ANDROID)
typedef QHash<jint, QBluetoothDeviceInfo::CoreConfigurations> JCachedBtTypes;
Q_GLOBAL_STATIC(JCachedBtTypes, cachedBtTypes)
typedef QHash<jint, QBluetoothDeviceInfo::MajorDeviceClass> JCachedMajorTypes;
Q_GLOBAL_STATIC(JCachedMajorTypes, cachedMajorTypes)
typedef QHash<jint, quint8> JCachedMinorTypes;
Q_GLOBAL_STATIC(JCachedMinorTypes, cachedMinorTypes)
static QBitArray initializeMinorCaches()
{
const qsizetype numberOfMajorDeviceClasses = 11; // count QBluetoothDeviceInfo::MajorDeviceClass values
// switch below used to ensure that we notice additions to MajorDeviceClass enum
const QBluetoothDeviceInfo::MajorDeviceClass classes = QBluetoothDeviceInfo::ComputerDevice;
switch (classes) {
case QBluetoothDeviceInfo::MiscellaneousDevice:
case QBluetoothDeviceInfo::ComputerDevice:
case QBluetoothDeviceInfo::PhoneDevice:
case QBluetoothDeviceInfo::NetworkDevice:
case QBluetoothDeviceInfo::AudioVideoDevice:
case QBluetoothDeviceInfo::PeripheralDevice:
case QBluetoothDeviceInfo::ImagingDevice:
case QBluetoothDeviceInfo::WearableDevice:
case QBluetoothDeviceInfo::ToyDevice:
case QBluetoothDeviceInfo::HealthDevice:
case QBluetoothDeviceInfo::UncategorizedDevice:
break;
default:
qCWarning(QT_BT_ANDROID) << "Unknown category of major device class:" << classes;
}
return QBitArray(numberOfMajorDeviceClasses, false);
}
Q_GLOBAL_STATIC_WITH_ARGS(QBitArray, initializedCacheTracker, (initializeMinorCaches()))
// class names
static const char javaBluetoothDeviceClassName[] = "android/bluetooth/BluetoothDevice";
static const char javaBluetoothClassDeviceMajorClassName[] = "android/bluetooth/BluetoothClass$Device$Major";
static const char javaBluetoothClassDeviceClassName[] = "android/bluetooth/BluetoothClass$Device";
// field names device type (LE vs classic)
static const char javaDeviceTypeClassic[] = "DEVICE_TYPE_CLASSIC";
static const char javaDeviceTypeDual[] = "DEVICE_TYPE_DUAL";
static const char javaDeviceTypeLE[] = "DEVICE_TYPE_LE";
static const char javaDeviceTypeUnknown[] = "DEVICE_TYPE_UNKNOWN";
struct MajorClassJavaToQtMapping
{
const char javaFieldName[14];
QBluetoothDeviceInfo::MajorDeviceClass qtMajor : 16;
};
static_assert(sizeof(MajorClassJavaToQtMapping) == 16);
static constexpr MajorClassJavaToQtMapping majorMappings[] = {
{ "AUDIO_VIDEO", QBluetoothDeviceInfo::AudioVideoDevice },
{ "COMPUTER", QBluetoothDeviceInfo::ComputerDevice },
{ "HEALTH", QBluetoothDeviceInfo::HealthDevice },
{ "IMAGING", QBluetoothDeviceInfo::ImagingDevice },
{ "MISC", QBluetoothDeviceInfo::MiscellaneousDevice },
{ "NETWORKING", QBluetoothDeviceInfo::NetworkDevice },
{ "PERIPHERAL", QBluetoothDeviceInfo::PeripheralDevice },
{ "PHONE", QBluetoothDeviceInfo::PhoneDevice },
{ "TOY", QBluetoothDeviceInfo::ToyDevice },
{ "UNCATEGORIZED", QBluetoothDeviceInfo::UncategorizedDevice },
{ "WEARABLE", QBluetoothDeviceInfo::WearableDevice },
};
// QBluetoothDeviceInfo::MajorDeviceClass value plus 1 matches index
// UncategorizedDevice shifts to index 0
static constexpr quint8 minorIndexSizes[] = {
64, // QBluetoothDevice::UncategorizedDevice
61, // QBluetoothDevice::MiscellaneousDevice
18, // QBluetoothDevice::ComputerDevice
35, // QBluetoothDevice::PhoneDevice
62, // QBluetoothDevice::NetworkDevice
0, // QBluetoothDevice::AudioVideoDevice
56, // QBluetoothDevice::PeripheralDevice
63, // QBluetoothDevice::ImagingDEvice
49, // QBluetoothDevice::WearableDevice
42, // QBluetoothDevice::ToyDevice
26, // QBluetoothDevice::HealthDevice
};
struct MinorClassJavaToQtMapping
{
char const * javaFieldName;
quint8 qtMinor;
};
static const MinorClassJavaToQtMapping minorMappings[] = {
// QBluetoothDevice::AudioVideoDevice -> 17 entries
{ "AUDIO_VIDEO_CAMCORDER", QBluetoothDeviceInfo::Camcorder }, //index 0
{ "AUDIO_VIDEO_CAR_AUDIO", QBluetoothDeviceInfo::CarAudio },
{ "AUDIO_VIDEO_HANDSFREE", QBluetoothDeviceInfo::HandsFreeDevice },
{ "AUDIO_VIDEO_HEADPHONES", QBluetoothDeviceInfo::Headphones },
{ "AUDIO_VIDEO_HIFI_AUDIO", QBluetoothDeviceInfo::HiFiAudioDevice },
{ "AUDIO_VIDEO_LOUDSPEAKER", QBluetoothDeviceInfo::Loudspeaker },
{ "AUDIO_VIDEO_MICROPHONE", QBluetoothDeviceInfo::Microphone },
{ "AUDIO_VIDEO_PORTABLE_AUDIO", QBluetoothDeviceInfo::PortableAudioDevice },
{ "AUDIO_VIDEO_SET_TOP_BOX", QBluetoothDeviceInfo::SetTopBox },
{ "AUDIO_VIDEO_UNCATEGORIZED", QBluetoothDeviceInfo::UncategorizedAudioVideoDevice },
{ "AUDIO_VIDEO_VCR", QBluetoothDeviceInfo::Vcr },
{ "AUDIO_VIDEO_VIDEO_CAMERA", QBluetoothDeviceInfo::VideoCamera },
{ "AUDIO_VIDEO_VIDEO_CONFERENCING", QBluetoothDeviceInfo::VideoConferencing },
{ "AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER", QBluetoothDeviceInfo::VideoDisplayAndLoudspeaker },
{ "AUDIO_VIDEO_VIDEO_GAMING_TOY", QBluetoothDeviceInfo::GamingDevice },
{ "AUDIO_VIDEO_VIDEO_MONITOR", QBluetoothDeviceInfo::VideoMonitor },
{ "AUDIO_VIDEO_WEARABLE_HEADSET", QBluetoothDeviceInfo::WearableHeadsetDevice },
{ nullptr, 0 }, // separator
// QBluetoothDevice::ComputerDevice -> 7 entries
{ "COMPUTER_UNCATEGORIZED", QBluetoothDeviceInfo::UncategorizedComputer }, // index 18
{ "COMPUTER_DESKTOP", QBluetoothDeviceInfo::DesktopComputer },
{ "COMPUTER_HANDHELD_PC_PDA", QBluetoothDeviceInfo::HandheldComputer },
{ "COMPUTER_LAPTOP", QBluetoothDeviceInfo::LaptopComputer },
{ "COMPUTER_PALM_SIZE_PC_PDA", QBluetoothDeviceInfo::HandheldClamShellComputer },
{ "COMPUTER_SERVER", QBluetoothDeviceInfo::ServerComputer },
{ "COMPUTER_WEARABLE", QBluetoothDeviceInfo::WearableComputer },
{ nullptr, 0 }, // separator
// QBluetoothDevice::HealthDevice -> 8 entries
{ "HEALTH_BLOOD_PRESSURE", QBluetoothDeviceInfo::HealthBloodPressureMonitor }, // index 26
{ "HEALTH_DATA_DISPLAY", QBluetoothDeviceInfo::HealthDataDisplay },
{ "HEALTH_GLUCOSE", QBluetoothDeviceInfo::HealthGlucoseMeter },
{ "HEALTH_PULSE_OXIMETER", QBluetoothDeviceInfo::HealthPulseOximeter },
{ "HEALTH_PULSE_RATE", QBluetoothDeviceInfo::HealthStepCounter },
{ "HEALTH_THERMOMETER", QBluetoothDeviceInfo::HealthThermometer },
{ "HEALTH_UNCATEGORIZED", QBluetoothDeviceInfo::UncategorizedHealthDevice },
{ "HEALTH_WEIGHING", QBluetoothDeviceInfo::HealthWeightScale },
{ nullptr, 0 }, // separator
// QBluetoothDevice::PhoneDevice -> 6 entries
{ "PHONE_CELLULAR", QBluetoothDeviceInfo::CellularPhone }, // index 35
{ "PHONE_CORDLESS", QBluetoothDeviceInfo::CordlessPhone },
{ "PHONE_ISDN", QBluetoothDeviceInfo::CommonIsdnAccessPhone },
{ "PHONE_MODEM_OR_GATEWAY", QBluetoothDeviceInfo::WiredModemOrVoiceGatewayPhone },
{ "PHONE_SMART", QBluetoothDeviceInfo::SmartPhone },
{ "PHONE_UNCATEGORIZED", QBluetoothDeviceInfo::UncategorizedPhone },
{ nullptr, 0 }, // separator
// QBluetoothDevice::ToyDevice -> 6 entries
{ "TOY_CONTROLLER", QBluetoothDeviceInfo::ToyController }, // index 42
{ "TOY_DOLL_ACTION_FIGURE", QBluetoothDeviceInfo::ToyDoll },
{ "TOY_GAME", QBluetoothDeviceInfo::ToyGame },
{ "TOY_ROBOT", QBluetoothDeviceInfo::ToyRobot },
{ "TOY_UNCATEGORIZED", QBluetoothDeviceInfo::UncategorizedToy },
{ "TOY_VEHICLE", QBluetoothDeviceInfo::ToyVehicle },
{ nullptr, 0 }, // separator
// QBluetoothDevice::WearableDevice -> 6 entries
{ "WEARABLE_GLASSES", QBluetoothDeviceInfo::WearableGlasses }, // index 49
{ "WEARABLE_HELMET", QBluetoothDeviceInfo::WearableHelmet },
{ "WEARABLE_JACKET", QBluetoothDeviceInfo::WearableJacket },
{ "WEARABLE_PAGER", QBluetoothDeviceInfo::WearablePager },
{ "WEARABLE_UNCATEGORIZED", QBluetoothDeviceInfo::UncategorizedWearableDevice },
{ "WEARABLE_WRIST_WATCH", QBluetoothDeviceInfo::WearableWristWatch },
{ nullptr, 0 }, // separator
// QBluetoothDevice::PeripheralDevice -> 3 entries
// For some reason these are not mentioned in Android docs but still exist
{ "PERIPHERAL_NON_KEYBOARD_NON_POINTING", QBluetoothDeviceInfo::UncategorizedPeripheral }, // index 56
{ "PERIPHERAL_KEYBOARD", QBluetoothDeviceInfo::KeyboardPeripheral },
{ "PERIPHERAL_POINTING", QBluetoothDeviceInfo::PointingDevicePeripheral },
{ "PERIPHERAL_KEYBOARD_POINTING", QBluetoothDeviceInfo::KeyboardWithPointingDevicePeripheral },
{ nullptr, 0 }, // separator
// the following entries do not exist on Android
// we map them to the unknown minor version case
// QBluetoothDevice::Miscellaneous
{ nullptr, 0 }, // index 61 & separator
// QBluetoothDevice::NetworkDevice
{ nullptr, 0 }, // index 62 & separator
// QBluetoothDevice::ImagingDevice
{ nullptr, 0 }, // index 63 & separator
// QBluetoothDevice::UncategorizedDevice
{ nullptr, 0 }, // index 64 & separator
};
/*
Advertising Data Type (AD type) for LE scan records, as defined in
*/
enum ADType {
ADType16BitUuidIncomplete = 0x02,
ADType16BitUuidComplete = 0x03,
ADType32BitUuidIncomplete = 0x04,
ADType32BitUuidComplete = 0x05,
ADType128BitUuidIncomplete = 0x06,
ADType128BitUuidComplete = 0x07,
ADTypeShortenedLocalName = 0x08,
ADTypeCompleteLocalName = 0x09,
ADTypeServiceData16Bit = 0x16,
ADTypeServiceData32Bit = 0x20,
ADTypeServiceData128Bit = 0x21,
ADTypeManufacturerSpecificData = 0xff,
// .. more will be added when required
};
QBluetoothDeviceInfo::CoreConfigurations qtBtTypeForJavaBtType(jint javaType)
{
const JCachedBtTypes::iterator it = cachedBtTypes()->find(javaType);
if (it == cachedBtTypes()->end()) {
if (javaType == QJniObject::getStaticField<jint>(
javaBluetoothDeviceClassName, javaDeviceTypeClassic)) {
cachedBtTypes()->insert(javaType,
QBluetoothDeviceInfo::BaseRateCoreConfiguration);
return QBluetoothDeviceInfo::BaseRateCoreConfiguration;
} else if (javaType == QJniObject::getStaticField<jint>(
javaBluetoothDeviceClassName, javaDeviceTypeLE)) {
cachedBtTypes()->insert(javaType,
QBluetoothDeviceInfo::LowEnergyCoreConfiguration);
return QBluetoothDeviceInfo::LowEnergyCoreConfiguration;
} else if (javaType == QJniObject::getStaticField<jint>(
javaBluetoothDeviceClassName, javaDeviceTypeDual)) {
cachedBtTypes()->insert(javaType,
QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration);
return QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration;
} else if (javaType == QJniObject::getStaticField<jint>(
javaBluetoothDeviceClassName, javaDeviceTypeUnknown)) {
cachedBtTypes()->insert(javaType,
QBluetoothDeviceInfo::UnknownCoreConfiguration);
} else {
qCWarning(QT_BT_ANDROID) << "Unknown Bluetooth device type value";
}
return QBluetoothDeviceInfo::UnknownCoreConfiguration;
} else {
return it.value();
}
}
QBluetoothDeviceInfo::MajorDeviceClass resolveAndroidMajorClass(jint javaType)
{
const JCachedMajorTypes::iterator it = cachedMajorTypes()->find(javaType);
if (it == cachedMajorTypes()->end()) {
QJniEnvironment env;
// precache all major device class fields
jint fieldValue;
QBluetoothDeviceInfo::MajorDeviceClass result = QBluetoothDeviceInfo::UncategorizedDevice;
auto clazz = env->FindClass(javaBluetoothClassDeviceMajorClassName);
for (const auto &majorMapping : majorMappings) {
auto fieldId = env->GetStaticFieldID(clazz, majorMapping.javaFieldName, "I");
if (!env->ExceptionCheck())
fieldValue = env->GetStaticIntField(clazz, fieldId);
if (env.checkAndClearExceptions()) {
qCWarning(QT_BT_ANDROID) << "Unknown BluetoothClass.Device.Major field" << javaType;
// add fallback value because field not readable
cachedMajorTypes()->insert(javaType, QBluetoothDeviceInfo::UncategorizedDevice);
} else {
cachedMajorTypes()->insert(fieldValue, majorMapping.qtMajor);
}
if (fieldValue == javaType)
result = majorMapping.qtMajor;
}
return result;
} else {
return it.value();
}
}
/*
The index for major into the MinorClassJavaToQtMapping and initializedCacheTracker
is major+1 except for UncategorizedDevice which is at index 0.
*/
int mappingIndexForMajor(QBluetoothDeviceInfo::MajorDeviceClass major)
{
int mappingIndex = (int) major;
if (major == QBluetoothDeviceInfo::UncategorizedDevice)
mappingIndex = 0;
else
mappingIndex++;
Q_ASSERT(mappingIndex >=0
&& mappingIndex <= (QBluetoothDeviceInfo::HealthDevice + 1));
return mappingIndex;
}
void triggerCachingOfMinorsForMajor(QBluetoothDeviceInfo::MajorDeviceClass major)
{
//qCDebug(QT_BT_ANDROID) << "Caching minor values for major" << major;
int mappingIndex = mappingIndexForMajor(major);
quint8 sizeIndex = minorIndexSizes[mappingIndex];
while (minorMappings[sizeIndex].javaFieldName != nullptr) {
jint fieldValue = QJniObject::getStaticField<jint>(
javaBluetoothClassDeviceClassName, minorMappings[sizeIndex].javaFieldName);
Q_ASSERT(fieldValue >= 0);
cachedMinorTypes()->insert(fieldValue, minorMappings[sizeIndex].qtMinor);
sizeIndex++;
}
initializedCacheTracker()->setBit(mappingIndex);
}
quint8 resolveAndroidMinorClass(QBluetoothDeviceInfo::MajorDeviceClass major, jint javaMinor)
{
// there are no minor device classes in java with value 0
//qCDebug(QT_BT_ANDROID) << "received minor class device:" << javaMinor;
if (javaMinor == 0)
return 0;
int mappingIndex = mappingIndexForMajor(major);
// whenever we encounter a not yet seen major device class
// we populate the cache with all its related minor values
if (!initializedCacheTracker()->at(mappingIndex))
triggerCachingOfMinorsForMajor(major);
const JCachedMinorTypes::iterator it = cachedMinorTypes()->find(javaMinor);
if (it == cachedMinorTypes()->end())
return 0;
else
return it.value();
}
DeviceDiscoveryBroadcastReceiver::DeviceDiscoveryBroadcastReceiver(QObject* parent): AndroidBroadcastReceiver(parent)
{
addAction(QJniObject::fromString(
valueForStaticField<QtJniTypes::BluetoothDevice, JavaNames::ActionFound>()));
addAction(QJniObject::fromString(
valueForStaticField<QtJniTypes::BluetoothAdapter, JavaNames::ActionDiscoveryStarted>()));
addAction(QJniObject::fromString(
valueForStaticField<QtJniTypes::BluetoothAdapter, JavaNames::ActionDiscoveryFinished>()));
}
// Runs in Java thread
void DeviceDiscoveryBroadcastReceiver::onReceive(JNIEnv *env, jobject context, jobject intent)
{
Q_UNUSED(context)
Q_UNUSED(env)
QJniObject intentObject(intent);
const QString action = intentObject.callMethod<jstring>("getAction").toString();
qCDebug(QT_BT_ANDROID) << "DeviceDiscoveryBroadcastReceiver::onReceive() - event:" << action;
if (action == valueForStaticField<QtJniTypes::BluetoothAdapter,
JavaNames::ActionDiscoveryFinished>()) {
emit finished();
} else if (action == valueForStaticField<QtJniTypes::BluetoothAdapter,
JavaNames::ActionDiscoveryStarted>()) {
emit discoveryStarted();
} else if (action == valueForStaticField<QtJniTypes::BluetoothDevice,
JavaNames::ActionFound>()) {
//get BluetoothDevice
QJniObject keyExtra =
QJniObject::fromString(valueForStaticField<QtJniTypes::BluetoothDevice,
JavaNames::ExtraDevice>());
const QJniObject bluetoothDevice =
intentObject.callMethod<QtJniTypes::Parcelable>("getParcelableExtra",
keyExtra.object<jstring>());
if (!bluetoothDevice.isValid())
return;
keyExtra = QJniObject::fromString(valueForStaticField<QtJniTypes::BluetoothDevice,
JavaNames::ExtraRssi>());
int rssi = intentObject.callMethod<jshort>("getShortExtra",
keyExtra.object<jstring>(), jshort(0));
const QBluetoothDeviceInfo info = retrieveDeviceInfo(bluetoothDevice, rssi);
if (info.isValid())
emit deviceDiscovered(info, false);
}
}
// Runs in Java thread
void DeviceDiscoveryBroadcastReceiver::onReceiveLeScan(
JNIEnv */*env*/, jobject jBluetoothDevice, jint rssi, jbyteArray scanRecord)
{
const QJniObject bluetoothDevice(jBluetoothDevice);
if (!bluetoothDevice.isValid())
return;
const QBluetoothDeviceInfo info = retrieveDeviceInfo(bluetoothDevice, rssi, scanRecord);
if (info.isValid())
emit deviceDiscovered(info, true);
}
Q_BLUETOOTH_EXPORT void parseScanRecord(QBluetoothDeviceInfo &info, jbyteArray scanRecord)
{
Q_ASSERT(scanRecord != nullptr);
QJniEnvironment env;
// Parse scan record
jboolean isCopy;
jbyte *elems = env->GetByteArrayElements(scanRecord, &isCopy);
const char *scanRecordBuffer = reinterpret_cast<const char *>(elems);
const jsize scanRecordLength = env->GetArrayLength(scanRecord);
QList<QBluetoothUuid> serviceUuids;
jsize i = 0;
// Spec 4.2, Vol 3, Part C, Chapter 11
QString localName;
while (i < scanRecordLength) {
// sizeof(EIR Data) = sizeof(Length) + sizeof(EIR data Type) + sizeof(EIR Data)
// Length = sizeof(EIR data Type) + sizeof(EIR Data)
const int nBytes = quint8(scanRecordBuffer[i]);
if (nBytes == 0)
break;
if (i >= scanRecordLength - nBytes)
break;
const int adType = quint8(scanRecordBuffer[i+1]);
const char *dataPtr = &scanRecordBuffer[i+2];
QBluetoothUuid foundService;
switch (adType) {
case ADType16BitUuidIncomplete:
case ADType16BitUuidComplete:
if (nBytes >= 3)
foundService = QBluetoothUuid(qFromLittleEndian<quint16>(dataPtr));
break;
case ADType32BitUuidIncomplete:
case ADType32BitUuidComplete:
if (nBytes >= 5)
foundService = QBluetoothUuid(qFromLittleEndian<quint32>(dataPtr));
break;
case ADType128BitUuidIncomplete:
case ADType128BitUuidComplete:
if (nBytes >= 17) {
foundService =
QBluetoothUuid(qToBigEndian<QUuid::Id128Bytes>(qFromLittleEndian<QUuid::Id128Bytes>(dataPtr)));
}
break;
case ADTypeServiceData16Bit:
if (nBytes >= 3) {
info.setServiceData(QBluetoothUuid(qFromLittleEndian<quint16>(dataPtr)),
QByteArray(dataPtr + 2, nBytes - 3));
}
break;
case ADTypeServiceData32Bit:
if (nBytes >= 5) {
info.setServiceData(QBluetoothUuid(qFromLittleEndian<quint32>(dataPtr)),
QByteArray(dataPtr + 4, nBytes - 5));
}
break;
case ADTypeServiceData128Bit:
if (nBytes >= 17) {
info.setServiceData(QBluetoothUuid(qToBigEndian<QUuid::Id128Bytes>(
qFromLittleEndian<QUuid::Id128Bytes>(dataPtr))),
QByteArray(dataPtr + 16, nBytes - 17));
}
break;
case ADTypeManufacturerSpecificData:
if (nBytes >= 3) {
info.setManufacturerData(qFromLittleEndian<quint16>(dataPtr),
QByteArray(dataPtr + 2, nBytes - 3));
}
break;
// According to Spec 5.0, Vol 3, Part C, Chapter 12.1
// the device's local name is utf8 encoded
case ADTypeShortenedLocalName:
if (localName.isEmpty())
localName = QString::fromUtf8(dataPtr, nBytes - 1);
break;
case ADTypeCompleteLocalName:
localName = QString::fromUtf8(dataPtr, nBytes - 1);
break;
default:
// qWarning() << "Unhandled AD Type" << Qt::hex << adType;
// no other types supported yet and therefore skipped
break;
}
i += nBytes + 1;
if (!foundService.isNull() && !serviceUuids.contains(foundService))
serviceUuids.append(foundService);
}
if (info.name().isEmpty())
info.setName(localName);
info.setServiceUuids(serviceUuids);
env->ReleaseByteArrayElements(scanRecord, elems, JNI_ABORT);
}
QBluetoothDeviceInfo DeviceDiscoveryBroadcastReceiver::retrieveDeviceInfo(const QJniObject &bluetoothDevice, int rssi, jbyteArray scanRecord)
{
const QString deviceName = bluetoothDevice.callMethod<jstring>("getName").toString();
const QBluetoothAddress deviceAddress(
bluetoothDevice.callMethod<jstring>("getAddress").toString());
const QJniObject bluetoothClass =
bluetoothDevice.callMethod<QtJniTypes::BluetoothClass>("getBluetoothClass");
if (!bluetoothClass.isValid())
return QBluetoothDeviceInfo();
QBluetoothDeviceInfo::MajorDeviceClass majorClass = resolveAndroidMajorClass(
bluetoothClass.callMethod<jint>("getMajorDeviceClass"));
// major device class is 5 bits from index 8 - 12
quint32 classType = ((quint32(majorClass) & 0x1f) << 8);
jint javaMinor = bluetoothClass.callMethod<jint>("getDeviceClass");
quint8 minorDeviceType = resolveAndroidMinorClass(majorClass, javaMinor);
// minor device class is 6 bits from index 2 - 7
classType |= ((quint32(minorDeviceType) & 0x3f) << 2);
static constexpr quint32 services[] = {
QBluetoothDeviceInfo::PositioningService,
QBluetoothDeviceInfo::NetworkingService,
QBluetoothDeviceInfo::RenderingService,
QBluetoothDeviceInfo::CapturingService,
QBluetoothDeviceInfo::ObjectTransferService,
QBluetoothDeviceInfo::AudioService,
QBluetoothDeviceInfo::TelephonyService,
QBluetoothDeviceInfo::InformationService,
};
// Matching BluetoothClass.Service values
quint32 serviceResult = 0;
for (quint32 current : services) {
int androidId = (current << 16); // Android values shift by 2 bytes compared to Qt enums
if (bluetoothClass.callMethod<jboolean>("hasService", androidId))
serviceResult |= current;
}
// service class info is 11 bits from index 13 - 23
classType |= (serviceResult << 13);
QBluetoothDeviceInfo info(deviceAddress, deviceName, classType);
info.setRssi(rssi);
QJniEnvironment env;
if (scanRecord != nullptr)
parseScanRecord(info, scanRecord);
auto methodId = env.findMethod(bluetoothDevice.objectClass(), "getType", "()I");
jint javaBtType = env->CallIntMethod(bluetoothDevice.object(), methodId);
if (!env.checkAndClearExceptions()) {
info.setCoreConfigurations(qtBtTypeForJavaBtType(javaBtType));
}
return info;
}
#if defined(Q_OS_ANDROID)
Q_BLUETOOTH_EXPORT void testEnumToString(std::function<void(const char*, int)> callback)
{
for (const auto &entry : minorMappings) {
if (entry.javaFieldName) {
callback(entry.javaFieldName, entry.qtMinor);
}
}
}
#endif
QT_END_NAMESPACE