diff options
Diffstat (limited to 'src/bluetooth/android/devicediscoverybroadcastreceiver.cpp')
-rw-r--r-- | src/bluetooth/android/devicediscoverybroadcastreceiver.cpp | 309 |
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); |