dom/bluetooth/tests/marionette/head.js

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:59dab80c373f
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: sw=2 ts=2 sts=2 et filetype=javascript
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 // https://github.com/mozilla-b2g/platform_external_qemu/blob/master/vl-android.c#L765
8 // static int bt_hci_parse(const char *str) {
9 // ...
10 // bdaddr.b[0] = 0x52;
11 // bdaddr.b[1] = 0x54;
12 // bdaddr.b[2] = 0x00;
13 // bdaddr.b[3] = 0x12;
14 // bdaddr.b[4] = 0x34;
15 // bdaddr.b[5] = 0x56 + nb_hcis;
16 const EMULATOR_ADDRESS = "56:34:12:00:54:52";
17
18 // $ adb shell hciconfig /dev/ttyS2 name
19 // hci0: Type: BR/EDR Bus: UART
20 // BD Address: 56:34:12:00:54:52 ACL MTU: 512:1 SCO MTU: 0:0
21 // Name: 'Full Android on Emulator'
22 const EMULATOR_NAME = "Full Android on Emulator";
23
24 // $ adb shell hciconfig /dev/ttyS2 class
25 // hci0: Type: BR/EDR Bus: UART
26 // BD Address: 56:34:12:00:54:52 ACL MTU: 512:1 SCO MTU: 0:0
27 // Class: 0x58020c
28 // Service Classes: Capturing, Object Transfer, Telephony
29 // Device Class: Phone, Smart phone
30 const EMULATOR_CLASS = 0x58020c;
31
32 // Use same definition in QEMU for special bluetooth address,
33 // which were defined at external/qemu/hw/bt.h:
34 const BDADDR_ANY = "00:00:00:00:00:00";
35 const BDADDR_ALL = "ff:ff:ff:ff:ff:ff";
36 const BDADDR_LOCAL = "ff:ff:ff:00:00:00";
37
38 // A user friendly name for remote BT device.
39 const REMOTE_DEVICE_NAME = "Remote BT Device";
40
41 let Promise =
42 SpecialPowers.Cu.import("resource://gre/modules/Promise.jsm").Promise;
43
44 let bluetoothManager;
45
46 let pendingEmulatorCmdCount = 0;
47
48 /**
49 * Send emulator command with safe guard.
50 *
51 * We should only call |finish()| after all emulator command transactions
52 * end, so here comes with the pending counter. Resolve when the emulator
53 * gives positive response, and reject otherwise.
54 *
55 * Fulfill params:
56 * result -- an array of emulator response lines.
57 *
58 * Reject params:
59 * result -- an array of emulator response lines.
60 *
61 * @return A deferred promise.
62 */
63 function runEmulatorCmdSafe(aCommand) {
64 let deferred = Promise.defer();
65
66 ++pendingEmulatorCmdCount;
67 runEmulatorCmd(aCommand, function(aResult) {
68 --pendingEmulatorCmdCount;
69
70 ok(true, "Emulator response: " + JSON.stringify(aResult));
71 if (Array.isArray(aResult) && aResult[aResult.length - 1] === "OK") {
72 deferred.resolve(aResult);
73 } else {
74 ok(false, "Got an abnormal response from emulator.");
75 log("Fail to execute emulator command: [" + aCommand + "]");
76 deferred.reject(aResult);
77 }
78 });
79
80 return deferred.promise;
81 }
82
83 /**
84 * Add a Bluetooth remote device to scatternet and set its properties.
85 *
86 * Use QEMU command 'bt remote add' to add a virtual Bluetooth remote
87 * and set its properties by setEmulatorDeviceProperty().
88 *
89 * Fulfill params:
90 * result -- bluetooth address of the remote device.
91 * Reject params: (none)
92 *
93 * @param aProperies
94 * A javascript object with zero or several properties for initializing
95 * the remote device. By now, the properies could be 'name' or
96 * 'discoverable'. It valid to put a null object or a javascript object
97 * which don't have any properies.
98 *
99 * @return A promise object.
100 */
101 function addEmulatorRemoteDevice(aProperties) {
102 let address;
103 let promise = runEmulatorCmdSafe("bt remote add")
104 .then(function(aResults) {
105 address = aResults[0].toUpperCase();
106 });
107
108 for (let key in aProperties) {
109 let value = aProperties[key];
110 let propertyName = key;
111 promise = promise.then(function() {
112 return setEmulatorDeviceProperty(address, propertyName, value);
113 });
114 }
115
116 return promise.then(function() {
117 return address;
118 });
119 }
120
121 /**
122 * Remove Bluetooth remote devices in scatternet.
123 *
124 * Use QEMU command 'bt remote remove <addr>' to remove a specific virtual
125 * Bluetooth remote device in scatternet or remove them all by QEMU command
126 * 'bt remote remove BDADDR_ALL'.
127 *
128 * @param aAddress
129 * The string of Bluetooth address with format xx:xx:xx:xx:xx:xx.
130 *
131 * Fulfill params:
132 * result -- an array of emulator response lines.
133 * Reject params: (none)
134 *
135 * @return A promise object.
136 */
137 function removeEmulatorRemoteDevice(aAddress) {
138 let cmd = "bt remote remove " + aAddress;
139 return runEmulatorCmdSafe(cmd)
140 .then(function(aResults) {
141 // 'bt remote remove <bd_addr>' returns a list of removed device one at a line.
142 // The last item is "OK".
143 return aResults.slice(0, -1);
144 });
145 }
146
147 /**
148 * Set a property for a Bluetooth device.
149 *
150 * Use QEMU command 'bt property <bd_addr> <prop_name> <value>' to set property.
151 *
152 * Fulfill params:
153 * result -- an array of emulator response lines.
154 * Reject params:
155 * result -- an array of emulator response lines.
156 *
157 * @param aAddress
158 * The string of Bluetooth address with format xx:xx:xx:xx:xx:xx.
159 * @param aPropertyName
160 * The property name of Bluetooth device.
161 * @param aValue
162 * The new value of the specifc property.
163 *
164 * @return A deferred promise.
165 */
166 function setEmulatorDeviceProperty(aAddress, aPropertyName, aValue) {
167 let cmd = "bt property " + aAddress + " " + aPropertyName + " " + aValue;
168 return runEmulatorCmdSafe(cmd);
169 }
170
171 /**
172 * Get a property from a Bluetooth device.
173 *
174 * Use QEMU command 'bt property <bd_addr> <prop_name>' to get properties.
175 *
176 * Fulfill params:
177 * result -- a string with format <prop_name>: <value_of_prop>
178 * Reject params:
179 * result -- an array of emulator response lines.
180 *
181 * @param aAddress
182 * The string of Bluetooth address with format xx:xx:xx:xx:xx:xx.
183 * @param aPropertyName
184 * The property name of Bluetooth device.
185 *
186 * @return A deferred promise.
187 */
188 function getEmulatorDeviceProperty(aAddress, aPropertyName) {
189 let cmd = "bt property " + aAddress + " " + aPropertyName;
190 return runEmulatorCmdSafe(cmd)
191 .then(function(aResults) {
192 return aResults[0];
193 });
194 }
195
196 /**
197 * Start dicovering Bluetooth devices.
198 *
199 * Allows the device's adapter to start seeking for remote devices.
200 *
201 * Fulfill params: (none)
202 * Reject params: a DOMError
203 *
204 * @param aAdapter
205 * A BluetoothAdapter which is used to interact with local BT dev
206 *
207 * @return A deferred promise.
208 */
209 function startDiscovery(aAdapter) {
210 let deferred = Promise.defer();
211
212 let request = aAdapter.startDiscovery();
213 request.onsuccess = function () {
214 log(" Start discovery - Success");
215 // TODO (bug 892207): Make Bluetooth APIs available for 3rd party apps.
216 // Currently, discovering state wouldn't change immediately here.
217 // We would turn on this check when the redesigned API are landed.
218 // is(aAdapter.discovering, true, "BluetoothAdapter.discovering");
219 deferred.resolve();
220 }
221 request.onerror = function (aEvent) {
222 ok(false, "Start discovery - Fail");
223 deferred.reject(aEvent.target.error);
224 }
225
226 return deferred.promise;
227 }
228
229 /**
230 * Stop dicovering Bluetooth devices.
231 *
232 * Allows the device's adapter to stop seeking for remote devices.
233 *
234 * Fulfill params: (none)
235 * Reject params: a DOMError
236 *
237 * @param aAdapter
238 * A BluetoothAdapter which is used to interact with local BT device.
239 *
240 * @return A deferred promise.
241 */
242 function stopDiscovery(aAdapter) {
243 let deferred = Promise.defer();
244
245 let request = aAdapter.stopDiscovery();
246 request.onsuccess = function () {
247 log(" Stop discovery - Success");
248 // TODO (bug 892207): Make Bluetooth APIs available for 3rd party apps.
249 // Currently, discovering state wouldn't change immediately here.
250 // We would turn on this check when the redesigned API are landed.
251 // is(aAdapter.discovering, false, "BluetoothAdapter.discovering");
252 deferred.resolve();
253 }
254 request.onerror = function (aEvent) {
255 ok(false, "Stop discovery - Fail");
256 deferred.reject(aEvent.target.error);
257 }
258 return deferred.promise;
259 }
260
261 /**
262 * Get mozSettings value specified by @aKey.
263 *
264 * Resolve if that mozSettings value is retrieved successfully, reject
265 * otherwise.
266 *
267 * Fulfill params:
268 * The corresponding mozSettings value of the key.
269 * Reject params: (none)
270 *
271 * @param aKey
272 * A string.
273 *
274 * @return A deferred promise.
275 */
276 function getSettings(aKey) {
277 let deferred = Promise.defer();
278
279 let request = navigator.mozSettings.createLock().get(aKey);
280 request.addEventListener("success", function(aEvent) {
281 ok(true, "getSettings(" + aKey + ")");
282 deferred.resolve(aEvent.target.result[aKey]);
283 });
284 request.addEventListener("error", function() {
285 ok(false, "getSettings(" + aKey + ")");
286 deferred.reject();
287 });
288
289 return deferred.promise;
290 }
291
292 /**
293 * Set mozSettings values.
294 *
295 * Resolve if that mozSettings value is set successfully, reject otherwise.
296 *
297 * Fulfill params: (none)
298 * Reject params: (none)
299 *
300 * @param aSettings
301 * An object of format |{key1: value1, key2: value2, ...}|.
302 *
303 * @return A deferred promise.
304 */
305 function setSettings(aSettings) {
306 let deferred = Promise.defer();
307
308 let request = navigator.mozSettings.createLock().set(aSettings);
309 request.addEventListener("success", function() {
310 ok(true, "setSettings(" + JSON.stringify(aSettings) + ")");
311 deferred.resolve();
312 });
313 request.addEventListener("error", function() {
314 ok(false, "setSettings(" + JSON.stringify(aSettings) + ")");
315 deferred.reject();
316 });
317
318 return deferred.promise;
319 }
320
321 /**
322 * Get mozSettings value of 'bluetooth.enabled'.
323 *
324 * Resolve if that mozSettings value is retrieved successfully, reject
325 * otherwise.
326 *
327 * Fulfill params:
328 * A boolean value.
329 * Reject params: (none)
330 *
331 * @return A deferred promise.
332 */
333 function getBluetoothEnabled() {
334 return getSettings("bluetooth.enabled");
335 }
336
337 /**
338 * Set mozSettings value of 'bluetooth.enabled'.
339 *
340 * Resolve if that mozSettings value is set successfully, reject otherwise.
341 *
342 * Fulfill params: (none)
343 * Reject params: (none)
344 *
345 * @param aEnabled
346 * A boolean value.
347 *
348 * @return A deferred promise.
349 */
350 function setBluetoothEnabled(aEnabled) {
351 let obj = {};
352 obj["bluetooth.enabled"] = aEnabled;
353 return setSettings(obj);
354 }
355
356 /**
357 * Push required permissions and test if |navigator.mozBluetooth| exists.
358 * Resolve if it does, reject otherwise.
359 *
360 * Fulfill params:
361 * bluetoothManager -- an reference to navigator.mozBluetooth.
362 * Reject params: (none)
363 *
364 * @param aPermissions
365 * Additional permissions to push before any test cases. Could be either
366 * a string or an array of strings.
367 *
368 * @return A deferred promise.
369 */
370 function ensureBluetoothManager(aPermissions) {
371 let deferred = Promise.defer();
372
373 let permissions = ["bluetooth"];
374 if (aPermissions) {
375 if (Array.isArray(aPermissions)) {
376 permissions = permissions.concat(aPermissions);
377 } else if (typeof aPermissions == "string") {
378 permissions.push(aPermissions);
379 }
380 }
381
382 let obj = [];
383 for (let perm of permissions) {
384 obj.push({
385 "type": perm,
386 "allow": 1,
387 "context": document,
388 });
389 }
390
391 SpecialPowers.pushPermissions(obj, function() {
392 ok(true, "permissions pushed: " + JSON.stringify(permissions));
393
394 bluetoothManager = window.navigator.mozBluetooth;
395 log("navigator.mozBluetooth is " +
396 (bluetoothManager ? "available" : "unavailable"));
397
398 if (bluetoothManager instanceof BluetoothManager) {
399 deferred.resolve(bluetoothManager);
400 } else {
401 deferred.reject();
402 }
403 });
404
405 return deferred.promise;
406 }
407
408 /**
409 * Wait for one named BluetoothManager event.
410 *
411 * Resolve if that named event occurs. Never reject.
412 *
413 * Fulfill params: the DOMEvent passed.
414 *
415 * @param aEventName
416 * The name of the EventHandler.
417 *
418 * @return A deferred promise.
419 */
420 function waitForManagerEvent(aEventName) {
421 let deferred = Promise.defer();
422
423 bluetoothManager.addEventListener(aEventName, function onevent(aEvent) {
424 bluetoothManager.removeEventListener(aEventName, onevent);
425
426 ok(true, "BluetoothManager event '" + aEventName + "' got.");
427 deferred.resolve(aEvent);
428 });
429
430 return deferred.promise;
431 }
432
433 /**
434 * Wait for one named BluetoothAdapter event.
435 *
436 * Resolve if that named event occurs. Never reject.
437 *
438 * Fulfill params: the DOMEvent passed.
439 *
440 * @param aAdapter
441 * The BluetoothAdapter you want to use.
442 * @param aEventName
443 * The name of the EventHandler.
444 *
445 * @return A deferred promise.
446 */
447 function waitForAdapterEvent(aAdapter, aEventName) {
448 let deferred = Promise.defer();
449
450 aAdapter.addEventListener(aEventName, function onevent(aEvent) {
451 aAdapter.removeEventListener(aEventName, onevent);
452
453 ok(true, "BluetoothAdapter event '" + aEventName + "' got.");
454 deferred.resolve(aEvent);
455 });
456
457 return deferred.promise;
458 }
459
460 /**
461 * Convenient function for setBluetoothEnabled and waitForManagerEvent
462 * combined.
463 *
464 * Resolve if that named event occurs. Reject if we can't set settings.
465 *
466 * Fulfill params: the DOMEvent passed.
467 * Reject params: (none)
468 *
469 * @return A deferred promise.
470 */
471 function setBluetoothEnabledAndWait(aEnabled) {
472 let promises = [];
473
474 // Bug 969109 - Intermittent test_dom_BluetoothManager_adapteradded.js
475 //
476 // Here we want to wait for two events coming up -- Bluetooth "settings-set"
477 // event and one of "enabled"/"disabled" events. Special care is taken here
478 // to ensure that we can always receive that "enabled"/"disabled" event by
479 // installing the event handler *before* we ever enable/disable Bluetooth. Or
480 // we might just miss those events and get a timeout error.
481 promises.push(waitForManagerEvent(aEnabled ? "enabled" : "disabled"));
482 promises.push(setBluetoothEnabled(aEnabled));
483
484 return Promise.all(promises);
485 }
486
487 /**
488 * Get default adapter.
489 *
490 * Resolve if that default adapter is got, reject otherwise.
491 *
492 * Fulfill params: a BluetoothAdapter instance.
493 * Reject params: a DOMError, or null if if there is no adapter ready yet.
494 *
495 * @return A deferred promise.
496 */
497 function getDefaultAdapter() {
498 let deferred = Promise.defer();
499
500 let request = bluetoothManager.getDefaultAdapter();
501 request.onsuccess = function(aEvent) {
502 let adapter = aEvent.target.result;
503 if (!(adapter instanceof BluetoothAdapter)) {
504 ok(false, "no BluetoothAdapter ready yet.");
505 deferred.reject(null);
506 return;
507 }
508
509 ok(true, "BluetoothAdapter got.");
510 // TODO: We have an adapter instance now, but some of its attributes may
511 // still remain unassigned/out-dated. Here we waste a few seconds to
512 // wait for the property changed events.
513 //
514 // See https://bugzilla.mozilla.org/show_bug.cgi?id=932914
515 window.setTimeout(function() {
516 deferred.resolve(adapter);
517 }, 3000);
518 };
519 request.onerror = function(aEvent) {
520 ok(false, "Failed to get default adapter.");
521 deferred.reject(aEvent.target.error);
522 };
523
524 return deferred.promise;
525 }
526
527 /**
528 * Flush permission settings and call |finish()|.
529 */
530 function cleanUp() {
531 waitFor(function() {
532 SpecialPowers.flushPermissions(function() {
533 // Use ok here so that we have at least one test run.
534 ok(true, "permissions flushed");
535
536 finish();
537 });
538 }, function() {
539 return pendingEmulatorCmdCount === 0;
540 });
541 }
542
543 function startBluetoothTestBase(aPermissions, aTestCaseMain) {
544 ensureBluetoothManager(aPermissions)
545 .then(aTestCaseMain)
546 .then(cleanUp, function() {
547 ok(false, "Unhandled rejected promise.");
548 cleanUp();
549 });
550 }
551
552 function startBluetoothTest(aReenable, aTestCaseMain) {
553 startBluetoothTestBase(["settings-read", "settings-write"], function() {
554 let origEnabled, needEnable;
555
556 return getBluetoothEnabled()
557 .then(function(aEnabled) {
558 origEnabled = aEnabled;
559 needEnable = !aEnabled;
560 log("Original 'bluetooth.enabled' is " + origEnabled);
561
562 if (aEnabled && aReenable) {
563 log(" Disable 'bluetooth.enabled' ...");
564 needEnable = true;
565 return setBluetoothEnabledAndWait(false);
566 }
567 })
568 .then(function() {
569 if (needEnable) {
570 log(" Enable 'bluetooth.enabled' ...");
571
572 // See setBluetoothEnabledAndWait(). We must install all event
573 // handlers *before* enabling Bluetooth.
574 let promises = [];
575 promises.push(waitForManagerEvent("adapteradded"));
576 promises.push(setBluetoothEnabledAndWait(true));
577 return Promise.all(promises);
578 }
579 })
580 .then(getDefaultAdapter)
581 .then(aTestCaseMain)
582 .then(function() {
583 if (!origEnabled) {
584 return setBluetoothEnabledAndWait(false);
585 }
586 });
587 });
588 }

mercurial