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