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 "qohosbluetoothremotedevice_p.h"
++
++ #include <QtBluetooth/private/qohosbluetoothcommon_p.h>
++
++ #include <QtCore/qloggingcategory.h>
++ #include <QtCore/private/qcore_ohos_p.h>
++ #include <QtCore/private/qnapi_p.h>
++ #include <QtCore/private/qohosjstools_p.h>
++ #include <QtCore/private/qohoslogger_p.h>
++
++ #include <optional>
++ #include <string>
++ #include <vector>
++
++ QT_BEGIN_NAMESPACE
++
++ Q_DECLARE_LOGGING_CATEGORY(QT_BT_OHOS)
++
++ namespace QtOhosBluetooth {
++
++ namespace {
++
++ std::shared_ptr<void> registerBondStateChangeConsumer(
++ QOhosJsState &jsState,
++ QOhosConsumer<std::string, std::optional<QOhosBluetoothRemoteDeviceProxy::BondState>, std::optional<QOhosBluetoothRemoteDeviceProxy::UnbondCause>> bondStateChangeConsumer)
++ {
++ return registerQOhosOnOffMethodsBasedEventHandler(
++ jsState.eval<QNapi::Object>("@ohos.bluetooth.connection"), "bondStateChange",
++ [bondStateChangeConsumer = std::move(bondStateChangeConsumer)](const QOhosCallbackInfo &cbInfo) {
++ auto bondStateObject = cbInfo.getFirstArg<QNapi::Object>(Q_FUNC_INFO);
++
++ auto deviceId = bondStateObject.get<QNapi::String>("deviceId").Utf8Value();
++ const auto optBondState = cbInfo.jsState().tryMapOhosEnumFromJs<QOhosBluetoothRemoteDeviceProxy::BondState>(
++ bondStateObject.get<QNapi::Number>("state"));
++ const auto unbondCauseValue =
++ QNapi::getOptionalPropOrEmpty<QNapi::Number>(bondStateObject, "cause");
++ const auto optUnbondCause = unbondCauseValue.IsEmpty()
++ ? std::optional<QOhosBluetoothRemoteDeviceProxy::UnbondCause>()
++ : cbInfo.jsState().tryMapOhosEnumFromJs<QOhosBluetoothRemoteDeviceProxy::UnbondCause>(
++ unbondCauseValue);
++
++ bondStateChangeConsumer(deviceId, optBondState, optUnbondCause);
++ },
++ {
++ .optOnCallExceptionHandler = [](const Napi::Error &error) {
++ if (const auto optErrorCode = tryGetKnownErrorCode(error.Value(), Q_FUNC_INFO)) {
++ qOhosPrintfWarning(
++ "%s: cannot register the bond state change consumer, error code: %u",
++ Q_FUNC_INFO, qToUnderlying(*optErrorCode));
++ }
++ },
++ });
++ }
++
++ std::optional<QOhosBluetoothRemoteDeviceProxy::BondState> readPairState(
++ QOhosJsState &jsState, const std::string &deviceId)
++ {
++ if (!isBluetoothEnabled(jsState))
++ return std::nullopt;
++
++ try {
++ return jsState.tryMapOhosEnumFromJs<QOhosBluetoothRemoteDeviceProxy::BondState>(
++ jsState.eval<QNapi::Number>(
++ "@ohos.bluetooth.connection.getPairState(*)", {deviceId}));
++ } catch (const Napi::Error &error) {
++ if (const auto optErrorCode = tryGetKnownErrorCode(error.Value(), Q_FUNC_INFO)) {
++ qOhosPrintfWarning(
++ "%s: cannot read the pair state of the device %s, error code: %u", Q_FUNC_INFO,
++ deviceId.c_str(), qToUnderlying(*optErrorCode));
++ }
++ return std::nullopt;
++ }
++ }
++
++ }
++
++ std::optional<std::string> readRemoteDeviceName(QOhosJsState &jsState, const std::string &deviceId)
++ {
++ try {
++ return jsState.eval<QNapi::String>(
++ "@ohos.bluetooth.connection.getRemoteDeviceName(*)", {deviceId}).Utf8Value();
++ } catch (const Napi::Error &error) {
++ if (const auto optErrorCode = tryGetKnownErrorCode(error.Value(), Q_FUNC_INFO)) {
++ qOhosPrintfWarning(
++ "%s: cannot read the name of the device %s, error code: %u", Q_FUNC_INFO,
++ deviceId.c_str(), qToUnderlying(*optErrorCode));
++ }
++ return std::nullopt;
++ }
++ }
++
++ std::optional<quint32> readRemoteDeviceClass(QOhosJsState &jsState, const std::string &deviceId)
++ {
++ try {
++ return jsState.eval<QNapi::Object>(
++ "@ohos.bluetooth.connection.getRemoteDeviceClass(*)", {deviceId})
++ .get<QNapi::Number>("classOfDevice").Uint32Value();
++ } catch (const Napi::Error &error) {
++ if (const auto optErrorCode = tryGetKnownErrorCode(error.Value(), Q_FUNC_INFO)) {
++ qOhosPrintfWarning(
++ "%s: cannot read the class of the device %s, error code: %u", Q_FUNC_INFO,
++ deviceId.c_str(), qToUnderlying(*optErrorCode));
++ }
++ return std::nullopt;
++ }
++ }
++
++ std::optional<QOhosBluetoothRemoteDeviceProxy::BluetoothTransport> readRemoteDeviceTransport(
++ QOhosJsState &jsState, const std::string &deviceId)
++ {
++ try {
++ return jsState.tryMapOhosEnumFromJs<QOhosBluetoothRemoteDeviceProxy::BluetoothTransport>(
++ jsState.eval<QNapi::Number>(
++ "@ohos.bluetooth.connection.getRemoteDeviceTransport(*)", {deviceId}));
++ } catch (const Napi::Error &error) {
++ if (const auto optErrorCode = tryGetKnownErrorCode(error.Value(), Q_FUNC_INFO)) {
++ qOhosPrintfWarning(
++ "%s: cannot read the transport of the device %s, error code: %u", Q_FUNC_INFO,
++ deviceId.c_str(), qToUnderlying(*optErrorCode));
++ }
++ return std::nullopt;
++ }
++ }
++
++ bool isRemoteDeviceLowEnergyOnly(QOhosJsState &jsState, const std::string &deviceId)
++ {
++ return readRemoteDeviceTransport(jsState, deviceId)
++ == QOhosBluetoothRemoteDeviceProxy::BluetoothTransport::TRANSPORT_LE;
++ }
++
++ QOhosBluetoothRemoteDeviceProxy::QOhosBluetoothRemoteDeviceProxy()
++ {
++ ensureBondStateChangeConsumerRegistered();
++ }
++
++ bool QOhosBluetoothRemoteDeviceProxy::ensureBondStateChangeConsumerRegistered()
++ {
++ if (m_bondStateChangeConsumerHandle)
++ return true;
++
++ if (!checkAccessBluetoothPermissionGranted(Q_FUNC_INFO))
++ return false;
++
++ auto selfRef = QtOhos::makeQThreadSafeRef(this);
++ m_bondStateChangeConsumerHandle = QOhosJsThreadGateway::eval(
++ [&](QOhosJsState &jsState) -> std::shared_ptr<void> {
++ auto bondStateChangeConsumerHandle = registerBondStateChangeConsumer(
++ jsState,
++ [selfRef](std::string deviceId, std::optional<BondState> optBondState, std::optional<UnbondCause> optUnbondCause) {
++ if (!optBondState)
++ return;
++
++ selfRef.visitInQtThreadIfAlive(
++ [deviceId = QString::fromStdString(deviceId), bondState = *optBondState, optUnbondCause](auto &self) {
++ Q_EMIT self.bondStateChanged(deviceId, bondState, optUnbondCause);
++ });
++ });
++
++ if (!bondStateChangeConsumerHandle)
++ return {};
++
++ return QtOhos::makeProxyWithJsThreadDeleter(std::move(bondStateChangeConsumerHandle));
++ },
++ Q_FUNC_INFO);
++
++ return m_bondStateChangeConsumerHandle != nullptr;
++ }
++
++ std::shared_ptr<QOhosBluetoothRemoteDeviceProxy> QOhosBluetoothRemoteDeviceProxy::instance()
++ {
++ static std::weak_ptr<QOhosBluetoothRemoteDeviceProxy> weakInstance;
++
++ auto instance = weakInstance.lock();
++ if (!instance) {
++ instance = std::shared_ptr<QOhosBluetoothRemoteDeviceProxy>(
++ new QOhosBluetoothRemoteDeviceProxy());
++ weakInstance = instance;
++ }
++
++ return instance;
++ }
++
++ std::optional<QString> QOhosBluetoothRemoteDeviceProxy::tryGetRemoteDeviceName(const QString &deviceId)
++ {
++ if (!checkAccessBluetoothPermissionGranted(Q_FUNC_INFO))
++ return std::nullopt;
++
++ return QOhosJsThreadGateway::eval(
++ [&](QOhosJsState &jsState) -> std::optional<QString> {
++ if (!isBluetoothEnabled(jsState))
++ return std::nullopt;
++
++ const auto optRemoteDeviceName = readRemoteDeviceName(jsState, deviceId.toStdString());
++
++ return optRemoteDeviceName
++ ? std::optional(QString::fromStdString(*optRemoteDeviceName))
++ : std::nullopt;
++ },
++ Q_FUNC_INFO);
++ }
++
++ std::optional<QOhosBluetoothRemoteDeviceProxy::BondState> QOhosBluetoothRemoteDeviceProxy::tryGetPairState(
++ const QString &deviceId)
++ {
++ if (!checkAccessBluetoothPermissionGranted(Q_FUNC_INFO))
++ return std::nullopt;
++
++ return QOhosJsThreadGateway::eval(
++ [&](QOhosJsState &jsState) {
++ return readPairState(jsState, deviceId.toStdString());
++ },
++ Q_FUNC_INFO);
++ }
++
++ std::optional<QOhosBluetoothErrorCode> QOhosBluetoothRemoteDeviceProxy::requestRemoteDeviceServices(
++ const QString &deviceId)
++ {
++ if (!checkAccessBluetoothPermissionGranted(Q_FUNC_INFO))
++ return QOhosBluetoothErrorCode::PermissionDenied;
++
++ auto selfRef = QtOhos::makeQThreadSafeRef(this);
++ return QOhosJsThreadGateway::eval(
++ [&](QOhosJsState &jsState) -> std::optional<QOhosBluetoothErrorCode> {
++ if (!isBluetoothEnabled(jsState))
++ return QOhosBluetoothErrorCode::BluetoothDisabled;
++
++ RemoteDeviceServices remoteDeviceServices;
++ remoteDeviceServices.deviceId = deviceId.toStdString();
++ remoteDeviceServices.optDeviceName =
++ readRemoteDeviceName(jsState, remoteDeviceServices.deviceId);
++ remoteDeviceServices.optClassOfDevice =
++ readRemoteDeviceClass(jsState, remoteDeviceServices.deviceId);
++ remoteDeviceServices.lowEnergyOnly =
++ isRemoteDeviceLowEnergyOnly(jsState, remoteDeviceServices.deviceId);
++
++ auto pendingServices = QtOhos::moveToSharedPtr(std::move(remoteDeviceServices));
++ auto reportRemoteDeviceServices =
++ [selfRef, pendingServices]() {
++ selfRef.visitInQtThreadIfAlive(
++ [pendingServices](auto &self) {
++ Q_EMIT self.remoteDeviceServicesRead(*pendingServices);
++ });
++ };
++
++ try {
++ jsState.evalToPromiseOrRejectOnThrow(
++ "@ohos.bluetooth.connection.getRemoteProfileUuids(*)",
++ {pendingServices->deviceId})
++ .onThen(
++ [reportRemoteDeviceServices, pendingServices](
++ const QOhosCallbackInfo &cbInfo) {
++ auto resultArray = cbInfo.getFirstArg<QNapi::Array>(Q_FUNC_INFO);
++ pendingServices->optProfileUuids =
++ QNapi::getArrayElements<std::vector<std::string>, QNapi::String>(
++ resultArray,
++ [](QNapi::String &&profileUuid) {
++ return profileUuid.Utf8Value();
++ });
++ reportRemoteDeviceServices();
++ },
++ [reportRemoteDeviceServices](const QOhosCallbackInfo &cbInfo) {
++ QtOhos::logJsCallbackError(cbInfo, "Got error from getRemoteProfileUuids()");
++ reportRemoteDeviceServices();
++ });
++ return std::nullopt;
++ } catch (const Napi::Error &error) {
++ const auto errorCode =
++ tryGetKnownErrorCode(error.Value(), Q_FUNC_INFO)
++ .value_or(QOhosBluetoothErrorCode::OperationFailed);
++ qOhosPrintfWarning(
++ "%s: cannot read the remote profile uuids, error code: %u", Q_FUNC_INFO,
++ qToUnderlying(errorCode));
++ return errorCode;
++ }
++ },
++ Q_FUNC_INFO);
++ }
++
++ bool QOhosBluetoothRemoteDeviceProxy::pairDevice(const QString &deviceId)
++ {
++ if (!checkAccessBluetoothPermissionGranted(Q_FUNC_INFO)) {
++ Q_EMIT missingPermission();
++ return false;
++ }
++
++ if (!ensureBondStateChangeConsumerRegistered()) {
++ qCCritical(
++ QT_BT_OHOS,
++ "%s: bond state change consumer not registered. Pairing result will not be reported.",
++ Q_FUNC_INFO);
++ }
++
++ auto selfRef = QtOhos::makeQThreadSafeRef(this);
++ const auto optErrorCode = QOhosJsThreadGateway::eval(
++ [&](QOhosJsState &jsState) -> std::optional<QOhosBluetoothErrorCode> {
++ try {
++ jsState.evalToPromiseOrRejectOnThrow(
++ "@ohos.bluetooth.connection.pairDevice(*)", {deviceId.toStdString()})
++ .onCatch(
++ [selfRef, pairedDeviceId = deviceId.toStdString()](
++ const QOhosCallbackInfo &cbInfo) {
++ QtOhos::logJsCallbackError(cbInfo, "Got error from pairDevice()");
++ selfRef.visitInQtThreadIfAlive(
++ [pairedDeviceId](auto &self) {
++ Q_EMIT self.pairDeviceFailed(QString::fromStdString(pairedDeviceId));
++ });
++ });
++ return std::nullopt;
++ } catch (const Napi::Error &error) {
++ return tryGetKnownErrorCode(error.Value(), Q_FUNC_INFO);
++ }
++ },
++ Q_FUNC_INFO);
++
++ if (!optErrorCode)
++ return true;
++
++ qCWarning(
++ QT_BT_OHOS, "%s: cannot start pairing with the device %ls, error code: %u", Q_FUNC_INFO,
++ qUtf16Printable(deviceId), qToUnderlying(*optErrorCode));
++
++ return false;
++ }
++
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qohosbluetoothremotedevice_p.cpp"