From 66fc8db55e03648f1c65dcbeb58ff8f06e10f6f5 Mon Sep 17 00:00:00 2001 From: Alex Blasche Date: Mon, 25 Jul 2016 15:39:21 +0200 Subject: Android: Device discovery doesn't provide major & minor device class info QBluetoothDeviceDiscoveryAgent did not extract the QBluetoothDeviceInfo::majorDeviceClass() and QBluetoothDeviceInfo::minorDeviceClass() information. The Android API provides info for all major device classes. However not each major device class has its set of API for the minor device class information. Caching is applied to match Android major device class values against their matching Qt enum. When a matching entry is found, the value is added to the cache. In principle, the mechanism is applied for minor device class values. However since there are many many more minor device class fields, the caching is a bit more proactive. The patch will proactively read and cache all minor device class values for a given major device class. This avoids a large overhead of very long if..else if...else if..else statements. Change-Id: I26a6c29c6f5dca6d4f3b4b25902cda03a10ae5de Reviewed-by: Eskil Abrahamsen Blomfeldt --- .../android/devicediscoverybroadcastreceiver.cpp | 309 ++++++++++++++++++++- 1 file changed, 298 insertions(+), 11 deletions(-) (limited to 'src/bluetooth/android') diff --git a/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp b/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp index af68bc0d..1c2b69fb 100644 --- a/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp +++ b/src/bluetooth/android/devicediscoverybroadcastreceiver.cpp @@ -39,6 +39,7 @@ #include "android/jni_android_p.h" #include #include +#include QT_BEGIN_NAMESPACE @@ -46,14 +47,187 @@ Q_DECLARE_LOGGING_CATEGORY(QT_BT_ANDROID) typedef QHash JCachedBtTypes; Q_GLOBAL_STATIC(JCachedBtTypes, cachedBtTypes) +typedef QHash JCachedMajorTypes; +Q_GLOBAL_STATIC(JCachedMajorTypes, cachedMajorTypes) -// class name +typedef QHash 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); @@ -80,6 +254,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"; } @@ -89,6 +267,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( + 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( + 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)); @@ -162,10 +440,19 @@ QBluetoothDeviceInfo DeviceDiscoveryBroadcastReceiver::retrieveDeviceInfo(JNIEnv "()Landroid/bluetooth/BluetoothClass;"); if (!bluetoothClass.isValid()) return QBluetoothDeviceInfo(); - int classType = bluetoothClass.callMethod("getDeviceClass"); + QBluetoothDeviceInfo::MajorDeviceClass majorClass = resolveAndroidMajorClass( + bluetoothClass.callMethod("getMajorDeviceClass")); + // major device class is 5 bits from index 8 - 12 + quint32 classType = ((quint32(majorClass) & 0x1f) << 8); + + jint javaMinor = bluetoothClass.callMethod("getDeviceClass"); + quint8 minorDeviceType = resolveAndroidMinorClass(majorClass, javaMinor); + + // minor device class is 6 bits from index 2 - 7 + classType |= ((quint32(minorDeviceType) & 0x3f) << 2); - static QList services; + static QList services; if (services.count() == 0) services << QBluetoothDeviceInfo::PositioningService << QBluetoothDeviceInfo::NetworkingService @@ -176,18 +463,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("hasService", "(I)Z", id)) - result |= current; + int androidId = (current << 16); // Android values shift by 2 bytes compared to Qt enums + if (bluetoothClass.callMethod("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); -- cgit v1.2.3