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
qbluetoothlocaldevice_android.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 Lauri Laanmets (Proekspert AS) <lauri.laanmets@eesti.ee>
2// Copyright (C) 2016 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
6#include "android/localdevicebroadcastreceiver_p.h"
7#include "android/androidutils_p.h"
8#include "android/jni_android_p.h"
9#include <QCoreApplication>
10#include <QtCore/QLoggingCategory>
11#include <QtCore/QJniEnvironment>
12#include <QtCore/QJniObject>
13#include <QtBluetooth/QBluetoothLocalDevice>
14#include <QtBluetooth/QBluetoothAddress>
15
17
18Q_DECLARE_LOGGING_CATEGORY(QT_BT_ANDROID)
19
20QBluetoothLocalDevicePrivate::QBluetoothLocalDevicePrivate(
21 QBluetoothLocalDevice *q, const QBluetoothAddress &address) :
22 q_ptr(q)
23{
24 registerQBluetoothLocalDeviceMetaType();
25
26 initialize(address);
27
28 receiver = new LocalDeviceBroadcastReceiver(q_ptr);
29 connect(receiver, &LocalDeviceBroadcastReceiver::hostModeStateChanged,
30 this, &QBluetoothLocalDevicePrivate::processHostModeChange);
31 connect(receiver, &LocalDeviceBroadcastReceiver::pairingStateChanged,
32 this, &QBluetoothLocalDevicePrivate::processPairingStateChanged);
33 connect(receiver, &LocalDeviceBroadcastReceiver::connectDeviceChanges,
34 this, &QBluetoothLocalDevicePrivate::processConnectDeviceChanges);
35}
36
37QBluetoothLocalDevicePrivate::~QBluetoothLocalDevicePrivate()
38{
39 receiver->unregisterReceiver();
40 delete receiver;
41 delete obj;
42}
43
44QJniObject *QBluetoothLocalDevicePrivate::adapter()
45{
46 return obj;
47}
48
49void QBluetoothLocalDevicePrivate::initialize(const QBluetoothAddress &address)
50{
51 QJniObject adapter = getDefaultBluetoothAdapter();
52
53 if (!adapter.isValid()) {
54 qCWarning(QT_BT_ANDROID) << "Device does not support Bluetooth";
55 return;
56 }
57
58 if (!ensureAndroidPermission(QBluetoothPermission::Access)) {
59 qCWarning(QT_BT_ANDROID) << "Local device initialize() failed due to missing permissions";
60 return;
61 }
62
63 obj = new QJniObject(adapter);
64 if (!address.isNull()) {
65 const QString localAddress = obj->callMethod<jstring>("getAddress").toString();
66 if (localAddress != address.toString()) {
67 // passed address not local one -> invalid
68 delete obj;
69 obj = nullptr;
70 }
71 }
72}
73
75{
76 return obj ? true : false;
77}
78
79void QBluetoothLocalDevicePrivate::processHostModeChange(QBluetoothLocalDevice::HostMode newMode)
80{
81 qCDebug(QT_BT_ANDROID) << "Processing host mode change:" << newMode
82 << ", pending transition:" << pendingConnectableHostModeTransition;
83 if (!pendingConnectableHostModeTransition) {
84 // If host mode is not in transition -> pass data on
85 emit q_ptr->hostModeStateChanged(newMode);
86 return;
87 }
88
89 // Host mode is in transition: check if the new mode is 'off' in which state
90 // we can enter the targeted 'Connectable' state
91 if (isValid() && newMode == QBluetoothLocalDevice::HostPoweredOff) {
92 const bool success = (bool)QJniObject::callStaticMethod<jboolean>(
93 QtJniTypes::Traits<QtJniTypes::QtBtBroadcastReceiver>::className(),
94 "setEnabled");
95 if (!success) {
96 qCWarning(QT_BT_ANDROID) << "Transitioning Bluetooth from OFF to ON failed";
97 emit q_ptr->errorOccurred(QBluetoothLocalDevice::UnknownError);
98 }
99 }
100 pendingConnectableHostModeTransition = false;
101}
102
103// Return -1 if address is not part of a pending pairing request
104// Otherwise it returns the index of address in pendingPairings
105int QBluetoothLocalDevicePrivate::pendingPairing(const QBluetoothAddress &address)
106{
107 for (qsizetype i = 0; i < pendingPairings.size(); ++i) {
108 if (pendingPairings.at(i).first == address)
109 return i;
110 }
111
112 return -1;
113}
114
115void QBluetoothLocalDevicePrivate::processPairingStateChanged(
116 const QBluetoothAddress &address, QBluetoothLocalDevice::Pairing pairing)
117{
118 int index = pendingPairing(address);
119
120 if (index < 0)
121 return; // ignore unrelated pairing signals
122
123 QPair<QBluetoothAddress, bool> entry = pendingPairings.takeAt(index);
124 if ((entry.second && pairing == QBluetoothLocalDevice::Paired)
125 || (!entry.second && pairing == QBluetoothLocalDevice::Unpaired)) {
126 emit q_ptr->pairingFinished(address, pairing);
127 } else {
128 emit q_ptr->errorOccurred(QBluetoothLocalDevice::PairingError);
129 }
130}
131
132void QBluetoothLocalDevicePrivate::processConnectDeviceChanges(const QBluetoothAddress &address,
133 bool isConnectEvent)
134{
135 if (isConnectEvent) { // connect event
136 if (connectedDevices.contains(address))
137 return;
138 connectedDevices.append(address);
139 emit q_ptr->deviceConnected(address);
140 } else { // disconnect event
141 connectedDevices.removeAll(address);
142 emit q_ptr->deviceDisconnected(address);
143 }
144}
145
146QBluetoothLocalDevice::QBluetoothLocalDevice(QObject *parent) :
147 QObject(parent),
148 d_ptr(new QBluetoothLocalDevicePrivate(this, QBluetoothAddress()))
149{
150}
151
152QBluetoothLocalDevice::QBluetoothLocalDevice(const QBluetoothAddress &address, QObject *parent) :
153 QObject(parent),
154 d_ptr(new QBluetoothLocalDevicePrivate(this, address))
155{
156}
157
158QString QBluetoothLocalDevice::name() const
159{
160 if (d_ptr->adapter())
161 return d_ptr->adapter()->callMethod<jstring>("getName").toString();
162
163 return QString();
164}
165
166QBluetoothAddress QBluetoothLocalDevice::address() const
167{
168 QString result;
169 if (d_ptr->adapter())
170 result = d_ptr->adapter()->callMethod<jstring>("getAddress").toString();
171
172 QBluetoothAddress address(result);
173 return address;
174}
175
176void QBluetoothLocalDevice::powerOn()
177{
178 if (hostMode() != HostPoweredOff)
179 return;
180
181 if (d_ptr->adapter()) {
182 bool success(false);
183 if (QNativeInterface::QAndroidApplication::sdkVersion() >= 31) {
184 success = (bool)QJniObject::callStaticMethod<jboolean>(
185 QtJniTypes::Traits<QtJniTypes::QtBtBroadcastReceiver>::className(),
186 "setEnabled");
187 } else {
188 success = (bool)d_ptr->adapter()->callMethod<jboolean>("enable");
189 }
190 if (!success) {
191 qCWarning(QT_BT_ANDROID) << "Enabling bluetooth failed";
192 emit errorOccurred(QBluetoothLocalDevice::UnknownError);
193 }
194 }
195}
196
197void QBluetoothLocalDevice::setHostMode(QBluetoothLocalDevice::HostMode requestedMode)
198{
199 QBluetoothLocalDevice::HostMode nextMode = requestedMode;
200 if (requestedMode == HostDiscoverableLimitedInquiry)
201 nextMode = HostDiscoverable;
202
203 if (nextMode == hostMode())
204 return;
205
206 switch (nextMode) {
207
208 case QBluetoothLocalDevice::HostPoweredOff: {
209 bool success = false;
210 if (d_ptr->adapter()) {
211 if (QNativeInterface::QAndroidApplication::sdkVersion() >= 31) {
212 success = (bool)QJniObject::callStaticMethod<jboolean>(
213 QtJniTypes::Traits<QtJniTypes::QtBtBroadcastReceiver>::className(),
214 "setDisabled");
215 } else {
216 success = (bool)d_ptr->adapter()->callMethod<jboolean>("disable");
217 }
218 }
219 if (!success) {
220 qCWarning(QT_BT_ANDROID) << "Unable to power off the adapter";
221 emit errorOccurred(QBluetoothLocalDevice::UnknownError);
222 }
223 break;
224 }
225
226 case QBluetoothLocalDevice::HostConnectable: {
227 if (hostMode() == QBluetoothLocalDevice::HostDiscoverable) {
228 // On Android 'Discoverable' is actually 'CONNECTABLE_DISCOVERABLE', and
229 // it seems we cannot go directly from "Discoverable" to "Connectable". Instead
230 // we need to go to disabled mode first and then to the 'Connectable' mode
231 setHostMode(QBluetoothLocalDevice::HostPoweredOff);
232 d_ptr->pendingConnectableHostModeTransition = true;
233 } else {
234 const bool success = (bool)QJniObject::callStaticMethod<jboolean>(
235 QtJniTypes::Traits<QtJniTypes::QtBtBroadcastReceiver>::className(),
236 "setEnabled");
237 if (!success) {
238 qCWarning(QT_BT_ANDROID) << "Unable to enable the Bluetooth";
239 emit errorOccurred(QBluetoothLocalDevice::UnknownError);
240 }
241 }
242 break;
243 }
244
245 case QBluetoothLocalDevice::HostDiscoverable: {
246 if (!ensureAndroidPermission(QBluetoothPermission::Advertise)) {
247 qCWarning(QT_BT_ANDROID) << "Local device setHostMode() failed due to "
248 "missing permissions";
249 emit errorOccurred(QBluetoothLocalDevice::MissingPermissionsError);
250 return;
251 }
252 const bool success = (bool)QJniObject::callStaticMethod<jboolean>(
253 QtJniTypes::Traits<QtJniTypes::QtBtBroadcastReceiver>::className(),
254 "setDiscoverable");
255 if (!success) {
256 qCWarning(QT_BT_ANDROID) << "Unable to set Bluetooth as discoverable";
257 emit errorOccurred(QBluetoothLocalDevice::UnknownError);
258 }
259 break;
260 }
261 default:
262 qCWarning(QT_BT_ANDROID) << "setHostMode() unsupported host mode:" << nextMode;
263 break;
264 }
265}
266
267QBluetoothLocalDevice::HostMode QBluetoothLocalDevice::hostMode() const
268{
269 if (d_ptr->adapter()) {
270 jint scanMode = d_ptr->adapter()->callMethod<jint>("getScanMode");
271
272 switch (scanMode) {
273 case 20: // BluetoothAdapter.SCAN_MODE_NONE
274 return HostPoweredOff;
275 case 21: // BluetoothAdapter.SCAN_MODE_CONNECTABLE
276 return HostConnectable;
277 case 23: // BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE
278 return HostDiscoverable;
279 default:
280 break;
281 }
282 }
283
284 return HostPoweredOff;
285}
286
287QList<QBluetoothHostInfo> QBluetoothLocalDevice::allDevices()
288{
289 // As a static class function we need to ensure permissions here (in addition to initialize())
290 if (!ensureAndroidPermission(QBluetoothPermission::Access)) {
291 qCWarning(QT_BT_ANDROID) << "Local device allDevices() failed due to "
292 "missing permissions";
293 return {};
294 }
295 // Android only supports max of one device (so far)
296 QList<QBluetoothHostInfo> localDevices;
297
298 QJniObject o = getDefaultBluetoothAdapter();
299 if (o.isValid()) {
300 QBluetoothHostInfo info;
301 info.setName(o.callMethod<jstring>("getName").toString());
302 info.setAddress(QBluetoothAddress(o.callMethod<jstring>("getAddress").toString()));
303 localDevices.append(info);
304 }
305 return localDevices;
306}
307
308void QBluetoothLocalDevice::requestPairing(const QBluetoothAddress &address, Pairing pairing)
309{
310 if (address.isNull()) {
311 QMetaObject::invokeMethod(this, "errorOccurred", Qt::QueuedConnection,
312 Q_ARG(QBluetoothLocalDevice::Error,
313 QBluetoothLocalDevice::PairingError));
314 return;
315 }
316
317 const Pairing previousPairing = pairingStatus(address);
318 Pairing newPairing = pairing;
319 if (pairing == AuthorizedPaired) // AuthorizedPaired same as Paired on Android
320 newPairing = Paired;
321
322 if (previousPairing == newPairing) {
323 QMetaObject::invokeMethod(this, "pairingFinished", Qt::QueuedConnection,
324 Q_ARG(QBluetoothAddress, address),
325 Q_ARG(QBluetoothLocalDevice::Pairing, newPairing));
326 return;
327 }
328
329 if (!d_ptr->adapter()) {
330 qCWarning(QT_BT_ANDROID) << "Unable to pair, invalid adapter";
331 QMetaObject::invokeMethod(this, "errorOccurred", Qt::QueuedConnection,
332 Q_ARG(QBluetoothLocalDevice::Error,
333 QBluetoothLocalDevice::PairingError));
334 return;
335 }
336
337 QJniObject inputString = QJniObject::fromString(address.toString());
338 jboolean success = QJniObject::callStaticMethod<jboolean>(
339 QtJniTypes::Traits<QtJniTypes::QtBtBroadcastReceiver>::className(),
340 "setPairingMode",
341 inputString.object<jstring>(),
342 jboolean(newPairing == Paired ? JNI_TRUE : JNI_FALSE));
343
344 if (!success) {
345 QMetaObject::invokeMethod(this, "errorOccurred", Qt::QueuedConnection,
346 Q_ARG(QBluetoothLocalDevice::Error,
347 QBluetoothLocalDevice::PairingError));
348 } else {
349 d_ptr->pendingPairings.append(qMakePair(address, newPairing == Paired ? true : false));
350 }
351}
352
353QBluetoothLocalDevice::Pairing QBluetoothLocalDevice::pairingStatus(
354 const QBluetoothAddress &address) const
355{
356 if (address.isNull() || !d_ptr->adapter())
357 return Unpaired;
358
359 QJniObject inputString = QJniObject::fromString(address.toString());
360 QJniObject remoteDevice
361 = d_ptr->adapter()->callMethod<QtJniTypes::BluetoothDevice>("getRemoteDevice",
362 inputString.object<jstring>());
363
364 if (!remoteDevice.isValid())
365 return Unpaired;
366
367 jint bondState = remoteDevice.callMethod<jint>("getBondState");
368 switch (bondState) {
369 case 12: // BluetoothDevice.BOND_BONDED
370 return Paired;
371 default:
372 break;
373 }
374
375 return Unpaired;
376}
377
378QList<QBluetoothAddress> QBluetoothLocalDevice::connectedDevices() const
379{
380 /*
381 * Android does not have an API to list all connected devices. We have to collect
382 * the information based on a few indicators.
383 *
384 * Primarily we detect connected devices by monitoring connect/disconnect signals.
385 * Unfortunately the list may only be complete after very long monitoring time.
386 * However there are some Android APIs which provide the list of connected devices
387 * for specific Bluetooth profiles. QtBluetoothBroadcastReceiver.getConnectedDevices()
388 * returns a few connections of common profiles. The returned list is not complete either
389 * but at least it can complement our already detected connections.
390 */
391 using namespace QtJniTypes;
392
393 const auto connectedDevices = QtBtBroadcastReceiver::callStaticMethod<String[]>("getConnectedDevices");
394
395 if (!connectedDevices.isValid())
396 return d_ptr->connectedDevices;
397
398 QList<QBluetoothAddress> knownAddresses = d_ptr->connectedDevices;
399 for (const auto &device : connectedDevices) {
400 QBluetoothAddress address(device.toString());
401 if (!address.isNull() && !knownAddresses.contains(address))
402 knownAddresses.append(address);
403 }
404
405 return knownAddresses;
406}
407
408QT_END_NAMESPACE