Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
devicediscoverybroadcastreceiver.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 Lauri Laanmets (Proekspert AS) <lauri.laanmets@eesti.ee>
2// Copyright (C) 2016 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4// Qt-Security score:critical reason:data-parser
5
6#include "android/devicediscoverybroadcastreceiver_p.h"
7#include <QCoreApplication>
8#include <QtCore/QtEndian>
9#include <QtCore/QLoggingCategory>
10#include <QtBluetooth/QBluetoothAddress>
11#include <QtBluetooth/QBluetoothDeviceInfo>
12#include <QtBluetooth/QBluetoothUuid>
13#include "android/jni_android_p.h"
14#include <QtCore/QHash>
15#include <QtCore/qbitarray.h>
16#include <algorithm>
17
19
21
26
29
31{
32 const qsizetype numberOfMajorDeviceClasses = 11; // count QBluetoothDeviceInfo::MajorDeviceClass values
33
34 // switch below used to ensure that we notice additions to MajorDeviceClass enum
35 const QBluetoothDeviceInfo::MajorDeviceClass classes = QBluetoothDeviceInfo::ComputerDevice;
36 switch (classes) {
37 case QBluetoothDeviceInfo::MiscellaneousDevice:
38 case QBluetoothDeviceInfo::ComputerDevice:
39 case QBluetoothDeviceInfo::PhoneDevice:
40 case QBluetoothDeviceInfo::NetworkDevice:
41 case QBluetoothDeviceInfo::AudioVideoDevice:
42 case QBluetoothDeviceInfo::PeripheralDevice:
43 case QBluetoothDeviceInfo::ImagingDevice:
44 case QBluetoothDeviceInfo::WearableDevice:
45 case QBluetoothDeviceInfo::ToyDevice:
46 case QBluetoothDeviceInfo::HealthDevice:
47 case QBluetoothDeviceInfo::UncategorizedDevice:
48 break;
49 default:
50 qCWarning(QT_BT_ANDROID) << "Unknown category of major device class:" << classes;
51 }
52
53 return QBitArray(numberOfMajorDeviceClasses, false);
54}
55
56Q_GLOBAL_STATIC_WITH_ARGS(QBitArray, initializedCacheTracker, (initializeMinorCaches()))
57
58
59// class names
60static const char javaBluetoothDeviceClassName[] = "android/bluetooth/BluetoothDevice";
61static const char javaBluetoothClassDeviceMajorClassName[] = "android/bluetooth/BluetoothClass$Device$Major";
62static const char javaBluetoothClassDeviceClassName[] = "android/bluetooth/BluetoothClass$Device";
63
64// field names device type (LE vs classic)
65static const char javaDeviceTypeClassic[] = "DEVICE_TYPE_CLASSIC";
66static const char javaDeviceTypeDual[] = "DEVICE_TYPE_DUAL";
67static const char javaDeviceTypeLE[] = "DEVICE_TYPE_LE";
68static const char javaDeviceTypeUnknown[] = "DEVICE_TYPE_UNKNOWN";
69
75static_assert(sizeof(MajorClassJavaToQtMapping) == 16);
76
78 { "AUDIO_VIDEO", QBluetoothDeviceInfo::AudioVideoDevice },
79 { "COMPUTER", QBluetoothDeviceInfo::ComputerDevice },
80 { "HEALTH", QBluetoothDeviceInfo::HealthDevice },
81 { "IMAGING", QBluetoothDeviceInfo::ImagingDevice },
82 { "MISC", QBluetoothDeviceInfo::MiscellaneousDevice },
83 { "NETWORKING", QBluetoothDeviceInfo::NetworkDevice },
84 { "PERIPHERAL", QBluetoothDeviceInfo::PeripheralDevice },
85 { "PHONE", QBluetoothDeviceInfo::PhoneDevice },
86 { "TOY", QBluetoothDeviceInfo::ToyDevice },
87 { "UNCATEGORIZED", QBluetoothDeviceInfo::UncategorizedDevice },
88 { "WEARABLE", QBluetoothDeviceInfo::WearableDevice },
89};
90
91// QBluetoothDeviceInfo::MajorDeviceClass value plus 1 matches index
92// UncategorizedDevice shifts to index 0
93static constexpr quint8 minorIndexSizes[] = {
94 64, // QBluetoothDevice::UncategorizedDevice
95 61, // QBluetoothDevice::MiscellaneousDevice
96 18, // QBluetoothDevice::ComputerDevice
97 35, // QBluetoothDevice::PhoneDevice
98 62, // QBluetoothDevice::NetworkDevice
99 0, // QBluetoothDevice::AudioVideoDevice
100 56, // QBluetoothDevice::PeripheralDevice
101 63, // QBluetoothDevice::ImagingDEvice
102 49, // QBluetoothDevice::WearableDevice
103 42, // QBluetoothDevice::ToyDevice
104 26, // QBluetoothDevice::HealthDevice
105};
106
112
114 // QBluetoothDevice::AudioVideoDevice -> 17 entries
115 { "AUDIO_VIDEO_CAMCORDER", QBluetoothDeviceInfo::Camcorder }, //index 0
116 { "AUDIO_VIDEO_CAR_AUDIO", QBluetoothDeviceInfo::CarAudio },
117 { "AUDIO_VIDEO_HANDSFREE", QBluetoothDeviceInfo::HandsFreeDevice },
118 { "AUDIO_VIDEO_HEADPHONES", QBluetoothDeviceInfo::Headphones },
119 { "AUDIO_VIDEO_HIFI_AUDIO", QBluetoothDeviceInfo::HiFiAudioDevice },
120 { "AUDIO_VIDEO_LOUDSPEAKER", QBluetoothDeviceInfo::Loudspeaker },
121 { "AUDIO_VIDEO_MICROPHONE", QBluetoothDeviceInfo::Microphone },
122 { "AUDIO_VIDEO_PORTABLE_AUDIO", QBluetoothDeviceInfo::PortableAudioDevice },
123 { "AUDIO_VIDEO_SET_TOP_BOX", QBluetoothDeviceInfo::SetTopBox },
124 { "AUDIO_VIDEO_UNCATEGORIZED", QBluetoothDeviceInfo::UncategorizedAudioVideoDevice },
125 { "AUDIO_VIDEO_VCR", QBluetoothDeviceInfo::Vcr },
126 { "AUDIO_VIDEO_VIDEO_CAMERA", QBluetoothDeviceInfo::VideoCamera },
127 { "AUDIO_VIDEO_VIDEO_CONFERENCING", QBluetoothDeviceInfo::VideoConferencing },
128 { "AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER", QBluetoothDeviceInfo::VideoDisplayAndLoudspeaker },
129 { "AUDIO_VIDEO_VIDEO_GAMING_TOY", QBluetoothDeviceInfo::GamingDevice },
130 { "AUDIO_VIDEO_VIDEO_MONITOR", QBluetoothDeviceInfo::VideoMonitor },
131 { "AUDIO_VIDEO_WEARABLE_HEADSET", QBluetoothDeviceInfo::WearableHeadsetDevice },
132 { nullptr, 0 }, // separator
133
134 // QBluetoothDevice::ComputerDevice -> 7 entries
135 { "COMPUTER_UNCATEGORIZED", QBluetoothDeviceInfo::UncategorizedComputer }, // index 18
136 { "COMPUTER_DESKTOP", QBluetoothDeviceInfo::DesktopComputer },
137 { "COMPUTER_HANDHELD_PC_PDA", QBluetoothDeviceInfo::HandheldComputer },
138 { "COMPUTER_LAPTOP", QBluetoothDeviceInfo::LaptopComputer },
139 { "COMPUTER_PALM_SIZE_PC_PDA", QBluetoothDeviceInfo::HandheldClamShellComputer },
140 { "COMPUTER_SERVER", QBluetoothDeviceInfo::ServerComputer },
141 { "COMPUTER_WEARABLE", QBluetoothDeviceInfo::WearableComputer },
142 { nullptr, 0 }, // separator
143
144 // QBluetoothDevice::HealthDevice -> 8 entries
145 { "HEALTH_BLOOD_PRESSURE", QBluetoothDeviceInfo::HealthBloodPressureMonitor }, // index 26
146 { "HEALTH_DATA_DISPLAY", QBluetoothDeviceInfo::HealthDataDisplay },
147 { "HEALTH_GLUCOSE", QBluetoothDeviceInfo::HealthGlucoseMeter },
148 { "HEALTH_PULSE_OXIMETER", QBluetoothDeviceInfo::HealthPulseOximeter },
149 { "HEALTH_PULSE_RATE", QBluetoothDeviceInfo::HealthStepCounter },
150 { "HEALTH_THERMOMETER", QBluetoothDeviceInfo::HealthThermometer },
151 { "HEALTH_UNCATEGORIZED", QBluetoothDeviceInfo::UncategorizedHealthDevice },
152 { "HEALTH_WEIGHING", QBluetoothDeviceInfo::HealthWeightScale },
153 { nullptr, 0 }, // separator
154
155 // QBluetoothDevice::PhoneDevice -> 6 entries
156 { "PHONE_CELLULAR", QBluetoothDeviceInfo::CellularPhone }, // index 35
157 { "PHONE_CORDLESS", QBluetoothDeviceInfo::CordlessPhone },
158 { "PHONE_ISDN", QBluetoothDeviceInfo::CommonIsdnAccessPhone },
159 { "PHONE_MODEM_OR_GATEWAY", QBluetoothDeviceInfo::WiredModemOrVoiceGatewayPhone },
160 { "PHONE_SMART", QBluetoothDeviceInfo::SmartPhone },
161 { "PHONE_UNCATEGORIZED", QBluetoothDeviceInfo::UncategorizedPhone },
162 { nullptr, 0 }, // separator
163
164 // QBluetoothDevice::ToyDevice -> 6 entries
165 { "TOY_CONTROLLER", QBluetoothDeviceInfo::ToyController }, // index 42
166 { "TOY_DOLL_ACTION_FIGURE", QBluetoothDeviceInfo::ToyDoll },
167 { "TOY_GAME", QBluetoothDeviceInfo::ToyGame },
168 { "TOY_ROBOT", QBluetoothDeviceInfo::ToyRobot },
169 { "TOY_UNCATEGORIZED", QBluetoothDeviceInfo::UncategorizedToy },
170 { "TOY_VEHICLE", QBluetoothDeviceInfo::ToyVehicle },
171 { nullptr, 0 }, // separator
172
173 // QBluetoothDevice::WearableDevice -> 6 entries
174 { "WEARABLE_GLASSES", QBluetoothDeviceInfo::WearableGlasses }, // index 49
175 { "WEARABLE_HELMET", QBluetoothDeviceInfo::WearableHelmet },
176 { "WEARABLE_JACKET", QBluetoothDeviceInfo::WearableJacket },
177 { "WEARABLE_PAGER", QBluetoothDeviceInfo::WearablePager },
178 { "WEARABLE_UNCATEGORIZED", QBluetoothDeviceInfo::UncategorizedWearableDevice },
179 { "WEARABLE_WRIST_WATCH", QBluetoothDeviceInfo::WearableWristWatch },
180 { nullptr, 0 }, // separator
181
182 // QBluetoothDevice::PeripheralDevice -> 3 entries
183 // For some reason these are not mentioned in Android docs but still exist
184 { "PERIPHERAL_NON_KEYBOARD_NON_POINTING", QBluetoothDeviceInfo::UncategorizedPeripheral }, // index 56
185 { "PERIPHERAL_KEYBOARD", QBluetoothDeviceInfo::KeyboardPeripheral },
186 { "PERIPHERAL_POINTING", QBluetoothDeviceInfo::PointingDevicePeripheral },
187 { "PERIPHERAL_KEYBOARD_POINTING", QBluetoothDeviceInfo::KeyboardWithPointingDevicePeripheral },
188 { nullptr, 0 }, // separator
189
190 // the following entries do not exist on Android
191 // we map them to the unknown minor version case
192 // QBluetoothDevice::Miscellaneous
193 { nullptr, 0 }, // index 61 & separator
194
195 // QBluetoothDevice::NetworkDevice
196 { nullptr, 0 }, // index 62 & separator
197
198 // QBluetoothDevice::ImagingDevice
199 { nullptr, 0 }, // index 63 & separator
200
201 // QBluetoothDevice::UncategorizedDevice
202 { nullptr, 0 }, // index 64 & separator
203};
204
205/*
206 Advertising Data Type (AD type) for LE scan records, as defined in
207 https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile
208*/
224
226{
227 const JCachedBtTypes::iterator it = cachedBtTypes()->find(javaType);
228 if (it == cachedBtTypes()->end()) {
229
230 if (javaType == QJniObject::getStaticField<jint>(
231 javaBluetoothDeviceClassName, javaDeviceTypeClassic)) {
232 cachedBtTypes()->insert(javaType,
233 QBluetoothDeviceInfo::BaseRateCoreConfiguration);
234 return QBluetoothDeviceInfo::BaseRateCoreConfiguration;
235 } else if (javaType == QJniObject::getStaticField<jint>(
236 javaBluetoothDeviceClassName, javaDeviceTypeLE)) {
237 cachedBtTypes()->insert(javaType,
238 QBluetoothDeviceInfo::LowEnergyCoreConfiguration);
239 return QBluetoothDeviceInfo::LowEnergyCoreConfiguration;
240 } else if (javaType == QJniObject::getStaticField<jint>(
241 javaBluetoothDeviceClassName, javaDeviceTypeDual)) {
242 cachedBtTypes()->insert(javaType,
243 QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration);
244 return QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration;
245 } else if (javaType == QJniObject::getStaticField<jint>(
246 javaBluetoothDeviceClassName, javaDeviceTypeUnknown)) {
247 cachedBtTypes()->insert(javaType,
248 QBluetoothDeviceInfo::UnknownCoreConfiguration);
249 } else {
250 qCWarning(QT_BT_ANDROID) << "Unknown Bluetooth device type value";
251 }
252
253 return QBluetoothDeviceInfo::UnknownCoreConfiguration;
254 } else {
255 return it.value();
256 }
257}
258
260{
261 const JCachedMajorTypes::iterator it = cachedMajorTypes()->find(javaType);
262 if (it == cachedMajorTypes()->end()) {
263 QJniEnvironment env;
264 // precache all major device class fields
265 jint fieldValue;
266 QBluetoothDeviceInfo::MajorDeviceClass result = QBluetoothDeviceInfo::UncategorizedDevice;
267 auto clazz = env->FindClass(javaBluetoothClassDeviceMajorClassName);
268 for (const auto &majorMapping : majorMappings) {
269 auto fieldId = env->GetStaticFieldID(clazz, majorMapping.javaFieldName, "I");
270 if (!env->ExceptionCheck())
271 fieldValue = env->GetStaticIntField(clazz, fieldId);
272 if (env.checkAndClearExceptions()) {
273 qCWarning(QT_BT_ANDROID) << "Unknown BluetoothClass.Device.Major field" << javaType;
274
275 // add fallback value because field not readable
276 cachedMajorTypes()->insert(javaType, QBluetoothDeviceInfo::UncategorizedDevice);
277 } else {
278 cachedMajorTypes()->insert(fieldValue, majorMapping.qtMajor);
279 }
280
281 if (fieldValue == javaType)
282 result = majorMapping.qtMajor;
283 }
284
285 return result;
286 } else {
287 return it.value();
288 }
289}
290
291/*
292 The index for major into the MinorClassJavaToQtMapping and initializedCacheTracker
293 is major+1 except for UncategorizedDevice which is at index 0.
294*/
295int mappingIndexForMajor(QBluetoothDeviceInfo::MajorDeviceClass major)
296{
297 int mappingIndex = (int) major;
298 if (major == QBluetoothDeviceInfo::UncategorizedDevice)
299 mappingIndex = 0;
300 else
301 mappingIndex++;
302
303 Q_ASSERT(mappingIndex >=0
304 && mappingIndex <= (QBluetoothDeviceInfo::HealthDevice + 1));
305
306 return mappingIndex;
307}
308
309void triggerCachingOfMinorsForMajor(QBluetoothDeviceInfo::MajorDeviceClass major)
310{
311 //qCDebug(QT_BT_ANDROID) << "Caching minor values for major" << major;
312 int mappingIndex = mappingIndexForMajor(major);
313 quint8 sizeIndex = minorIndexSizes[mappingIndex];
314
315 while (minorMappings[sizeIndex].javaFieldName != nullptr) {
316 jint fieldValue = QJniObject::getStaticField<jint>(
317 javaBluetoothClassDeviceClassName, minorMappings[sizeIndex].javaFieldName);
318
319 Q_ASSERT(fieldValue >= 0);
320 cachedMinorTypes()->insert(fieldValue, minorMappings[sizeIndex].qtMinor);
321 sizeIndex++;
322 }
323
324 initializedCacheTracker()->setBit(mappingIndex);
325}
326
327quint8 resolveAndroidMinorClass(QBluetoothDeviceInfo::MajorDeviceClass major, jint javaMinor)
328{
329 // there are no minor device classes in java with value 0
330 //qCDebug(QT_BT_ANDROID) << "received minor class device:" << javaMinor;
331 if (javaMinor == 0)
332 return 0;
333
334 int mappingIndex = mappingIndexForMajor(major);
335
336 // whenever we encounter a not yet seen major device class
337 // we populate the cache with all its related minor values
338 if (!initializedCacheTracker()->at(mappingIndex))
339 triggerCachingOfMinorsForMajor(major);
340
341 const JCachedMinorTypes::iterator it = cachedMinorTypes()->find(javaMinor);
342 if (it == cachedMinorTypes()->end())
343 return 0;
344 else
345 return it.value();
346}
347
348
349DeviceDiscoveryBroadcastReceiver::DeviceDiscoveryBroadcastReceiver(QObject* parent): AndroidBroadcastReceiver(parent)
350{
351 addAction(QJniObject::fromString(
352 valueForStaticField<QtJniTypes::BluetoothDevice, JavaNames::ActionFound>()));
353 addAction(QJniObject::fromString(
354 valueForStaticField<QtJniTypes::BluetoothAdapter, JavaNames::ActionDiscoveryStarted>()));
355 addAction(QJniObject::fromString(
356 valueForStaticField<QtJniTypes::BluetoothAdapter, JavaNames::ActionDiscoveryFinished>()));
357}
358
359// Runs in Java thread
360void DeviceDiscoveryBroadcastReceiver::onReceive(JNIEnv *env, jobject context, jobject intent)
361{
362 Q_UNUSED(context)
363 Q_UNUSED(env)
364
365 QJniObject intentObject(intent);
366 const QString action = intentObject.callMethod<jstring>("getAction").toString();
367
368 qCDebug(QT_BT_ANDROID) << "DeviceDiscoveryBroadcastReceiver::onReceive() - event:" << action;
369
370 if (action == valueForStaticField<QtJniTypes::BluetoothAdapter,
371 JavaNames::ActionDiscoveryFinished>()) {
372 emit finished();
373 } else if (action == valueForStaticField<QtJniTypes::BluetoothAdapter,
374 JavaNames::ActionDiscoveryStarted>()) {
375 emit discoveryStarted();
376 } else if (action == valueForStaticField<QtJniTypes::BluetoothDevice,
377 JavaNames::ActionFound>()) {
378 //get BluetoothDevice
379 QJniObject keyExtra =
380 QJniObject::fromString(valueForStaticField<QtJniTypes::BluetoothDevice,
381 JavaNames::ExtraDevice>());
382 const QJniObject bluetoothDevice =
383 intentObject.callMethod<QtJniTypes::Parcelable>("getParcelableExtra",
384 keyExtra.object<jstring>());
385
386 if (!bluetoothDevice.isValid())
387 return;
388
389 keyExtra = QJniObject::fromString(valueForStaticField<QtJniTypes::BluetoothDevice,
390 JavaNames::ExtraRssi>());
391 int rssi = intentObject.callMethod<jshort>("getShortExtra",
392 keyExtra.object<jstring>(), jshort(0));
393
394 const QBluetoothDeviceInfo info = retrieveDeviceInfo(bluetoothDevice, rssi);
395 if (info.isValid())
396 emit deviceDiscovered(info, false);
397 }
398}
399
400// Runs in Java thread
402 JNIEnv */*env*/, jobject jBluetoothDevice, jint rssi, jbyteArray scanRecord)
403{
404 const QJniObject bluetoothDevice(jBluetoothDevice);
405 if (!bluetoothDevice.isValid())
406 return;
407
408 const QBluetoothDeviceInfo info = retrieveDeviceInfo(bluetoothDevice, rssi, scanRecord);
409 if (info.isValid())
410 emit deviceDiscovered(info, true);
411}
412
413Q_BLUETOOTH_EXPORT void parseScanRecord(QBluetoothDeviceInfo &info, jbyteArray scanRecord)
414{
415 Q_ASSERT(scanRecord != nullptr);
416 QJniEnvironment env;
417
418 // Parse scan record
419 jboolean isCopy;
420 jbyte *elems = env->GetByteArrayElements(scanRecord, &isCopy);
421 const char *scanRecordBuffer = reinterpret_cast<const char *>(elems);
422 const jsize scanRecordLength = env->GetArrayLength(scanRecord);
423
424 QList<QBluetoothUuid> serviceUuids;
425 jsize i = 0;
426
427 // Spec 4.2, Vol 3, Part C, Chapter 11
428 QString localName;
429 while (i < scanRecordLength) {
430 // sizeof(EIR Data) = sizeof(Length) + sizeof(EIR data Type) + sizeof(EIR Data)
431 // Length = sizeof(EIR data Type) + sizeof(EIR Data)
432
433 const int nBytes = quint8(scanRecordBuffer[i]);
434 if (nBytes == 0)
435 break;
436
437 if (i >= scanRecordLength - nBytes)
438 break;
439
440 const int adType = quint8(scanRecordBuffer[i+1]);
441 const char *dataPtr = &scanRecordBuffer[i+2];
442 QBluetoothUuid foundService;
443
444 switch (adType) {
447 if (nBytes >= 3)
448 foundService = QBluetoothUuid(qFromLittleEndian<quint16>(dataPtr));
449 break;
452 if (nBytes >= 5)
453 foundService = QBluetoothUuid(qFromLittleEndian<quint32>(dataPtr));
454 break;
457 if (nBytes >= 17) {
458 foundService =
459 QBluetoothUuid(qToBigEndian<QUuid::Id128Bytes>(qFromLittleEndian<QUuid::Id128Bytes>(dataPtr)));
460 }
461 break;
463 if (nBytes >= 3) {
464 info.setServiceData(QBluetoothUuid(qFromLittleEndian<quint16>(dataPtr)),
465 QByteArray(dataPtr + 2, nBytes - 3));
466 }
467 break;
469 if (nBytes >= 5) {
470 info.setServiceData(QBluetoothUuid(qFromLittleEndian<quint32>(dataPtr)),
471 QByteArray(dataPtr + 4, nBytes - 5));
472 }
473 break;
475 if (nBytes >= 17) {
476 info.setServiceData(QBluetoothUuid(qToBigEndian<QUuid::Id128Bytes>(
477 qFromLittleEndian<QUuid::Id128Bytes>(dataPtr))),
478 QByteArray(dataPtr + 16, nBytes - 17));
479 }
480 break;
482 if (nBytes >= 3) {
483 info.setManufacturerData(qFromLittleEndian<quint16>(dataPtr),
484 QByteArray(dataPtr + 2, nBytes - 3));
485 }
486 break;
487 // According to Spec 5.0, Vol 3, Part C, Chapter 12.1
488 // the device's local name is utf8 encoded
489 case ADTypeShortenedLocalName:
490 if (localName.isEmpty())
491 localName = QString::fromUtf8(dataPtr, nBytes - 1);
492 break;
493 case ADTypeCompleteLocalName:
494 localName = QString::fromUtf8(dataPtr, nBytes - 1);
495 break;
496 default:
497 // qWarning() << "Unhandled AD Type" << Qt::hex << adType;
498 // no other types supported yet and therefore skipped
499 // https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile
500 break;
501 }
502
503 i += nBytes + 1;
504
505 if (!foundService.isNull() && !serviceUuids.contains(foundService))
506 serviceUuids.append(foundService);
507 }
508
509 if (info.name().isEmpty())
510 info.setName(localName);
511
512 info.setServiceUuids(serviceUuids);
513
514 env->ReleaseByteArrayElements(scanRecord, elems, JNI_ABORT);
515}
516
517QBluetoothDeviceInfo DeviceDiscoveryBroadcastReceiver::retrieveDeviceInfo(const QJniObject &bluetoothDevice, int rssi, jbyteArray scanRecord)
518{
519 const QString deviceName = bluetoothDevice.callMethod<jstring>("getName").toString();
520 const QBluetoothAddress deviceAddress(
521 bluetoothDevice.callMethod<jstring>("getAddress").toString());
522 const QJniObject bluetoothClass =
523 bluetoothDevice.callMethod<QtJniTypes::BluetoothClass>("getBluetoothClass");
524
525 if (!bluetoothClass.isValid())
526 return QBluetoothDeviceInfo();
527
528 QBluetoothDeviceInfo::MajorDeviceClass majorClass = resolveAndroidMajorClass(
529 bluetoothClass.callMethod<jint>("getMajorDeviceClass"));
530 // major device class is 5 bits from index 8 - 12
531 quint32 classType = ((quint32(majorClass) & 0x1f) << 8);
532
533 jint javaMinor = bluetoothClass.callMethod<jint>("getDeviceClass");
534 quint8 minorDeviceType = resolveAndroidMinorClass(majorClass, javaMinor);
535
536 // minor device class is 6 bits from index 2 - 7
537 classType |= ((quint32(minorDeviceType) & 0x3f) << 2);
538
539 static constexpr quint32 services[] = {
540 QBluetoothDeviceInfo::PositioningService,
541 QBluetoothDeviceInfo::NetworkingService,
542 QBluetoothDeviceInfo::RenderingService,
543 QBluetoothDeviceInfo::CapturingService,
544 QBluetoothDeviceInfo::ObjectTransferService,
545 QBluetoothDeviceInfo::AudioService,
546 QBluetoothDeviceInfo::TelephonyService,
547 QBluetoothDeviceInfo::InformationService,
548 };
549
550 // Matching BluetoothClass.Service values
551 quint32 serviceResult = 0;
552 for (quint32 current : services) {
553 int androidId = (current << 16); // Android values shift by 2 bytes compared to Qt enums
554 if (bluetoothClass.callMethod<jboolean>("hasService", androidId))
555 serviceResult |= current;
556 }
557
558 // service class info is 11 bits from index 13 - 23
559 classType |= (serviceResult << 13);
560
561 QBluetoothDeviceInfo info(deviceAddress, deviceName, classType);
562 info.setRssi(rssi);
563 QJniEnvironment env;
564 if (scanRecord != nullptr)
565 parseScanRecord(info, scanRecord);
566
567 auto methodId = env.findMethod(bluetoothDevice.objectClass(), "getType", "()I");
568 jint javaBtType = env->CallIntMethod(bluetoothDevice.object(), methodId);
569 if (!env.checkAndClearExceptions()) {
570 info.setCoreConfigurations(qtBtTypeForJavaBtType(javaBtType));
571 }
572
573 return info;
574}
575
576#if defined(Q_OS_ANDROID)
577Q_BLUETOOTH_EXPORT void testEnumToString(std::function<void(const char*, int)> callback)
578{
579 for (const auto &entry : minorMappings) {
580 if (entry.javaFieldName) {
581 callback(entry.javaFieldName, entry.qtMinor);
582 }
583 }
584}
585#endif
586
587QT_END_NAMESPACE
virtual void onReceiveLeScan(JNIEnv *env, jobject jBluetoothDevice, jint rssi, jbyteArray scanRecord)
virtual void onReceive(JNIEnv *env, jobject context, jobject intent)
static QBitArray initializeMinorCaches()
static constexpr quint8 minorIndexSizes[]
QHash< jint, QBluetoothDeviceInfo::MajorDeviceClass > JCachedMajorTypes
static const char javaBluetoothClassDeviceClassName[]
static const char javaBluetoothClassDeviceMajorClassName[]
int mappingIndexForMajor(QBluetoothDeviceInfo::MajorDeviceClass major)
QT_BEGIN_NAMESPACE typedef QHash< jint, QBluetoothDeviceInfo::CoreConfigurations > JCachedBtTypes
static const char javaDeviceTypeLE[]
Q_BLUETOOTH_EXPORT void parseScanRecord(QBluetoothDeviceInfo &info, jbyteArray scanRecord)
static const char javaDeviceTypeClassic[]
static const char javaDeviceTypeDual[]
QBluetoothDeviceInfo::MajorDeviceClass resolveAndroidMajorClass(jint javaType)
quint8 resolveAndroidMinorClass(QBluetoothDeviceInfo::MajorDeviceClass major, jint javaMinor)
static constexpr MajorClassJavaToQtMapping majorMappings[]
QHash< jint, quint8 > JCachedMinorTypes
static const char javaDeviceTypeUnknown[]
static const MinorClassJavaToQtMapping minorMappings[]
void triggerCachingOfMinorsForMajor(QBluetoothDeviceInfo::MajorDeviceClass major)
QBluetoothDeviceInfo::CoreConfigurations qtBtTypeForJavaBtType(jint javaType)
QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(lcQIORing)
Q_GLOBAL_STATIC(QReadWriteLock, g_updateMutex)
Q_GLOBAL_STATIC_WITH_ARGS(PermissionStatusHash, g_permissionStatusHash,({ { qMetaTypeId< QCameraPermission >(), Qt::PermissionStatus::Undetermined }, { qMetaTypeId< QMicrophonePermission >(), Qt::PermissionStatus::Undetermined }, { qMetaTypeId< QBluetoothPermission >(), Qt::PermissionStatus::Undetermined }, { qMetaTypeId< QContactsPermission >(), Qt::PermissionStatus::Undetermined }, { qMetaTypeId< QCalendarPermission >(), Qt::PermissionStatus::Undetermined }, { qMetaTypeId< QLocationPermission >(), Qt::PermissionStatus::Undetermined } }))
QBluetoothDeviceInfo::MajorDeviceClass qtMajor