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 | |
51 | QT_BEGIN_NAMESPACE |
52 | |
53 | Q_DECLARE_LOGGING_CATEGORY(QT_BT_ANDROID) |
54 | |
55 | QBluetoothLocalDevicePrivate::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 | |
76 | QBluetoothLocalDevicePrivate::~QBluetoothLocalDevicePrivate() |
77 | { |
78 | receiver->unregisterReceiver(); |
79 | delete receiver; |
80 | delete obj; |
81 | } |
82 | |
83 | QAndroidJniObject *QBluetoothLocalDevicePrivate::adapter() |
84 | { |
85 | return obj; |
86 | } |
87 | |
88 | static 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 | |
107 | void 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 | |
127 | bool QBluetoothLocalDevicePrivate::isValid() const |
128 | { |
129 | return obj ? true : false; |
130 | } |
131 | |
132 | void 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 |
151 | int 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 | |
161 | void 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 | |
178 | void 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 | |
200 | void 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 | |
211 | void 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 | |
221 | QBluetoothLocalDevice::QBluetoothLocalDevice(QObject *parent) : |
222 | QObject(parent), |
223 | d_ptr(new QBluetoothLocalDevicePrivate(this, QBluetoothAddress())) |
224 | { |
225 | } |
226 | |
227 | QBluetoothLocalDevice::QBluetoothLocalDevice(const QBluetoothAddress &address, QObject *parent) : |
228 | QObject(parent), |
229 | d_ptr(new QBluetoothLocalDevicePrivate(this, address)) |
230 | { |
231 | } |
232 | |
233 | QString 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 | |
241 | QBluetoothAddress 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 | |
253 | void 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 | |
265 | void 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 | |
300 | QBluetoothLocalDevice::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 | |
320 | QList<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 | |
336 | void 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 | |
383 | QBluetoothLocalDevice::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 | |
411 | void 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 | |
421 | QList<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 | |
461 | QT_END_NAMESPACE |
462 |
Warning: That file was not part of the compilation database. It may have many parsing errors.