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 "qbluetoothsocket.h" |
42 | #include "qbluetoothsocket_android_p.h" |
43 | #include "qbluetoothaddress.h" |
44 | #include "qbluetoothdeviceinfo.h" |
45 | #include "qbluetoothserviceinfo.h" |
46 | #include <QtCore/QLoggingCategory> |
47 | #include <QtCore/QThread> |
48 | #include <QtCore/QTime> |
49 | #include <QtCore/private/qjni_p.h> |
50 | #include <QtAndroidExtras/QAndroidJniEnvironment> |
51 | #include <QtAndroid> |
52 | |
53 | QT_BEGIN_NAMESPACE |
54 | |
55 | Q_DECLARE_LOGGING_CATEGORY(QT_BT_ANDROID) |
56 | |
57 | #define FALLBACK_CHANNEL 1 |
58 | #define USE_FALLBACK true |
59 | |
60 | Q_DECLARE_METATYPE(QAndroidJniObject) |
61 | |
62 | Q_BLUETOOTH_EXPORT bool useReverseUuidWorkAroundConnect = true; |
63 | |
64 | /* BluetoothSocket.connect() can block up to 10s. Therefore it must be |
65 | * in a separate thread. Unfortunately if BluetoothSocket.close() is |
66 | * called while connect() is still blocking the resulting behavior is not reliable. |
67 | * This may well be an Android platform bug. In any case, close() must |
68 | * be queued up until connect() has returned. |
69 | * |
70 | * The WorkerThread manages the connect() and close() calls. Interaction |
71 | * with the main thread happens via signals and slots. There is an accepted but |
72 | * undesirable side effect of this approach as the user may call connect() |
73 | * and close() and the socket would continue to successfully connect to |
74 | * the remote device just to immidiately close the physical connection again. |
75 | * |
76 | * WorkerThread and SocketConnectWorker are cleaned up via the threads |
77 | * finished() signal. |
78 | */ |
79 | |
80 | class SocketConnectWorker : public QObject |
81 | { |
82 | Q_OBJECT |
83 | public: |
84 | SocketConnectWorker(const QAndroidJniObject& socket, |
85 | const QAndroidJniObject& targetUuid, |
86 | const QBluetoothUuid& qtTargetUuid) |
87 | : QObject(), |
88 | mSocketObject(socket), |
89 | mTargetUuid(targetUuid), |
90 | mQtTargetUuid(qtTargetUuid) |
91 | { |
92 | static int t = qRegisterMetaType<QAndroidJniObject>(); |
93 | Q_UNUSED(t); |
94 | } |
95 | |
96 | signals: |
97 | void socketConnectDone(const QAndroidJniObject &socket); |
98 | void socketConnectFailed(const QAndroidJniObject &socket, |
99 | const QAndroidJniObject &targetUuid, |
100 | const QBluetoothUuid &qtUuid); |
101 | public slots: |
102 | void connectSocket() |
103 | { |
104 | QAndroidJniEnvironment env; |
105 | |
106 | qCDebug(QT_BT_ANDROID) << "Connecting socket"; |
107 | mSocketObject.callMethod<void>("connect"); |
108 | if (env->ExceptionCheck()) { |
109 | env->ExceptionDescribe(); |
110 | env->ExceptionClear(); |
111 | |
112 | emit socketConnectFailed(mSocketObject, mTargetUuid, mQtTargetUuid); |
113 | QThread::currentThread()->quit(); |
114 | return; |
115 | } |
116 | |
117 | qCDebug(QT_BT_ANDROID) << "Socket connection established"; |
118 | emit socketConnectDone(mSocketObject); |
119 | } |
120 | |
121 | void closeSocket() |
122 | { |
123 | qCDebug(QT_BT_ANDROID) << "Executing queued closeSocket()"; |
124 | |
125 | QAndroidJniEnvironment env; |
126 | mSocketObject.callMethod<void>("close"); |
127 | if (env->ExceptionCheck()) { |
128 | |
129 | qCWarning(QT_BT_ANDROID) << "Error during closure of abandoned socket"; |
130 | env->ExceptionDescribe(); |
131 | env->ExceptionClear(); |
132 | } |
133 | |
134 | QThread::currentThread()->quit(); |
135 | } |
136 | |
137 | private: |
138 | QAndroidJniObject mSocketObject; |
139 | QAndroidJniObject mTargetUuid; |
140 | // same as mTargetUuid above - just the Qt C++ version rather than jni uuid |
141 | QBluetoothUuid mQtTargetUuid; |
142 | }; |
143 | |
144 | class WorkerThread: public QThread |
145 | { |
146 | Q_OBJECT |
147 | public: |
148 | WorkerThread() |
149 | : QThread(), workerPointer(0) |
150 | { |
151 | } |
152 | |
153 | // Runs in same thread as QBluetoothSocketPrivateAndroid |
154 | void setupWorker(QBluetoothSocketPrivateAndroid* d_ptr, const QAndroidJniObject& socketObject, |
155 | const QAndroidJniObject& uuidObject, bool useFallback, |
156 | const QBluetoothUuid& qtUuid = QBluetoothUuid()) |
157 | { |
158 | SocketConnectWorker* worker = new SocketConnectWorker( |
159 | socketObject, uuidObject, qtUuid); |
160 | worker->moveToThread(this); |
161 | |
162 | connect(this, &QThread::finished, worker, &QObject::deleteLater); |
163 | connect(this, &QThread::finished, this, &QObject::deleteLater); |
164 | connect(d_ptr, &QBluetoothSocketPrivateAndroid::connectJavaSocket, |
165 | worker, &SocketConnectWorker::connectSocket); |
166 | connect(d_ptr, &QBluetoothSocketPrivateAndroid::closeJavaSocket, |
167 | worker, &SocketConnectWorker::closeSocket); |
168 | connect(worker, &SocketConnectWorker::socketConnectDone, |
169 | d_ptr, &QBluetoothSocketPrivateAndroid::socketConnectSuccess); |
170 | if (useFallback) { |
171 | connect(worker, &SocketConnectWorker::socketConnectFailed, |
172 | d_ptr, &QBluetoothSocketPrivateAndroid::fallbackSocketConnectFailed); |
173 | } else { |
174 | connect(worker, &SocketConnectWorker::socketConnectFailed, |
175 | d_ptr, &QBluetoothSocketPrivateAndroid::defaultSocketConnectFailed); |
176 | } |
177 | |
178 | workerPointer = worker; |
179 | } |
180 | |
181 | private: |
182 | QPointer<SocketConnectWorker> workerPointer; |
183 | }; |
184 | |
185 | QBluetoothSocketPrivateAndroid::QBluetoothSocketPrivateAndroid() |
186 | : |
187 | inputThread(0) |
188 | { |
189 | secFlags = QBluetooth::Secure; |
190 | adapter = QAndroidJniObject::callStaticObjectMethod("android/bluetooth/BluetoothAdapter", |
191 | "getDefaultAdapter", |
192 | "()Landroid/bluetooth/BluetoothAdapter;"); |
193 | qRegisterMetaType<QBluetoothSocket::SocketError>(); |
194 | qRegisterMetaType<QBluetoothSocket::SocketState>(); |
195 | } |
196 | |
197 | QBluetoothSocketPrivateAndroid::~QBluetoothSocketPrivateAndroid() |
198 | { |
199 | if (state != QBluetoothSocket::UnconnectedState) |
200 | emit closeJavaSocket(); |
201 | } |
202 | |
203 | bool QBluetoothSocketPrivateAndroid::ensureNativeSocket(QBluetoothServiceInfo::Protocol type) |
204 | { |
205 | socketType = type; |
206 | if (socketType == QBluetoothServiceInfo::RfcommProtocol) |
207 | return true; |
208 | |
209 | return false; |
210 | } |
211 | |
212 | bool QBluetoothSocketPrivateAndroid::fallBackConnect(QAndroidJniObject uuid, int channel) |
213 | { |
214 | qCWarning(QT_BT_ANDROID) << "Falling back to getServiceChannel() workaround."; |
215 | |
216 | QAndroidJniEnvironment env; |
217 | |
218 | QAndroidJniObject remoteDeviceClass = remoteDevice.callObjectMethod("getClass", "()Ljava/lang/Class;"); |
219 | if (!remoteDeviceClass.isValid()) { |
220 | qCWarning(QT_BT_ANDROID) << "Could not invoke BluetoothDevice.getClass."; |
221 | return false; |
222 | } |
223 | |
224 | QAndroidJniObject integerObject = QAndroidJniObject::getStaticObjectField( |
225 | "java/lang/Integer", "TYPE", "Ljava/lang/Class;"); |
226 | if (!integerObject.isValid()) { |
227 | qCWarning(QT_BT_ANDROID) << "Could not get Integer.TYPE"; |
228 | if (env->ExceptionCheck()) { |
229 | env->ExceptionDescribe(); |
230 | env->ExceptionClear(); |
231 | } |
232 | |
233 | return false; |
234 | } |
235 | |
236 | jclass classClass = QJNIEnvironmentPrivate::findClass("java/lang/Class"); |
237 | jobjectArray rawArray = env->NewObjectArray(1, classClass, |
238 | integerObject.object<jobject>()); |
239 | QAndroidJniObject paramTypes(rawArray); |
240 | env->DeleteLocalRef(rawArray); |
241 | if (!paramTypes.isValid()) { |
242 | qCWarning(QT_BT_ANDROID) << "Could not create new Class[]{Integer.TYPE}"; |
243 | |
244 | if (env->ExceptionCheck()) { |
245 | env->ExceptionDescribe(); |
246 | env->ExceptionClear(); |
247 | } |
248 | return false; |
249 | } |
250 | |
251 | QAndroidJniObject parcelUuid("android/os/ParcelUuid", "(Ljava/util/UUID;)V", |
252 | uuid.object()); |
253 | if (parcelUuid.isValid()) { |
254 | jint socketChannel = remoteDevice.callMethod<jint>("getServiceChannel", |
255 | "(Landroid/os/ParcelUuid;)I", |
256 | parcelUuid.object()); |
257 | if (env->ExceptionCheck()) { |
258 | env->ExceptionDescribe(); |
259 | env->ExceptionClear(); |
260 | } else { |
261 | if (socketChannel |
262 | == remoteDevice.getStaticField<jint>("android/bluetooth/BluetoothDevice", "ERROR") |
263 | || socketChannel == -1) { |
264 | qCWarning(QT_BT_ANDROID) << "Cannot determine RFCOMM service channel."; |
265 | } else { |
266 | qCWarning(QT_BT_ANDROID) << "Using found rfcomm channel"<< socketChannel; |
267 | channel = socketChannel; |
268 | } |
269 | } |
270 | } |
271 | |
272 | QAndroidJniObject method; |
273 | if (secFlags == QBluetooth::NoSecurity) { |
274 | qCDebug(QT_BT_ANDROID) << "Connnecting via insecure rfcomm"; |
275 | method = remoteDeviceClass.callObjectMethod( |
276 | "getMethod", |
277 | "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", |
278 | QAndroidJniObject::fromString(QLatin1String("createInsecureRfcommSocket")).object<jstring>(), |
279 | paramTypes.object<jobjectArray>()); |
280 | } else { |
281 | qCDebug(QT_BT_ANDROID) << "Connnecting via secure rfcomm"; |
282 | method = remoteDeviceClass.callObjectMethod( |
283 | "getMethod", |
284 | "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", |
285 | QAndroidJniObject::fromString(QLatin1String("createRfcommSocket")).object<jstring>(), |
286 | paramTypes.object<jobjectArray>()); |
287 | } |
288 | if (!method.isValid() || env->ExceptionCheck()) { |
289 | qCWarning(QT_BT_ANDROID) << "Could not invoke getMethod"; |
290 | if (env->ExceptionCheck()) { |
291 | env->ExceptionDescribe(); |
292 | env->ExceptionClear(); |
293 | } |
294 | return false; |
295 | } |
296 | |
297 | jclass objectClass = QJNIEnvironmentPrivate::findClass("java/lang/Object"); |
298 | QAndroidJniObject channelObject = QAndroidJniObject::callStaticObjectMethod( |
299 | "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", channel); |
300 | rawArray = env->NewObjectArray(1, objectClass, channelObject.object<jobject>()); |
301 | |
302 | QAndroidJniObject invokeResult = method.callObjectMethod("invoke", |
303 | "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", |
304 | remoteDevice.object<jobject>(), rawArray); |
305 | env->DeleteLocalRef(rawArray); |
306 | if (!invokeResult.isValid()) |
307 | { |
308 | qCWarning(QT_BT_ANDROID) << "Invoke Resulted with error."; |
309 | if (env->ExceptionCheck()) { |
310 | env->ExceptionDescribe(); |
311 | env->ExceptionClear(); |
312 | } |
313 | |
314 | return false; |
315 | } |
316 | |
317 | socketObject = QAndroidJniObject(invokeResult); |
318 | |
319 | WorkerThread *workerThread = new WorkerThread(); |
320 | workerThread->setupWorker(this, socketObject, uuid, USE_FALLBACK); |
321 | workerThread->start(); |
322 | emit connectJavaSocket(); |
323 | |
324 | qCWarning(QT_BT_ANDROID) << "Workaround thread invoked."; |
325 | return true; |
326 | } |
327 | |
328 | /* |
329 | * Workaround for QTBUG-61392 |
330 | */ |
331 | bool QBluetoothSocketPrivateAndroid::fallBackReversedConnect(const QBluetoothUuid &uuid) |
332 | { |
333 | Q_Q(QBluetoothSocket); |
334 | |
335 | qCWarning(QT_BT_ANDROID) << "Falling back to reverse uuid workaround."; |
336 | const QBluetoothUuid reverse = reverseUuid(uuid); |
337 | if (reverse.isNull()) |
338 | return false; |
339 | |
340 | //cut leading { and trailing } {xxx-xxx} |
341 | QString tempUuid = reverse.toString(); |
342 | tempUuid.chop(1); //remove trailing '}' |
343 | tempUuid.remove(0, 1); //remove first '{' |
344 | |
345 | QAndroidJniEnvironment env; |
346 | const QAndroidJniObject inputString = QAndroidJniObject::fromString(tempUuid); |
347 | const QAndroidJniObject uuidObject = QAndroidJniObject::callStaticObjectMethod("java/util/UUID", "fromString", |
348 | "(Ljava/lang/String;)Ljava/util/UUID;", |
349 | inputString.object<jstring>()); |
350 | |
351 | if (secFlags == QBluetooth::NoSecurity) { |
352 | qCDebug(QT_BT_ANDROID) << "Connnecting via insecure rfcomm"; |
353 | socketObject = remoteDevice.callObjectMethod("createInsecureRfcommSocketToServiceRecord", |
354 | "(Ljava/util/UUID;)Landroid/bluetooth/BluetoothSocket;", |
355 | uuidObject.object<jobject>()); |
356 | } else { |
357 | qCDebug(QT_BT_ANDROID) << "Connnecting via secure rfcomm"; |
358 | socketObject = remoteDevice.callObjectMethod("createRfcommSocketToServiceRecord", |
359 | "(Ljava/util/UUID;)Landroid/bluetooth/BluetoothSocket;", |
360 | uuidObject.object<jobject>()); |
361 | } |
362 | |
363 | if (env->ExceptionCheck()) { |
364 | env->ExceptionDescribe(); |
365 | env->ExceptionClear(); |
366 | |
367 | socketObject = remoteDevice = QAndroidJniObject(); |
368 | errorString = QBluetoothSocket::tr("Cannot connect to %1", |
369 | "%1 = uuid").arg(reverse.toString()); |
370 | q->setSocketError(QBluetoothSocket::ServiceNotFoundError); |
371 | q->setSocketState(QBluetoothSocket::UnconnectedState); |
372 | return false; |
373 | } |
374 | |
375 | WorkerThread *workerThread = new WorkerThread(); |
376 | workerThread->setupWorker(this, socketObject, uuidObject, USE_FALLBACK); |
377 | workerThread->start(); |
378 | emit connectJavaSocket(); |
379 | |
380 | return true; |
381 | } |
382 | |
383 | /* |
384 | * The call order during a connectToServiceHelper() is as follows: |
385 | * |
386 | * 1. call connectToServiceHelper() |
387 | * 2. wait for execution of SocketConnectThread::run() |
388 | * 3. if threaded connect succeeds call socketConnectSuccess() via signals |
389 | * -> done |
390 | * 4. if threaded connect fails call defaultSocketConnectFailed() via signals |
391 | * 5. call fallBackConnect() if Android version 22 or below |
392 | * -> Android 23+ complete failure of entire connectToServiceHelper() |
393 | * 6. call fallBackReversedConnect() if Android version 23 or above |
394 | * -> if failure entire connectToServiceHelper() fails |
395 | * 7. if threaded connect on one of above fallbacks succeeds call socketConnectSuccess() |
396 | * via signals |
397 | * -> done |
398 | * 8. if threaded connect on fallback channel fails call fallbackSocketConnectFailed() |
399 | * -> complete failure of entire connectToServiceHelper() |
400 | * */ |
401 | void QBluetoothSocketPrivateAndroid::connectToServiceHelper(const QBluetoothAddress &address, |
402 | const QBluetoothUuid &uuid, |
403 | QIODevice::OpenMode openMode) |
404 | { |
405 | Q_Q(QBluetoothSocket); |
406 | Q_UNUSED(openMode); |
407 | |
408 | qCDebug(QT_BT_ANDROID) << "connectToServiceHelper()"<< address.toString() << uuid.toString(); |
409 | |
410 | q->setSocketState(QBluetoothSocket::ConnectingState); |
411 | |
412 | if (!adapter.isValid()) { |
413 | qCWarning(QT_BT_ANDROID) << "Device does not support Bluetooth"; |
414 | errorString = QBluetoothSocket::tr("Device does not support Bluetooth"); |
415 | q->setSocketError(QBluetoothSocket::NetworkError); |
416 | q->setSocketState(QBluetoothSocket::UnconnectedState); |
417 | return; |
418 | } |
419 | |
420 | const int state = adapter.callMethod<jint>("getState"); |
421 | if (state != 12 ) { //BluetoothAdapter.STATE_ON |
422 | qCWarning(QT_BT_ANDROID) << "Bt device offline"; |
423 | errorString = QBluetoothSocket::tr("Device is powered off"); |
424 | q->setSocketError(QBluetoothSocket::NetworkError); |
425 | q->setSocketState(QBluetoothSocket::UnconnectedState); |
426 | return; |
427 | } |
428 | |
429 | QAndroidJniEnvironment env; |
430 | QAndroidJniObject inputString = QAndroidJniObject::fromString(address.toString()); |
431 | remoteDevice = adapter.callObjectMethod("getRemoteDevice", |
432 | "(Ljava/lang/String;)Landroid/bluetooth/BluetoothDevice;", |
433 | inputString.object<jstring>()); |
434 | if (env->ExceptionCheck()) { |
435 | env->ExceptionDescribe(); |
436 | env->ExceptionClear(); |
437 | |
438 | errorString = QBluetoothSocket::tr("Cannot access address %1", "%1 = Bt address e.g. 11:22:33:44:55:66").arg(address.toString()); |
439 | q->setSocketError(QBluetoothSocket::HostNotFoundError); |
440 | q->setSocketState(QBluetoothSocket::UnconnectedState); |
441 | return; |
442 | } |
443 | |
444 | //cut leading { and trailing } {xxx-xxx} |
445 | QString tempUuid = uuid.toString(); |
446 | tempUuid.chop(1); //remove trailing '}' |
447 | tempUuid.remove(0, 1); //remove first '{' |
448 | |
449 | inputString = QAndroidJniObject::fromString(tempUuid); |
450 | QAndroidJniObject uuidObject = QAndroidJniObject::callStaticObjectMethod("java/util/UUID", "fromString", |
451 | "(Ljava/lang/String;)Ljava/util/UUID;", |
452 | inputString.object<jstring>()); |
453 | |
454 | if (secFlags == QBluetooth::NoSecurity) { |
455 | qCDebug(QT_BT_ANDROID) << "Connnecting via insecure rfcomm"; |
456 | socketObject = remoteDevice.callObjectMethod("createInsecureRfcommSocketToServiceRecord", |
457 | "(Ljava/util/UUID;)Landroid/bluetooth/BluetoothSocket;", |
458 | uuidObject.object<jobject>()); |
459 | } else { |
460 | qCDebug(QT_BT_ANDROID) << "Connnecting via secure rfcomm"; |
461 | socketObject = remoteDevice.callObjectMethod("createRfcommSocketToServiceRecord", |
462 | "(Ljava/util/UUID;)Landroid/bluetooth/BluetoothSocket;", |
463 | uuidObject.object<jobject>()); |
464 | } |
465 | |
466 | if (env->ExceptionCheck()) { |
467 | env->ExceptionDescribe(); |
468 | env->ExceptionClear(); |
469 | |
470 | socketObject = remoteDevice = QAndroidJniObject(); |
471 | errorString = QBluetoothSocket::tr("Cannot connect to %1 on %2", |
472 | "%1 = uuid, %2 = Bt address").arg(uuid.toString()).arg(address.toString()); |
473 | q->setSocketError(QBluetoothSocket::ServiceNotFoundError); |
474 | q->setSocketState(QBluetoothSocket::UnconnectedState); |
475 | return; |
476 | } |
477 | |
478 | WorkerThread *workerThread = new WorkerThread(); |
479 | workerThread->setupWorker(this, socketObject, uuidObject, !USE_FALLBACK, uuid); |
480 | workerThread->start(); |
481 | emit connectJavaSocket(); |
482 | } |
483 | |
484 | void QBluetoothSocketPrivateAndroid::connectToService( |
485 | const QBluetoothServiceInfo &service, QIODevice::OpenMode openMode) |
486 | { |
487 | Q_Q(QBluetoothSocket); |
488 | |
489 | if (q->state() != QBluetoothSocket::UnconnectedState |
490 | && q->state() != QBluetoothSocket::ServiceLookupState) { |
491 | qCWarning(QT_BT_ANDROID) << "QBluetoothSocketPrivateAndroid::connectToService called on busy socket"; |
492 | errorString = QBluetoothSocket::tr("Trying to connect while connection is in progress"); |
493 | q->setSocketError(QBluetoothSocket::OperationError); |
494 | return; |
495 | } |
496 | |
497 | // Workaround for QTBUG-75035 |
498 | /* Not all Android devices publish or discover the SPP uuid for serial services. |
499 | * Also, Android does not permit the detection of the protocol used by a serial |
500 | * Bluetooth connection. |
501 | * |
502 | * Therefore, QBluetoothServiceDiscoveryAgentPrivate::populateDiscoveredServices() |
503 | * may have to guess what protocol a potential custom uuid uses. The guessing works |
504 | * reasonably well as long as the SDP discovery finds the SPP uuid. Otherwise |
505 | * the SPP and rfcomm protocol info is missing in \a service. |
506 | * |
507 | * Android only supports RFCOMM (no L2CP). We assume (in favor of user experience) |
508 | * that a non-RFCOMM protocol implies a missing SPP uuid during discovery but the user |
509 | * still wanting to connect with the given \a service instance. |
510 | */ |
511 | |
512 | auto protocol = service.socketProtocol(); |
513 | switch (protocol) { |
514 | case QBluetoothServiceInfo::L2capProtocol: |
515 | case QBluetoothServiceInfo::UnknownProtocol: |
516 | qCWarning(QT_BT_ANDROID) << "Changing socket protocol to RFCOMM"; |
517 | protocol = QBluetoothServiceInfo::RfcommProtocol; |
518 | break; |
519 | case QBluetoothServiceInfo::RfcommProtocol: |
520 | break; |
521 | } |
522 | |
523 | if (!ensureNativeSocket(protocol)) { |
524 | errorString = QBluetoothSocket::tr("Socket type not supported"); |
525 | q->setSocketError(QBluetoothSocket::UnsupportedProtocolError); |
526 | return; |
527 | } |
528 | connectToServiceHelper(service.device().address(), service.serviceUuid(), openMode); |
529 | } |
530 | |
531 | void QBluetoothSocketPrivateAndroid::connectToService( |
532 | const QBluetoothAddress &address, const QBluetoothUuid &uuid, |
533 | QIODevice::OpenMode openMode) |
534 | { |
535 | Q_Q(QBluetoothSocket); |
536 | |
537 | if (q->state() != QBluetoothSocket::UnconnectedState) { |
538 | qCWarning(QT_BT_ANDROID) << "QBluetoothSocketPrivateAndroid::connectToService called on busy socket"; |
539 | errorString = QBluetoothSocket::tr("Trying to connect while connection is in progress"); |
540 | q->setSocketError(QBluetoothSocket::OperationError); |
541 | return; |
542 | } |
543 | |
544 | if (q->socketType() == QBluetoothServiceInfo::UnknownProtocol) { |
545 | qCWarning(QT_BT_ANDROID) << "QBluetoothSocketPrivateAndroid::connectToService cannot " |
546 | "connect with 'UnknownProtocol' (type provided by given service)"; |
547 | errorString = QBluetoothSocket::tr("Socket type not supported"); |
548 | q->setSocketError(QBluetoothSocket::UnsupportedProtocolError); |
549 | return; |
550 | } |
551 | |
552 | if (!ensureNativeSocket(q->socketType())) { |
553 | errorString = QBluetoothSocket::tr("Socket type not supported"); |
554 | q->setSocketError(QBluetoothSocket::UnsupportedProtocolError); |
555 | return; |
556 | } |
557 | connectToServiceHelper(address, uuid, openMode); |
558 | } |
559 | |
560 | void QBluetoothSocketPrivateAndroid::connectToService( |
561 | const QBluetoothAddress &address, quint16 port, QIODevice::OpenMode openMode) |
562 | { |
563 | Q_UNUSED(port); |
564 | Q_UNUSED(openMode); |
565 | Q_UNUSED(address); |
566 | |
567 | Q_Q(QBluetoothSocket); |
568 | |
569 | errorString = tr("Connecting to port is not supported"); |
570 | q->setSocketError(QBluetoothSocket::ServiceNotFoundError); |
571 | qCWarning(QT_BT_ANDROID) << "Connecting to port is not supported"; |
572 | } |
573 | |
574 | void QBluetoothSocketPrivateAndroid::socketConnectSuccess(const QAndroidJniObject &socket) |
575 | { |
576 | Q_Q(QBluetoothSocket); |
577 | QAndroidJniEnvironment env; |
578 | |
579 | // test we didn't get a success from a previous connect |
580 | // which was cleaned up late |
581 | if (socket != socketObject) |
582 | return; |
583 | |
584 | if (inputThread) { |
585 | inputThread->deleteLater(); |
586 | inputThread = 0; |
587 | } |
588 | |
589 | inputStream = socketObject.callObjectMethod("getInputStream", "()Ljava/io/InputStream;"); |
590 | outputStream = socketObject.callObjectMethod("getOutputStream", "()Ljava/io/OutputStream;"); |
591 | |
592 | if (env->ExceptionCheck() || !inputStream.isValid() || !outputStream.isValid()) { |
593 | env->ExceptionDescribe(); |
594 | env->ExceptionClear(); |
595 | |
596 | emit closeJavaSocket(); |
597 | socketObject = inputStream = outputStream = remoteDevice = QAndroidJniObject(); |
598 | |
599 | |
600 | errorString = QBluetoothSocket::tr("Obtaining streams for service failed"); |
601 | q->setSocketError(QBluetoothSocket::NetworkError); |
602 | q->setSocketState(QBluetoothSocket::UnconnectedState); |
603 | return; |
604 | } |
605 | |
606 | inputThread = new InputStreamThread(this); |
607 | QObject::connect(inputThread, SIGNAL(dataAvailable()), |
608 | q, SIGNAL(readyRead()), Qt::QueuedConnection); |
609 | QObject::connect(inputThread, SIGNAL(error(int)), |
610 | this, SLOT(inputThreadError(int)), Qt::QueuedConnection); |
611 | |
612 | if (!inputThread->run()) { |
613 | //close socket again |
614 | emit closeJavaSocket(); |
615 | |
616 | socketObject = inputStream = outputStream = remoteDevice = QAndroidJniObject(); |
617 | |
618 | delete inputThread; |
619 | inputThread = 0; |
620 | |
621 | errorString = QBluetoothSocket::tr("Input stream thread cannot be started"); |
622 | q->setSocketError(QBluetoothSocket::NetworkError); |
623 | q->setSocketState(QBluetoothSocket::UnconnectedState); |
624 | return; |
625 | } |
626 | |
627 | // only unbuffered behavior supported at this stage |
628 | q->setOpenMode(QIODevice::ReadWrite|QIODevice::Unbuffered); |
629 | |
630 | q->setSocketState(QBluetoothSocket::ConnectedState); |
631 | } |
632 | |
633 | void QBluetoothSocketPrivateAndroid::defaultSocketConnectFailed( |
634 | const QAndroidJniObject &socket, const QAndroidJniObject &targetUuid, |
635 | const QBluetoothUuid &qtTargetUuid) |
636 | { |
637 | Q_Q(QBluetoothSocket); |
638 | |
639 | // test we didn't get a fail from a previous connect |
640 | // which was cleaned up late - should be same socket |
641 | if (socket != socketObject) |
642 | return; |
643 | |
644 | bool success = false; |
645 | if (QtAndroid::androidSdkVersion() <= 22) |
646 | success = fallBackConnect(targetUuid, FALLBACK_CHANNEL); |
647 | else if (useReverseUuidWorkAroundConnect) // version 23+ has Android bug (see QTBUG-61392) |
648 | success = fallBackReversedConnect(qtTargetUuid); |
649 | |
650 | if (!success) { |
651 | errorString = QBluetoothSocket::tr("Connection to service failed"); |
652 | socketObject = remoteDevice = QAndroidJniObject(); |
653 | q->setSocketError(QBluetoothSocket::ServiceNotFoundError); |
654 | q->setSocketState(QBluetoothSocket::UnconnectedState); |
655 | |
656 | QAndroidJniEnvironment env; |
657 | env->ExceptionClear(); // just in case |
658 | qCWarning(QT_BT_ANDROID) << "Workaround failed"; |
659 | } |
660 | } |
661 | |
662 | void QBluetoothSocketPrivateAndroid::fallbackSocketConnectFailed( |
663 | const QAndroidJniObject &socket, const QAndroidJniObject &targetUuid) |
664 | { |
665 | Q_UNUSED(targetUuid); |
666 | Q_Q(QBluetoothSocket); |
667 | |
668 | // test we didn't get a fail from a previous connect |
669 | // which was cleaned up late - should be same socket |
670 | if (socket != socketObject) |
671 | return; |
672 | |
673 | qCWarning(QT_BT_ANDROID) << "Socket connect via workaround failed."; |
674 | errorString = QBluetoothSocket::tr("Connection to service failed"); |
675 | socketObject = remoteDevice = QAndroidJniObject(); |
676 | |
677 | q->setSocketError(QBluetoothSocket::ServiceNotFoundError); |
678 | q->setSocketState(QBluetoothSocket::UnconnectedState); |
679 | } |
680 | |
681 | void QBluetoothSocketPrivateAndroid::abort() |
682 | { |
683 | if (state == QBluetoothSocket::UnconnectedState) |
684 | return; |
685 | |
686 | if (socketObject.isValid()) { |
687 | QAndroidJniEnvironment env; |
688 | |
689 | /* |
690 | * BluetoothSocket.close() triggers an abort of the input stream |
691 | * thread because inputStream.read() throws IOException |
692 | * In turn the thread stops and throws an error which sets |
693 | * new state, error and emits relevant signals. |
694 | * See QBluetoothSocketPrivateAndroid::inputThreadError() for details |
695 | */ |
696 | |
697 | if (inputThread) |
698 | inputThread->prepareForClosure(); |
699 | |
700 | emit closeJavaSocket(); |
701 | |
702 | inputStream = outputStream = socketObject = remoteDevice = QAndroidJniObject(); |
703 | |
704 | if (inputThread) { |
705 | // inputThread exists hence we had a successful connect |
706 | // which means inputThread is responsible for setting Unconnected |
707 | |
708 | //don't delete here as signals caused by Java Thread are still |
709 | //going to be emitted |
710 | //delete occurs in inputThreadError() |
711 | inputThread = 0; |
712 | } else { |
713 | // inputThread doesn't exist hence |
714 | // we abort in the middle of connect(). WorkerThread will do |
715 | // close() without further feedback. Therefore we have to set |
716 | // Unconnected (now) in advance |
717 | Q_Q(QBluetoothSocket); |
718 | q->setOpenMode(QIODevice::NotOpen); |
719 | q->setSocketState(QBluetoothSocket::UnconnectedState); |
720 | emit q->readChannelFinished(); |
721 | } |
722 | } |
723 | } |
724 | |
725 | QString QBluetoothSocketPrivateAndroid::localName() const |
726 | { |
727 | if (adapter.isValid()) |
728 | return adapter.callObjectMethod<jstring>("getName").toString(); |
729 | |
730 | return QString(); |
731 | } |
732 | |
733 | QBluetoothAddress QBluetoothSocketPrivateAndroid::localAddress() const |
734 | { |
735 | QString result; |
736 | if (adapter.isValid()) |
737 | result = adapter.callObjectMethod("getAddress", "()Ljava/lang/String;").toString(); |
738 | |
739 | return QBluetoothAddress(result); |
740 | } |
741 | |
742 | quint16 QBluetoothSocketPrivateAndroid::localPort() const |
743 | { |
744 | // Impossible to get channel number with current Android API (Levels 5 to 19) |
745 | return 0; |
746 | } |
747 | |
748 | QString QBluetoothSocketPrivateAndroid::peerName() const |
749 | { |
750 | if (!remoteDevice.isValid()) |
751 | return QString(); |
752 | |
753 | return remoteDevice.callObjectMethod("getName", "()Ljava/lang/String;").toString(); |
754 | } |
755 | |
756 | QBluetoothAddress QBluetoothSocketPrivateAndroid::peerAddress() const |
757 | { |
758 | if (!remoteDevice.isValid()) |
759 | return QBluetoothAddress(); |
760 | |
761 | const QString address = remoteDevice.callObjectMethod("getAddress", |
762 | "()Ljava/lang/String;").toString(); |
763 | |
764 | return QBluetoothAddress(address); |
765 | } |
766 | |
767 | quint16 QBluetoothSocketPrivateAndroid::peerPort() const |
768 | { |
769 | // Impossible to get channel number with current Android API (Levels 5 to 13) |
770 | return 0; |
771 | } |
772 | |
773 | qint64 QBluetoothSocketPrivateAndroid::writeData(const char *data, qint64 maxSize) |
774 | { |
775 | //TODO implement buffered behavior (so far only unbuffered) |
776 | Q_Q(QBluetoothSocket); |
777 | if (state != QBluetoothSocket::ConnectedState || !outputStream.isValid()) { |
778 | qCWarning(QT_BT_ANDROID) << "Socket::writeData: "<< state << outputStream.isValid(); |
779 | errorString = QBluetoothSocket::tr("Cannot write while not connected"); |
780 | q->setSocketError(QBluetoothSocket::OperationError); |
781 | return -1; |
782 | } |
783 | |
784 | QAndroidJniEnvironment env; |
785 | jbyteArray nativeData = env->NewByteArray((qint32)maxSize); |
786 | env->SetByteArrayRegion(nativeData, 0, (qint32)maxSize, reinterpret_cast<const jbyte*>(data)); |
787 | outputStream.callMethod<void>("write", "([BII)V", nativeData, 0, (qint32)maxSize); |
788 | env->DeleteLocalRef(nativeData); |
789 | |
790 | if (env->ExceptionCheck()) { |
791 | qCWarning(QT_BT_ANDROID) << "Error while writing"; |
792 | env->ExceptionDescribe(); |
793 | env->ExceptionClear(); |
794 | errorString = QBluetoothSocket::tr("Error during write on socket."); |
795 | q->setSocketError(QBluetoothSocket::NetworkError); |
796 | return -1; |
797 | } |
798 | |
799 | emit q->bytesWritten(maxSize); |
800 | return maxSize; |
801 | } |
802 | |
803 | qint64 QBluetoothSocketPrivateAndroid::readData(char *data, qint64 maxSize) |
804 | { |
805 | Q_Q(QBluetoothSocket); |
806 | if (state != QBluetoothSocket::ConnectedState || !inputThread) { |
807 | qCWarning(QT_BT_ANDROID) << "Socket::readData: "<< state << inputThread ; |
808 | errorString = QBluetoothSocket::tr("Cannot read while not connected"); |
809 | q->setSocketError(QBluetoothSocket::OperationError); |
810 | return -1; |
811 | } |
812 | |
813 | return inputThread->readData(data, maxSize); |
814 | } |
815 | |
816 | void QBluetoothSocketPrivateAndroid::inputThreadError(int errorCode) |
817 | { |
818 | Q_Q(QBluetoothSocket); |
819 | |
820 | if (errorCode != -1) { //magic error which is expected and can be ignored |
821 | errorString = QBluetoothSocket::tr("Network error during read"); |
822 | q->setSocketError(QBluetoothSocket::NetworkError); |
823 | } |
824 | |
825 | //finally we can delete the InputStreamThread |
826 | InputStreamThread *client = qobject_cast<InputStreamThread *>(sender()); |
827 | if (client) |
828 | client->deleteLater(); |
829 | |
830 | if (socketObject.isValid()) { |
831 | //triggered when remote side closed the socket |
832 | //cleanup internal objects |
833 | //if it was call to local close()/abort() the objects are cleaned up already |
834 | |
835 | emit closeJavaSocket(); |
836 | |
837 | inputStream = outputStream = remoteDevice = socketObject = QAndroidJniObject(); |
838 | if (inputThread) { |
839 | // deleted already above (client->deleteLater()) |
840 | inputThread = 0; |
841 | } |
842 | } |
843 | |
844 | q->setOpenMode(QIODevice::NotOpen); |
845 | q->setSocketState(QBluetoothSocket::UnconnectedState); |
846 | emit q->readChannelFinished(); |
847 | } |
848 | |
849 | void QBluetoothSocketPrivateAndroid::close() |
850 | { |
851 | /* This function is called by QBluetoothSocket::close and softer version |
852 | QBluetoothSocket::disconnectFromService() which difference I do not quite fully understand. |
853 | Anyways we end up in Android "close" function call. |
854 | */ |
855 | abort(); |
856 | } |
857 | |
858 | bool QBluetoothSocketPrivateAndroid::setSocketDescriptor(int socketDescriptor, QBluetoothServiceInfo::Protocol socketType, |
859 | QBluetoothSocket::SocketState socketState, QBluetoothSocket::OpenMode openMode) |
860 | { |
861 | Q_UNUSED(socketDescriptor); |
862 | Q_UNUSED(socketType) |
863 | Q_UNUSED(socketState); |
864 | Q_UNUSED(openMode); |
865 | qCWarning(QT_BT_ANDROID) << "No socket descriptor support on Android."; |
866 | return false; |
867 | } |
868 | |
869 | bool QBluetoothSocketPrivateAndroid::setSocketDescriptor(const QAndroidJniObject &socket, QBluetoothServiceInfo::Protocol socketType_, |
870 | QBluetoothSocket::SocketState socketState, QBluetoothSocket::OpenMode openMode) |
871 | { |
872 | Q_Q(QBluetoothSocket); |
873 | |
874 | if (q->state() != QBluetoothSocket::UnconnectedState || !socket.isValid()) |
875 | return false; |
876 | |
877 | if (!ensureNativeSocket(socketType_)) |
878 | return false; |
879 | |
880 | socketObject = socket; |
881 | |
882 | QAndroidJniEnvironment env; |
883 | inputStream = socketObject.callObjectMethod("getInputStream", "()Ljava/io/InputStream;"); |
884 | outputStream = socketObject.callObjectMethod("getOutputStream", "()Ljava/io/OutputStream;"); |
885 | |
886 | if (env->ExceptionCheck() || !inputStream.isValid() || !outputStream.isValid()) { |
887 | env->ExceptionDescribe(); |
888 | env->ExceptionClear(); |
889 | |
890 | //close socket again |
891 | socketObject.callMethod<void>("close"); |
892 | if (env->ExceptionCheck()) { |
893 | env->ExceptionDescribe(); |
894 | env->ExceptionClear(); |
895 | } |
896 | |
897 | socketObject = inputStream = outputStream = remoteDevice = QAndroidJniObject(); |
898 | |
899 | |
900 | errorString = QBluetoothSocket::tr("Obtaining streams for service failed"); |
901 | q->setSocketError(QBluetoothSocket::NetworkError); |
902 | q->setSocketState(QBluetoothSocket::UnconnectedState); |
903 | return false; |
904 | } |
905 | |
906 | remoteDevice = socketObject.callObjectMethod("getRemoteDevice", "()Landroid/bluetooth/BluetoothDevice;"); |
907 | |
908 | if (inputThread) { |
909 | inputThread->deleteLater(); |
910 | inputThread = 0; |
911 | } |
912 | inputThread = new InputStreamThread(this); |
913 | QObject::connect(inputThread, SIGNAL(dataAvailable()), |
914 | q, SIGNAL(readyRead()), Qt::QueuedConnection); |
915 | QObject::connect(inputThread, SIGNAL(error(int)), |
916 | this, SLOT(inputThreadError(int)), Qt::QueuedConnection); |
917 | inputThread->run(); |
918 | |
919 | // WorkerThread manages all sockets for us |
920 | // When we come through here the socket was already connected by |
921 | // server socket listener (see QBluetoothServer) |
922 | // Therefore we only use WorkerThread to potentially close it later on |
923 | WorkerThread *workerThread = new WorkerThread(); |
924 | workerThread->setupWorker(this, socketObject, QAndroidJniObject(), !USE_FALLBACK); |
925 | workerThread->start(); |
926 | |
927 | q->setOpenMode(openMode | QIODevice::Unbuffered); |
928 | q->setSocketState(socketState); |
929 | |
930 | return true; |
931 | } |
932 | |
933 | qint64 QBluetoothSocketPrivateAndroid::bytesAvailable() const |
934 | { |
935 | //We cannot access buffer directly as it is part of different thread |
936 | if (inputThread) |
937 | return inputThread->bytesAvailable(); |
938 | |
939 | return 0; |
940 | } |
941 | |
942 | qint64 QBluetoothSocketPrivateAndroid::bytesToWrite() const |
943 | { |
944 | return 0; // nothing because always unbuffered |
945 | } |
946 | |
947 | /* |
948 | * This function is part of a workaround for QTBUG-61392 |
949 | * |
950 | * Returns null uuid if the given \a serviceUuid is not a uuid |
951 | * derived from the Bluetooth base uuid. |
952 | */ |
953 | QBluetoothUuid QBluetoothSocketPrivateAndroid::reverseUuid(const QBluetoothUuid &serviceUuid) |
954 | { |
955 | if (QtAndroid::androidSdkVersion() < 23) |
956 | return serviceUuid; |
957 | |
958 | if (serviceUuid.isNull()) |
959 | return QBluetoothUuid(); |
960 | |
961 | bool isBaseUuid = false; |
962 | serviceUuid.toUInt32(&isBaseUuid); |
963 | if (isBaseUuid) |
964 | return serviceUuid; |
965 | |
966 | const quint128 original = serviceUuid.toUInt128(); |
967 | quint128 reversed; |
968 | for (int i = 0; i < 16; i++) |
969 | reversed.data[15-i] = original.data[i]; |
970 | return QBluetoothUuid{reversed}; |
971 | } |
972 | |
973 | bool QBluetoothSocketPrivateAndroid::canReadLine() const |
974 | { |
975 | // We cannot access buffer directly as it is part of different thread |
976 | if (inputThread) |
977 | return inputThread->canReadLine(); |
978 | |
979 | return false; |
980 | } |
981 | |
982 | QT_END_NAMESPACE |
983 | |
984 | #include <qbluetoothsocket_android.moc> |
985 |
Warning: That file was not part of the compilation database. It may have many parsing errors.