1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtBluetooth module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | #include <QtTest/QtTest> |
30 | |
31 | #include <QDebug> |
32 | #include <QVariant> |
33 | #include <QList> |
34 | #include <QLoggingCategory> |
35 | |
36 | #include <private/qtbluetoothglobal_p.h> |
37 | #include <qbluetoothaddress.h> |
38 | #include <qbluetoothdevicediscoveryagent.h> |
39 | #include <qbluetoothlocaldevice.h> |
40 | |
41 | QT_USE_NAMESPACE |
42 | |
43 | /* |
44 | * Some parts of this test require a remote and discoverable Bluetooth |
45 | * device. Setting the BT_TEST_DEVICE environment variable will |
46 | * set the test up to fail if it cannot find a remote device. |
47 | * BT_TEST_DEVICE should contain the address of the device the |
48 | * test expects to find. Ensure that the device is running |
49 | * in discovery mode. |
50 | **/ |
51 | |
52 | // Maximum time to for bluetooth device scan |
53 | const int MaxScanTime = 5 * 60 * 1000; // 5 minutes in ms |
54 | |
55 | //Bluez needs at least 10s for a device discovery to be cancelled |
56 | const int MaxWaitForCancelTime = 15 * 1000; // 15 seconds in ms |
57 | |
58 | class tst_QBluetoothDeviceDiscoveryAgent : public QObject |
59 | { |
60 | Q_OBJECT |
61 | |
62 | public: |
63 | tst_QBluetoothDeviceDiscoveryAgent(); |
64 | ~tst_QBluetoothDeviceDiscoveryAgent(); |
65 | |
66 | public slots: |
67 | void deviceDiscoveryDebug(const QBluetoothDeviceInfo &info); |
68 | void finished(); |
69 | |
70 | private slots: |
71 | void initTestCase(); |
72 | |
73 | void tst_properties(); |
74 | |
75 | void tst_invalidBtAddress(); |
76 | |
77 | void tst_startStopDeviceDiscoveries(); |
78 | |
79 | void tst_deviceDiscovery_data(); |
80 | void tst_deviceDiscovery(); |
81 | |
82 | void tst_discoveryTimeout(); |
83 | |
84 | void tst_discoveryMethods(); |
85 | private: |
86 | int noOfLocalDevices; |
87 | bool isBluez5Runtime = false; |
88 | }; |
89 | |
90 | tst_QBluetoothDeviceDiscoveryAgent::tst_QBluetoothDeviceDiscoveryAgent() |
91 | { |
92 | QLoggingCategory::setFilterRules(QStringLiteral("qt.bluetooth* = true" )); |
93 | qRegisterMetaType<QBluetoothDeviceDiscoveryAgent::Error>(); |
94 | } |
95 | |
96 | tst_QBluetoothDeviceDiscoveryAgent::~tst_QBluetoothDeviceDiscoveryAgent() |
97 | { |
98 | } |
99 | |
100 | #if QT_CONFIG(bluez) |
101 | // This section was adopted from tst_qloggingcategory.cpp |
102 | QString logMessage; |
103 | |
104 | QByteArray qMyMessageFormatString(QtMsgType type, const QMessageLogContext &context, |
105 | const QString &str) |
106 | { |
107 | QByteArray message; |
108 | message.append(context.category); |
109 | switch (type) { |
110 | case QtDebugMsg: message.append(".debug" ); break; |
111 | case QtInfoMsg: message.append(".info" ); break; |
112 | case QtWarningMsg: message.append(".warning" ); break; |
113 | case QtCriticalMsg:message.append(".critical" ); break; |
114 | case QtFatalMsg: message.append(".fatal" ); break; |
115 | } |
116 | message.append(": " ); |
117 | message.append(qPrintable(str)); |
118 | |
119 | return message.simplified(); |
120 | } |
121 | |
122 | static void myCustomMessageHandler(QtMsgType type, |
123 | const QMessageLogContext &context, |
124 | const QString &msg) |
125 | { |
126 | logMessage = qMyMessageFormatString(type, context, msg); |
127 | } |
128 | #endif |
129 | |
130 | |
131 | |
132 | void tst_QBluetoothDeviceDiscoveryAgent::initTestCase() |
133 | { |
134 | qRegisterMetaType<QBluetoothDeviceInfo>(); |
135 | qRegisterMetaType<QBluetoothDeviceDiscoveryAgent::InquiryType>(); |
136 | |
137 | #if QT_CONFIG(bluez) |
138 | // To distinguish Bluez 4 and 5 we peek into the debug output |
139 | // of first Bluetooth ctor. It executes a runtime test and prints the result |
140 | // as logging output. This avoids more complex runtime detection logic within this unit test. |
141 | QtMessageHandler oldMessageHandler; |
142 | oldMessageHandler = qInstallMessageHandler(myCustomMessageHandler); |
143 | |
144 | noOfLocalDevices = QBluetoothLocalDevice::allDevices().count(); |
145 | qInstallMessageHandler(oldMessageHandler); |
146 | isBluez5Runtime = logMessage.contains(QStringLiteral("Bluez 5" )); |
147 | if (isBluez5Runtime) |
148 | qDebug() << "BlueZ 5 runtime detected." ; |
149 | #else |
150 | noOfLocalDevices = QBluetoothLocalDevice::allDevices().count(); |
151 | #endif |
152 | |
153 | if (!noOfLocalDevices) |
154 | return; |
155 | |
156 | // turn on BT in case it is not on |
157 | QBluetoothLocalDevice *device = new QBluetoothLocalDevice(); |
158 | if (device->hostMode() == QBluetoothLocalDevice::HostPoweredOff) { |
159 | QSignalSpy hostModeSpy(device, SIGNAL(hostModeStateChanged(QBluetoothLocalDevice::HostMode))); |
160 | QVERIFY(hostModeSpy.isEmpty()); |
161 | device->powerOn(); |
162 | int connectTime = 5000; // ms |
163 | while (hostModeSpy.count() < 1 && connectTime > 0) { |
164 | QTest::qWait(ms: 500); |
165 | connectTime -= 500; |
166 | } |
167 | QVERIFY(hostModeSpy.count() > 0); |
168 | } |
169 | QBluetoothLocalDevice::HostMode hostMode= device->hostMode(); |
170 | QVERIFY(hostMode == QBluetoothLocalDevice::HostConnectable |
171 | || hostMode == QBluetoothLocalDevice::HostDiscoverable |
172 | || hostMode == QBluetoothLocalDevice::HostDiscoverableLimitedInquiry); |
173 | delete device; |
174 | } |
175 | |
176 | void tst_QBluetoothDeviceDiscoveryAgent::tst_properties() |
177 | { |
178 | QBluetoothDeviceDiscoveryAgent discoveryAgent; |
179 | |
180 | QCOMPARE(discoveryAgent.inquiryType(), QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry); |
181 | discoveryAgent.setInquiryType(QBluetoothDeviceDiscoveryAgent::LimitedInquiry); |
182 | QCOMPARE(discoveryAgent.inquiryType(), QBluetoothDeviceDiscoveryAgent::LimitedInquiry); |
183 | discoveryAgent.setInquiryType(QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry); |
184 | QCOMPARE(discoveryAgent.inquiryType(), QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry); |
185 | } |
186 | |
187 | void tst_QBluetoothDeviceDiscoveryAgent::tst_invalidBtAddress() |
188 | { |
189 | QBluetoothDeviceDiscoveryAgent *discoveryAgent = new QBluetoothDeviceDiscoveryAgent(QBluetoothAddress("11:11:11:11:11:11" )); |
190 | |
191 | QCOMPARE(discoveryAgent->error(), QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError); |
192 | discoveryAgent->start(); |
193 | QCOMPARE(discoveryAgent->isActive(), false); |
194 | delete discoveryAgent; |
195 | |
196 | discoveryAgent = new QBluetoothDeviceDiscoveryAgent(QBluetoothAddress()); |
197 | QCOMPARE(discoveryAgent->error(), QBluetoothDeviceDiscoveryAgent::NoError); |
198 | if (QBluetoothLocalDevice::allDevices().count() > 0) { |
199 | discoveryAgent->start(); |
200 | QCOMPARE(discoveryAgent->isActive(), true); |
201 | } |
202 | delete discoveryAgent; |
203 | } |
204 | |
205 | void tst_QBluetoothDeviceDiscoveryAgent::deviceDiscoveryDebug(const QBluetoothDeviceInfo &info) |
206 | { |
207 | qDebug() << "Discovered device:" << info.address().toString() << info.name(); |
208 | } |
209 | |
210 | void tst_QBluetoothDeviceDiscoveryAgent::tst_startStopDeviceDiscoveries() |
211 | { |
212 | QBluetoothDeviceDiscoveryAgent::InquiryType inquiryType = QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry; |
213 | QBluetoothDeviceDiscoveryAgent discoveryAgent; |
214 | |
215 | QVERIFY(discoveryAgent.error() == discoveryAgent.NoError); |
216 | QVERIFY(discoveryAgent.errorString().isEmpty()); |
217 | QVERIFY(!discoveryAgent.isActive()); |
218 | QVERIFY(discoveryAgent.discoveredDevices().isEmpty()); |
219 | |
220 | QSignalSpy finishedSpy(&discoveryAgent, SIGNAL(finished())); |
221 | QSignalSpy cancelSpy(&discoveryAgent, SIGNAL(canceled())); |
222 | QSignalSpy errorSpy(&discoveryAgent, SIGNAL(error(QBluetoothDeviceDiscoveryAgent::Error))); |
223 | |
224 | // Starting case 1: start-stop, expecting cancel signal |
225 | discoveryAgent.setInquiryType(inquiryType); |
226 | // we should have no errors at this point. |
227 | QVERIFY(errorSpy.isEmpty()); |
228 | |
229 | discoveryAgent.start(); |
230 | |
231 | if (errorSpy.isEmpty()) { |
232 | QVERIFY(discoveryAgent.isActive()); |
233 | QCOMPARE(discoveryAgent.errorString(), QString()); |
234 | QCOMPARE(discoveryAgent.error(), QBluetoothDeviceDiscoveryAgent::NoError); |
235 | } else { |
236 | QCOMPARE(noOfLocalDevices, 0); |
237 | QVERIFY(!discoveryAgent.isActive()); |
238 | QVERIFY(!discoveryAgent.errorString().isEmpty()); |
239 | QVERIFY(discoveryAgent.error() != QBluetoothDeviceDiscoveryAgent::NoError); |
240 | QSKIP("No local Bluetooth device available. Skipping remaining part of test." ); |
241 | } |
242 | // cancel current request. |
243 | discoveryAgent.stop(); |
244 | |
245 | // Wait for up to MaxWaitForCancelTime for the cancel to finish |
246 | int waitTime = MaxWaitForCancelTime; |
247 | while (cancelSpy.count() == 0 && waitTime > 0) { |
248 | QTest::qWait(ms: 100); |
249 | waitTime-=100; |
250 | } |
251 | |
252 | // we should not be active anymore |
253 | QVERIFY(!discoveryAgent.isActive()); |
254 | QVERIFY(errorSpy.isEmpty()); |
255 | QCOMPARE(cancelSpy.count(), 1); |
256 | cancelSpy.clear(); |
257 | // Starting case 2: start-start-stop, expecting cancel signal |
258 | discoveryAgent.start(); |
259 | // we should be active now |
260 | QVERIFY(discoveryAgent.isActive()); |
261 | QVERIFY(errorSpy.isEmpty()); |
262 | // start again. should this be error? |
263 | discoveryAgent.start(); |
264 | QVERIFY(discoveryAgent.isActive()); |
265 | QVERIFY(errorSpy.isEmpty()); |
266 | // stop |
267 | discoveryAgent.stop(); |
268 | |
269 | // Wait for up to MaxWaitForCancelTime for the cancel to finish |
270 | waitTime = MaxWaitForCancelTime; |
271 | while (cancelSpy.count() == 0 && waitTime > 0) { |
272 | QTest::qWait(ms: 100); |
273 | waitTime-=100; |
274 | } |
275 | |
276 | // we should not be active anymore |
277 | QVERIFY(!discoveryAgent.isActive()); |
278 | QVERIFY(errorSpy.isEmpty()); |
279 | QVERIFY(cancelSpy.count() == 1); |
280 | cancelSpy.clear(); |
281 | |
282 | // Starting case 3: stop |
283 | discoveryAgent.stop(); |
284 | QVERIFY(!discoveryAgent.isActive()); |
285 | QVERIFY(errorSpy.isEmpty()); |
286 | |
287 | // Don't expect finished signal and no error |
288 | QVERIFY(finishedSpy.count() == 0); |
289 | QVERIFY(discoveryAgent.error() == discoveryAgent.NoError); |
290 | QVERIFY(discoveryAgent.errorString().isEmpty()); |
291 | |
292 | /* |
293 | Starting case 4: start-stop-start-stop: |
294 | We are testing that two subsequent stop() calls reduce total number |
295 | of cancel() signals to 1 if the true cancellation requires |
296 | asynchronous function calls (signal consolidation); otherwise we |
297 | expect 2x cancel() signal. |
298 | |
299 | Examples are: |
300 | - Bluez4 (event loop needs to run for cancel) |
301 | - Bluez5 (no event loop required) |
302 | */ |
303 | |
304 | bool immediateSignal = false; |
305 | discoveryAgent.start(); |
306 | QVERIFY(discoveryAgent.isActive()); |
307 | QVERIFY(errorSpy.isEmpty()); |
308 | // cancel current request. |
309 | discoveryAgent.stop(); |
310 | //should only have triggered cancel() if stop didn't involve the event loop |
311 | if (cancelSpy.count() == 1) immediateSignal = true; |
312 | |
313 | // start a new one |
314 | discoveryAgent.start(); |
315 | // we should be active now |
316 | QVERIFY(discoveryAgent.isActive()); |
317 | QVERIFY(errorSpy.isEmpty()); |
318 | // stop |
319 | discoveryAgent.stop(); |
320 | if (immediateSignal) |
321 | QVERIFY(cancelSpy.count() == 2); |
322 | |
323 | // Wait for up to MaxWaitForCancelTime for the cancel to finish |
324 | waitTime = MaxWaitForCancelTime; |
325 | while (cancelSpy.count() == 0 && waitTime > 0) { |
326 | QTest::qWait(ms: 100); |
327 | waitTime-=100; |
328 | } |
329 | // we should not be active anymore |
330 | QVERIFY(!discoveryAgent.isActive()); |
331 | QVERIFY(errorSpy.isEmpty()); |
332 | // should only have 1 cancel |
333 | |
334 | if (immediateSignal) |
335 | QVERIFY(cancelSpy.count() == 2); |
336 | else |
337 | QVERIFY(cancelSpy.count() == 1); |
338 | cancelSpy.clear(); |
339 | |
340 | // Starting case 5: start-stop-start: expecting finished signal & no cancel |
341 | discoveryAgent.start(); |
342 | QVERIFY(discoveryAgent.isActive()); |
343 | QVERIFY(errorSpy.isEmpty()); |
344 | // cancel current request. |
345 | discoveryAgent.stop(); |
346 | // start a new one |
347 | discoveryAgent.start(); |
348 | // we should be active now |
349 | QVERIFY(discoveryAgent.isActive()); |
350 | QVERIFY(errorSpy.isEmpty()); |
351 | |
352 | // Wait for up to MaxScanTime for the cancel to finish |
353 | waitTime = MaxScanTime; |
354 | while (finishedSpy.count() == 0 && waitTime > 0) { |
355 | QTest::qWait(ms: 1000); |
356 | waitTime-=1000; |
357 | } |
358 | |
359 | // we should not be active anymore |
360 | QVERIFY(!discoveryAgent.isActive()); |
361 | QVERIFY(errorSpy.isEmpty()); |
362 | // should only have 1 cancel |
363 | QVERIFY(finishedSpy.count() == 1); |
364 | |
365 | // On OS X, stop is synchronous (signal will be emitted immediately). |
366 | if (!immediateSignal) |
367 | QVERIFY(cancelSpy.isEmpty()); |
368 | } |
369 | |
370 | void tst_QBluetoothDeviceDiscoveryAgent::finished() |
371 | { |
372 | qDebug() << "Finished called" ; |
373 | } |
374 | |
375 | void tst_QBluetoothDeviceDiscoveryAgent::tst_deviceDiscovery_data() |
376 | { |
377 | QTest::addColumn<QBluetoothDeviceDiscoveryAgent::InquiryType>(name: "inquiryType" ); |
378 | |
379 | QTest::newRow(dataTag: "general unlimited inquiry" ) << QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry; |
380 | QTest::newRow(dataTag: "limited inquiry" ) << QBluetoothDeviceDiscoveryAgent::LimitedInquiry; |
381 | } |
382 | |
383 | void tst_QBluetoothDeviceDiscoveryAgent::tst_deviceDiscovery() |
384 | { |
385 | { |
386 | QFETCH(QBluetoothDeviceDiscoveryAgent::InquiryType, inquiryType); |
387 | |
388 | //Run test in case of multiple Bluetooth adapters |
389 | QBluetoothLocalDevice localDevice; |
390 | //We will use default adapter if there is no other adapter |
391 | QBluetoothAddress address = localDevice.address(); |
392 | int numberOfAdapters = (localDevice.allDevices()).size(); |
393 | QList<QBluetoothAddress> addresses; |
394 | if (numberOfAdapters > 1) { |
395 | |
396 | for (int i=0; i < numberOfAdapters; i++) { |
397 | addresses.append(t: ((QBluetoothHostInfo)localDevice.allDevices().at(i)).address()); |
398 | } |
399 | address = (QBluetoothAddress)addresses.at(i: 0); |
400 | } |
401 | |
402 | QBluetoothDeviceDiscoveryAgent discoveryAgent(address); |
403 | QVERIFY(discoveryAgent.error() == discoveryAgent.NoError); |
404 | QVERIFY(discoveryAgent.errorString().isEmpty()); |
405 | QVERIFY(!discoveryAgent.isActive()); |
406 | |
407 | QVERIFY(discoveryAgent.discoveredDevices().isEmpty()); |
408 | |
409 | QSignalSpy finishedSpy(&discoveryAgent, SIGNAL(finished())); |
410 | QSignalSpy errorSpy(&discoveryAgent, SIGNAL(error(QBluetoothDeviceDiscoveryAgent::Error))); |
411 | QSignalSpy discoveredSpy(&discoveryAgent, SIGNAL(deviceDiscovered(QBluetoothDeviceInfo))); |
412 | // connect(&discoveryAgent, SIGNAL(finished()), this, SLOT(finished())); |
413 | // connect(&discoveryAgent, SIGNAL(deviceDiscovered(QBluetoothDeviceInfo)), |
414 | // this, SLOT(deviceDiscoveryDebug(QBluetoothDeviceInfo))); |
415 | |
416 | discoveryAgent.setInquiryType(inquiryType); |
417 | discoveryAgent.start(); |
418 | if (!errorSpy.isEmpty()) { |
419 | QCOMPARE(noOfLocalDevices, 0); |
420 | QVERIFY(!discoveryAgent.isActive()); |
421 | QSKIP("No local Bluetooth device available. Skipping remaining part of test." ); |
422 | } |
423 | |
424 | QVERIFY(discoveryAgent.isActive()); |
425 | |
426 | // Wait for up to MaxScanTime for the scan to finish |
427 | int scanTime = MaxScanTime; |
428 | while (finishedSpy.count() == 0 && scanTime > 0) { |
429 | QTest::qWait(ms: 15000); |
430 | scanTime -= 15000; |
431 | } |
432 | |
433 | // verify that we are finished |
434 | QVERIFY(!discoveryAgent.isActive()); |
435 | // stop |
436 | discoveryAgent.stop(); |
437 | QVERIFY(!discoveryAgent.isActive()); |
438 | qDebug() << "Scan time left:" << scanTime; |
439 | // Expect finished signal with no error |
440 | QVERIFY(finishedSpy.count() == 1); |
441 | QVERIFY(errorSpy.isEmpty()); |
442 | QVERIFY(discoveryAgent.error() == discoveryAgent.NoError); |
443 | QVERIFY(discoveryAgent.errorString().isEmpty()); |
444 | |
445 | // verify that the list is as big as the signals received. |
446 | QVERIFY(discoveredSpy.count() == discoveryAgent.discoveredDevices().length()); |
447 | // verify that there really was some devices in the array |
448 | |
449 | const QString remote = qgetenv(varName: "BT_TEST_DEVICE" ); |
450 | QBluetoothAddress remoteDevice; |
451 | if (!remote.isEmpty()) { |
452 | remoteDevice = QBluetoothAddress(remote); |
453 | QVERIFY2(!remote.isNull(), "Expecting valid Bluetooth address to be passed via BT_TEST_DEVICE" ); |
454 | } else { |
455 | qWarning() << "Not using a remote device for testing. Set BT_TEST_DEVICE env to run extended tests involving a remote device" ; |
456 | } |
457 | |
458 | if (!remoteDevice.isNull()) |
459 | QVERIFY(discoveredSpy.count() > 0); |
460 | int counter = 0; |
461 | // All returned QBluetoothDeviceInfo should be valid. |
462 | while (!discoveredSpy.isEmpty()) { |
463 | const QBluetoothDeviceInfo info = |
464 | qvariant_cast<QBluetoothDeviceInfo>(v: discoveredSpy.takeFirst().at(i: 0)); |
465 | QVERIFY(info.isValid()); |
466 | qDebug() << "Discovered device:" << info.address().toString() << info.name(); |
467 | |
468 | if (numberOfAdapters > 1) { |
469 | for (int i= 1; i < numberOfAdapters; i++) { |
470 | if (info.address().toString() == addresses[i].toString()) |
471 | counter++; |
472 | } |
473 | } |
474 | } |
475 | #if defined(Q_OS_IOS) || defined(Q_OS_TVOS) || QT_CONFIG(winrt_bt) |
476 | //On iOS/WinRT, we do not have access to the local device/adapter, numberOfAdapters is 0, |
477 | //so we skip this test at all. |
478 | QSKIP("iOS/WinRT: no local Bluetooth device available. Skipping remaining part of test." ); |
479 | #endif |
480 | |
481 | //For multiple Bluetooth adapter do the check only for GeneralUnlimitedInquiry. |
482 | if (!(inquiryType == QBluetoothDeviceDiscoveryAgent::LimitedInquiry)) |
483 | QVERIFY((numberOfAdapters-1) == counter); |
484 | } |
485 | } |
486 | |
487 | |
488 | void tst_QBluetoothDeviceDiscoveryAgent::tst_discoveryTimeout() |
489 | { |
490 | QBluetoothDeviceDiscoveryAgent agent; |
491 | |
492 | // check default values |
493 | #if defined(Q_OS_MACOS) || defined(Q_OS_IOS) || defined(Q_OS_ANDROID) || QT_CONFIG(winrt_bt) |
494 | QCOMPARE(agent.lowEnergyDiscoveryTimeout(), 25000); |
495 | agent.setLowEnergyDiscoveryTimeout(-1); // negative ignored |
496 | QCOMPARE(agent.lowEnergyDiscoveryTimeout(), 25000); |
497 | agent.setLowEnergyDiscoveryTimeout(20000); |
498 | QCOMPARE(agent.lowEnergyDiscoveryTimeout(), 20000); |
499 | #elif QT_CONFIG(bluez) |
500 | if (isBluez5Runtime) { |
501 | QCOMPARE(agent.lowEnergyDiscoveryTimeout(), 20000); |
502 | agent.setLowEnergyDiscoveryTimeout(-1); // negative ignored |
503 | QCOMPARE(agent.lowEnergyDiscoveryTimeout(), 20000); |
504 | agent.setLowEnergyDiscoveryTimeout(25000); |
505 | QCOMPARE(agent.lowEnergyDiscoveryTimeout(), 25000); |
506 | } else { |
507 | QCOMPARE(agent.lowEnergyDiscoveryTimeout(), -1); |
508 | agent.setLowEnergyDiscoveryTimeout(20000); // feature not supported -> ignored |
509 | QCOMPARE(agent.lowEnergyDiscoveryTimeout(), -1); |
510 | } |
511 | #else |
512 | QCOMPARE(agent.lowEnergyDiscoveryTimeout(), -1); |
513 | agent.setLowEnergyDiscoveryTimeout(20000); // feature not supported -> ignored |
514 | QCOMPARE(agent.lowEnergyDiscoveryTimeout(), -1); |
515 | #endif |
516 | } |
517 | |
518 | void tst_QBluetoothDeviceDiscoveryAgent::tst_discoveryMethods() |
519 | { |
520 | const QBluetoothLocalDevice localDevice; |
521 | if (localDevice.allDevices().size() != 1) { |
522 | // On iOS it returns 0 but we still have working BT. |
523 | #ifndef Q_OS_IOS |
524 | QSKIP("This test expects exactly one local device working" ); |
525 | #endif |
526 | } |
527 | |
528 | const QBluetoothDeviceDiscoveryAgent::DiscoveryMethods |
529 | supportedMethods = QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods(); |
530 | |
531 | QVERIFY(supportedMethods != QBluetoothDeviceDiscoveryAgent::NoMethod); |
532 | |
533 | QBluetoothDeviceDiscoveryAgent::DiscoveryMethod |
534 | unsupportedMethods = QBluetoothDeviceDiscoveryAgent::NoMethod; |
535 | QBluetoothDeviceInfo::CoreConfiguration |
536 | expectedConfiguration = QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration; |
537 | |
538 | if (supportedMethods == QBluetoothDeviceDiscoveryAgent::ClassicMethod) { |
539 | unsupportedMethods = QBluetoothDeviceDiscoveryAgent::LowEnergyMethod; |
540 | expectedConfiguration = QBluetoothDeviceInfo::BaseRateCoreConfiguration; |
541 | } else if (supportedMethods == QBluetoothDeviceDiscoveryAgent::LowEnergyMethod) { |
542 | unsupportedMethods = QBluetoothDeviceDiscoveryAgent::ClassicMethod; |
543 | expectedConfiguration = QBluetoothDeviceInfo::LowEnergyCoreConfiguration; |
544 | } |
545 | |
546 | QBluetoothDeviceDiscoveryAgent agent; |
547 | QSignalSpy finishedSpy(&agent, SIGNAL(finished())); |
548 | QSignalSpy errorSpy(&agent, SIGNAL(error(QBluetoothDeviceDiscoveryAgent::Error))); |
549 | QSignalSpy discoveredSpy(&agent, SIGNAL(deviceDiscovered(QBluetoothDeviceInfo))); |
550 | |
551 | // NoMethod - should just immediately return: |
552 | agent.start(method: QBluetoothDeviceDiscoveryAgent::NoMethod); |
553 | QCOMPARE(agent.error(), QBluetoothDeviceDiscoveryAgent::NoError); |
554 | QVERIFY(!agent.isActive()); |
555 | QCOMPARE(finishedSpy.size(), 0); |
556 | QCOMPARE(errorSpy.size(), 0); |
557 | QCOMPARE(discoveredSpy.size(), 0); |
558 | |
559 | if (unsupportedMethods != QBluetoothDeviceDiscoveryAgent::NoMethod) { |
560 | agent.start(method: unsupportedMethods); |
561 | QCOMPARE(agent.error(), QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod); |
562 | QVERIFY(!agent.isActive()); |
563 | QVERIFY(finishedSpy.isEmpty()); |
564 | QCOMPARE(errorSpy.size(), 1); |
565 | errorSpy.clear(); |
566 | QVERIFY(discoveredSpy.isEmpty()); |
567 | } |
568 | |
569 | // Start discovery, probably both Classic and LE methods: |
570 | agent.start(method: supportedMethods); |
571 | QVERIFY(agent.isActive()); |
572 | QVERIFY(errorSpy.isEmpty()); |
573 | |
574 | |
575 | #define RUN_DISCOVERY(maxTimeout, step, condition) \ |
576 | for (int scanTime = maxTimeout; (condition) && scanTime > 0; scanTime -= step) \ |
577 | QTest::qWait(step); |
578 | |
579 | // Wait for up to MaxScanTime for the scan to finish |
580 | const int timeStep = 15000; |
581 | RUN_DISCOVERY(MaxScanTime, timeStep, finishedSpy.isEmpty()) |
582 | |
583 | QVERIFY(!agent.isActive()); |
584 | QVERIFY(errorSpy.size() <= 1); |
585 | |
586 | if (errorSpy.size()) { |
587 | // For example, old iOS device could report it supports LE method, |
588 | // but it actually does not. |
589 | QVERIFY(supportedMethods == QBluetoothDeviceDiscoveryAgent::LowEnergyMethod); |
590 | QCOMPARE(agent.error(), QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod); |
591 | } else { |
592 | QVERIFY(finishedSpy.count() == 1); |
593 | QVERIFY(agent.error() == QBluetoothDeviceDiscoveryAgent::NoError); |
594 | QVERIFY(agent.errorString().isEmpty()); |
595 | |
596 | while (!discoveredSpy.isEmpty()) { |
597 | const QBluetoothDeviceInfo info = |
598 | qvariant_cast<QBluetoothDeviceInfo>(v: discoveredSpy.takeFirst().at(i: 0)); |
599 | QVERIFY(info.isValid()); |
600 | // on Android we do find devices with unknown configuration |
601 | if (info.coreConfigurations() != QBluetoothDeviceInfo::UnknownCoreConfiguration) |
602 | QVERIFY(info.coreConfigurations() & expectedConfiguration); |
603 | } |
604 | } |
605 | |
606 | if (unsupportedMethods != QBluetoothDeviceDiscoveryAgent::NoMethod) |
607 | return; |
608 | |
609 | // Both methods were reported as supported. We already tested them |
610 | // above, now let's test first Classic then LE. |
611 | finishedSpy.clear(); |
612 | errorSpy.clear(); |
613 | discoveredSpy.clear(); |
614 | |
615 | agent.start(method: QBluetoothDeviceDiscoveryAgent::ClassicMethod); |
616 | QVERIFY(agent.isActive()); |
617 | QVERIFY(errorSpy.isEmpty()); |
618 | QCOMPARE(agent.error(), QBluetoothDeviceDiscoveryAgent::NoError); |
619 | |
620 | RUN_DISCOVERY(MaxScanTime, timeStep, finishedSpy.isEmpty()) |
621 | |
622 | QVERIFY(!agent.isActive()); |
623 | QVERIFY(errorSpy.isEmpty()); |
624 | QCOMPARE(finishedSpy.size(), 1); |
625 | |
626 | finishedSpy.clear(); |
627 | discoveredSpy.clear(); |
628 | |
629 | agent.start(method: QBluetoothDeviceDiscoveryAgent::LowEnergyMethod); |
630 | QVERIFY(agent.isActive()); |
631 | QVERIFY(errorSpy.isEmpty()); |
632 | |
633 | RUN_DISCOVERY(MaxScanTime, timeStep, finishedSpy.isEmpty() && errorSpy.isEmpty()) |
634 | |
635 | QVERIFY(!agent.isActive()); |
636 | QVERIFY(errorSpy.size() <= 1); |
637 | |
638 | if (errorSpy.size()) { |
639 | QCOMPARE(agent.error(), QBluetoothDeviceDiscoveryAgent::UnsupportedDiscoveryMethod); |
640 | qDebug() << "QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods is inaccurate" |
641 | " on your platform/with your device, LowEnergyMethod is not supported" ; |
642 | } else { |
643 | QCOMPARE(agent.error(), QBluetoothDeviceDiscoveryAgent::NoError); |
644 | QCOMPARE(finishedSpy.size(), 1); |
645 | } |
646 | } |
647 | |
648 | QTEST_MAIN(tst_QBluetoothDeviceDiscoveryAgent) |
649 | |
650 | #include "tst_qbluetoothdevicediscoveryagent.moc" |
651 | |