Revision control
+ // Copyright (C) 2026 The Qt Company Ltd.
+ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+ #include "qohosbluetoothdevicediscovery_p.h"
+
+ #include <QtBluetooth/private/qohosbluetoothcommon_p.h>
+
+ #include <QtCore/qloggingcategory.h>
+ #include <QtCore/private/qnapi_p.h>
+ #include <QtCore/private/qohosjstools_p.h>
+ #include <QtCore/private/qohoslogger_p.h>
+
+ #include <chrono>
+ #include <optional>
+
+ using namespace std::chrono_literals;
+
+ QT_BEGIN_NAMESPACE
+
+ Q_DECLARE_LOGGING_CATEGORY(QT_BT_OHOS)
+
+ namespace QtOhosBluetooth {
+
+ namespace {
+
+
+ std::optional<bool> tryReadBluetoothDiscoveringState(QOhosJsState &jsState)
+ {
+ if (!isBluetoothEnabled(jsState))
+ return false;
+
+ try {
+ return jsState.eval<QNapi::Boolean>("@ohos.bluetooth.connection.isBluetoothDiscovering()").Value();
+ } catch (const Napi::Error &error) {
+ if (const auto optErrorCode = tryGetKnownErrorCode(error.Value(), Q_FUNC_INFO)) {
+ qOhosPrintfWarning(
+ "%s: cannot read the bluetooth discovering state, error code: %u", Q_FUNC_INFO,
+ qToUnderlying(*optErrorCode));
+ }
+ return std::nullopt;
+ }
+ }
+
+ bool requestBluetoothDiscoveryStop(QOhosJsState &jsState)
+ {
+ const auto optDiscovering = tryReadBluetoothDiscoveringState(jsState);
+ if (optDiscovering && !*optDiscovering)
+ return true;
+
+ try {
+ jsState.eval("@ohos.bluetooth.connection.stopBluetoothDiscovery()");
+ } catch (const Napi::Error &error) {
+ if (const auto optErrorCode = tryGetKnownErrorCode(error.Value(), Q_FUNC_INFO)) {
+ qOhosPrintfWarning(
+ "%s: cannot stop the bluetooth discovery, error code: %u", Q_FUNC_INFO, qToUnderlying(*optErrorCode));
+ }
+ return false;
+ }
+
+ return true;
+ }
+
+ }
+
+ DiscoveryResult DiscoveryResult::makeFromOhosDiscoveryResultObject(QNapi::Object discoveryResultObject)
+ {
+ return DiscoveryResult {
+ .deviceId = discoveryResultObject.get<QNapi::String>("deviceId").Utf8Value(),
+ .deviceName = discoveryResultObject.get<QNapi::String>("deviceName").Utf8Value(),
+ .classOfDevice = discoveryResultObject.get<QNapi::Number>("deviceClass.classOfDevice").Uint32Value(),
+ .rssi = static_cast<qint16>(discoveryResultObject.get<QNapi::Number>("rssi").Int32Value()),
+ };
+ }
+
+ QOhosBluetoothDeviceDiscoveryAgentProxy::QOhosBluetoothDeviceDiscoveryAgentProxy()
+ : m_accessProxy(QOhosBluetoothAccessProxy::instance())
+ {
+ QObject::connect(
+ m_accessProxy.get(), &QOhosBluetoothAccessProxy::stateChanged, this,
+ [this](QOhosBluetoothAccessProxy::BluetoothState bluetoothState) {
+ if (bluetoothState == QOhosBluetoothAccessProxy::BluetoothState::STATE_ON)
+ return;
+
+ if (!m_discoveryStoppedPollTimer.isActive())
+ return;
+
+ resetDiscoveryTracking();
+ Q_EMIT bluetoothPoweredOff();
+ });
+
+ constexpr auto discoveryStoppedPollInterval = 500ms;
+ m_discoveryStoppedPollTimer.setInterval(discoveryStoppedPollInterval);
+ QObject::connect(
+ &m_discoveryStoppedPollTimer, &QTimer::timeout, this,
+ [this]() {
+ if (!checkAccessBluetoothPermissionGranted(Q_FUNC_INFO)) {
+ resetDiscoveryTracking();
+ m_discoveredDevicesConsumerHandle.reset();
+ Q_EMIT missingPermission();
+ return;
+ }
+
+ if (tryCheckBluetoothDiscovering().value_or(true))
+ return;
+
+ resetDiscoveryTracking();
+ Q_EMIT discoveryStopped();
+ });
+
+ constexpr auto discoveryStoppedTimeout = 30s;
+ m_discoveryStoppedTimeoutTimer.setSingleShot(true);
+ m_discoveryStoppedTimeoutTimer.setInterval(discoveryStoppedTimeout);
+ QObject::connect(
+ &m_discoveryStoppedTimeoutTimer, &QTimer::timeout, this,
+ [this]() {
+ resetDiscoveryTracking();
+ Q_EMIT discoveryStopFailed();
+ });
+ }
+
+ std::shared_ptr<QOhosBluetoothDeviceDiscoveryAgentProxy> QOhosBluetoothDeviceDiscoveryAgentProxy::instance()
+ {
+ static std::weak_ptr<QOhosBluetoothDeviceDiscoveryAgentProxy> weakInstance;
+
+ auto instance = weakInstance.lock();
+ if (!instance) {
+ instance = std::shared_ptr<QOhosBluetoothDeviceDiscoveryAgentProxy>(
+ new QOhosBluetoothDeviceDiscoveryAgentProxy());
+ weakInstance = instance;
+ }
+
+ return instance;
+ }
+
+ bool QOhosBluetoothDeviceDiscoveryAgentProxy::ensureDiscoveredDevicesConsumerRegistered()
+ {
+ if (m_discoveredDevicesConsumerHandle)
+ return true;
+
+ if (!checkAccessBluetoothPermissionGranted(Q_FUNC_INFO))
+ return false;
+
+ auto selfRef = QtOhos::makeQThreadSafeRef(this);
+ m_discoveredDevicesConsumerHandle = QOhosJsThreadGateway::eval(
+ [&](QOhosJsState &jsState) -> std::shared_ptr<void> {
+ auto discoveredDevicesConsumerHandle = registerQOhosOnOffMethodsBasedEventHandler(
+ jsState.eval<QNapi::Object>("@ohos.bluetooth.connection"), "discoveryResult",
+ [selfRef](const QOhosCallbackInfo &cbInfo) {
+ auto resultArray = cbInfo.getFirstArg<QNapi::Array>(Q_FUNC_INFO);
+ auto discoveredDevices =
+ QNapi::getArrayElements<std::vector<DiscoveryResult>, QNapi::Object>(
+ resultArray, DiscoveryResult::makeFromOhosDiscoveryResultObject);
+
+ selfRef.visitInQtThreadIfAlive(
+ [discoveredDevices = std::move(discoveredDevices)](auto &self) {
+ Q_EMIT self.bluetoothDevicesFound(discoveredDevices);
+ });
+ },
+ {
+ .optOnCallExceptionHandler = [](const Napi::Error &error) {
+ if (const auto optErrorCode = tryGetKnownErrorCode(error.Value(), Q_FUNC_INFO)) {
+ qOhosPrintfWarning(
+ "%s: cannot register the discovery result consumer, error code: %u",
+ Q_FUNC_INFO, qToUnderlying(*optErrorCode));
+ }
+ },
+ });
+
+ if (!discoveredDevicesConsumerHandle)
+ return {};
+
+ return QtOhos::makeProxyWithJsThreadDeleter(
+ std::move(discoveredDevicesConsumerHandle));
+ },
+ Q_FUNC_INFO);
+
+ return m_discoveredDevicesConsumerHandle != nullptr;
+ }
+
+ std::optional<bool> QOhosBluetoothDeviceDiscoveryAgentProxy::tryCheckBluetoothDiscovering()
+ {
+ if (!checkAccessBluetoothPermissionGranted(Q_FUNC_INFO))
+ return std::nullopt;
+
+ return QOhosJsThreadGateway::eval(tryReadBluetoothDiscoveringState, Q_FUNC_INFO);
+ }
+
+ void QOhosBluetoothDeviceDiscoveryAgentProxy::resetDiscoveryTracking()
+ {
+ m_discoveryStoppedPollTimer.stop();
+ m_discoveryStoppedTimeoutTimer.stop();
+ m_discoveryStartedByProxy = false;
+ }
+
+ bool QOhosBluetoothDeviceDiscoveryAgentProxy::isForeignDiscoveryOngoing()
+ {
+ return !m_discoveryStartedByProxy && tryCheckBluetoothDiscovering().value_or(false);
+ }
+
+ void QOhosBluetoothDeviceDiscoveryAgentProxy::startBluetoothDiscovery()
+ {
+ if (!checkAccessBluetoothPermissionGranted(Q_FUNC_INFO)) {
+ Q_EMIT missingPermission();
+ return;
+ }
+
+ if (!ensureDiscoveredDevicesConsumerRegistered()) {
+ qCCritical(
+ QT_BT_OHOS,
+ "%s: discovery result consumer not registered. Found devices would not be reported.",
+ Q_FUNC_INFO);
+ Q_EMIT discoveryStartFailed();
+ return;
+ }
+
+ const auto discoveryOngoing = QOhosJsThreadGateway::eval(
+ [](QOhosJsState &jsState) {
+ const auto optDiscovering = tryReadBluetoothDiscoveringState(jsState);
+ if (optDiscovering && *optDiscovering)
+ return true;
+
+ try {
+ jsState.eval("@ohos.bluetooth.connection.startBluetoothDiscovery()");
+ } catch (const Napi::Error &error) {
+ if (const auto optErrorCode = tryGetKnownErrorCode(error.Value(), Q_FUNC_INFO)) {
+ qOhosPrintfWarning(
+ "%s: cannot start the bluetooth discovery, error code: %u", Q_FUNC_INFO,
+ qToUnderlying(*optErrorCode));
+ }
+ return false;
+ }
+
+ return true;
+ },
+ Q_FUNC_INFO);
+
+ if (!discoveryOngoing) {
+ Q_EMIT discoveryStartFailed();
+ return;
+ }
+
+ m_discoveryStartedByProxy = true;
+ m_discoveryStoppedTimeoutTimer.stop();
+ m_discoveryStoppedPollTimer.start();
+ }
+
+ void QOhosBluetoothDeviceDiscoveryAgentProxy::stopBluetoothDiscovery()
+ {
+ if (!checkAccessBluetoothPermissionGranted(Q_FUNC_INFO)) {
+ Q_EMIT missingPermission();
+ return;
+ }
+
+ if (!QOhosJsThreadGateway::eval(requestBluetoothDiscoveryStop, Q_FUNC_INFO)) {
+ Q_EMIT discoveryStopFailed();
+ return;
+ }
+
+ m_discoveryStoppedPollTimer.start();
+ m_discoveryStoppedTimeoutTimer.start();
+ }
+
+ }
+
+ QT_END_NAMESPACE
+
+ #include "moc_qohosbluetoothdevicediscovery_p.cpp"