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 "qbluetoothlocaldevice_p.h"
++
++ #include <QtBluetooth/qbluetoothaddress.h>
++ #include <QtBluetooth/qbluetoothlocaldevice.h>
++ #include <QtBluetooth/private/qohosbluetoothaccess_p.h>
++ #include <QtBluetooth/private/qohosbluetoothcommon_p.h>
++ #include <QtBluetooth/private/qohosbluetoothlocaldevice_p.h>
++ #include <QtBluetooth/private/qohosbluetoothremotedevice_p.h>
++
++ #include <QtCore/qdeadlinetimer.h>
++ #include <QtCore/qhash.h>
++ #include <QtCore/qloggingcategory.h>
++ #include <QtCore/qtimer.h>
++
++ #include <algorithm>
++ #include <chrono>
++ #include <memory>
++ #include <optional>
++
++ QT_BEGIN_NAMESPACE
++
++ using QtOhosBluetooth::QOhosBluetoothErrorCode;
++
++ Q_DECLARE_LOGGING_CATEGORY(QT_BT_OHOS)
++
++ namespace {
++
++ constexpr auto pairingRequestTimeout = std::chrono::seconds(30);
++ constexpr auto hostModeRequestRetryDelay = std::chrono::milliseconds(500);
++ constexpr auto hostModeRequestGiveUpDelay = std::chrono::seconds(20);
++
++ QBluetoothLocalDevice::HostMode mapScanModeToQtHostMode(
++ QtOhosBluetooth::QOhosBluetoothLocalDeviceProxy::ScanMode scanMode)
++ {
++ using ScanMode = QtOhosBluetooth::QOhosBluetoothLocalDeviceProxy::ScanMode;
++
++ switch (scanMode) {
++ case ScanMode::SCAN_MODE_CONNECTABLE:
++ return QBluetoothLocalDevice::HostMode::HostConnectable;
++ case ScanMode::SCAN_MODE_GENERAL_DISCOVERABLE:
++ case ScanMode::SCAN_MODE_CONNECTABLE_GENERAL_DISCOVERABLE:
++ return QBluetoothLocalDevice::HostMode::HostDiscoverable;
++ case ScanMode::SCAN_MODE_LIMITED_DISCOVERABLE:
++ case ScanMode::SCAN_MODE_CONNECTABLE_LIMITED_DISCOVERABLE:
++ return QBluetoothLocalDevice::HostMode::HostDiscoverableLimitedInquiry;
++ case ScanMode::SCAN_MODE_NONE:
++ return QBluetoothLocalDevice::HostMode::HostPoweredOff;
++ }
++
++ qOhosReportFatalErrorAndAbort(
++ "%s: unexpected ScanMode value %d", Q_FUNC_INFO, static_cast<int>(scanMode));
++ }
++
++ QtOhosBluetooth::QOhosBluetoothLocalDeviceProxy::ScanMode mapHostModeToOhosScanMode(
++ QBluetoothLocalDevice::HostMode hostMode)
++ {
++ using ScanMode = QtOhosBluetooth::QOhosBluetoothLocalDeviceProxy::ScanMode;
++
++ switch (hostMode) {
++ case QBluetoothLocalDevice::HostMode::HostPoweredOff:
++ return ScanMode::SCAN_MODE_NONE;
++ case QBluetoothLocalDevice::HostMode::HostConnectable:
++ return ScanMode::SCAN_MODE_CONNECTABLE;
++ case QBluetoothLocalDevice::HostMode::HostDiscoverable:
++ return ScanMode::SCAN_MODE_CONNECTABLE_GENERAL_DISCOVERABLE;
++ case QBluetoothLocalDevice::HostMode::HostDiscoverableLimitedInquiry:
++ return ScanMode::SCAN_MODE_CONNECTABLE_LIMITED_DISCOVERABLE;
++ }
++
++ qOhosReportFatalErrorAndAbort(
++ "%s: unexpected HostMode value %d", Q_FUNC_INFO, static_cast<int>(hostMode));
++ }
++
++ bool isAdapterStateSettled(QtOhosBluetooth::QOhosBluetoothAccessProxy::BluetoothState bluetoothState)
++ {
++ using BluetoothState = QtOhosBluetooth::QOhosBluetoothAccessProxy::BluetoothState;
++
++ switch (bluetoothState) {
++ case BluetoothState::STATE_BLE_TURNING_OFF:
++ case BluetoothState::STATE_BLE_TURNING_ON:
++ case BluetoothState::STATE_TURNING_OFF:
++ case BluetoothState::STATE_TURNING_ON:
++ return false;
++ case BluetoothState::STATE_BLE_ON:
++ case BluetoothState::STATE_OFF:
++ case BluetoothState::STATE_ON:
++ return true;
++ }
++
++ qOhosReportFatalErrorAndAbort(
++ "%s: unexpected BluetoothState value %d", Q_FUNC_INFO, static_cast<int>(bluetoothState));
++ }
++
++ QBluetoothLocalDevice::HostMode getBluetoothLocalDeviceHostMode(
++ QtOhosBluetooth::QOhosBluetoothAccessProxy::BluetoothState bluetoothLocalAdapterState)
++ {
++ if (bluetoothLocalAdapterState != QtOhosBluetooth::QOhosBluetoothAccessProxy::BluetoothState::STATE_ON)
++ return QBluetoothLocalDevice::HostMode::HostPoweredOff;
++
++ const auto optScanMode = QtOhosBluetooth::QOhosBluetoothLocalDeviceProxy::instance()->tryGetScanMode();
++ return optScanMode
++ ? mapScanModeToQtHostMode(*optScanMode)
++ : QBluetoothLocalDevice::HostMode::HostPoweredOff;
++ }
++
++ QBluetoothLocalDevice::Pairing mapOhosBondStateToLocalDevicePairing(
++ QtOhosBluetooth::QOhosBluetoothRemoteDeviceProxy::BondState bondState)
++ {
++ return bondState == QtOhosBluetooth::QOhosBluetoothRemoteDeviceProxy::BondState::BOND_STATE_BONDED
++ ? QBluetoothLocalDevice::Pairing::Paired
++ : QBluetoothLocalDevice::Pairing::Unpaired;
++ }
++
++ enum class HostModeUpdateSource { ChangeEvent, LiveRead };
++
++ class QOhosBluetoothLocalDevicePrivate : public QBluetoothLocalDevicePrivate
++ {
++ public:
++ QOhosBluetoothLocalDevicePrivate(QBluetoothLocalDevice *qBluetoothLocalDevice, const QBluetoothAddress &address);
++
++ bool isValid() const override;
++ std::optional<QtOhosBluetooth::QOhosBluetoothAccessProxy::BluetoothState> tryGetBluetoothState() const;
++ std::optional<QtOhosBluetooth::QOhosBluetoothRemoteDeviceProxy::BondState> tryGetPairState(
++ const QBluetoothAddress &address) const;
++ void requestHostMode(QBluetoothLocalDevice::HostMode requestedHostMode);
++ void requestPowerOn();
++ void cancelHostModeRequest();
++ void handleAdapterHostModeChanged(
++ QBluetoothLocalDevice::HostMode hostMode,
++ std::optional<QtOhosBluetooth::QOhosBluetoothAccessProxy::BluetoothState> optBluetoothState,
++ HostModeUpdateSource source = HostModeUpdateSource::ChangeEvent);
++
++ bool pairDevice(const QBluetoothAddress &address);
++ void cancelPendingPairingRequests();
++
++ private:
++ struct HostModeRequest
++ {
++ QBluetoothLocalDevice::HostMode target = QBluetoothLocalDevice::HostMode::HostPoweredOff;
++ bool anyPoweredOnAccepted = false;
++ bool awaitingOutcome = false;
++ bool powerSwitchIssued = false;
++ bool scanModeIssued = false;
++ QDeadlineTimer deadline;
++ };
++
++ void registerHostModeRequest(
++ QBluetoothLocalDevice::HostMode target, bool anyPoweredOnAccepted);
++ void issueHostModeRequest();
++ void resolveHostModeRequestFromAdapterState();
++ void scheduleHostModeRetry();
++ void awaitHostModeRequestOutcome(bool powerSwitchIssued);
++ void giveUpHostModeRequest();
++ static bool isRequestSatisfiedBy(
++ const HostModeRequest &request, QBluetoothLocalDevice::HostMode hostMode,
++ bool adapterPoweredOn);
++ static bool isPowerSwitchOutcomePending(
++ const HostModeRequest &request, bool adapterPoweredOn);
++ void rearmPendingPairingRequestsTimer();
++ void handleExpiredPendingPairingRequests();
++ void emitAsyncError(QBluetoothLocalDevice::Error error);
++
++ QBluetoothLocalDevice *m_qBluetoothLocalDevice;
++ std::shared_ptr<QtOhosBluetooth::QOhosBluetoothRemoteDeviceProxy> m_remoteDeviceProxy;
++ bool m_isValid = false;
++ QBluetoothLocalDevice::HostMode m_lastHostMode =
++ QBluetoothLocalDevice::HostMode::HostPoweredOff;
++ std::optional<HostModeRequest> m_optHostModeRequest = std::nullopt;
++ QHash<QBluetoothAddress, QDeadlineTimer> m_pendingPairingRequests;
++ QTimer m_pendingPairingRequestsTimer;
++ QTimer m_hostModeRequestTimer;
++ };
++
++ QOhosBluetoothLocalDevicePrivate::QOhosBluetoothLocalDevicePrivate(
++ QBluetoothLocalDevice *qBluetoothLocalDevice, const QBluetoothAddress &address)
++ : m_qBluetoothLocalDevice(qBluetoothLocalDevice)
++ , m_remoteDeviceProxy(QtOhosBluetooth::QOhosBluetoothRemoteDeviceProxy::instance())
++ {
++ if (!address.isNull()) {
++ qCWarning(
++ QT_BT_OHOS, "%s: HarmonyOS does not support device adapter setting. Ignoring address %ls",
++ Q_FUNC_INFO, qUtf16Printable(address.toString()));
++
++ return;
++ }
++
++ m_isValid = true;
++
++ if (const auto optBluetoothState = tryGetBluetoothState())
++ m_lastHostMode = getBluetoothLocalDeviceHostMode(*optBluetoothState);
++
++ QObject::connect(
++ QtOhosBluetooth::QOhosBluetoothAccessProxy::instance().get(),
++ &QtOhosBluetooth::QOhosBluetoothAccessProxy::stateChanged,
++ this,
++ [this](auto state) {
++ if (state != QtOhosBluetooth::QOhosBluetoothAccessProxy::BluetoothState::STATE_ON)
++ cancelPendingPairingRequests();
++
++ handleAdapterHostModeChanged(getBluetoothLocalDeviceHostMode(state), state);
++ });
++
++ QObject::connect(
++ QtOhosBluetooth::QOhosBluetoothAccessProxy::instance().get(),
++ &QtOhosBluetooth::QOhosBluetoothAccessProxy::setBluetoothEnabledFailed,
++ this,
++ [this](QOhosBluetoothErrorCode errorCode) {
++ if (!m_optHostModeRequest || !m_optHostModeRequest->powerSwitchIssued)
++ return;
++
++ qCWarning(
++ QT_BT_OHOS, "%s: the bluetooth switch was not performed, error code: %u",
++ Q_FUNC_INFO, qToUnderlying(errorCode));
++ giveUpHostModeRequest();
++ });
++
++ QObject::connect(
++ QtOhosBluetooth::QOhosBluetoothLocalDeviceProxy::instance().get(),
++ &QtOhosBluetooth::QOhosBluetoothLocalDeviceProxy::scanModeChanged,
++ this,
++ [this](auto scanMode) {
++ const auto optBluetoothState = tryGetBluetoothState();
++ if (optBluetoothState != QtOhosBluetooth::QOhosBluetoothAccessProxy::BluetoothState::STATE_ON)
++ return;
++
++ handleAdapterHostModeChanged(mapScanModeToQtHostMode(scanMode), optBluetoothState);
++ });
++
++ QObject::connect(
++ m_remoteDeviceProxy.get(),
++ &QtOhosBluetooth::QOhosBluetoothRemoteDeviceProxy::bondStateChanged,
++ this,
++ [this](const QString &deviceId, QtOhosBluetooth::QOhosBluetoothRemoteDeviceProxy::BondState bondState,
++ std::optional<QtOhosBluetooth::QOhosBluetoothRemoteDeviceProxy::UnbondCause> optUnbondCause) {
++ const QBluetoothAddress deviceAddress(deviceId);
++ if (deviceAddress.isNull()) {
++ qCWarning(
++ QT_BT_OHOS,
++ "%s: ignoring the bond state change for a device with an unexpected address: '%ls'",
++ Q_FUNC_INFO, qUtf16Printable(deviceId));
++ return;
++ }
++
++ if (!m_pendingPairingRequests.contains(deviceAddress))
++ return;
++
++ switch (bondState) {
++ case QtOhosBluetooth::QOhosBluetoothRemoteDeviceProxy::BondState::BOND_STATE_INVALID:
++ qCWarning(
++ QT_BT_OHOS, "%s: pairing with the device %ls failed with the unbond cause %d",
++ Q_FUNC_INFO, qUtf16Printable(deviceAddress.toString()),
++ optUnbondCause ? static_cast<int>(*optUnbondCause) : -1);
++ m_pendingPairingRequests.remove(deviceAddress);
++ rearmPendingPairingRequestsTimer();
++ emitAsyncError(QBluetoothLocalDevice::Error::PairingError);
++ break;
++ case QtOhosBluetooth::QOhosBluetoothRemoteDeviceProxy::BondState::BOND_STATE_BONDING:
++ break;
++ case QtOhosBluetooth::QOhosBluetoothRemoteDeviceProxy::BondState::BOND_STATE_BONDED:
++ m_pendingPairingRequests.remove(deviceAddress);
++ rearmPendingPairingRequestsTimer();
++ Q_EMIT m_qBluetoothLocalDevice->pairingFinished(
++ deviceAddress, QBluetoothLocalDevice::Paired);
++ break;
++ }
++ });
++
++ QObject::connect(
++ m_remoteDeviceProxy.get(),
++ &QtOhosBluetooth::QOhosBluetoothRemoteDeviceProxy::pairDeviceFailed,
++ this,
++ [this](const QString &deviceId) {
++ const QBluetoothAddress deviceAddress(deviceId);
++ if (!m_pendingPairingRequests.contains(deviceAddress))
++ return;
++
++ qCWarning(
++ QT_BT_OHOS, "%s: the pairing request for the device %ls was rejected", Q_FUNC_INFO,
++ qUtf16Printable(deviceId));
++ m_pendingPairingRequests.remove(deviceAddress);
++ rearmPendingPairingRequestsTimer();
++ emitAsyncError(QBluetoothLocalDevice::Error::PairingError);
++ });
++
++ auto emitMissingPermissionsError = [this]() {
++ emitAsyncError(QBluetoothLocalDevice::MissingPermissionsError);
++ };
++ QObject::connect(
++ QtOhosBluetooth::QOhosBluetoothAccessProxy::instance().get(),
++ &QtOhosBluetooth::QOhosBluetoothAccessProxy::missingPermission,
++ this, emitMissingPermissionsError);
++ QObject::connect(
++ QtOhosBluetooth::QOhosBluetoothLocalDeviceProxy::instance().get(),
++ &QtOhosBluetooth::QOhosBluetoothLocalDeviceProxy::missingPermission,
++ this, emitMissingPermissionsError);
++ QObject::connect(
++ m_remoteDeviceProxy.get(),
++ &QtOhosBluetooth::QOhosBluetoothRemoteDeviceProxy::missingPermission,
++ this, emitMissingPermissionsError);
++
++ m_pendingPairingRequestsTimer.setSingleShot(true);
++ QObject::connect(
++ &m_pendingPairingRequestsTimer, &QTimer::timeout, this,
++ [this]() {
++ handleExpiredPendingPairingRequests();
++ });
++
++ m_hostModeRequestTimer.setSingleShot(true);
++ QObject::connect(
++ &m_hostModeRequestTimer, &QTimer::timeout, this,
++ [this]() {
++ if (!m_optHostModeRequest)
++ return;
++
++ if (m_optHostModeRequest->awaitingOutcome
++ || m_optHostModeRequest->deadline.hasExpired()) {
++ resolveHostModeRequestFromAdapterState();
++
++ if (m_optHostModeRequest && !m_hostModeRequestTimer.isActive())
++ giveUpHostModeRequest();
++
++ return;
++ }
++
++ issueHostModeRequest();
++ });
++ }
++
++ bool QOhosBluetoothLocalDevicePrivate::isValid() const
++ {
++ return m_isValid;
++ }
++
++ std::optional<QtOhosBluetooth::QOhosBluetoothAccessProxy::BluetoothState> QOhosBluetoothLocalDevicePrivate::tryGetBluetoothState() const
++ {
++ return QtOhosBluetooth::QOhosBluetoothAccessProxy::instance()->tryGetBluetoothState();
++ }
++
++ std::optional<QtOhosBluetooth::QOhosBluetoothRemoteDeviceProxy::BondState> QOhosBluetoothLocalDevicePrivate::tryGetPairState(
++ const QBluetoothAddress &address) const
++ {
++ return m_remoteDeviceProxy->tryGetPairState(address.toString());
++ }
++
++ void QOhosBluetoothLocalDevicePrivate::requestHostMode(
++ QBluetoothLocalDevice::HostMode requestedHostMode)
++ {
++ registerHostModeRequest(requestedHostMode, false);
++ }
++
++ void QOhosBluetoothLocalDevicePrivate::requestPowerOn()
++ {
++ registerHostModeRequest(QBluetoothLocalDevice::HostMode::HostConnectable, true);
++ }
++
++ void QOhosBluetoothLocalDevicePrivate::registerHostModeRequest(
++ QBluetoothLocalDevice::HostMode target, bool anyPoweredOnAccepted)
++ {
++ m_hostModeRequestTimer.stop();
++ m_optHostModeRequest = HostModeRequest {
++ .target = target,
++ .anyPoweredOnAccepted = anyPoweredOnAccepted,
++ .awaitingOutcome = false,
++ .powerSwitchIssued = false,
++ .scanModeIssued = false,
++ .deadline = QDeadlineTimer(hostModeRequestGiveUpDelay),
++ };
++
++ issueHostModeRequest();
++ }
++
++ bool QOhosBluetoothLocalDevicePrivate::isRequestSatisfiedBy(
++ const HostModeRequest &request, QBluetoothLocalDevice::HostMode hostMode,
++ bool adapterPoweredOn)
++ {
++ if (request.anyPoweredOnAccepted)
++ return adapterPoweredOn && hostMode != QBluetoothLocalDevice::HostMode::HostPoweredOff;
++
++ if (request.target == QBluetoothLocalDevice::HostMode::HostPoweredOff)
++ return !adapterPoweredOn;
++
++ if (request.target == QBluetoothLocalDevice::HostMode::HostDiscoverableLimitedInquiry)
++ return hostMode == QBluetoothLocalDevice::HostMode::HostDiscoverableLimitedInquiry
++ || hostMode == QBluetoothLocalDevice::HostMode::HostDiscoverable;
++
++ return hostMode == request.target;
++ }
++
++ void QOhosBluetoothLocalDevicePrivate::resolveHostModeRequestFromAdapterState()
++ {
++ const auto optBluetoothState = tryGetBluetoothState();
++ if (!optBluetoothState)
++ return;
++
++ handleAdapterHostModeChanged(
++ getBluetoothLocalDeviceHostMode(*optBluetoothState), optBluetoothState,
++ HostModeUpdateSource::LiveRead);
++ }
++
++ void QOhosBluetoothLocalDevicePrivate::issueHostModeRequest()
++ {
++ Q_ASSERT(m_optHostModeRequest);
++ using BluetoothState = QtOhosBluetooth::QOhosBluetoothAccessProxy::BluetoothState;
++
++ const auto target = m_optHostModeRequest->target;
++ const auto optBluetoothState = tryGetBluetoothState();
++
++ if (optBluetoothState && isAdapterStateSettled(*optBluetoothState)
++ && isRequestSatisfiedBy(
++ *m_optHostModeRequest, getBluetoothLocalDeviceHostMode(*optBluetoothState),
++ *optBluetoothState == BluetoothState::STATE_ON)) {
++ cancelHostModeRequest();
++ return;
++ }
++
++ const auto handleOutcome =
++ [this](std::optional<QOhosBluetoothErrorCode> optErrorCode, bool isPowerSwitchCall) {
++ if (!optErrorCode) {
++ if (isPowerSwitchCall) {
++ awaitHostModeRequestOutcome(true);
++ return;
++ }
++
++ m_optHostModeRequest->scanModeIssued = true;
++ resolveHostModeRequestFromAdapterState();
++
++ if (m_optHostModeRequest && !m_hostModeRequestTimer.isActive())
++ awaitHostModeRequestOutcome(false);
++
++ return;
++ }
++
++ if (*optErrorCode == QOhosBluetoothErrorCode::OperationFailed
++ || *optErrorCode == QOhosBluetoothErrorCode::BluetoothDisabled) {
++ if (isPowerSwitchCall)
++ awaitHostModeRequestOutcome(true);
++ else
++ scheduleHostModeRetry();
++
++ return;
++ }
++
++ if (*optErrorCode == QOhosBluetoothErrorCode::PermissionDenied) {
++ cancelHostModeRequest();
++ return;
++ }
++
++ qCWarning(
++ QT_BT_OHOS, "%s: the host mode request was rejected with error code: %u",
++ Q_FUNC_INFO, qToUnderlying(*optErrorCode));
++ giveUpHostModeRequest();
++ };
++
++ if (!optBluetoothState) {
++ scheduleHostModeRetry();
++ return;
++ }
++
++ if (!isAdapterStateSettled(*optBluetoothState)) {
++ awaitHostModeRequestOutcome(false);
++ return;
++ }
++
++ m_optHostModeRequest->scanModeIssued = false;
++
++ if (target == QBluetoothLocalDevice::HostMode::HostPoweredOff) {
++ Q_ASSERT(*optBluetoothState == BluetoothState::STATE_ON);
++ handleOutcome(
++ QtOhosBluetooth::QOhosBluetoothAccessProxy::instance()->trySetBluetoothEnabled(false),
++ true);
++ return;
++ }
++
++ if (*optBluetoothState != BluetoothState::STATE_ON) {
++ handleOutcome(
++ QtOhosBluetooth::QOhosBluetoothAccessProxy::instance()->trySetBluetoothEnabled(true),
++ true);
++ return;
++ }
++
++ handleOutcome(
++ QtOhosBluetooth::QOhosBluetoothLocalDeviceProxy::instance()->trySetBluetoothScanMode(
++ mapHostModeToOhosScanMode(target)),
++ false);
++ }
++
++ void QOhosBluetoothLocalDevicePrivate::scheduleHostModeRetry()
++ {
++ Q_ASSERT(m_optHostModeRequest);
++ if (m_optHostModeRequest->deadline.hasExpired()) {
++ giveUpHostModeRequest();
++ return;
++ }
++
++ m_optHostModeRequest->awaitingOutcome = false;
++ m_optHostModeRequest->powerSwitchIssued = false;
++ m_hostModeRequestTimer.start(hostModeRequestRetryDelay);
++ }
++
++ bool QOhosBluetoothLocalDevicePrivate::isPowerSwitchOutcomePending(
++ const HostModeRequest &request, bool adapterPoweredOn)
++ {
++ if (!request.powerSwitchIssued)
++ return false;
++
++ return request.target == QBluetoothLocalDevice::HostMode::HostPoweredOff
++ ? adapterPoweredOn
++ : !adapterPoweredOn;
++ }
++
++ void QOhosBluetoothLocalDevicePrivate::awaitHostModeRequestOutcome(bool powerSwitchIssued)
++ {
++ Q_ASSERT(m_optHostModeRequest);
++ const auto remainingTime = m_optHostModeRequest->deadline.remainingTimeAsDuration();
++ if (remainingTime <= std::chrono::milliseconds::zero()) {
++ giveUpHostModeRequest();
++ return;
++ }
++
++ m_optHostModeRequest->awaitingOutcome = true;
++ m_optHostModeRequest->powerSwitchIssued = powerSwitchIssued;
++ m_hostModeRequestTimer.start(
++ std::chrono::ceil<std::chrono::milliseconds>(remainingTime));
++ }
++
++ void QOhosBluetoothLocalDevicePrivate::giveUpHostModeRequest()
++ {
++ Q_ASSERT(m_optHostModeRequest);
++ qCWarning(
++ QT_BT_OHOS, "%s: giving up on the requested host mode %d, the adapter stays in %d",
++ Q_FUNC_INFO, static_cast<int>(m_optHostModeRequest->target),
++ static_cast<int>(m_lastHostMode));
++
++ cancelHostModeRequest();
++
++ emitAsyncError(QBluetoothLocalDevice::Error::UnknownError);
++ }
++
++ void QOhosBluetoothLocalDevicePrivate::cancelHostModeRequest()
++ {
++ m_hostModeRequestTimer.stop();
++ m_optHostModeRequest.reset();
++ }
++
++ void QOhosBluetoothLocalDevicePrivate::handleAdapterHostModeChanged(
++ QBluetoothLocalDevice::HostMode hostMode,
++ std::optional<QtOhosBluetooth::QOhosBluetoothAccessProxy::BluetoothState> optBluetoothState,
++ HostModeUpdateSource source)
++ {
++ const auto hostModeChanged = m_lastHostMode != hostMode;
++ m_lastHostMode = hostMode;
++
++ const auto adapterSettled = optBluetoothState && isAdapterStateSettled(*optBluetoothState);
++ const auto adapterPoweredOn =
++ optBluetoothState == QtOhosBluetooth::QOhosBluetoothAccessProxy::BluetoothState::STATE_ON;
++
++ if (m_optHostModeRequest && adapterSettled) {
++ const auto scanModeApplied = source == HostModeUpdateSource::LiveRead
++ || m_optHostModeRequest->anyPoweredOnAccepted
++ || m_optHostModeRequest->target == QBluetoothLocalDevice::HostMode::HostPoweredOff
++ || m_optHostModeRequest->scanModeIssued;
++
++ if (scanModeApplied
++ && isRequestSatisfiedBy(*m_optHostModeRequest, hostMode, adapterPoweredOn)) {
++ cancelHostModeRequest();
++ } else if (!isPowerSwitchOutcomePending(*m_optHostModeRequest, adapterPoweredOn)) {
++ if (m_optHostModeRequest->powerSwitchIssued && adapterPoweredOn)
++ m_optHostModeRequest->deadline = QDeadlineTimer(hostModeRequestGiveUpDelay);
++
++ scheduleHostModeRetry();
++ }
++ }
++
++ if (hostModeChanged) {
++ QMetaObject::invokeMethod(
++ m_qBluetoothLocalDevice,
++ [qBluetoothLocalDevice = m_qBluetoothLocalDevice, hostMode]() {
++ Q_EMIT qBluetoothLocalDevice->hostModeStateChanged(hostMode);
++ },
++ Qt::QueuedConnection);
++ }
++ }
++
++ bool QOhosBluetoothLocalDevicePrivate::pairDevice(const QBluetoothAddress &address)
++ {
++ const auto addressStr = address.toString().toStdString();
++ if (m_pendingPairingRequests.contains(address)) {
++ qCDebug(
++ QT_BT_OHOS,
++ "%s: pairing request ignored due to already pending pairing process to the device with address %s",
++ Q_FUNC_INFO, addressStr.c_str());
++ return true;
++ }
++
++ m_pendingPairingRequests.insert(address, QDeadlineTimer(pairingRequestTimeout));
++ rearmPendingPairingRequestsTimer();
++
++ if (!m_remoteDeviceProxy->pairDevice(address.toString())) {
++ m_pendingPairingRequests.remove(address);
++ rearmPendingPairingRequestsTimer();
++ return false;
++ }
++
++ return true;
++ }
++
++ void QOhosBluetoothLocalDevicePrivate::cancelPendingPairingRequests()
++ {
++ if (m_pendingPairingRequests.isEmpty())
++ return;
++
++ m_pendingPairingRequests.clear();
++ rearmPendingPairingRequestsTimer();
++ emitAsyncError(QBluetoothLocalDevice::Error::PairingError);
++ }
++
++ void QOhosBluetoothLocalDevicePrivate::emitAsyncError(QBluetoothLocalDevice::Error error)
++ {
++ QMetaObject::invokeMethod(
++ m_qBluetoothLocalDevice,
++ [this, error]() {
++ Q_EMIT m_qBluetoothLocalDevice->errorOccurred(error);
++ },
++ Qt::QueuedConnection);
++ }
++
++ void QOhosBluetoothLocalDevicePrivate::rearmPendingPairingRequestsTimer()
++ {
++ if (m_pendingPairingRequests.isEmpty()) {
++ m_pendingPairingRequestsTimer.stop();
++ return;
++ }
++
++ const auto earliestDeadline = std::min_element(
++ m_pendingPairingRequests.cbegin(), m_pendingPairingRequests.cend());
++
++ m_pendingPairingRequestsTimer.start(
++ std::max(
++ std::chrono::ceil<std::chrono::milliseconds>(earliestDeadline->remainingTimeAsDuration()),
++ std::chrono::milliseconds::zero()));
++ }
++
++ void QOhosBluetoothLocalDevicePrivate::handleExpiredPendingPairingRequests()
++ {
++ QList<QBluetoothAddress> expiredAddresses;
++ for (auto pendingPairingRequest = m_pendingPairingRequests.cbegin();
++ pendingPairingRequest != m_pendingPairingRequests.cend(); ++pendingPairingRequest) {
++ if (pendingPairingRequest.value().hasExpired())
++ expiredAddresses.append(pendingPairingRequest.key());
++ }
++
++ for (const auto &expiredAddress : expiredAddresses)
++ m_pendingPairingRequests.remove(expiredAddress);
++
++ rearmPendingPairingRequestsTimer();
++
++ for (const auto &expiredAddress : expiredAddresses) {
++ const auto optPairState = tryGetPairState(expiredAddress);
++ if (optPairState
++ && *optPairState == QtOhosBluetooth::QOhosBluetoothRemoteDeviceProxy::BondState::BOND_STATE_BONDED) {
++ Q_EMIT m_qBluetoothLocalDevice->pairingFinished(
++ expiredAddress, QBluetoothLocalDevice::Paired);
++ continue;
++ }
++
++ qCWarning(
++ QT_BT_OHOS, "%s: pairing with the device %ls not confirmed by the system in time",
++ Q_FUNC_INFO, qUtf16Printable(expiredAddress.toString()));
++ emitAsyncError(QBluetoothLocalDevice::Error::PairingError);
++ }
++ }
++
++ }
++
++ QBluetoothLocalDevicePrivate::QBluetoothLocalDevicePrivate() = default;
++
++ QBluetoothLocalDevicePrivate::~QBluetoothLocalDevicePrivate() = default;
++
++ QBluetoothLocalDevice::QBluetoothLocalDevice(QObject *parent)
++ : QBluetoothLocalDevice(QBluetoothAddress(), parent)
++ {
++ }
++
++ QBluetoothLocalDevice::QBluetoothLocalDevice(const QBluetoothAddress &address, QObject *parent)
++ : QObject(parent)
++ , d_ptr(new QOhosBluetoothLocalDevicePrivate(this, address))
++ {
++ registerQBluetoothLocalDeviceMetaType();
++ }
++
++ QString QBluetoothLocalDevice::name() const
++ {
++ if (!d_ptr->isValid()) {
++ qCWarning(
++ QT_BT_OHOS, "%s: local device is not valid. Can't get local device name.", Q_FUNC_INFO);
++ return QString();
++ }
++
++ const auto optLocalDeviceName = QtOhosBluetooth::QOhosBluetoothLocalDeviceProxy::instance()->tryGetLocalDeviceName();
++
++ return optLocalDeviceName
++ ? *optLocalDeviceName
++ : QString();
++ }
++
++ QBluetoothAddress QBluetoothLocalDevice::address() const
++ {
++ return {};
++ }
++
++ void QBluetoothLocalDevice::powerOn()
++ {
++ if (!d_ptr->isValid()) {
++ qCWarning(QT_BT_OHOS, "%s: local device is not valid. Ignoring...", Q_FUNC_INFO);
++ return;
++ }
++
++ if (hostMode() != HostMode::HostPoweredOff) {
++ qCWarning(QT_BT_OHOS, "%s: bluetooth is not powered off. Ignoring...", Q_FUNC_INFO);
++ return;
++ }
++
++ static_cast<QOhosBluetoothLocalDevicePrivate *>(d_ptr)->requestPowerOn();
++ }
++
++ void QBluetoothLocalDevice::setHostMode(QBluetoothLocalDevice::HostMode mode)
++ {
++ if (!d_ptr->isValid()) {
++ qCWarning(QT_BT_OHOS, "%s: local device is not valid. Ignoring...", Q_FUNC_INFO);
++ return;
++ }
++
++ auto *ohosBluetoothLocalDevicePriv = static_cast<QOhosBluetoothLocalDevicePrivate *>(d_ptr);
++
++ const auto optBluetoothState = ohosBluetoothLocalDevicePriv->tryGetBluetoothState();
++ const auto adapterSettled = optBluetoothState && isAdapterStateSettled(*optBluetoothState);
++
++ if (adapterSettled && mode == QBluetoothLocalDevice::HostMode::HostPoweredOff
++ && *optBluetoothState != QtOhosBluetooth::QOhosBluetoothAccessProxy::BluetoothState::STATE_ON) {
++ qCWarning(
++ QT_BT_OHOS,
++ "%s: Bluetooth is not in powered on state. Cancelling the pending host mode request.",
++ Q_FUNC_INFO);
++ ohosBluetoothLocalDevicePriv->cancelHostModeRequest();
++ return;
++ }
++
++ ohosBluetoothLocalDevicePriv->requestHostMode(mode);
++ }
++
++ QBluetoothLocalDevice::HostMode QBluetoothLocalDevice::hostMode() const
++ {
++ if (!d_ptr->isValid()) {
++ qCWarning(
++ QT_BT_OHOS, "%s: local device is not valid. Reporting powered off host mode.",
++ Q_FUNC_INFO);
++ return QBluetoothLocalDevice::HostPoweredOff;
++ }
++
++ const auto optBluetoothState = static_cast<QOhosBluetoothLocalDevicePrivate *>(d_ptr)->tryGetBluetoothState();
++ if (!optBluetoothState)
++ return QBluetoothLocalDevice::HostPoweredOff;
++
++ return getBluetoothLocalDeviceHostMode(*optBluetoothState);
++ }
++
++ QList<QBluetoothAddress> QBluetoothLocalDevice::connectedDevices() const
++ {
++ return {};
++ }
++
++ QList<QBluetoothHostInfo> QBluetoothLocalDevice::allDevices()
++ {
++ const auto optLocalDeviceName =
++ QtOhosBluetooth::QOhosBluetoothLocalDeviceProxy::instance()->tryGetLocalDeviceName();
++ if (!optLocalDeviceName)
++ return {};
++
++ QBluetoothHostInfo bluetoothHostInfo;
++ bluetoothHostInfo.setName(*optLocalDeviceName);
++ return { bluetoothHostInfo };
++ }
++
++ void QBluetoothLocalDevice::requestPairing(const QBluetoothAddress &address, Pairing pairing)
++ {
++ auto emitPairingError = [this]() {
++ QMetaObject::invokeMethod(
++ this,
++ [this]() {
++ Q_EMIT errorOccurred(QBluetoothLocalDevice::PairingError);
++ },
++ Qt::QueuedConnection);
++ };
++
++ auto emitPairingFinishedAsync = [this, address](Pairing finishedPairing) {
++ QMetaObject::invokeMethod(
++ this,
++ [this, address, finishedPairing]() {
++ Q_EMIT pairingFinished(address, finishedPairing);
++ },
++ Qt::QueuedConnection);
++ };
++
++ if (!isValid() || address.isNull()) {
++ qCWarning(
++ QT_BT_OHOS, "%s: local device is not valid or the device address is null. Ignoring...",
++ Q_FUNC_INFO);
++ emitPairingError();
++ return;
++ }
++
++ const auto optBluetoothState = static_cast<QOhosBluetoothLocalDevicePrivate *>(d_ptr)->tryGetBluetoothState();
++ if (optBluetoothState != QtOhosBluetooth::QOhosBluetoothAccessProxy::BluetoothState::STATE_ON) {
++ qCWarning(QT_BT_OHOS, "%s: bluetooth not in powered on state. Ignoring...", Q_FUNC_INFO);
++ emitPairingError();
++ return;
++ }
++
++ switch (pairing) {
++ case AuthorizedPaired:
++ qCWarning(
++ QT_BT_OHOS,
++ "%s: AuthorizedPaired not supported on HarmonyOS. Falling back to Paired.",
++ Q_FUNC_INFO);
++ Q_FALLTHROUGH();
++ case Paired:
++ if (pairingStatus(address) == QBluetoothLocalDevice::Pairing::Paired) {
++ qCDebug(
++ QT_BT_OHOS, "%s: Already paired with the device %ls. Ignoring pairing request.",
++ Q_FUNC_INFO, qUtf16Printable(address.toString()));
++ emitPairingFinishedAsync(QBluetoothLocalDevice::Pairing::Paired);
++ break;
++ }
++
++ if (!static_cast<QOhosBluetoothLocalDevicePrivate *>(d_ptr)->pairDevice(address))
++ emitPairingError();
++ break;
++ case Unpaired:
++ if (pairingStatus(address) == QBluetoothLocalDevice::Pairing::Unpaired) {
++ qCDebug(
++ QT_BT_OHOS, "%s: Not paired with the device %ls. Ignoring unpairing request.",
++ Q_FUNC_INFO, qUtf16Printable(address.toString()));
++ emitPairingFinishedAsync(QBluetoothLocalDevice::Pairing::Unpaired);
++ break;
++ }
++
++ qCWarning(
++ QT_BT_OHOS, "%s: Unpairing not supported on HarmonyOS for paired devices.",
++ Q_FUNC_INFO);
++ emitPairingError();
++ break;
++ }
++ }
++
++ QBluetoothLocalDevice::Pairing QBluetoothLocalDevice::pairingStatus(const QBluetoothAddress &address) const
++ {
++ if (!d_ptr->isValid()) {
++ qCWarning(
++ QT_BT_OHOS, "%s: local device is not valid. Reporting unpaired device.", Q_FUNC_INFO);
++ return Unpaired;
++ }
++
++ if (address.isNull()) {
++ qCWarning(
++ QT_BT_OHOS, "%s: device address is null. Reporting unpaired device.", Q_FUNC_INFO);
++ return Unpaired;
++ }
++
++ const auto optBluetoothState = static_cast<QOhosBluetoothLocalDevicePrivate *>(d_ptr)->tryGetBluetoothState();
++ if (optBluetoothState != QtOhosBluetooth::QOhosBluetoothAccessProxy::BluetoothState::STATE_ON)
++ return Unpaired;
++
++ const auto optPairState =
++ static_cast<QOhosBluetoothLocalDevicePrivate *>(d_ptr)->tryGetPairState(address);
++ if (!optPairState)
++ return QBluetoothLocalDevice::Pairing::Unpaired;
++
++ return mapOhosBondStateToLocalDevicePairing(*optPairState);
++ }
++
++ QT_END_NAMESPACE