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 <QtBluetooth/private/qohosbluetoothremotedevice_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 {
++
++ constexpr qint16 unknownRssi = 0;
++
++ 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;
++ }
++
++ std::vector<std::string> readPairedDeviceIds(QOhosJsState &jsState)
++ {
++ if (!isBluetoothEnabled(jsState))
++ return {};
++
++ try {
++ return QNapi::getArrayElements<std::vector<std::string>, QNapi::String>(
++ jsState.eval<QNapi::Array>("@ohos.bluetooth.connection.getPairedDevices()"),
++ [](QNapi::String &&pairedDeviceId) {
++ return pairedDeviceId.Utf8Value();
++ });
++ } catch (const Napi::Error &error) {
++ if (const auto optErrorCode = tryGetKnownErrorCode(error.Value(), Q_FUNC_INFO)) {
++ qOhosPrintfWarning(
++ "%s: cannot read the paired devices, error code: %u", Q_FUNC_INFO, qToUnderlying(*optErrorCode));
++ }
++ return {};
++ }
++ }
++
++ std::vector<DiscoveryResult> readPairedDevices(QOhosJsState &jsState)
++ {
++ auto pairedDeviceIds = readPairedDeviceIds(jsState);
++
++ std::vector<DiscoveryResult> pairedDevices;
++ pairedDevices.reserve(pairedDeviceIds.size());
++ for (auto &pairedDeviceId : pairedDeviceIds) {
++ if (isRemoteDeviceLowEnergyOnly(jsState, pairedDeviceId))
++ continue;
++
++ auto pairedDeviceName = readRemoteDeviceName(jsState, pairedDeviceId);
++ const auto optPairedDeviceClass = readRemoteDeviceClass(jsState, pairedDeviceId);
++
++ pairedDevices.push_back(
++ DiscoveryResult {
++ .deviceId = std::move(pairedDeviceId),
++ .deviceName = pairedDeviceName
++ ? *pairedDeviceName
++ : std::string(),
++ .classOfDevice = optPairedDeviceClass.value_or(0),
++ .rssi = unknownRssi,
++ });
++ }
++
++ return pairedDevices;
++ }
++
++ }
++
++ DiscoveryResult DiscoveryResult::makeFromOhosDiscoveryResultObject(
++ QOhosJsState &jsState, QNapi::Object discoveryResultObject)
++ {
++ auto deviceId = discoveryResultObject.get<QNapi::String>("deviceId").Utf8Value();
++
++ auto deviceName = discoveryResultObject.get<QNapi::String>("deviceName").Utf8Value();
++ if (deviceName.empty()) {
++ if (auto optRemoteDeviceName = readRemoteDeviceName(jsState, deviceId))
++ deviceName = std::move(*optRemoteDeviceName);
++ }
++
++ return DiscoveryResult {
++ .deviceId = std::move(deviceId),
++ .deviceName = std::move(deviceName),
++ .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,
++ [&](QNapi::Object &&discoveryResultObject) {
++ return DiscoveryResult::makeFromOhosDiscoveryResultObject(
++ cbInfo.jsState(), std::move(discoveryResultObject));
++ });
++
++ 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();
++ }
++
++ std::vector<DiscoveryResult> QOhosBluetoothDeviceDiscoveryAgentProxy::getPairedDevices()
++ {
++ if (!checkAccessBluetoothPermissionGranted(Q_FUNC_INFO))
++ return {};
++
++ return QOhosJsThreadGateway::eval(readPairedDevices, Q_FUNC_INFO);
++ }
++
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qohosbluetoothdevicediscovery_p.cpp"