Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qohosbluetoothdevicediscovery.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
6#include <QtBluetooth/private/qohosbluetoothcommon_p.h>
7#include <QtBluetooth/private/qohosbluetoothremotedevice_p.h>
8
9#include <QtCore/qloggingcategory.h>
10#include <QtCore/private/qnapi_p.h>
11#include <QtCore/private/qohosjstools_p.h>
12#include <QtCore/private/qohoslogger_p.h>
13
14#include <chrono>
15#include <optional>
16
17using namespace std::chrono_literals;
18
20
22
23namespace QtOhosBluetooth {
24
25namespace {
26
27constexpr qint16 unknownRssi = 0;
28
29std::optional<bool> tryReadBluetoothDiscoveringState(QOhosJsState &jsState)
30{
31 if (!isBluetoothEnabled(jsState))
32 return false;
33
34 try {
35 return jsState.eval<QNapi::Boolean>("@ohos.bluetooth.connection.isBluetoothDiscovering()").Value();
36 } catch (const Napi::Error &error) {
37 if (const auto optErrorCode = tryGetKnownErrorCode(error.Value(), Q_FUNC_INFO)) {
38 qOhosPrintfWarning(
39 "%s: cannot read the bluetooth discovering state, error code: %u", Q_FUNC_INFO,
40 qToUnderlying(*optErrorCode));
41 }
42 return std::nullopt;
43 }
44}
45
46bool requestBluetoothDiscoveryStop(QOhosJsState &jsState)
47{
48 const auto optDiscovering = tryReadBluetoothDiscoveringState(jsState);
49 if (optDiscovering && !*optDiscovering)
50 return true;
51
52 try {
53 jsState.eval("@ohos.bluetooth.connection.stopBluetoothDiscovery()");
54 } catch (const Napi::Error &error) {
55 if (const auto optErrorCode = tryGetKnownErrorCode(error.Value(), Q_FUNC_INFO)) {
56 qOhosPrintfWarning(
57 "%s: cannot stop the bluetooth discovery, error code: %u", Q_FUNC_INFO, qToUnderlying(*optErrorCode));
58 }
59 return false;
60 }
61
62 return true;
63}
64
65std::vector<std::string> readPairedDeviceIds(QOhosJsState &jsState)
66{
67 if (!isBluetoothEnabled(jsState))
68 return {};
69
70 try {
71 return QNapi::getArrayElements<std::vector<std::string>, QNapi::String>(
72 jsState.eval<QNapi::Array>("@ohos.bluetooth.connection.getPairedDevices()"),
73 [](QNapi::String &&pairedDeviceId) {
74 return pairedDeviceId.Utf8Value();
75 });
76 } catch (const Napi::Error &error) {
77 if (const auto optErrorCode = tryGetKnownErrorCode(error.Value(), Q_FUNC_INFO)) {
78 qOhosPrintfWarning(
79 "%s: cannot read the paired devices, error code: %u", Q_FUNC_INFO, qToUnderlying(*optErrorCode));
80 }
81 return {};
82 }
83}
84
85std::vector<DiscoveryResult> readPairedDevices(QOhosJsState &jsState)
86{
87 auto pairedDeviceIds = readPairedDeviceIds(jsState);
88
89 std::vector<DiscoveryResult> pairedDevices;
90 pairedDevices.reserve(pairedDeviceIds.size());
91 for (auto &pairedDeviceId : pairedDeviceIds) {
92 if (isRemoteDeviceLowEnergyOnly(jsState, pairedDeviceId))
93 continue;
94
95 auto pairedDeviceName = readRemoteDeviceName(jsState, pairedDeviceId);
96 const auto optPairedDeviceClass = readRemoteDeviceClass(jsState, pairedDeviceId);
97
98 pairedDevices.push_back(
99 DiscoveryResult {
100 .deviceId = std::move(pairedDeviceId),
101 .deviceName = pairedDeviceName
102 ? *pairedDeviceName
103 : std::string(),
104 .classOfDevice = optPairedDeviceClass.value_or(0),
105 .rssi = unknownRssi,
106 });
107 }
108
109 return pairedDevices;
110}
111
112}
113
114DiscoveryResult DiscoveryResult::makeFromOhosDiscoveryResultObject(
115 QOhosJsState &jsState, QNapi::Object discoveryResultObject)
116{
117 auto deviceId = discoveryResultObject.get<QNapi::String>("deviceId").Utf8Value();
118
119 auto deviceName = discoveryResultObject.get<QNapi::String>("deviceName").Utf8Value();
120 if (deviceName.empty()) {
121 if (auto optRemoteDeviceName = readRemoteDeviceName(jsState, deviceId))
122 deviceName = std::move(*optRemoteDeviceName);
123 }
124
125 return DiscoveryResult {
126 .deviceId = std::move(deviceId),
127 .deviceName = std::move(deviceName),
128 .classOfDevice = discoveryResultObject.get<QNapi::Number>("deviceClass.classOfDevice").Uint32Value(),
129 .rssi = static_cast<qint16>(discoveryResultObject.get<QNapi::Number>("rssi").Int32Value()),
130 };
131}
132
135{
136 QObject::connect(
137 m_accessProxy.get(), &QOhosBluetoothAccessProxy::stateChanged, this,
138 [this](QOhosBluetoothAccessProxy::BluetoothState bluetoothState) {
139 if (bluetoothState == QOhosBluetoothAccessProxy::BluetoothState::STATE_ON)
140 return;
141
142 if (!m_discoveryStoppedPollTimer.isActive())
143 return;
144
145 resetDiscoveryTracking();
146 Q_EMIT bluetoothPoweredOff();
147 });
148
149 constexpr auto discoveryStoppedPollInterval = 500ms;
150 m_discoveryStoppedPollTimer.setInterval(discoveryStoppedPollInterval);
151 QObject::connect(
152 &m_discoveryStoppedPollTimer, &QTimer::timeout, this,
153 [this]() {
154 if (!checkAccessBluetoothPermissionGranted(Q_FUNC_INFO)) {
155 resetDiscoveryTracking();
156 m_discoveredDevicesConsumerHandle.reset();
157 Q_EMIT missingPermission();
158 return;
159 }
160
161 if (tryCheckBluetoothDiscovering().value_or(true))
162 return;
163
164 resetDiscoveryTracking();
165 Q_EMIT discoveryStopped();
166 });
167
168 constexpr auto discoveryStoppedTimeout = 30s;
169 m_discoveryStoppedTimeoutTimer.setSingleShot(true);
170 m_discoveryStoppedTimeoutTimer.setInterval(discoveryStoppedTimeout);
171 QObject::connect(
172 &m_discoveryStoppedTimeoutTimer, &QTimer::timeout, this,
173 [this]() {
174 resetDiscoveryTracking();
175 Q_EMIT discoveryStopFailed();
176 });
177}
178
180{
181 static std::weak_ptr<QOhosBluetoothDeviceDiscoveryAgentProxy> weakInstance;
182
183 auto instance = weakInstance.lock();
184 if (!instance) {
185 instance = std::shared_ptr<QOhosBluetoothDeviceDiscoveryAgentProxy>(
187 weakInstance = instance;
188 }
189
190 return instance;
191}
192
193bool QOhosBluetoothDeviceDiscoveryAgentProxy::ensureDiscoveredDevicesConsumerRegistered()
194{
195 if (m_discoveredDevicesConsumerHandle)
196 return true;
197
198 if (!checkAccessBluetoothPermissionGranted(Q_FUNC_INFO))
199 return false;
200
201 auto selfRef = QtOhos::makeQThreadSafeRef(this);
202 m_discoveredDevicesConsumerHandle = QOhosJsThreadGateway::eval(
203 [&](QOhosJsState &jsState) -> std::shared_ptr<void> {
204 auto discoveredDevicesConsumerHandle = registerQOhosOnOffMethodsBasedEventHandler(
205 jsState.eval<QNapi::Object>("@ohos.bluetooth.connection"), "discoveryResult",
206 [selfRef](const QOhosCallbackInfo &cbInfo) {
207 auto resultArray = cbInfo.getFirstArg<QNapi::Array>(Q_FUNC_INFO);
208 auto discoveredDevices =
209 QNapi::getArrayElements<std::vector<DiscoveryResult>, QNapi::Object>(
210 resultArray,
211 [&](QNapi::Object &&discoveryResultObject) {
212 return DiscoveryResult::makeFromOhosDiscoveryResultObject(
213 cbInfo.jsState(), std::move(discoveryResultObject));
214 });
215
216 selfRef.visitInQtThreadIfAlive(
217 [discoveredDevices = std::move(discoveredDevices)](auto &self) {
218 Q_EMIT self.bluetoothDevicesFound(discoveredDevices);
219 });
220 },
221 {
222 .optOnCallExceptionHandler = [](const Napi::Error &error) {
223 if (const auto optErrorCode = tryGetKnownErrorCode(error.Value(), Q_FUNC_INFO)) {
224 qOhosPrintfWarning(
225 "%s: cannot register the discovery result consumer, error code: %u",
226 Q_FUNC_INFO, qToUnderlying(*optErrorCode));
227 }
228 },
229 });
230
231 if (!discoveredDevicesConsumerHandle)
232 return {};
233
234 return QtOhos::makeProxyWithJsThreadDeleter(
235 std::move(discoveredDevicesConsumerHandle));
236 },
237 Q_FUNC_INFO);
238
239 return m_discoveredDevicesConsumerHandle != nullptr;
240}
241
242std::optional<bool> QOhosBluetoothDeviceDiscoveryAgentProxy::tryCheckBluetoothDiscovering()
243{
244 if (!checkAccessBluetoothPermissionGranted(Q_FUNC_INFO))
245 return std::nullopt;
246
247 return QOhosJsThreadGateway::eval(tryReadBluetoothDiscoveringState, Q_FUNC_INFO);
248}
249
250void QOhosBluetoothDeviceDiscoveryAgentProxy::resetDiscoveryTracking()
251{
252 m_discoveryStoppedPollTimer.stop();
253 m_discoveryStoppedTimeoutTimer.stop();
254 m_discoveryStartedByProxy = false;
255}
256
258{
259 return !m_discoveryStartedByProxy && tryCheckBluetoothDiscovering().value_or(false);
260}
261
263{
264 if (!checkAccessBluetoothPermissionGranted(Q_FUNC_INFO)) {
265 Q_EMIT missingPermission();
266 return;
267 }
268
269 if (!ensureDiscoveredDevicesConsumerRegistered()) {
270 qCCritical(
271 QT_BT_OHOS,
272 "%s: discovery result consumer not registered. Found devices would not be reported.",
273 Q_FUNC_INFO);
274 Q_EMIT discoveryStartFailed();
275 return;
276 }
277
278 const auto discoveryOngoing = QOhosJsThreadGateway::eval(
279 [](QOhosJsState &jsState) {
280 const auto optDiscovering = tryReadBluetoothDiscoveringState(jsState);
281 if (optDiscovering && *optDiscovering)
282 return true;
283
284 try {
285 jsState.eval("@ohos.bluetooth.connection.startBluetoothDiscovery()");
286 } catch (const Napi::Error &error) {
287 if (const auto optErrorCode = tryGetKnownErrorCode(error.Value(), Q_FUNC_INFO)) {
288 qOhosPrintfWarning(
289 "%s: cannot start the bluetooth discovery, error code: %u", Q_FUNC_INFO,
290 qToUnderlying(*optErrorCode));
291 }
292 return false;
293 }
294
295 return true;
296 },
297 Q_FUNC_INFO);
298
299 if (!discoveryOngoing) {
300 Q_EMIT discoveryStartFailed();
301 return;
302 }
303
304 m_discoveryStartedByProxy = true;
305 m_discoveryStoppedTimeoutTimer.stop();
306 m_discoveryStoppedPollTimer.start();
307}
308
310{
311 if (!checkAccessBluetoothPermissionGranted(Q_FUNC_INFO)) {
312 Q_EMIT missingPermission();
313 return;
314 }
315
316 if (!QOhosJsThreadGateway::eval(requestBluetoothDiscoveryStop, Q_FUNC_INFO)) {
317 Q_EMIT discoveryStopFailed();
318 return;
319 }
320
321 m_discoveryStoppedPollTimer.start();
322 m_discoveryStoppedTimeoutTimer.start();
323}
324
326{
327 if (!checkAccessBluetoothPermissionGranted(Q_FUNC_INFO))
328 return {};
329
330 return QOhosJsThreadGateway::eval(readPairedDevices, Q_FUNC_INFO);
331}
332
333}
334
335QT_END_NAMESPACE
336
337#include "moc_qohosbluetoothdevicediscovery_p.cpp"
QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(lcQIORing)