summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorIvan Solovev <ivan.solovev@qt.io>2023-03-17 13:53:40 +0100
committerIvan Solovev <ivan.solovev@qt.io>2023-03-21 12:02:20 +0100
commit6168203d7c0e8e4df1908a62338b8abdd57855eb (patch)
tree056982aae0f5be2523e1e071457f62341f28a1a5 /tests
parent7d20a4fb6a520b13666c8808ae74fb9a43b6532c (diff)
BtScanner example: move to manual tests
The example mostly shows the usage of QBluetooth{Device,Service}DiscoveryAgent classes, which is also illustrated by other examples. Move it, because it's not fully functional on mobile platforms (because it's widget based). Do not remove it completely, because it might still be useful for doing Classic device scan. Also implement some improvements while on it: * fix CMakeLists.txt by using qt_standard_project_setup() and PRIVATE linking * fix memory leak in DeviceDiscoveryDialog * rework the code to use Qt parent-child model instead of manually deleting objects where possible * fix includes * fix forward declarations Task-number: QTBUG-111972 Pick-to: 6.5 6.5.0 Change-Id: Ie4bc9e25ccdda6d5f5de2f57528df349c71cdc12 Reviewed-by: Juha Vuolle <juha.vuolle@qt.io>
Diffstat (limited to 'tests')
-rw-r--r--tests/manual/CMakeLists.txt1
-rw-r--r--tests/manual/examples/btscanner/CMakeLists.txt52
-rw-r--r--tests/manual/examples/btscanner/Info.plist41
-rw-r--r--tests/manual/examples/btscanner/btscanner.pro24
-rw-r--r--tests/manual/examples/btscanner/device.cpp182
-rw-r--r--tests/manual/examples/btscanner/device.h49
-rw-r--r--tests/manual/examples/btscanner/device.ui118
-rw-r--r--tests/manual/examples/btscanner/main.cpp33
-rw-r--r--tests/manual/examples/btscanner/service.cpp62
-rw-r--r--tests/manual/examples/btscanner/service.h36
-rw-r--r--tests/manual/examples/btscanner/service.ui71
11 files changed, 669 insertions, 0 deletions
diff --git a/tests/manual/CMakeLists.txt b/tests/manual/CMakeLists.txt
index 2cce14f6..47fdbfb8 100644
--- a/tests/manual/CMakeLists.txt
+++ b/tests/manual/CMakeLists.txt
@@ -4,5 +4,6 @@
if(TARGET Qt::Bluetooth)
add_subdirectory(qlowenergycontroller)
add_subdirectory(qlowenergycontroller_peripheral)
+ add_subdirectory(examples/btscanner)
endif()
diff --git a/tests/manual/examples/btscanner/CMakeLists.txt b/tests/manual/examples/btscanner/CMakeLists.txt
new file mode 100644
index 00000000..61d44dfc
--- /dev/null
+++ b/tests/manual/examples/btscanner/CMakeLists.txt
@@ -0,0 +1,52 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(btscanner LANGUAGES CXX)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/bluetooth/btscanner")
+
+find_package(Qt6 REQUIRED COMPONENTS Bluetooth Core Widgets)
+
+qt_standard_project_setup()
+
+qt_add_executable(btscanner
+ device.cpp device.h device.ui
+ main.cpp
+ service.cpp service.h service.ui
+)
+
+set_target_properties(btscanner PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE TRUE
+)
+
+if (APPLE)
+ if (IOS)
+ set_target_properties(btscanner PROPERTIES
+ MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Info.plist"
+ )
+ else()
+ # Using absolute path for shared plist files is a Ninja bug workaround
+ get_filename_component(SHARED_PLIST_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../../examples/bluetooth/shared ABSOLUTE)
+ set_target_properties(btscanner PROPERTIES
+ MACOSX_BUNDLE_INFO_PLIST "${SHARED_PLIST_DIR}/Info.cmake.macos.plist"
+ )
+ endif()
+endif()
+
+target_link_libraries(btscanner PRIVATE
+ Qt::Bluetooth
+ Qt::Core
+ Qt::Widgets
+)
+
+install(TARGETS btscanner
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/tests/manual/examples/btscanner/Info.plist b/tests/manual/examples/btscanner/Info.plist
new file mode 100644
index 00000000..49fd2191
--- /dev/null
+++ b/tests/manual/examples/btscanner/Info.plist
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDisplayName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIconFile</key>
+ <string>${ASSETCATALOG_COMPILER_APPICON_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>${QMAKE_SHORT_VERSION}</string>
+ <key>CFBundleSignature</key>
+ <string>${QMAKE_PKGINFO_TYPEINFO}</string>
+ <key>CFBundleVersion</key>
+ <string>${QMAKE_FULL_VERSION}</string>
+ <key>LSRequiresIPhoneOS</key>
+ <true/>
+ <key>MinimumOSVersion</key>
+ <string>${IPHONEOS_DEPLOYMENT_TARGET}</string>
+ <key>NSBluetoothAlwaysUsageDescription</key>
+ <string>Qt's BT scanner wants to access your Bluetooth adapter!</string>
+ <key>UILaunchStoryboardName</key>
+ <string>LaunchScreen</string>
+ <key>UISupportedInterfaceOrientations</key>
+ <array>
+ <string>UIInterfaceOrientationPortrait</string>
+ <string>UIInterfaceOrientationPortraitUpsideDown</string>
+ <string>UIInterfaceOrientationLandscapeLeft</string>
+ <string>UIInterfaceOrientationLandscapeRight</string>
+ </array>
+</dict>
+</plist>
diff --git a/tests/manual/examples/btscanner/btscanner.pro b/tests/manual/examples/btscanner/btscanner.pro
new file mode 100644
index 00000000..3ef49969
--- /dev/null
+++ b/tests/manual/examples/btscanner/btscanner.pro
@@ -0,0 +1,24 @@
+TARGET = btscanner
+
+QT = core bluetooth widgets
+requires(qtConfig(listwidget))
+TEMPLATE = app
+
+SOURCES = \
+ main.cpp \
+ device.cpp \
+ service.cpp
+
+ios: QMAKE_INFO_PLIST = Info.plist
+macos: QMAKE_INFO_PLIST = ../../../../examples/bluetooth/shared/Info.qmake.macos.plist
+
+HEADERS = \
+ device.h \
+ service.h
+
+FORMS = \
+ device.ui \
+ service.ui
+
+target.path = $$[QT_INSTALL_EXAMPLES]/bluetooth/btscanner
+INSTALLS += target
diff --git a/tests/manual/examples/btscanner/device.cpp b/tests/manual/examples/btscanner/device.cpp
new file mode 100644
index 00000000..6d1f775f
--- /dev/null
+++ b/tests/manual/examples/btscanner/device.cpp
@@ -0,0 +1,182 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "device.h"
+#include "service.h"
+#include "ui_device.h"
+
+#include <QtBluetooth/qbluetoothaddress.h>
+#include <QtBluetooth/qbluetoothdevicediscoveryagent.h>
+#include <QtBluetooth/qbluetoothlocaldevice.h>
+
+#include <QtWidgets/qmenu.h>
+
+#if QT_CONFIG(permissions)
+#include <QtCore/qpermissions.h>
+#include <QtWidgets/qmessagebox.h>
+#include <QtWidgets/qapplication.h>
+#endif // QT_CONFIG(permissions)
+
+static QColor colorForPairing(QBluetoothLocalDevice::Pairing pairing)
+{
+ return pairing == QBluetoothLocalDevice::Paired
+ || pairing == QBluetoothLocalDevice::AuthorizedPaired
+ ? QColor(Qt::green) : QColor(Qt::red);
+}
+
+DeviceDiscoveryDialog::DeviceDiscoveryDialog(QWidget *parent) :
+ QDialog(parent),
+ localDevice(new QBluetoothLocalDevice(this)),
+ ui(new Ui::DeviceDiscovery)
+{
+#ifdef Q_OS_ANDROID
+ this->setWindowState(Qt::WindowMaximized);
+#endif
+ ui->setupUi(this);
+ ui->stopScan->setVisible(false);
+
+ // In case of multiple Bluetooth adapters it is possible to set the adapter
+ // to be used. Example code:
+ //
+ // QBluetoothAddress address("XX:XX:XX:XX:XX:XX");
+ // discoveryAgent = new QBluetoothDeviceDiscoveryAgent(address, this);
+
+ discoveryAgent = new QBluetoothDeviceDiscoveryAgent(this);
+
+ connect(ui->scan, &QAbstractButton::clicked, this, &DeviceDiscoveryDialog::startScan);
+ connect(ui->stopScan, &QAbstractButton::clicked, this, &DeviceDiscoveryDialog::stopScan);
+
+ connect(discoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered,
+ this, &DeviceDiscoveryDialog::addDevice);
+ connect(discoveryAgent, &QBluetoothDeviceDiscoveryAgent::finished,
+ this, &DeviceDiscoveryDialog::scanFinished);
+
+ connect(ui->list, &QListWidget::itemActivated,
+ this, &DeviceDiscoveryDialog::itemActivated);
+
+ connect(localDevice, &QBluetoothLocalDevice::hostModeStateChanged,
+ this, &DeviceDiscoveryDialog::hostModeStateChanged);
+
+ hostModeStateChanged(localDevice->hostMode());
+ // add context menu for devices to be able to pair device
+ ui->list->setContextMenuPolicy(Qt::CustomContextMenu);
+ connect(ui->list, &QWidget::customContextMenuRequested,
+ this, &DeviceDiscoveryDialog::displayPairingMenu);
+ connect(localDevice, &QBluetoothLocalDevice::pairingFinished,
+ this, &DeviceDiscoveryDialog::pairingDone);
+}
+
+DeviceDiscoveryDialog::~DeviceDiscoveryDialog()
+{
+ delete ui;
+}
+
+void DeviceDiscoveryDialog::addDevice(const QBluetoothDeviceInfo &info)
+{
+ const QString label = info.address().toString() + u' ' + info.name();
+ const auto items = ui->list->findItems(label, Qt::MatchExactly);
+ if (items.isEmpty()) {
+ QListWidgetItem *item = new QListWidgetItem(label);
+ QBluetoothLocalDevice::Pairing pairingStatus = localDevice->pairingStatus(info.address());
+ item->setForeground(colorForPairing(pairingStatus));
+ ui->list->addItem(item);
+ }
+}
+
+void DeviceDiscoveryDialog::startScan()
+{
+#if QT_CONFIG(permissions)
+ if (qApp->checkPermission(QBluetoothPermission{}) != Qt::PermissionStatus::Granted) {
+ QMessageBox::warning(this, tr("Missing permission"),
+ tr("Permission is needed to use Bluetooth. "\
+ "Please grant the permission to this "\
+ "application in the system settings."));
+ return;
+ }
+#endif // QT_CONFIG(permissions)
+ discoveryAgent->start();
+ ui->scan->setVisible(false);
+ ui->stopScan->setVisible(true);
+}
+
+void DeviceDiscoveryDialog::stopScan()
+{
+ discoveryAgent->stop();
+ scanFinished();
+}
+
+void DeviceDiscoveryDialog::scanFinished()
+{
+ ui->scan->setVisible(true);
+ ui->stopScan->setVisible(false);
+}
+
+void DeviceDiscoveryDialog::itemActivated(QListWidgetItem *item)
+{
+ const QString text = item->text();
+ const auto index = text.indexOf(' ');
+ if (index == -1)
+ return;
+
+ QBluetoothAddress address(text.left(index));
+ QString name(text.mid(index + 1));
+
+ ServiceDiscoveryDialog d(name, address);
+ d.exec();
+}
+
+void DeviceDiscoveryDialog::on_discoverable_clicked(bool clicked)
+{
+ if (clicked)
+ localDevice->setHostMode(QBluetoothLocalDevice::HostDiscoverable);
+ else
+ localDevice->setHostMode(QBluetoothLocalDevice::HostConnectable);
+}
+
+void DeviceDiscoveryDialog::on_power_clicked(bool clicked)
+{
+ if (clicked)
+ localDevice->powerOn();
+ else
+ localDevice->setHostMode(QBluetoothLocalDevice::HostPoweredOff);
+}
+
+void DeviceDiscoveryDialog::hostModeStateChanged(QBluetoothLocalDevice::HostMode mode)
+{
+ ui->power->setChecked(mode != QBluetoothLocalDevice::HostPoweredOff);
+ ui->discoverable->setChecked(mode == QBluetoothLocalDevice::HostDiscoverable);
+
+ const bool on = mode != QBluetoothLocalDevice::HostPoweredOff;
+ ui->scan->setEnabled(on);
+ ui->discoverable->setEnabled(on);
+}
+void DeviceDiscoveryDialog::displayPairingMenu(const QPoint &pos)
+{
+ if (ui->list->count() == 0)
+ return;
+ QMenu menu(this);
+ QAction *pairAction = menu.addAction("Pair");
+ QAction *removePairAction = menu.addAction("Remove Pairing");
+ QAction *chosenAction = menu.exec(ui->list->viewport()->mapToGlobal(pos));
+ QListWidgetItem *currentItem = ui->list->currentItem();
+
+ QString text = currentItem->text();
+ const auto index = text.indexOf(' ');
+ if (index == -1)
+ return;
+
+ QBluetoothAddress address (text.left(index));
+ if (chosenAction == pairAction) {
+ localDevice->requestPairing(address, QBluetoothLocalDevice::Paired);
+ } else if (chosenAction == removePairAction) {
+ localDevice->requestPairing(address, QBluetoothLocalDevice::Unpaired);
+ }
+}
+void DeviceDiscoveryDialog::pairingDone(const QBluetoothAddress &address,
+ QBluetoothLocalDevice::Pairing pairing)
+{
+ const auto items = ui->list->findItems(address.toString(), Qt::MatchContains);
+ const QColor color = colorForPairing(pairing);
+ for (auto *item : items)
+ item->setForeground(color);
+}
diff --git a/tests/manual/examples/btscanner/device.h b/tests/manual/examples/btscanner/device.h
new file mode 100644
index 00000000..389062bb
--- /dev/null
+++ b/tests/manual/examples/btscanner/device.h
@@ -0,0 +1,49 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef DEVICE_H
+#define DEVICE_H
+
+#include <QtBluetooth/qbluetoothlocaldevice.h>
+
+#include <QtWidgets/qdialog.h>
+
+QT_BEGIN_NAMESPACE
+class QBluetoothAddress;
+class QBluetoothDeviceDiscoveryAgent;
+class QBluetoothDeviceInfo;
+class QListWidgetItem;
+
+namespace Ui {
+ class DeviceDiscovery;
+}
+QT_END_NAMESPACE
+
+class DeviceDiscoveryDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ DeviceDiscoveryDialog(QWidget *parent = nullptr);
+ ~DeviceDiscoveryDialog();
+
+public slots:
+ void addDevice(const QBluetoothDeviceInfo &info);
+ void on_power_clicked(bool clicked);
+ void on_discoverable_clicked(bool clicked);
+ void displayPairingMenu(const QPoint &pos);
+ void pairingDone(const QBluetoothAddress &address, QBluetoothLocalDevice::Pairing pairing);
+private slots:
+ void startScan();
+ void stopScan();
+ void scanFinished();
+ void itemActivated(QListWidgetItem *item);
+ void hostModeStateChanged(QBluetoothLocalDevice::HostMode mode);
+
+private:
+ QBluetoothDeviceDiscoveryAgent *discoveryAgent;
+ QBluetoothLocalDevice *localDevice;
+ Ui::DeviceDiscovery *ui;
+};
+
+#endif
diff --git a/tests/manual/examples/btscanner/device.ui b/tests/manual/examples/btscanner/device.ui
new file mode 100644
index 00000000..fd86a358
--- /dev/null
+++ b/tests/manual/examples/btscanner/device.ui
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>DeviceDiscovery</class>
+ <widget class="QDialog" name="DeviceDiscovery">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>411</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Bluetooth Scanner</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QListWidget" name="list"/>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Local Device</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QCheckBox" name="power">
+ <property name="text">
+ <string>Bluetooth Powered On</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="discoverable">
+ <property name="text">
+ <string>Discoverable</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QPushButton" name="scan">
+ <property name="text">
+ <string>Scan</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="stopScan">
+ <property name="text">
+ <string>Stop scan</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="clear">
+ <property name="text">
+ <string>Clear</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="quit">
+ <property name="text">
+ <string>Quit</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>quit</sender>
+ <signal>clicked()</signal>
+ <receiver>DeviceDiscovery</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>323</x>
+ <y>275</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>396</x>
+ <y>268</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>clear</sender>
+ <signal>clicked()</signal>
+ <receiver>list</receiver>
+ <slot>clear()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>188</x>
+ <y>276</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>209</x>
+ <y>172</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/tests/manual/examples/btscanner/main.cpp b/tests/manual/examples/btscanner/main.cpp
new file mode 100644
index 00000000..8ed2e652
--- /dev/null
+++ b/tests/manual/examples/btscanner/main.cpp
@@ -0,0 +1,33 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "device.h"
+
+#include <QtCore/qloggingcategory.h>
+#include <QtWidgets/qapplication.h>
+
+#if QT_CONFIG(permissions)
+#include <QtCore/qpermissions.h>
+#endif
+
+int main(int argc, char *argv[])
+{
+ // QLoggingCategory::setFilterRules(QStringLiteral("qt.bluetooth* = true"));
+ QApplication app(argc, argv);
+ DeviceDiscoveryDialog d;
+
+ d.show();
+
+ // Check, and if needed, request a permission to use Bluetooth.
+#if QT_CONFIG(permissions)
+ const auto permissionStatus = app.checkPermission(QBluetoothPermission{});
+ if (permissionStatus == Qt::PermissionStatus::Undetermined) {
+ app.requestPermission(QBluetoothPermission{}, [](const QPermission &){
+ });
+ }
+ // Else means either 'Granted' or 'Denied' and both normally must be
+ // changed using the system's settings application.
+#endif // QT_CONFIG(permissions)
+
+ return app.exec();
+}
diff --git a/tests/manual/examples/btscanner/service.cpp b/tests/manual/examples/btscanner/service.cpp
new file mode 100644
index 00000000..aa6fe43c
--- /dev/null
+++ b/tests/manual/examples/btscanner/service.cpp
@@ -0,0 +1,62 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// Copyright (C) 2013 BlackBerry Limited. All rights reserved.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "service.h"
+#include "ui_service.h"
+
+#include <QtBluetooth/qbluetoothaddress.h>
+#include <QtBluetooth/qbluetoothlocaldevice.h>
+#include <QtBluetooth/qbluetoothservicediscoveryagent.h>
+#include <QtBluetooth/qbluetoothserviceinfo.h>
+#include <QtBluetooth/qbluetoothuuid.h>
+
+
+ServiceDiscoveryDialog::ServiceDiscoveryDialog(const QString &name,
+ const QBluetoothAddress &address, QWidget *parent)
+ : QDialog(parent), ui(new Ui::ServiceDiscovery)
+{
+ ui->setupUi(this);
+
+ //Using default Bluetooth adapter
+ QBluetoothLocalDevice localDevice;
+ QBluetoothAddress adapterAddress = localDevice.address();
+
+ // In case of multiple Bluetooth adapters it is possible to
+ // set which adapter will be used by providing MAC Address.
+ // Example code:
+ //
+ // QBluetoothAddress adapterAddress("XX:XX:XX:XX:XX:XX");
+ // discoveryAgent = new QBluetoothServiceDiscoveryAgent(adapterAddress, this);
+
+ discoveryAgent = new QBluetoothServiceDiscoveryAgent(adapterAddress, this);
+ discoveryAgent->setRemoteAddress(address);
+
+ setWindowTitle(name);
+
+ connect(discoveryAgent, &QBluetoothServiceDiscoveryAgent::serviceDiscovered,
+ this, &ServiceDiscoveryDialog::addService);
+ connect(discoveryAgent, &QBluetoothServiceDiscoveryAgent::finished,
+ ui->status, &QWidget::hide);
+
+ discoveryAgent->start();
+}
+
+ServiceDiscoveryDialog::~ServiceDiscoveryDialog()
+{
+ delete ui;
+}
+
+void ServiceDiscoveryDialog::addService(const QBluetoothServiceInfo &info)
+{
+ if (info.serviceName().isEmpty())
+ return;
+
+ QString line = info.serviceName();
+ if (!info.serviceDescription().isEmpty())
+ line.append("\n\t" + info.serviceDescription());
+ if (!info.serviceProvider().isEmpty())
+ line.append("\n\t" + info.serviceProvider());
+
+ ui->list->addItem(line);
+}
diff --git a/tests/manual/examples/btscanner/service.h b/tests/manual/examples/btscanner/service.h
new file mode 100644
index 00000000..1c84f38c
--- /dev/null
+++ b/tests/manual/examples/btscanner/service.h
@@ -0,0 +1,36 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef SERVICE_H
+#define SERVICE_H
+
+#include <QtWidgets/qdialog.h>
+
+QT_BEGIN_NAMESPACE
+class QBluetoothAddress;
+class QBluetoothServiceDiscoveryAgent;
+class QBluetoothServiceInfo;
+
+namespace Ui {
+ class ServiceDiscovery;
+}
+QT_END_NAMESPACE
+
+class ServiceDiscoveryDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ ServiceDiscoveryDialog(const QString &name, const QBluetoothAddress &address,
+ QWidget *parent = nullptr);
+ ~ServiceDiscoveryDialog();
+
+public slots:
+ void addService(const QBluetoothServiceInfo &info);
+
+private:
+ QBluetoothServiceDiscoveryAgent *discoveryAgent;
+ Ui::ServiceDiscovery *ui;
+};
+
+#endif
diff --git a/tests/manual/examples/btscanner/service.ui b/tests/manual/examples/btscanner/service.ui
new file mode 100644
index 00000000..4ca12ee0
--- /dev/null
+++ b/tests/manual/examples/btscanner/service.ui
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ServiceDiscovery</class>
+ <widget class="QDialog" name="ServiceDiscovery">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>539</width>
+ <height>486</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Available Services</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QListWidget" name="list"/>
+ </item>
+ <item>
+ <widget class="QLabel" name="status">
+ <property name="text">
+ <string>Querying...</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Close</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ServiceDiscovery</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>396</x>
+ <y>457</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>535</x>
+ <y>443</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>ServiceDiscovery</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>339</x>
+ <y>464</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>535</x>
+ <y>368</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>