Warning: That file was not part of the compilation database. It may have many parsing errors.

1/****************************************************************************
2**
3** Copyright (C) 2016 Lauri Laanmets (Proekspert AS) <lauri.laanmets@eesti.ee>
4** Copyright (C) 2016 The Qt Company Ltd.
5** Contact: https://www.qt.io/licensing/
6**
7** This file is part of the QtBluetooth module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial License Usage
11** Licensees holding valid commercial Qt licenses may use this file in
12** accordance with the commercial license agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and The Qt Company. For licensing terms
15** and conditions see https://www.qt.io/terms-conditions. For further
16** information use the contact form at https://www.qt.io/contact-us.
17**
18** GNU Lesser General Public License Usage
19** Alternatively, this file may be used under the terms of the GNU Lesser
20** General Public License version 3 as published by the Free Software
21** Foundation and appearing in the file LICENSE.LGPL3 included in the
22** packaging of this file. Please review the following information to
23** ensure the GNU Lesser General Public License version 3 requirements
24** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
25**
26** GNU General Public License Usage
27** Alternatively, this file may be used under the terms of the GNU
28** General Public License version 2.0 or (at your option) the GNU General
29** Public license version 3 or any later version approved by the KDE Free
30** Qt Foundation. The licenses are as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
32** included in the packaging of this file. Please review the following
33** information to ensure the GNU General Public License requirements will
34** be met: https://www.gnu.org/licenses/gpl-2.0.html and
35** https://www.gnu.org/licenses/gpl-3.0.html.
36**
37** $QT_END_LICENSE$
38**
39****************************************************************************/
40
41#include <QtCore/QLoggingCategory>
42#include <QtCore/private/qjnihelpers_p.h>
43#include <QtAndroidExtras/QAndroidJniEnvironment>
44#include <QtAndroidExtras/QAndroidJniObject>
45#include <QtBluetooth/QBluetoothLocalDevice>
46#include <QtBluetooth/QBluetoothAddress>
47
48#include "qbluetoothlocaldevice_p.h"
49#include "android/localdevicebroadcastreceiver_p.h"
50
51QT_BEGIN_NAMESPACE
52
53Q_DECLARE_LOGGING_CATEGORY(QT_BT_ANDROID)
54
55QBluetoothLocalDevicePrivate::QBluetoothLocalDevicePrivate(
56 QBluetoothLocalDevice *q, const QBluetoothAddress &address) :
57 q_ptr(q)
58{
59 registerQBluetoothLocalDeviceMetaType();
60
61 initialize(address);
62
63 receiver = new LocalDeviceBroadcastReceiver(q_ptr);
64 connect(receiver, &LocalDeviceBroadcastReceiver::hostModeStateChanged,
65 this, &QBluetoothLocalDevicePrivate::processHostModeChange);
66 connect(receiver, &LocalDeviceBroadcastReceiver::pairingStateChanged,
67 this, &QBluetoothLocalDevicePrivate::processPairingStateChanged);
68 connect(receiver, &LocalDeviceBroadcastReceiver::connectDeviceChanges,
69 this, &QBluetoothLocalDevicePrivate::processConnectDeviceChanges);
70 connect(receiver, &LocalDeviceBroadcastReceiver::pairingDisplayConfirmation,
71 this, &QBluetoothLocalDevicePrivate::processDisplayConfirmation);
72 connect(receiver, &LocalDeviceBroadcastReceiver::pairingDisplayPinCode,
73 this, &QBluetoothLocalDevicePrivate::processDisplayPinCode);
74}
75
76QBluetoothLocalDevicePrivate::~QBluetoothLocalDevicePrivate()
77{
78 receiver->unregisterReceiver();
79 delete receiver;
80 delete obj;
81}
82
83QAndroidJniObject *QBluetoothLocalDevicePrivate::adapter()
84{
85 return obj;
86}
87
88static QAndroidJniObject getDefaultAdapter()
89{
90 QAndroidJniObject adapter = QAndroidJniObject::callStaticObjectMethod(
91 "android/bluetooth/BluetoothAdapter", "getDefaultAdapter",
92 "()Landroid/bluetooth/BluetoothAdapter;");
93 QAndroidJniExceptionCleaner exCleaner{QAndroidJniExceptionCleaner::OutputMode::Verbose};
94 if (!adapter.isValid()) {
95 exCleaner.clean();
96
97 // workaround stupid bt implementations where first call of BluetoothAdapter.getDefaultAdapter() always fails
98 adapter = QAndroidJniObject::callStaticObjectMethod(
99 "android/bluetooth/BluetoothAdapter", "getDefaultAdapter",
100 "()Landroid/bluetooth/BluetoothAdapter;");
101 if (!adapter.isValid())
102 exCleaner.clean();
103 }
104 return adapter;
105}
106
107void QBluetoothLocalDevicePrivate::initialize(const QBluetoothAddress &address)
108{
109 QAndroidJniObject adapter = getDefaultAdapter();
110 if (!adapter.isValid()) {
111 qCWarning(QT_BT_ANDROID) << "Device does not support Bluetooth";
112 return;
113 }
114
115 obj = new QAndroidJniObject(adapter);
116 if (!address.isNull()) {
117 const QString localAddress
118 = obj->callObjectMethod("getAddress", "()Ljava/lang/String;").toString();
119 if (localAddress != address.toString()) {
120 // passed address not local one -> invalid
121 delete obj;
122 obj = nullptr;
123 }
124 }
125}
126
127bool QBluetoothLocalDevicePrivate::isValid() const
128{
129 return obj ? true : false;
130}
131
132void QBluetoothLocalDevicePrivate::processHostModeChange(QBluetoothLocalDevice::HostMode newMode)
133{
134 if (!pendingHostModeTransition) {
135 // if not in transition -> pass data on
136 emit q_ptr->hostModeStateChanged(newMode);
137 return;
138 }
139
140 if (isValid() && newMode == QBluetoothLocalDevice::HostPoweredOff) {
141 bool success = (bool)obj->callMethod<jboolean>("enable", "()Z");
142 if (!success)
143 emit q_ptr->error(QBluetoothLocalDevice::UnknownError);
144 }
145
146 pendingHostModeTransition = false;
147}
148
149// Return -1 if address is not part of a pending pairing request
150// Otherwise it returns the index of address in pendingPairings
151int QBluetoothLocalDevicePrivate::pendingPairing(const QBluetoothAddress &address)
152{
153 for (int i = 0; i < pendingPairings.count(); i++) {
154 if (pendingPairings.at(i).first == address)
155 return i;
156 }
157
158 return -1;
159}
160
161void QBluetoothLocalDevicePrivate::processPairingStateChanged(
162 const QBluetoothAddress &address, QBluetoothLocalDevice::Pairing pairing)
163{
164 int index = pendingPairing(address);
165
166 if (index < 0)
167 return; // ignore unrelated pairing signals
168
169 QPair<QBluetoothAddress, bool> entry = pendingPairings.takeAt(index);
170 if ((entry.second && pairing == QBluetoothLocalDevice::Paired)
171 || (!entry.second && pairing == QBluetoothLocalDevice::Unpaired)) {
172 emit q_ptr->pairingFinished(address, pairing);
173 } else {
174 emit q_ptr->error(QBluetoothLocalDevice::PairingError);
175 }
176}
177
178void QBluetoothLocalDevicePrivate::processConnectDeviceChanges(const QBluetoothAddress &address,
179 bool isConnectEvent)
180{
181 int index = -1;
182 for (int i = 0; i < connectedDevices.count(); i++) {
183 if (connectedDevices.at(i) == address) {
184 index = i;
185 break;
186 }
187 }
188
189 if (isConnectEvent) { // connect event
190 if (index >= 0)
191 return;
192 connectedDevices.append(address);
193 emit q_ptr->deviceConnected(address);
194 } else { // disconnect event
195 connectedDevices.removeAll(address);
196 emit q_ptr->deviceDisconnected(address);
197 }
198}
199
200void QBluetoothLocalDevicePrivate::processDisplayConfirmation(const QBluetoothAddress &address,
201 const QString &pin)
202{
203 // only send pairing notification for pairing requests issued by
204 // this QBluetoothLocalDevice instance
205 if (pendingPairing(address) == -1)
206 return;
207
208 emit q_ptr->pairingDisplayConfirmation(address, pin);
209}
210
211void QBluetoothLocalDevicePrivate::processDisplayPinCode(const QBluetoothAddress &address, const QString &pin)
212{
213 // only send pairing notification for pairing requests issued by
214 // this QBluetoothLocalDevice instance
215 if (pendingPairing(address) == -1)
216 return;
217
218 emit q_ptr->pairingDisplayPinCode(address, pin);
219}
220
221QBluetoothLocalDevice::QBluetoothLocalDevice(QObject *parent) :
222 QObject(parent),
223 d_ptr(new QBluetoothLocalDevicePrivate(this, QBluetoothAddress()))
224{
225}
226
227QBluetoothLocalDevice::QBluetoothLocalDevice(const QBluetoothAddress &address, QObject *parent) :
228 QObject(parent),
229 d_ptr(new QBluetoothLocalDevicePrivate(this, address))
230{
231}
232
233QString QBluetoothLocalDevice::name() const
234{
235 if (d_ptr->adapter())
236 return d_ptr->adapter()->callObjectMethod("getName", "()Ljava/lang/String;").toString();
237
238 return QString();
239}
240
241QBluetoothAddress QBluetoothLocalDevice::address() const
242{
243 QString result;
244 if (d_ptr->adapter()) {
245 result
246 = d_ptr->adapter()->callObjectMethod("getAddress", "()Ljava/lang/String;").toString();
247 }
248
249 QBluetoothAddress address(result);
250 return address;
251}
252
253void QBluetoothLocalDevice::powerOn()
254{
255 if (hostMode() != HostPoweredOff)
256 return;
257
258 if (d_ptr->adapter()) {
259 bool ret = (bool)d_ptr->adapter()->callMethod<jboolean>("enable", "()Z");
260 if (!ret)
261 emit error(QBluetoothLocalDevice::UnknownError);
262 }
263}
264
265void QBluetoothLocalDevice::setHostMode(QBluetoothLocalDevice::HostMode requestedMode)
266{
267 QBluetoothLocalDevice::HostMode mode = requestedMode;
268 if (requestedMode == HostDiscoverableLimitedInquiry)
269 mode = HostDiscoverable;
270
271 if (mode == hostMode())
272 return;
273
274 if (mode == QBluetoothLocalDevice::HostPoweredOff) {
275 bool success = false;
276 if (d_ptr->adapter())
277 success = (bool)d_ptr->adapter()->callMethod<jboolean>("disable", "()Z");
278
279 if (!success)
280 emit error(QBluetoothLocalDevice::UnknownError);
281 } else if (mode == QBluetoothLocalDevice::HostConnectable) {
282 if (hostMode() == QBluetoothLocalDevice::HostDiscoverable) {
283 // cannot directly go from Discoverable to Connectable
284 // we need to go to disabled mode and enable once disabling came through
285
286 setHostMode(QBluetoothLocalDevice::HostPoweredOff);
287 d_ptr->pendingHostModeTransition = true;
288 } else {
289 QAndroidJniObject::callStaticMethod<void>(
290 "org/qtproject/qt5/android/bluetooth/QtBluetoothBroadcastReceiver",
291 "setConnectable");
292 }
293 } else if (mode == QBluetoothLocalDevice::HostDiscoverable
294 || mode == QBluetoothLocalDevice::HostDiscoverableLimitedInquiry) {
295 QAndroidJniObject::callStaticMethod<void>(
296 "org/qtproject/qt5/android/bluetooth/QtBluetoothBroadcastReceiver", "setDiscoverable");
297 }
298}
299
300QBluetoothLocalDevice::HostMode QBluetoothLocalDevice::hostMode() const
301{
302 if (d_ptr->adapter()) {
303 jint scanMode = d_ptr->adapter()->callMethod<jint>("getScanMode");
304
305 switch (scanMode) {
306 case 20: // BluetoothAdapter.SCAN_MODE_NONE
307 return HostPoweredOff;
308 case 21: // BluetoothAdapter.SCAN_MODE_CONNECTABLE
309 return HostConnectable;
310 case 23: // BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE
311 return HostDiscoverable;
312 default:
313 break;
314 }
315 }
316
317 return HostPoweredOff;
318}
319
320QList<QBluetoothHostInfo> QBluetoothLocalDevice::allDevices()
321{
322 // Android only supports max of one device (so far)
323 QList<QBluetoothHostInfo> localDevices;
324
325 QAndroidJniObject o = getDefaultAdapter();
326 if (o.isValid()) {
327 QBluetoothHostInfo info;
328 info.setName(o.callObjectMethod("getName", "()Ljava/lang/String;").toString());
329 info.setAddress(QBluetoothAddress(o.callObjectMethod("getAddress",
330 "()Ljava/lang/String;").toString()));
331 localDevices.append(info);
332 }
333 return localDevices;
334}
335
336void QBluetoothLocalDevice::requestPairing(const QBluetoothAddress &address, Pairing pairing)
337{
338 if (address.isNull()) {
339 QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
340 Q_ARG(QBluetoothLocalDevice::Error,
341 QBluetoothLocalDevice::PairingError));
342 return;
343 }
344
345 const Pairing previousPairing = pairingStatus(address);
346 Pairing newPairing = pairing;
347 if (pairing == AuthorizedPaired) // AuthorizedPaired same as Paired on Android
348 newPairing = Paired;
349
350 if (previousPairing == newPairing) {
351 QMetaObject::invokeMethod(this, "pairingFinished", Qt::QueuedConnection,
352 Q_ARG(QBluetoothAddress, address),
353 Q_ARG(QBluetoothLocalDevice::Pairing, pairing));
354 return;
355 }
356
357 // BluetoothDevice::createBond() requires Android API 15
358 if (QtAndroidPrivate::androidSdkVersion() < 15 || !d_ptr->adapter()) {
359 qCWarning(QT_BT_ANDROID) << "Unable to pair: requires Android API 15+";
360 QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
361 Q_ARG(QBluetoothLocalDevice::Error,
362 QBluetoothLocalDevice::PairingError));
363 return;
364 }
365
366 QAndroidJniObject inputString = QAndroidJniObject::fromString(address.toString());
367 jboolean success = QAndroidJniObject::callStaticMethod<jboolean>(
368 "org/qtproject/qt5/android/bluetooth/QtBluetoothBroadcastReceiver",
369 "setPairingMode",
370 "(Ljava/lang/String;Z)Z",
371 inputString.object<jstring>(),
372 newPairing == Paired ? JNI_TRUE : JNI_FALSE);
373
374 if (!success) {
375 QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection,
376 Q_ARG(QBluetoothLocalDevice::Error,
377 QBluetoothLocalDevice::PairingError));
378 } else {
379 d_ptr->pendingPairings.append(qMakePair(address, newPairing == Paired ? true : false));
380 }
381}
382
383QBluetoothLocalDevice::Pairing QBluetoothLocalDevice::pairingStatus(
384 const QBluetoothAddress &address) const
385{
386 if (address.isNull() || !d_ptr->adapter())
387 return Unpaired;
388
389 QAndroidJniObject inputString = QAndroidJniObject::fromString(address.toString());
390 QAndroidJniObject remoteDevice
391 = d_ptr->adapter()->callObjectMethod("getRemoteDevice",
392 "(Ljava/lang/String;)Landroid/bluetooth/BluetoothDevice;",
393 inputString.object<jstring>());
394 QAndroidJniEnvironment env;
395 if (env->ExceptionCheck()) {
396 env->ExceptionClear();
397 return Unpaired;
398 }
399
400 jint bondState = remoteDevice.callMethod<jint>("getBondState");
401 switch (bondState) {
402 case 12: // BluetoothDevice.BOND_BONDED
403 return Paired;
404 default:
405 break;
406 }
407
408 return Unpaired;
409}
410
411void QBluetoothLocalDevice::pairingConfirmation(bool confirmation)
412{
413 if (!d_ptr->adapter())
414 return;
415
416 bool success = d_ptr->receiver->pairingConfirmation(confirmation);
417 if (!success)
418 emit error(PairingError);
419}
420
421QList<QBluetoothAddress> QBluetoothLocalDevice::connectedDevices() const
422{
423 /*
424 * Android does not have an API to list all connected devices. We have to collect
425 * the information based on a few indicators.
426 *
427 * Primarily we detect connected devices by monitoring connect/disconnect signals.
428 * Unfortunately the list may only be complete after very long monitoring time.
429 * However there are some Android APIs which provide the list of connected devices
430 * for specific Bluetooth profiles. QtBluetoothBroadcastReceiver.getConnectedDevices()
431 * returns a few connections of common profiles. The returned list is not complete either
432 * but at least it can complement our already detected connections.
433 */
434 QAndroidJniObject connectedDevices = QAndroidJniObject::callStaticObjectMethod(
435 "org/qtproject/qt5/android/bluetooth/QtBluetoothBroadcastReceiver",
436 "getConnectedDevices",
437 "()[Ljava/lang/String;");
438
439 if (!connectedDevices.isValid())
440 return d_ptr->connectedDevices;
441
442 jobjectArray connectedDevicesArray = connectedDevices.object<jobjectArray>();
443 if (!connectedDevicesArray)
444 return d_ptr->connectedDevices;
445
446 QAndroidJniEnvironment env;
447 QList<QBluetoothAddress> knownAddresses = d_ptr->connectedDevices;
448 QAndroidJniObject p;
449
450 jint size = env->GetArrayLength(connectedDevicesArray);
451 for (int i = 0; i < size; i++) {
452 p = env->GetObjectArrayElement(connectedDevicesArray, i);
453 QBluetoothAddress address(p.toString());
454 if (!address.isNull() && !knownAddresses.contains(address))
455 knownAddresses.append(address);
456 }
457
458 return knownAddresses;
459}
460
461QT_END_NAMESPACE
462

Warning: That file was not part of the compilation database. It may have many parsing errors.