Revision control
Copy as Markdown
// 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"