summaryrefslogtreecommitdiffstats
path: root/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/bluetooth/android/devicediscoverybroadcastreceiver.cpp')
-rw-r--r--src/bluetooth/android/devicediscoverybroadcastreceiver.cpp309
1 files changed, 298 insertions, 11 deletions
diff --git a/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp b/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp
index a3d17b90..a246889f 100644
--- a/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp
+++ b/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp
@@ -45,6 +45,7 @@
#include "android/jni_android_p.h"
#include <QtCore/private/qjnihelpers_p.h>
#include <QtCore/QHash>
+#include <QtCore/qbitarray.h>
QT_BEGIN_NAMESPACE
@@ -52,14 +53,187 @@ 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)
-// class name
+typedef QHash<jint, quint8> JCachedMinorTypes;
+Q_GLOBAL_STATIC(JCachedMinorTypes, cachedMinorTypes)
+
+static QBitArray initializeMinorCaches()
+{
+ const int 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::LANAccessDevice:
+ 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 * const javaBluetoothDeviceClassName = "android/bluetooth/BluetoothDevice";
+static const char * const javaBluetoothClassDeviceMajorClassName = "android/bluetooth/BluetoothClass$Device$Major";
+static const char * const javaBluetoothClassDeviceClassName = "android/bluetooth/BluetoothClass$Device";
+
+// field names device type (LE vs classic)
static const char * const javaDeviceTypeClassic = "DEVICE_TYPE_CLASSIC";
static const char * const javaDeviceTypeDual = "DEVICE_TYPE_DUAL";
static const char * const javaDeviceTypeLE = "DEVICE_TYPE_LE";
static const char * const javaDeviceTypeUnknown = "DEVICE_TYPE_UNKNOWN";
+struct MajorClassJavaToQtMapping
+{
+ char const * javaFieldName;
+ QBluetoothDeviceInfo::MajorDeviceClass qtMajor;
+};
+
+static const MajorClassJavaToQtMapping majorMappings[] = {
+ { "AUDIO_VIDEO", QBluetoothDeviceInfo::AudioVideoDevice },
+ { "COMPUTER", QBluetoothDeviceInfo::ComputerDevice },
+ { "HEALTH", QBluetoothDeviceInfo::HealthDevice },
+ { "IMAGING", QBluetoothDeviceInfo::ImagingDevice },
+ { "MISC", QBluetoothDeviceInfo::MiscellaneousDevice },
+ { "NETWORKING", QBluetoothDeviceInfo::LANAccessDevice },
+ { "PERIPHERAL", QBluetoothDeviceInfo::PeripheralDevice },
+ { "PHONE", QBluetoothDeviceInfo::PhoneDevice },
+ { "TOY", QBluetoothDeviceInfo::ToyDevice },
+ { "UNCATEGORIZED", QBluetoothDeviceInfo::UncategorizedDevice },
+ { "WEARABLE", QBluetoothDeviceInfo::WearableDevice },
+ { Q_NULLPTR, QBluetoothDeviceInfo::UncategorizedDevice } //end of list
+};
+
+// QBluetoothDeviceInfo::MajorDeviceClass value plus 1 matches index
+// UncategorizedDevice shifts to index 0
+static const int minorIndexSizes[] = {
+ 64, // QBluetoothDevice::UncategorizedDevice
+ 61, // QBluetoothDevice::MiscellaneousDevice
+ 18, // QBluetoothDevice::ComputerDevice
+ 35, // QBluetoothDevice::PhoneDevice
+ 62, // QBluetoothDevice::LANAccessDevice
+ 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 },
+ { Q_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 },
+ { Q_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 },
+ { Q_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 },
+ { Q_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 },
+ { Q_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 },
+ { Q_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 },
+ { Q_NULLPTR, 0 }, // separator
+
+ // the following entries do not exist on Android
+ // we map them to the unknown minor version case
+ // QBluetoothDevice::Miscellaneous
+ { Q_NULLPTR, 0 }, // index 61 & separator
+
+ // QBluetoothDevice::LANAccessDevice
+ { Q_NULLPTR, 0 }, // index 62 & separator
+
+ // QBluetoothDevice::ImagingDevice
+ { Q_NULLPTR, 0 }, // index 63 & separator
+
+ // QBluetoothDevice::UncategorizedDevice
+ { Q_NULLPTR, 0 }, // index 64 & separator
+};
+
QBluetoothDeviceInfo::CoreConfigurations qtBtTypeForJavaBtType(jint javaType)
{
const JCachedBtTypes::iterator it = cachedBtTypes()->find(javaType);
@@ -86,6 +260,10 @@ QBluetoothDeviceInfo::CoreConfigurations qtBtTypeForJavaBtType(jint javaType)
cachedBtTypes()->insert(javaType,
QBluetoothDeviceInfo::UnknownCoreConfiguration);
} else {
+ if (env->ExceptionCheck()) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ }
qCWarning(QT_BT_ANDROID) << "Unknown Bluetooth device type value";
}
@@ -95,6 +273,106 @@ QBluetoothDeviceInfo::CoreConfigurations qtBtTypeForJavaBtType(jint javaType)
}
}
+QBluetoothDeviceInfo::MajorDeviceClass resolveAndroidMajorClass(jint javaType)
+{
+ QAndroidJniEnvironment env;
+
+ const JCachedMajorTypes::iterator it = cachedMajorTypes()->find(javaType);
+ if (it == cachedMajorTypes()->end()) {
+ QAndroidJniEnvironment env;
+ // precache all major device class fields
+ int i = 0;
+ jint fieldValue;
+ QBluetoothDeviceInfo::MajorDeviceClass result = QBluetoothDeviceInfo::UncategorizedDevice;
+ while (majorMappings[i].javaFieldName != Q_NULLPTR) {
+ fieldValue = QAndroidJniObject::getStaticField<jint>(
+ javaBluetoothClassDeviceMajorClassName, majorMappings[i].javaFieldName);
+ if (env->ExceptionCheck()) {
+ qCWarning(QT_BT_ANDROID) << "Unknown BluetoothClass.Device.Major field" << javaType;
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+
+ // add fallback value because field not readable
+ cachedMajorTypes()->insert(javaType, QBluetoothDeviceInfo::UncategorizedDevice);
+ } else {
+ cachedMajorTypes()->insert(fieldValue, majorMappings[i].qtMajor);
+ }
+
+ if (fieldValue == javaType)
+ result = majorMappings[i].qtMajor;
+
+ i++;
+ }
+
+ 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);
+ int sizeIndex = minorIndexSizes[mappingIndex];
+ QAndroidJniEnvironment env;
+
+ while (minorMappings[sizeIndex].javaFieldName != Q_NULLPTR) {
+ jint fieldValue = QAndroidJniObject::getStaticField<jint>(
+ javaBluetoothClassDeviceClassName, minorMappings[sizeIndex].javaFieldName);
+ if (env->ExceptionCheck()) { // field lookup failed? skip it
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ }
+
+ 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(valueForStaticField(JavaNames::BluetoothDevice, JavaNames::ActionFound));
@@ -168,10 +446,19 @@ QBluetoothDeviceInfo DeviceDiscoveryBroadcastReceiver::retrieveDeviceInfo(JNIEnv
"()Landroid/bluetooth/BluetoothClass;");
if (!bluetoothClass.isValid())
return QBluetoothDeviceInfo();
- int classType = bluetoothClass.callMethod<jint>("getDeviceClass");
+ 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 QList<qint32> services;
+ static QList<quint32> services;
if (services.count() == 0)
services << QBluetoothDeviceInfo::PositioningService
<< QBluetoothDeviceInfo::NetworkingService
@@ -182,18 +469,18 @@ QBluetoothDeviceInfo DeviceDiscoveryBroadcastReceiver::retrieveDeviceInfo(JNIEnv
<< QBluetoothDeviceInfo::TelephonyService
<< QBluetoothDeviceInfo::InformationService;
- //Matching BluetoothClass.Service values
- qint32 result = 0;
- qint32 current = 0;
+ // Matching BluetoothClass.Service values
+ quint32 serviceResult = 0;
+ quint32 current = 0;
for (int i = 0; i < services.count(); i++) {
current = services.at(i);
- int id = (current << 16);
- if (bluetoothClass.callMethod<jboolean>("hasService", "(I)Z", id))
- result |= current;
+ int androidId = (current << 16); // Android values shift by 2 bytes compared to Qt enums
+ if (bluetoothClass.callMethod<jboolean>("hasService", "(I)Z", androidId))
+ serviceResult |= current;
}
- result = result << 13;
- classType |= result;
+ // service class info is 11 bits from index 13 - 23
+ classType |= (serviceResult << 13);
QBluetoothDeviceInfo info(deviceAddress, deviceName, classType);
info.setRssi(rssi);