Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

How to resolve QBluetoothDeviceDiscoveryAgent::InputOutputError



  • Hi,

    I'm developing an app for Android that communicates using Classic Bluetooth. I'm using Qt 5.15.2, using android build sdk 30, SDK Version 3.0, NDK Version 21.1.6352462. The AndroidManifest.xml requests for the following permissions:
    Cattura.PNG

    Moreover, in the main.cpp:

    const QVector<QString> permissions({"android.permission.BLUETOOTH",
                                        "android.permission.BLUETOOTH_ADMIN",
                                        "android.permission.ACCESS_COARSE_LOCATION",
                                        "android.permission.WRITE_EXTERNAL_STORAGE",
                                        "android.permission.READ_EXTERNAL_STORAGE",
                                        "android.permission.INTERNET"});
    ...
    ...
    ...
    int main(int argc, char *argv[])
    {
        QApplication app(argc, argv);
    
    #if defined (Q_OS_ANDROID)
        for (const QString &permission : permissions) {
            auto result = QtAndroid::checkPermission(permission);
            if (result == QtAndroid::PermissionResult::Denied) {
                auto resultHash = QtAndroid::requestPermissionsSync(QStringList({permission}));
                if (resultHash[permission] == QtAndroid::PermissionResult::Denied)
                    return 0;
            }
        }
    #endif
    ...
    }
    

    Then I start a bluetooth discovery:

    ...
        QBluetoothDeviceDiscoveryAgent *discoveryAgent = new QBluetoothDeviceDiscoveryAgent(this);
        connect(discoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered,
                this, &BluetoothSearcher::deviceDiscovered);
        connect(discoveryAgent, &QBluetoothDeviceDiscoveryAgent::finished,
                this, &BluetoothSearcher::deviceDiscoveryFinished);
        connect(discoveryAgent, static_cast<void (QBluetoothDeviceDiscoveryAgent::*)(QBluetoothDeviceDiscoveryAgent::Error)>(&QBluetoothDeviceDiscoveryAgent::error),
                this, &BluetoothSearcher::scanError);
    
    
    #if defined (Q_OS_ANDROID) || defined (Q_OS_WINDOWS)
        discoveryAgent->start(QBluetoothDeviceDiscoveryAgent::ClassicMethod);
    #endif
    ...
    
    void BluetoothSearcher::scanError(QBluetoothDeviceDiscoveryAgent::Error error)
    {
        QBluetoothDeviceDiscoveryAgent *deviceAgent = static_cast<QBluetoothDeviceDiscoveryAgent*>(QObject::sender());
        if (error == QBluetoothDeviceDiscoveryAgent::PoweredOffError)
            qInfo() << "The Bluetooth adaptor is powered off.";
        else if (error == QBluetoothDeviceDiscoveryAgent::InputOutputError) {
            qInfo() << "Writing or reading from the device resulted in an error." << deviceAgent->errorString();
            emit bluetoothError(0);
        } else {
            qInfo() << "An unknown error has occurred." << error << deviceAgent->errorString();
            emit bluetoothError(0);
        }
    }
    

    When I try this on my samsung Galaxy tab s7+ I get the error: "Writing or reading from the device resulted in an error. Classic Discovery cannot be started" and I can't understand why. What does the error "Classic Discovery cannot be started" mean?
    The bluetooth is on, the location is on, what should I check? If I start the discovery without specifying the method, the discovery starts but it looks like it's only searching for BLE devices because none of mine device is found.

    If I start the discovery from the samsung galaxy Bluetooth menu I can see my devices.

    Am I missing something?
    Thanks in advance for the help.



  • I have done further investigation on the problem because I had an earlier version of the app that was working correctly. I found out that the problem is caused by the following line in the .pro file:

    ANDROID_TARGET_SDK_VERSION = 30
    

    If I remove this line I don't get the error and I can find all my devices. How is it possible?
    I had to set this line because in the AndroidManifest.xml the "Minimun required SDK" and the "Target SDK" lines are freezed and I cannot set the versions from there (link to the bug). On the other hand, I had to set the TARGET_SDK_VERSION to 30 because I want to upload the app on the Google Play Store.

    Why that line causes the BT classic to output the error "Classic Discovery cannot be started"?
    Are there other ways to specify ANDROID_TARGET_SDK_VERSION = 30 without having this problem?

    UPDATE: actually I found out that if I set ANDROID_TARGET_SDK_VERSION = 28 everything works fine but obviously that's not a solution since I have to target the 30.



  • The following is a minimal example where I have the problem:
    BtTrial.pro

    QT += bluetooth qml quick
    
    android: QT+= androidextras
    CONFIG += c++11
    
    # You can make your code fail to compile if it uses deprecated APIs.
    # In order to do so, uncomment the following line.
    #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
    
    SOURCES += \
            bluetoothsearcher.cpp \
            main.cpp
    
    RESOURCES += qml.qrc
    
    # Additional import path used to resolve QML modules in Qt Creator's code model
    QML_IMPORT_PATH =
    
    # Additional import path used to resolve QML modules just for Qt Quick Designer
    QML_DESIGNER_IMPORT_PATH =
    
    ANDROID_TARGET_SDK_VERSION = 30
    
    # Default rules for deployment.
    qnx: target.path = /tmp/$${TARGET}/bin
    else: unix:!android: target.path = /opt/$${TARGET}/bin
    !isEmpty(target.path): INSTALLS += target
    
    DISTFILES += \
        android/AndroidManifest.xml \
        android/build.gradle \
        android/gradle.properties \
        android/gradle/wrapper/gradle-wrapper.jar \
        android/gradle/wrapper/gradle-wrapper.properties \
        android/gradlew \
        android/gradlew.bat \
        android/res/values/libs.xml
    
    ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android
    
    HEADERS += \
        bluetoothsearcher.h
    
    

    bluetoothsearcher.h

    #ifndef BLUETOOTHSEARCHER_H
    #define BLUETOOTHSEARCHER_H
    
    #include <QObject>
    #include <QBluetoothDeviceDiscoveryAgent>
    #include <QBluetoothAddress>
    #include <QBluetoothLocalDevice>
    #include <QDebug>
    
    class BluetoothSearcher : public QObject
    {
        Q_OBJECT
    public:
        explicit BluetoothSearcher(QObject *parent = nullptr);
    
        Q_INVOKABLE void searchBluetooth();
    
    signals:
        void newDevicesFound(QStringList devicesFound);
        void newDeviceFound(QString, QString, bool);
        void discoveryFinished();
        void bluetoothError(int);
        void deviceCorrectlyPaired(QString address);
    
    public slots:
        void requestPairing(QString address);
    
    private slots:
        void deviceDiscovered(const QBluetoothDeviceInfo &device);
        void deviceDiscoveryFinished();
        void pairingDone(const QBluetoothAddress &address, QBluetoothLocalDevice::Pairing pairing);
    
        void pairingMyDisplayConfirmation(const QBluetoothAddress &, QString);
        void displayPin(QBluetoothAddress, QString);
        void scanError(QBluetoothDeviceDiscoveryAgent::Error error);
    
    private:
        QBluetoothDeviceDiscoveryAgent *discoveryAgent{nullptr};
        QBluetoothLocalDevice *localDevice{nullptr};
    };
    
    #endif // BLUETOOTHSEARCHER_H
    
    

    bluetoothsearcher.cpp

    #include "bluetoothsearcher.h"
    
    BluetoothSearcher::BluetoothSearcher(QObject *parent) : QObject(parent)
    {
    
    }
    
    void BluetoothSearcher::searchBluetooth() {
    
        qDebug() << "Bluetooth Searcher Starting";
        if (localDevice == nullptr) {
    
            localDevice = new QBluetoothLocalDevice(this);
            connect(localDevice, &QBluetoothLocalDevice::pairingFinished,
                    this, &BluetoothSearcher::pairingDone);
            connect(localDevice, &QBluetoothLocalDevice::pairingDisplayConfirmation,
                    this, &BluetoothSearcher::pairingMyDisplayConfirmation);
            connect(localDevice, &QBluetoothLocalDevice::pairingDisplayPinCode,
                    this, &BluetoothSearcher::displayPin);
        }
    
        QString localDeviceName;
        if (localDevice->isValid()) {
            localDevice->powerOn();
            localDeviceName = localDevice->name();
    
            QList<QBluetoothAddress> remotes;
            remotes = localDevice->connectedDevices();
            foreach (QBluetoothAddress address, remotes) {
                qInfo() << address;
            }
        } else qInfo() << "Attenzione, il bt non è valido";
    
        QBluetoothDeviceDiscoveryAgent *discoveryAgent = new QBluetoothDeviceDiscoveryAgent(this);
        connect(discoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered,
                this, &BluetoothSearcher::deviceDiscovered);
        connect(discoveryAgent, &QBluetoothDeviceDiscoveryAgent::finished,
                this, &BluetoothSearcher::deviceDiscoveryFinished);
        connect(discoveryAgent, static_cast<void (QBluetoothDeviceDiscoveryAgent::*)(QBluetoothDeviceDiscoveryAgent::Error)>(&QBluetoothDeviceDiscoveryAgent::error),
                this, &BluetoothSearcher::scanError);
    
    
    #if defined (Q_OS_ANDROID) || defined (Q_OS_WINDOWS)
        discoveryAgent->start(QBluetoothDeviceDiscoveryAgent::ClassicMethod);
    #else
        qWarning() << "Must Implement The Ble Communication for IOS";
    #endif
    }
    
    
    void BluetoothSearcher::requestPairing(QString address)
    {
        QBluetoothLocalDevice::Pairing pairingStatus = localDevice->pairingStatus(QBluetoothAddress(address));
        qDebug() << "Pairing Status:" << pairingStatus << address;
        if (pairingStatus != QBluetoothLocalDevice::Paired && pairingStatus != QBluetoothLocalDevice::AuthorizedPaired )
            localDevice->requestPairing(QBluetoothAddress(address), QBluetoothLocalDevice::Paired);
    }
    
    void BluetoothSearcher::deviceDiscovered(const QBluetoothDeviceInfo &device)
    {
        qDebug() << device.name();
        if (device.name().contains("_C") || device.name().contains("_V")) {
            qDebug() << "Found new device:" << device.name() << '(' << device.address().toString() << ')';
            QBluetoothLocalDevice::Pairing pairingStatus = localDevice->pairingStatus(device.address());
            qDebug() << "Pairing Status:" << pairingStatus << device.address();
            bool paired = true;
            if (pairingStatus != QBluetoothLocalDevice::Paired && pairingStatus != QBluetoothLocalDevice::AuthorizedPaired )
                paired = false;
    
    #ifdef Q_OS_WIN
            /* this is because the windows bt api on Qt 5.15.2 shows only previously paired device from windows */
            paired = true;
    #endif
            emit newDeviceFound(device.name(), device.address().toString(), paired);
        }
    }
    
    void BluetoothSearcher::deviceDiscoveryFinished()
    {
        emit discoveryFinished();
        qInfo() << "Device Discovery Finished";
    }
    
    void BluetoothSearcher::pairingDone(const QBluetoothAddress &address, QBluetoothLocalDevice::Pairing pairing)
    {
        qInfo() << "Pairing Result:" << pairing << " for device " << address.toString();
        emit deviceCorrectlyPaired(address.toString());
    }
    
    void BluetoothSearcher::pairingMyDisplayConfirmation(const QBluetoothAddress& address, QString pin)
    {
        qDebug() << "#PAIRING_DISPLAY_CONFIRMATION_PIN: " << pin << "for" << address;
    }
    
    void BluetoothSearcher::displayPin(QBluetoothAddress address,QString pin)
    {
        qDebug() << "&DISPLAY_CONFIRMATION_PIN: " << pin << "for" << address;
    }
    
    void BluetoothSearcher::scanError(QBluetoothDeviceDiscoveryAgent::Error error)
    {
        QBluetoothDeviceDiscoveryAgent *deviceAgent = static_cast<QBluetoothDeviceDiscoveryAgent*>(QObject::sender());
        if (error == QBluetoothDeviceDiscoveryAgent::PoweredOffError)
            qInfo() << "The Bluetooth adaptor is powered off.";
        else if (error == QBluetoothDeviceDiscoveryAgent::InputOutputError) {
            qInfo() << "Writing or reading from the device resulted in an error." << deviceAgent->errorString();
            emit bluetoothError(0);
        } else {
            qInfo() << "An unknown error has occurred." << error << deviceAgent->errorString();
            emit bluetoothError(0);
        }
    }
    

    main.cpp

    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    #include <QQmlContext>
    
    #include "bluetoothsearcher.h"
    
    #if defined (Q_OS_ANDROID)
    #include <QtAndroid>
    const QVector<QString> permissions({"android.permission.BLUETOOTH",
                                        "android.permission.BLUETOOTH_ADMIN",
                                        "android.permission.ACCESS_COARSE_LOCATION",
                                        "android.permission.WRITE_EXTERNAL_STORAGE",
                                        "android.permission.READ_EXTERNAL_STORAGE",
                                        "android.permission.INTERNET"});
    #endif
    
    int main(int argc, char *argv[])
    {
    #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
        QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    #endif
    
        QGuiApplication app(argc, argv);
    
    #if defined (Q_OS_ANDROID)
        for (const QString &permission : permissions) {
            auto result = QtAndroid::checkPermission(permission);
            if (result == QtAndroid::PermissionResult::Denied) {
                auto resultHash = QtAndroid::requestPermissionsSync(QStringList({permission}));
                if (resultHash[permission] == QtAndroid::PermissionResult::Denied)
                    return 0;
            }
        }
    #endif
    
        BluetoothSearcher bluetoothSearcher;
    
        QQmlApplicationEngine engine;
    
        engine.rootContext()->setContextProperty("bluetoothSearcher", &bluetoothSearcher);
    
        const QUrl url(QStringLiteral("qrc:/main.qml"));
        QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                         &app, [url](QObject *obj, const QUrl &objUrl) {
            if (!obj && url == objUrl)
                QCoreApplication::exit(-1);
        }, Qt::QueuedConnection);
        engine.load(url);
    
        return app.exec();
    }
    
    

    main.qml

    import QtQuick 2.12
    import QtQuick.Window 2.12
    
    Window {
        width: 640
        height: 480
        visible: true
        title: qsTr("Hello World")
    
        Rectangle {
            anchors.fill: parent
    
    
            Rectangle {
                anchors {
                    right: parent.right
                    left: parent.left
                    bottom: parent.bottom
                }
                color: "red"
    
                height: 32
    
                MouseArea {
                    anchors.fill: parent
                    onClicked: bluetoothSearcher.searchBluetooth()
                }
            }
        }
    
    
    }
    

    Androidmanifest.xml

    <?xml version="1.0"?>
    <manifest package="org.qtproject.example" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="0.0.030" android:versionCode="1" android:installLocation="auto">
        <!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
             Remove the comment if you do not require these default permissions. -->
        <!-- %%INSERT_PERMISSIONS -->
    
        <!-- The following comment will be replaced upon deployment with default features based on the dependencies of the application.
             Remove the comment if you do not require these default features. -->
        <!-- %%INSERT_FEATURES -->
    
        <supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
        <application android:hardwareAccelerated="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="BtTrial" android:extractNativeLibs="true">
            <activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" android:name="org.qtproject.qt5.android.bindings.QtActivity" android:label="BtTrial" android:screenOrientation="sensorLandscape" android:launchMode="singleTop">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN"/>
                    <category android:name="android.intent.category.LAUNCHER"/>
                </intent-filter>
                <!-- Application arguments -->
                <!-- meta-data android:name="android.app.arguments" android:value="arg1 arg2 arg3"/ -->
                <!-- Application arguments -->
                <meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
                <meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
                <meta-data android:name="android.app.repository" android:value="default"/>
                <meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
                <meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
                <!-- Deploy Qt libs as part of package -->
                <meta-data android:name="android.app.bundle_local_qt_libs" android:value="-- %%BUNDLE_LOCAL_QT_LIBS%% --"/>
                <!-- Run with local libs -->
                <meta-data android:name="android.app.use_local_qt_libs" android:value="-- %%USE_LOCAL_QT_LIBS%% --"/>
                <meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
                <meta-data android:name="android.app.load_local_libs_resource_id" android:resource="@array/load_local_libs"/>
                <meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/>
                <meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/>
                <!-- Used to specify custom system library path to run with local system libs -->
                <!-- <meta-data android:name="android.app.system_libs_prefix" android:value="/system/lib/"/> -->
                <!--  Messages maps -->
                <meta-data android:value="@string/ministro_not_found_msg" android:name="android.app.ministro_not_found_msg"/>
                <meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/>
                <meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
                <meta-data android:value="@string/unsupported_android_version" android:name="android.app.unsupported_android_version"/>
                <!--  Messages maps -->
                <!-- Splash screen -->
                <!-- Orientation-specific (portrait/landscape) data is checked first. If not available for current orientation,
                     then android.app.splash_screen_drawable. For best results, use together with splash_screen_sticky and
                     use hideSplashScreen() with a fade-out animation from Qt Android Extras to hide the splash screen when you
                     are done populating your window with content. -->
                <!-- meta-data android:name="android.app.splash_screen_drawable_portrait" android:resource="@drawable/logo_portrait" / -->
                <!-- meta-data android:name="android.app.splash_screen_drawable_landscape" android:resource="@drawable/logo_landscape" / -->
                <!-- meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/logo"/ -->
                <!-- meta-data android:name="android.app.splash_screen_sticky" android:value="true"/ -->
                <!-- Splash screen -->
                <!-- Background running -->
                <!-- Warning: changing this value to true may cause unexpected crashes if the
                              application still try to draw after
                              "applicationStateChanged(Qt::ApplicationSuspended)"
                              signal is sent! -->
                <meta-data android:name="android.app.background_running" android:value="false"/>
                <!-- Background running -->
                <!-- auto screen scale factor -->
                <meta-data android:name="android.app.auto_screen_scale_factor" android:value="false"/>
                <!-- auto screen scale factor -->
                <!-- extract android style -->
                <!-- available android:values :
                    * default - In most cases this will be the same as "full", but it can also be something else if needed, e.g., for compatibility reasons
                    * full - useful QWidget & Quick Controls 1 apps
                    * minimal - useful for Quick Controls 2 apps, it is much faster than "full"
                    * none - useful for apps that don't use any of the above Qt modules
                    -->
                <meta-data android:name="android.app.extract_android_style" android:value="default"/>
                <!-- extract android style -->
            </activity>
            <!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
        </application>
    
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
        <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
        <uses-permission android:name="android.permission.BLUETOOTH"/>
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    
    
    </manifest>
    
    

    When I click the red Rectangle using ANDROID_TARGET_SDK_VERSION = 30 in the Application Output I can see these lines:

    W libBtTrial_arm64-v8a.so: qt.bluetooth.android: ACCESS_COARSE|FINE_LOCATION permission available
    I BluetoothAdapter: startDiscovery
    I libBtTrial_arm64-v8a.so: Writing or reading from the device resulted in an error. "Classic Discovery cannot be started"
    

    if I set ANDROID_TARGET_SDK_VERSION = 28 the discovery starts and I can see the devices it found:

    W libBtTrial_arm64-v8a.so: qt.bluetooth.android: ACCESS_COARSE|FINE_LOCATION permission available
    I BluetoothAdapter: startDiscovery
    D libBtTrial_arm64-v8a.so: "BtDevice1"
    D libBtTrial_arm64-v8a.so: Found new device: "BtDevice1" ( "00:12:F3:29:51:7C" )
    D libBtTrial_arm64-v8a.so: Pairing Status: QBluetoothLocalDevice::Paired "00:12:F3:29:51:7C"
    I libBtTrial_arm64-v8a.so: Device Discovery Finished
    


  • Well, I think I found what was causing the error. I do not see the problem if I remove the Adnroid Request Permissions on runtime:

    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    #include <QQmlContext>
    
    #include "bluetoothsearcher.h"
    
    #if defined (Q_OS_ANDROID)
    //#include <QtAndroid>
    //const QVector<QString> permissions({"android.permission.BLUETOOTH",
    //                                    "android.permission.BLUETOOTH_ADMIN",
    //                                    "android.permission.ACCESS_COARSE_LOCATION",
    //                                    "android.permission.WRITE_EXTERNAL_STORAGE",
    //                                    "android.permission.READ_EXTERNAL_STORAGE",
    //                                    "android.permission.INTERNET"});
    #endif
    
    int main(int argc, char *argv[])
    {
    #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
        QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    #endif
    
        QGuiApplication app(argc, argv);
    
    #if defined (Q_OS_ANDROID)
    //    for (const QString &permission : permissions) {
    //        auto result = QtAndroid::checkPermission(permission);
    //        if (result == QtAndroid::PermissionResult::Denied) {
    //            auto resultHash = QtAndroid::requestPermissionsSync(QStringList({permission}));
    //            if (resultHash[permission] == QtAndroid::PermissionResult::Denied)
    //                return 0;
    //        }
    //    }
    #endif
    

    Can somebody explain why I get this behaviour? I got this problem only with my Samsung galaxy tab s7+ that has android 11, on a samsung J3 I did not get the problem but it has android 5.1.
    Moreover I noticed that if I compile with the above lines, I get that I'm using deprecated API. Maybe that's the problem?
    I'm not an android developer, I'm just trying to create a small app, so I'm a little confused.


Log in to reply