1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 Lauri Laanmets (Proekspert AS) <lauri.laanmets@eesti.ee>
4 ** Copyright (C) 2016 The Qt Company Ltd.
5 ** Contact: https://www.qt.io/licensing/
6 **
7 ** This file is part of the QtBluetooth module of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** Commercial License Usage
11 ** Licensees holding valid commercial Qt licenses may use this file in
12 ** accordance with the commercial license agreement provided with the
13 ** Software or, alternatively, in accordance with the terms contained in
14 ** a written agreement between you and The Qt Company. For licensing terms
15 ** and conditions see https://www.qt.io/terms-conditions. For further
16 ** information use the contact form at https://www.qt.io/contact-us.
17 **
18 ** GNU Lesser General Public License Usage
19 ** Alternatively, this file may be used under the terms of the GNU Lesser
20 ** General Public License version 3 as published by the Free Software
21 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
22 ** packaging of this file. Please review the following information to
23 ** ensure the GNU Lesser General Public License version 3 requirements
24 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
25 **
26 ** GNU General Public License Usage
27 ** Alternatively, this file may be used under the terms of the GNU
28 ** General Public License version 2.0 or (at your option) the GNU General
29 ** Public license version 3 or any later version approved by the KDE Free
30 ** Qt Foundation. The licenses are as published by the Free Software
31 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
32 ** included in the packaging of this file. Please review the following
33 ** information to ensure the GNU General Public License requirements will
34 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
35 ** https://www.gnu.org/licenses/gpl-3.0.html.
36 **
37 ** $QT_END_LICENSE$
38 **
39 ****************************************************************************/
40 
41 #include "android/devicediscoverybroadcastreceiver_p.h"
42 #include <QtCore/QtEndian>
43 #include <QtCore/QLoggingCategory>
44 #include <QtBluetooth/QBluetoothAddress>
45 #include <QtBluetooth/QBluetoothDeviceInfo>
46 #include <QtBluetooth/QBluetoothUuid>
47 #include "android/jni_android_p.h"
48 #include <QtCore/private/qjnihelpers_p.h>
49 #include <QtCore/QHash>
50 #include <QtCore/qbitarray.h>
51 #include <algorithm>
52 
53 QT_BEGIN_NAMESPACE
54 
55 Q_DECLARE_LOGGING_CATEGORY(QT_BT_ANDROID)
56 
57 typedef QHash<jint, QBluetoothDeviceInfo::CoreConfigurations> JCachedBtTypes;
58 Q_GLOBAL_STATIC(JCachedBtTypes, cachedBtTypes)
59 typedef QHash<jint, QBluetoothDeviceInfo::MajorDeviceClass> JCachedMajorTypes;
60 Q_GLOBAL_STATIC(JCachedMajorTypes, cachedMajorTypes)
61 
62 typedef QHash<jint, quint8> JCachedMinorTypes;
Q_GLOBAL_STATIC(JCachedMinorTypes,cachedMinorTypes)63 Q_GLOBAL_STATIC(JCachedMinorTypes, cachedMinorTypes)
64 
65 static QBitArray initializeMinorCaches()
66 {
67     const int numberOfMajorDeviceClasses = 11; // count QBluetoothDeviceInfo::MajorDeviceClass values
68 
69     // switch below used to ensure that we notice additions to MajorDeviceClass enum
70     const QBluetoothDeviceInfo::MajorDeviceClass classes = QBluetoothDeviceInfo::ComputerDevice;
71     switch (classes) {
72     case QBluetoothDeviceInfo::MiscellaneousDevice:
73     case QBluetoothDeviceInfo::ComputerDevice:
74     case QBluetoothDeviceInfo::PhoneDevice:
75     case QBluetoothDeviceInfo::LANAccessDevice:
76     case QBluetoothDeviceInfo::AudioVideoDevice:
77     case QBluetoothDeviceInfo::PeripheralDevice:
78     case QBluetoothDeviceInfo::ImagingDevice:
79     case QBluetoothDeviceInfo::WearableDevice:
80     case QBluetoothDeviceInfo::ToyDevice:
81     case QBluetoothDeviceInfo::HealthDevice:
82     case QBluetoothDeviceInfo::UncategorizedDevice:
83         break;
84     default:
85         qCWarning(QT_BT_ANDROID) << "Unknown category of major device class:" << classes;
86     }
87 
88     return QBitArray(numberOfMajorDeviceClasses, false);
89 }
90 
91 Q_GLOBAL_STATIC_WITH_ARGS(QBitArray, initializedCacheTracker, (initializeMinorCaches()))
92 
93 
94 // class names
95 static const char * const javaBluetoothDeviceClassName = "android/bluetooth/BluetoothDevice";
96 static const char * const javaBluetoothClassDeviceMajorClassName = "android/bluetooth/BluetoothClass$Device$Major";
97 static const char * const javaBluetoothClassDeviceClassName = "android/bluetooth/BluetoothClass$Device";
98 
99 // field names device type (LE vs classic)
100 static const char * const javaDeviceTypeClassic = "DEVICE_TYPE_CLASSIC";
101 static const char * const javaDeviceTypeDual = "DEVICE_TYPE_DUAL";
102 static const char * const javaDeviceTypeLE = "DEVICE_TYPE_LE";
103 static const char * const javaDeviceTypeUnknown = "DEVICE_TYPE_UNKNOWN";
104 
105 struct MajorClassJavaToQtMapping
106 {
107     char const * javaFieldName;
108     QBluetoothDeviceInfo::MajorDeviceClass qtMajor;
109 };
110 
111 static const MajorClassJavaToQtMapping majorMappings[] = {
112     { "AUDIO_VIDEO", QBluetoothDeviceInfo::AudioVideoDevice },
113     { "COMPUTER", QBluetoothDeviceInfo::ComputerDevice },
114     { "HEALTH", QBluetoothDeviceInfo::HealthDevice },
115     { "IMAGING", QBluetoothDeviceInfo::ImagingDevice },
116     { "MISC", QBluetoothDeviceInfo::MiscellaneousDevice },
117     { "NETWORKING", QBluetoothDeviceInfo::LANAccessDevice },
118     { "PERIPHERAL", QBluetoothDeviceInfo::PeripheralDevice },
119     { "PHONE", QBluetoothDeviceInfo::PhoneDevice },
120     { "TOY", QBluetoothDeviceInfo::ToyDevice },
121     { "UNCATEGORIZED", QBluetoothDeviceInfo::UncategorizedDevice },
122     { "WEARABLE", QBluetoothDeviceInfo::WearableDevice },
123     { nullptr, QBluetoothDeviceInfo::UncategorizedDevice } //end of list
124 };
125 
126 // QBluetoothDeviceInfo::MajorDeviceClass value plus 1 matches index
127 // UncategorizedDevice shifts to index 0
128 static const int minorIndexSizes[] = {
129   64,  // QBluetoothDevice::UncategorizedDevice
130   61,  // QBluetoothDevice::MiscellaneousDevice
131   18,  // QBluetoothDevice::ComputerDevice
132   35,  // QBluetoothDevice::PhoneDevice
133   62,  // QBluetoothDevice::LANAccessDevice
134   0,  // QBluetoothDevice::AudioVideoDevice
135   56,  // QBluetoothDevice::PeripheralDevice
136   63,  // QBluetoothDevice::ImagingDEvice
137   49,  // QBluetoothDevice::WearableDevice
138   42,  // QBluetoothDevice::ToyDevice
139   26,  // QBluetoothDevice::HealthDevice
140 };
141 
142 struct MinorClassJavaToQtMapping
143 {
144     char const * javaFieldName;
145     quint8 qtMinor;
146 };
147 
148 static const MinorClassJavaToQtMapping minorMappings[] = {
149     // QBluetoothDevice::AudioVideoDevice -> 17 entries
150     { "AUDIO_VIDEO_CAMCORDER", QBluetoothDeviceInfo::Camcorder }, //index 0
151     { "AUDIO_VIDEO_CAR_AUDIO", QBluetoothDeviceInfo::CarAudio },
152     { "AUDIO_VIDEO_HANDSFREE", QBluetoothDeviceInfo::HandsFreeDevice },
153     { "AUDIO_VIDEO_HEADPHONES", QBluetoothDeviceInfo::Headphones },
154     { "AUDIO_VIDEO_HIFI_AUDIO", QBluetoothDeviceInfo::HiFiAudioDevice },
155     { "AUDIO_VIDEO_LOUDSPEAKER", QBluetoothDeviceInfo::Loudspeaker },
156     { "AUDIO_VIDEO_MICROPHONE", QBluetoothDeviceInfo::Microphone },
157     { "AUDIO_VIDEO_PORTABLE_AUDIO", QBluetoothDeviceInfo::PortableAudioDevice },
158     { "AUDIO_VIDEO_SET_TOP_BOX", QBluetoothDeviceInfo::SetTopBox },
159     { "AUDIO_VIDEO_UNCATEGORIZED", QBluetoothDeviceInfo::UncategorizedAudioVideoDevice },
160     { "AUDIO_VIDEO_VCR", QBluetoothDeviceInfo::Vcr },
161     { "AUDIO_VIDEO_VIDEO_CAMERA", QBluetoothDeviceInfo::VideoCamera },
162     { "AUDIO_VIDEO_VIDEO_CONFERENCING", QBluetoothDeviceInfo::VideoConferencing },
163     { "AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER", QBluetoothDeviceInfo::VideoDisplayAndLoudspeaker },
164     { "AUDIO_VIDEO_VIDEO_GAMING_TOY", QBluetoothDeviceInfo::GamingDevice },
165     { "AUDIO_VIDEO_VIDEO_MONITOR", QBluetoothDeviceInfo::VideoMonitor },
166     { "AUDIO_VIDEO_WEARABLE_HEADSET", QBluetoothDeviceInfo::WearableHeadsetDevice },
167     { nullptr, 0 }, // separator
168 
169     // QBluetoothDevice::ComputerDevice -> 7 entries
170     { "COMPUTER_UNCATEGORIZED", QBluetoothDeviceInfo::UncategorizedComputer }, // index 18
171     { "COMPUTER_DESKTOP", QBluetoothDeviceInfo::DesktopComputer },
172     { "COMPUTER_HANDHELD_PC_PDA", QBluetoothDeviceInfo::HandheldComputer },
173     { "COMPUTER_LAPTOP", QBluetoothDeviceInfo::LaptopComputer },
174     { "COMPUTER_PALM_SIZE_PC_PDA", QBluetoothDeviceInfo::HandheldClamShellComputer },
175     { "COMPUTER_SERVER", QBluetoothDeviceInfo::ServerComputer },
176     { "COMPUTER_WEARABLE", QBluetoothDeviceInfo::WearableComputer },
177     { nullptr, 0 },  // separator
178 
179     // QBluetoothDevice::HealthDevice -> 8 entries
180     { "HEALTH_BLOOD_PRESSURE", QBluetoothDeviceInfo::HealthBloodPressureMonitor }, // index 26
181     { "HEALTH_DATA_DISPLAY", QBluetoothDeviceInfo::HealthDataDisplay },
182     { "HEALTH_GLUCOSE", QBluetoothDeviceInfo::HealthGlucoseMeter },
183     { "HEALTH_PULSE_OXIMETER", QBluetoothDeviceInfo::HealthPulseOximeter },
184     { "HEALTH_PULSE_RATE", QBluetoothDeviceInfo::HealthStepCounter },
185     { "HEALTH_THERMOMETER", QBluetoothDeviceInfo::HealthThermometer },
186     { "HEALTH_UNCATEGORIZED", QBluetoothDeviceInfo::UncategorizedHealthDevice },
187     { "HEALTH_WEIGHING", QBluetoothDeviceInfo::HealthWeightScale },
188     { nullptr, 0 }, // separator
189 
190     // QBluetoothDevice::PhoneDevice -> 6 entries
191     { "PHONE_CELLULAR", QBluetoothDeviceInfo::CellularPhone }, // index 35
192     { "PHONE_CORDLESS", QBluetoothDeviceInfo::CordlessPhone },
193     { "PHONE_ISDN", QBluetoothDeviceInfo::CommonIsdnAccessPhone },
194     { "PHONE_MODEM_OR_GATEWAY", QBluetoothDeviceInfo::WiredModemOrVoiceGatewayPhone },
195     { "PHONE_SMART", QBluetoothDeviceInfo::SmartPhone },
196     { "PHONE_UNCATEGORIZED", QBluetoothDeviceInfo::UncategorizedPhone },
197     { nullptr, 0 }, // separator
198 
199     // QBluetoothDevice::ToyDevice -> 6 entries
200     { "TOY_CONTROLLER", QBluetoothDeviceInfo::ToyController }, // index 42
201     { "TOY_DOLL_ACTION_FIGURE", QBluetoothDeviceInfo::ToyDoll },
202     { "TOY_GAME", QBluetoothDeviceInfo::ToyGame },
203     { "TOY_ROBOT", QBluetoothDeviceInfo::ToyRobot },
204     { "TOY_UNCATEGORIZED", QBluetoothDeviceInfo::UncategorizedToy },
205     { "TOY_VEHICLE", QBluetoothDeviceInfo::ToyVehicle },
206     { nullptr, 0 }, // separator
207 
208     // QBluetoothDevice::WearableDevice -> 6 entries
209     { "WEARABLE_GLASSES", QBluetoothDeviceInfo::WearableGlasses }, // index 49
210     { "WEARABLE_HELMET", QBluetoothDeviceInfo::WearableHelmet },
211     { "WEARABLE_JACKET", QBluetoothDeviceInfo::WearableJacket },
212     { "WEARABLE_PAGER", QBluetoothDeviceInfo::WearablePager },
213     { "WEARABLE_UNCATEGORIZED", QBluetoothDeviceInfo::UncategorizedWearableDevice },
214     { "WEARABLE_WRIST_WATCH", QBluetoothDeviceInfo::WearableWristWatch },
215     { nullptr, 0 }, // separator
216 
217     // QBluetoothDevice::PeripheralDevice -> 3 entries
218     // For some reason these are not mentioned in Android docs but still exist
219     { "PERIPHERAL_NON_KEYBOARD_NON_POINTING", QBluetoothDeviceInfo::UncategorizedPeripheral }, // index 56
220     { "PERIPHERAL_KEYBOARD", QBluetoothDeviceInfo::KeyboardPeripheral },
221     { "PERIPHERAL_POINTING", QBluetoothDeviceInfo::PointingDevicePeripheral },
222     { "PERIPHERAL_KEYBOARD_POINTING", QBluetoothDeviceInfo::KeyboardWithPointingDevicePeripheral },
223     { nullptr, 0 }, // separator
224 
225     // the following entries do not exist on Android
226     // we map them to the unknown minor version case
227     // QBluetoothDevice::Miscellaneous
228     { nullptr, 0 }, // index 61 & separator
229 
230     // QBluetoothDevice::LANAccessDevice
231     { nullptr, 0 }, // index 62 & separator
232 
233     // QBluetoothDevice::ImagingDevice
234     { nullptr, 0 }, // index 63 & separator
235 
236     // QBluetoothDevice::UncategorizedDevice
237     { nullptr, 0 }, // index 64 & separator
238 };
239 
240 /* Advertising Data Type (AD type) for LE scan records, as defined in Bluetooth CSS v6. */
241 enum ADType {
242     ADType16BitUuidIncomplete = 0x02,
243     ADType16BitUuidComplete = 0x03,
244     ADType32BitUuidIncomplete = 0x04,
245     ADType32BitUuidComplete = 0x05,
246     ADType128BitUuidIncomplete = 0x06,
247     ADType128BitUuidComplete = 0x07,
248     ADTypeManufacturerSpecificData = 0xff,
249     // .. more will be added when required
250 };
251 
252 // Endianness conversion for quint128 doesn't (yet) exist in qtendian.h
253 template <>
qbswap(const quint128 src)254 inline quint128 qbswap<quint128>(const quint128 src)
255 {
256     quint128 dst;
257     for (int i = 0; i < 16; i++)
258         dst.data[i] = src.data[15 - i];
259     return dst;
260 }
261 
qtBtTypeForJavaBtType(jint javaType)262 QBluetoothDeviceInfo::CoreConfigurations qtBtTypeForJavaBtType(jint javaType)
263 {
264     const JCachedBtTypes::iterator it = cachedBtTypes()->find(javaType);
265     if (it == cachedBtTypes()->end()) {
266         QAndroidJniEnvironment env;
267 
268         if (javaType == QAndroidJniObject::getStaticField<jint>(
269                             javaBluetoothDeviceClassName, javaDeviceTypeClassic)) {
270             cachedBtTypes()->insert(javaType,
271                                     QBluetoothDeviceInfo::BaseRateCoreConfiguration);
272             return QBluetoothDeviceInfo::BaseRateCoreConfiguration;
273         } else if (javaType == QAndroidJniObject::getStaticField<jint>(
274                         javaBluetoothDeviceClassName, javaDeviceTypeLE)) {
275             cachedBtTypes()->insert(javaType,
276                                     QBluetoothDeviceInfo::LowEnergyCoreConfiguration);
277             return QBluetoothDeviceInfo::LowEnergyCoreConfiguration;
278         } else if (javaType == QAndroidJniObject::getStaticField<jint>(
279                             javaBluetoothDeviceClassName, javaDeviceTypeDual)) {
280             cachedBtTypes()->insert(javaType,
281                                     QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration);
282             return QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration;
283         } else if (javaType == QAndroidJniObject::getStaticField<jint>(
284                                 javaBluetoothDeviceClassName, javaDeviceTypeUnknown)) {
285             cachedBtTypes()->insert(javaType,
286                                 QBluetoothDeviceInfo::UnknownCoreConfiguration);
287         } else {
288             if (env->ExceptionCheck()) {
289                 env->ExceptionDescribe();
290                 env->ExceptionClear();
291             }
292             qCWarning(QT_BT_ANDROID) << "Unknown Bluetooth device type value";
293         }
294 
295         return QBluetoothDeviceInfo::UnknownCoreConfiguration;
296     } else {
297         return it.value();
298     }
299 }
300 
resolveAndroidMajorClass(jint javaType)301 QBluetoothDeviceInfo::MajorDeviceClass resolveAndroidMajorClass(jint javaType)
302 {
303     QAndroidJniEnvironment env;
304 
305     const JCachedMajorTypes::iterator it = cachedMajorTypes()->find(javaType);
306     if (it == cachedMajorTypes()->end()) {
307         QAndroidJniEnvironment env;
308         // precache all major device class fields
309         int i = 0;
310         jint fieldValue;
311         QBluetoothDeviceInfo::MajorDeviceClass result = QBluetoothDeviceInfo::UncategorizedDevice;
312         while (majorMappings[i].javaFieldName != nullptr) {
313             fieldValue = QAndroidJniObject::getStaticField<jint>(
314                                     javaBluetoothClassDeviceMajorClassName, majorMappings[i].javaFieldName);
315             if (env->ExceptionCheck()) {
316                 qCWarning(QT_BT_ANDROID) << "Unknown BluetoothClass.Device.Major field" << javaType;
317                 env->ExceptionDescribe();
318                 env->ExceptionClear();
319 
320                 // add fallback value because field not readable
321                 cachedMajorTypes()->insert(javaType, QBluetoothDeviceInfo::UncategorizedDevice);
322             } else {
323                 cachedMajorTypes()->insert(fieldValue, majorMappings[i].qtMajor);
324             }
325 
326             if (fieldValue == javaType)
327                 result = majorMappings[i].qtMajor;
328 
329             i++;
330         }
331 
332         return result;
333     } else {
334         return it.value();
335     }
336 }
337 
338 /*
339     The index for major into the MinorClassJavaToQtMapping and initializedCacheTracker
340     is major+1 except for UncategorizedDevice which is at index 0.
341 */
mappingIndexForMajor(QBluetoothDeviceInfo::MajorDeviceClass major)342 int mappingIndexForMajor(QBluetoothDeviceInfo::MajorDeviceClass major)
343 {
344     int mappingIndex = (int) major;
345     if (major == QBluetoothDeviceInfo::UncategorizedDevice)
346         mappingIndex = 0;
347     else
348         mappingIndex++;
349 
350     Q_ASSERT(mappingIndex >=0
351              && mappingIndex <= (QBluetoothDeviceInfo::HealthDevice + 1));
352 
353     return mappingIndex;
354 }
355 
triggerCachingOfMinorsForMajor(QBluetoothDeviceInfo::MajorDeviceClass major)356 void triggerCachingOfMinorsForMajor(QBluetoothDeviceInfo::MajorDeviceClass major)
357 {
358     //qCDebug(QT_BT_ANDROID) << "Caching minor values for major" << major;
359     int mappingIndex = mappingIndexForMajor(major);
360     int sizeIndex = minorIndexSizes[mappingIndex];
361     QAndroidJniEnvironment env;
362 
363     while (minorMappings[sizeIndex].javaFieldName != nullptr) {
364         jint fieldValue = QAndroidJniObject::getStaticField<jint>(
365                     javaBluetoothClassDeviceClassName, minorMappings[sizeIndex].javaFieldName);
366         if (env->ExceptionCheck()) { // field lookup failed? skip it
367             env->ExceptionDescribe();
368             env->ExceptionClear();
369         }
370 
371         Q_ASSERT(fieldValue >= 0);
372         cachedMinorTypes()->insert(fieldValue, minorMappings[sizeIndex].qtMinor);
373         sizeIndex++;
374     }
375 
376     initializedCacheTracker()->setBit(mappingIndex);
377 }
378 
resolveAndroidMinorClass(QBluetoothDeviceInfo::MajorDeviceClass major,jint javaMinor)379 quint8 resolveAndroidMinorClass(QBluetoothDeviceInfo::MajorDeviceClass major, jint javaMinor)
380 {
381     // there are no minor device classes in java with value 0
382     //qCDebug(QT_BT_ANDROID) << "received minor class device:" << javaMinor;
383     if (javaMinor == 0)
384         return 0;
385 
386     int mappingIndex = mappingIndexForMajor(major);
387 
388     // whenever we encounter a not yet seen major device class
389     // we populate the cache with all its related minor values
390     if (!initializedCacheTracker()->at(mappingIndex))
391         triggerCachingOfMinorsForMajor(major);
392 
393     const JCachedMinorTypes::iterator it = cachedMinorTypes()->find(javaMinor);
394     if (it == cachedMinorTypes()->end())
395         return 0;
396     else
397         return it.value();
398 }
399 
400 
DeviceDiscoveryBroadcastReceiver(QObject * parent)401 DeviceDiscoveryBroadcastReceiver::DeviceDiscoveryBroadcastReceiver(QObject* parent): AndroidBroadcastReceiver(parent)
402 {
403     addAction(valueForStaticField(JavaNames::BluetoothDevice, JavaNames::ActionFound));
404     addAction(valueForStaticField(JavaNames::BluetoothAdapter, JavaNames::ActionDiscoveryStarted));
405     addAction(valueForStaticField(JavaNames::BluetoothAdapter, JavaNames::ActionDiscoveryFinished));
406 }
407 
408 // Runs in Java thread
onReceive(JNIEnv * env,jobject context,jobject intent)409 void DeviceDiscoveryBroadcastReceiver::onReceive(JNIEnv *env, jobject context, jobject intent)
410 {
411     Q_UNUSED(context);
412     Q_UNUSED(env);
413 
414     QAndroidJniObject intentObject(intent);
415     const QString action = intentObject.callObjectMethod("getAction", "()Ljava/lang/String;").toString();
416 
417     qCDebug(QT_BT_ANDROID) << "DeviceDiscoveryBroadcastReceiver::onReceive() - event:" << action;
418 
419     if (action == valueForStaticField(JavaNames::BluetoothAdapter,
420                                       JavaNames::ActionDiscoveryFinished).toString()) {
421         emit finished();
422     } else if (action == valueForStaticField(JavaNames::BluetoothAdapter,
423                                              JavaNames::ActionDiscoveryStarted).toString()) {
424 
425     } else if (action == valueForStaticField(JavaNames::BluetoothDevice,
426                                              JavaNames::ActionFound).toString()) {
427         //get BluetoothDevice
428         QAndroidJniObject keyExtra = valueForStaticField(JavaNames::BluetoothDevice,
429                                                          JavaNames::ExtraDevice);
430         const QAndroidJniObject bluetoothDevice =
431                 intentObject.callObjectMethod("getParcelableExtra",
432                                               "(Ljava/lang/String;)Landroid/os/Parcelable;",
433                                               keyExtra.object<jstring>());
434 
435         if (!bluetoothDevice.isValid())
436             return;
437 
438         keyExtra = valueForStaticField(JavaNames::BluetoothDevice,
439                                        JavaNames::ExtraRssi);
440         int rssi = intentObject.callMethod<jshort>("getShortExtra",
441                                                 "(Ljava/lang/String;S)S",
442                                                 keyExtra.object<jstring>(),
443                                                 0);
444 
445         const QBluetoothDeviceInfo info = retrieveDeviceInfo(env, bluetoothDevice, rssi);
446         if (info.isValid())
447             emit deviceDiscovered(info, false);
448     }
449 }
450 
451 // Runs in Java thread
onReceiveLeScan(JNIEnv * env,jobject jBluetoothDevice,jint rssi,jbyteArray scanRecord)452 void DeviceDiscoveryBroadcastReceiver::onReceiveLeScan(
453         JNIEnv *env, jobject jBluetoothDevice, jint rssi, jbyteArray scanRecord)
454 {
455     const QAndroidJniObject bluetoothDevice(jBluetoothDevice);
456     if (!bluetoothDevice.isValid())
457         return;
458 
459     const QBluetoothDeviceInfo info = retrieveDeviceInfo(env, bluetoothDevice, rssi, scanRecord);
460     if (info.isValid())
461         emit deviceDiscovered(info, true);
462 }
463 
retrieveDeviceInfo(JNIEnv * env,const QAndroidJniObject & bluetoothDevice,int rssi,jbyteArray scanRecord)464 QBluetoothDeviceInfo DeviceDiscoveryBroadcastReceiver::retrieveDeviceInfo(JNIEnv *env, const QAndroidJniObject &bluetoothDevice, int rssi, jbyteArray scanRecord)
465 {
466     const QString deviceName = bluetoothDevice.callObjectMethod<jstring>("getName").toString();
467     const QBluetoothAddress deviceAddress(bluetoothDevice.callObjectMethod<jstring>("getAddress").toString());
468 
469     const QAndroidJniObject bluetoothClass = bluetoothDevice.callObjectMethod("getBluetoothClass",
470                                                                         "()Landroid/bluetooth/BluetoothClass;");
471     if (!bluetoothClass.isValid())
472         return QBluetoothDeviceInfo();
473 
474     QBluetoothDeviceInfo::MajorDeviceClass majorClass = resolveAndroidMajorClass(
475                                 bluetoothClass.callMethod<jint>("getMajorDeviceClass"));
476     // major device class is 5 bits from index 8 - 12
477     quint32 classType = ((quint32(majorClass) & 0x1f) << 8);
478 
479     jint javaMinor = bluetoothClass.callMethod<jint>("getDeviceClass");
480     quint8 minorDeviceType = resolveAndroidMinorClass(majorClass, javaMinor);
481 
482     // minor device class is 6 bits from index 2 - 7
483     classType |= ((quint32(minorDeviceType) & 0x3f) << 2);
484 
485     static QList<quint32> services;
486     if (services.count() == 0)
487         services << QBluetoothDeviceInfo::PositioningService
488                  << QBluetoothDeviceInfo::NetworkingService
489                  << QBluetoothDeviceInfo::RenderingService
490                  << QBluetoothDeviceInfo::CapturingService
491                  << QBluetoothDeviceInfo::ObjectTransferService
492                  << QBluetoothDeviceInfo::AudioService
493                  << QBluetoothDeviceInfo::TelephonyService
494                  << QBluetoothDeviceInfo::InformationService;
495 
496     // Matching BluetoothClass.Service values
497     quint32 serviceResult = 0;
498     quint32 current = 0;
499     for (int i = 0; i < services.count(); i++) {
500         current = services.at(i);
501         int androidId = (current << 16); // Android values shift by 2 bytes compared to Qt enums
502         if (bluetoothClass.callMethod<jboolean>("hasService", "(I)Z", androidId))
503             serviceResult |= current;
504     }
505 
506     // service class info is 11 bits from index 13 - 23
507     classType |= (serviceResult << 13);
508 
509     QBluetoothDeviceInfo info(deviceAddress, deviceName, classType);
510     info.setRssi(rssi);
511 
512     if (scanRecord != nullptr) {
513         // Parse scan record
514         jboolean isCopy;
515         jbyte *elems = env->GetByteArrayElements(scanRecord, &isCopy);
516         const char *scanRecordBuffer = reinterpret_cast<const char *>(elems);
517         const int scanRecordLength = env->GetArrayLength(scanRecord);
518 
519         QVector<QBluetoothUuid> serviceUuids;
520         int i = 0;
521 
522         // Spec 4.2, Vol 3, Part C, Chapter 11
523         while (i < scanRecordLength) {
524             // sizeof(EIR Data) = sizeof(Length) + sizeof(EIR data Type) + sizeof(EIR Data)
525             // Length = sizeof(EIR data Type) + sizeof(EIR Data)
526 
527             const int nBytes = scanRecordBuffer[i];
528             if (nBytes == 0)
529                 break;
530 
531             if ((i + nBytes) >= scanRecordLength)
532                 break;
533 
534             const int adType = scanRecordBuffer[i+1];
535             const char *dataPtr = &scanRecordBuffer[i+2];
536             QBluetoothUuid foundService;
537 
538             switch (adType) {
539             case ADType16BitUuidIncomplete:
540             case ADType16BitUuidComplete:
541                 foundService = QBluetoothUuid(qFromLittleEndian<quint16>(dataPtr));
542                 break;
543             case ADType32BitUuidIncomplete:
544             case ADType32BitUuidComplete:
545                 foundService = QBluetoothUuid(qFromLittleEndian<quint32>(dataPtr));
546                 break;
547             case ADType128BitUuidIncomplete:
548             case ADType128BitUuidComplete:
549                 foundService =
550                     QBluetoothUuid(qToBigEndian<quint128>(qFromLittleEndian<quint128>(dataPtr)));
551                 break;
552             case ADTypeManufacturerSpecificData:
553                 if (nBytes >= 3) {
554                     info.setManufacturerData(qFromLittleEndian<quint16>(dataPtr),
555                                               QByteArray(dataPtr + 2, nBytes - 3));
556                 }
557                 break;
558             default:
559                 // no other types supported yet and therefore skipped
560                 // https://www.bluetooth.org/en-us/specification/assigned-numbers/generic-access-profile
561                 break;
562             }
563 
564             i += nBytes + 1;
565 
566             if (!foundService.isNull() && !serviceUuids.contains(foundService))
567                 serviceUuids.append(foundService);
568         }
569 
570         info.setServiceUuids(serviceUuids);
571 
572         env->ReleaseByteArrayElements(scanRecord, elems, JNI_ABORT);
573     }
574 
575     if (QtAndroidPrivate::androidSdkVersion() >= 18) {
576         jint javaBtType = bluetoothDevice.callMethod<jint>("getType");
577 
578         if (env->ExceptionCheck()) {
579             env->ExceptionDescribe();
580             env->ExceptionClear();
581         } else {
582             info.setCoreConfigurations(qtBtTypeForJavaBtType(javaBtType));
583         }
584     }
585 
586     return info;
587 }
588 
589 QT_END_NAMESPACE
590 
591