1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/bluetooth/tests/marionette/head.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,588 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 1.5 + * vim: sw=2 ts=2 sts=2 et filetype=javascript 1.6 + * This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.8 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +// https://github.com/mozilla-b2g/platform_external_qemu/blob/master/vl-android.c#L765 1.11 +// static int bt_hci_parse(const char *str) { 1.12 +// ... 1.13 +// bdaddr.b[0] = 0x52; 1.14 +// bdaddr.b[1] = 0x54; 1.15 +// bdaddr.b[2] = 0x00; 1.16 +// bdaddr.b[3] = 0x12; 1.17 +// bdaddr.b[4] = 0x34; 1.18 +// bdaddr.b[5] = 0x56 + nb_hcis; 1.19 +const EMULATOR_ADDRESS = "56:34:12:00:54:52"; 1.20 + 1.21 +// $ adb shell hciconfig /dev/ttyS2 name 1.22 +// hci0: Type: BR/EDR Bus: UART 1.23 +// BD Address: 56:34:12:00:54:52 ACL MTU: 512:1 SCO MTU: 0:0 1.24 +// Name: 'Full Android on Emulator' 1.25 +const EMULATOR_NAME = "Full Android on Emulator"; 1.26 + 1.27 +// $ adb shell hciconfig /dev/ttyS2 class 1.28 +// hci0: Type: BR/EDR Bus: UART 1.29 +// BD Address: 56:34:12:00:54:52 ACL MTU: 512:1 SCO MTU: 0:0 1.30 +// Class: 0x58020c 1.31 +// Service Classes: Capturing, Object Transfer, Telephony 1.32 +// Device Class: Phone, Smart phone 1.33 +const EMULATOR_CLASS = 0x58020c; 1.34 + 1.35 +// Use same definition in QEMU for special bluetooth address, 1.36 +// which were defined at external/qemu/hw/bt.h: 1.37 +const BDADDR_ANY = "00:00:00:00:00:00"; 1.38 +const BDADDR_ALL = "ff:ff:ff:ff:ff:ff"; 1.39 +const BDADDR_LOCAL = "ff:ff:ff:00:00:00"; 1.40 + 1.41 +// A user friendly name for remote BT device. 1.42 +const REMOTE_DEVICE_NAME = "Remote BT Device"; 1.43 + 1.44 +let Promise = 1.45 + SpecialPowers.Cu.import("resource://gre/modules/Promise.jsm").Promise; 1.46 + 1.47 +let bluetoothManager; 1.48 + 1.49 +let pendingEmulatorCmdCount = 0; 1.50 + 1.51 +/** 1.52 + * Send emulator command with safe guard. 1.53 + * 1.54 + * We should only call |finish()| after all emulator command transactions 1.55 + * end, so here comes with the pending counter. Resolve when the emulator 1.56 + * gives positive response, and reject otherwise. 1.57 + * 1.58 + * Fulfill params: 1.59 + * result -- an array of emulator response lines. 1.60 + * 1.61 + * Reject params: 1.62 + * result -- an array of emulator response lines. 1.63 + * 1.64 + * @return A deferred promise. 1.65 + */ 1.66 +function runEmulatorCmdSafe(aCommand) { 1.67 + let deferred = Promise.defer(); 1.68 + 1.69 + ++pendingEmulatorCmdCount; 1.70 + runEmulatorCmd(aCommand, function(aResult) { 1.71 + --pendingEmulatorCmdCount; 1.72 + 1.73 + ok(true, "Emulator response: " + JSON.stringify(aResult)); 1.74 + if (Array.isArray(aResult) && aResult[aResult.length - 1] === "OK") { 1.75 + deferred.resolve(aResult); 1.76 + } else { 1.77 + ok(false, "Got an abnormal response from emulator."); 1.78 + log("Fail to execute emulator command: [" + aCommand + "]"); 1.79 + deferred.reject(aResult); 1.80 + } 1.81 + }); 1.82 + 1.83 + return deferred.promise; 1.84 +} 1.85 + 1.86 +/** 1.87 + * Add a Bluetooth remote device to scatternet and set its properties. 1.88 + * 1.89 + * Use QEMU command 'bt remote add' to add a virtual Bluetooth remote 1.90 + * and set its properties by setEmulatorDeviceProperty(). 1.91 + * 1.92 + * Fulfill params: 1.93 + * result -- bluetooth address of the remote device. 1.94 + * Reject params: (none) 1.95 + * 1.96 + * @param aProperies 1.97 + * A javascript object with zero or several properties for initializing 1.98 + * the remote device. By now, the properies could be 'name' or 1.99 + * 'discoverable'. It valid to put a null object or a javascript object 1.100 + * which don't have any properies. 1.101 + * 1.102 + * @return A promise object. 1.103 + */ 1.104 +function addEmulatorRemoteDevice(aProperties) { 1.105 + let address; 1.106 + let promise = runEmulatorCmdSafe("bt remote add") 1.107 + .then(function(aResults) { 1.108 + address = aResults[0].toUpperCase(); 1.109 + }); 1.110 + 1.111 + for (let key in aProperties) { 1.112 + let value = aProperties[key]; 1.113 + let propertyName = key; 1.114 + promise = promise.then(function() { 1.115 + return setEmulatorDeviceProperty(address, propertyName, value); 1.116 + }); 1.117 + } 1.118 + 1.119 + return promise.then(function() { 1.120 + return address; 1.121 + }); 1.122 +} 1.123 + 1.124 +/** 1.125 + * Remove Bluetooth remote devices in scatternet. 1.126 + * 1.127 + * Use QEMU command 'bt remote remove <addr>' to remove a specific virtual 1.128 + * Bluetooth remote device in scatternet or remove them all by QEMU command 1.129 + * 'bt remote remove BDADDR_ALL'. 1.130 + * 1.131 + * @param aAddress 1.132 + * The string of Bluetooth address with format xx:xx:xx:xx:xx:xx. 1.133 + * 1.134 + * Fulfill params: 1.135 + * result -- an array of emulator response lines. 1.136 + * Reject params: (none) 1.137 + * 1.138 + * @return A promise object. 1.139 + */ 1.140 +function removeEmulatorRemoteDevice(aAddress) { 1.141 + let cmd = "bt remote remove " + aAddress; 1.142 + return runEmulatorCmdSafe(cmd) 1.143 + .then(function(aResults) { 1.144 + // 'bt remote remove <bd_addr>' returns a list of removed device one at a line. 1.145 + // The last item is "OK". 1.146 + return aResults.slice(0, -1); 1.147 + }); 1.148 +} 1.149 + 1.150 +/** 1.151 + * Set a property for a Bluetooth device. 1.152 + * 1.153 + * Use QEMU command 'bt property <bd_addr> <prop_name> <value>' to set property. 1.154 + * 1.155 + * Fulfill params: 1.156 + * result -- an array of emulator response lines. 1.157 + * Reject params: 1.158 + * result -- an array of emulator response lines. 1.159 + * 1.160 + * @param aAddress 1.161 + * The string of Bluetooth address with format xx:xx:xx:xx:xx:xx. 1.162 + * @param aPropertyName 1.163 + * The property name of Bluetooth device. 1.164 + * @param aValue 1.165 + * The new value of the specifc property. 1.166 + * 1.167 + * @return A deferred promise. 1.168 + */ 1.169 +function setEmulatorDeviceProperty(aAddress, aPropertyName, aValue) { 1.170 + let cmd = "bt property " + aAddress + " " + aPropertyName + " " + aValue; 1.171 + return runEmulatorCmdSafe(cmd); 1.172 +} 1.173 + 1.174 +/** 1.175 + * Get a property from a Bluetooth device. 1.176 + * 1.177 + * Use QEMU command 'bt property <bd_addr> <prop_name>' to get properties. 1.178 + * 1.179 + * Fulfill params: 1.180 + * result -- a string with format <prop_name>: <value_of_prop> 1.181 + * Reject params: 1.182 + * result -- an array of emulator response lines. 1.183 + * 1.184 + * @param aAddress 1.185 + * The string of Bluetooth address with format xx:xx:xx:xx:xx:xx. 1.186 + * @param aPropertyName 1.187 + * The property name of Bluetooth device. 1.188 + * 1.189 + * @return A deferred promise. 1.190 + */ 1.191 +function getEmulatorDeviceProperty(aAddress, aPropertyName) { 1.192 + let cmd = "bt property " + aAddress + " " + aPropertyName; 1.193 + return runEmulatorCmdSafe(cmd) 1.194 + .then(function(aResults) { 1.195 + return aResults[0]; 1.196 + }); 1.197 +} 1.198 + 1.199 +/** 1.200 + * Start dicovering Bluetooth devices. 1.201 + * 1.202 + * Allows the device's adapter to start seeking for remote devices. 1.203 + * 1.204 + * Fulfill params: (none) 1.205 + * Reject params: a DOMError 1.206 + * 1.207 + * @param aAdapter 1.208 + * A BluetoothAdapter which is used to interact with local BT dev 1.209 + * 1.210 + * @return A deferred promise. 1.211 + */ 1.212 +function startDiscovery(aAdapter) { 1.213 + let deferred = Promise.defer(); 1.214 + 1.215 + let request = aAdapter.startDiscovery(); 1.216 + request.onsuccess = function () { 1.217 + log(" Start discovery - Success"); 1.218 + // TODO (bug 892207): Make Bluetooth APIs available for 3rd party apps. 1.219 + // Currently, discovering state wouldn't change immediately here. 1.220 + // We would turn on this check when the redesigned API are landed. 1.221 + // is(aAdapter.discovering, true, "BluetoothAdapter.discovering"); 1.222 + deferred.resolve(); 1.223 + } 1.224 + request.onerror = function (aEvent) { 1.225 + ok(false, "Start discovery - Fail"); 1.226 + deferred.reject(aEvent.target.error); 1.227 + } 1.228 + 1.229 + return deferred.promise; 1.230 +} 1.231 + 1.232 +/** 1.233 + * Stop dicovering Bluetooth devices. 1.234 + * 1.235 + * Allows the device's adapter to stop seeking for remote devices. 1.236 + * 1.237 + * Fulfill params: (none) 1.238 + * Reject params: a DOMError 1.239 + * 1.240 + * @param aAdapter 1.241 + * A BluetoothAdapter which is used to interact with local BT device. 1.242 + * 1.243 + * @return A deferred promise. 1.244 + */ 1.245 +function stopDiscovery(aAdapter) { 1.246 + let deferred = Promise.defer(); 1.247 + 1.248 + let request = aAdapter.stopDiscovery(); 1.249 + request.onsuccess = function () { 1.250 + log(" Stop discovery - Success"); 1.251 + // TODO (bug 892207): Make Bluetooth APIs available for 3rd party apps. 1.252 + // Currently, discovering state wouldn't change immediately here. 1.253 + // We would turn on this check when the redesigned API are landed. 1.254 + // is(aAdapter.discovering, false, "BluetoothAdapter.discovering"); 1.255 + deferred.resolve(); 1.256 + } 1.257 + request.onerror = function (aEvent) { 1.258 + ok(false, "Stop discovery - Fail"); 1.259 + deferred.reject(aEvent.target.error); 1.260 + } 1.261 + return deferred.promise; 1.262 +} 1.263 + 1.264 +/** 1.265 + * Get mozSettings value specified by @aKey. 1.266 + * 1.267 + * Resolve if that mozSettings value is retrieved successfully, reject 1.268 + * otherwise. 1.269 + * 1.270 + * Fulfill params: 1.271 + * The corresponding mozSettings value of the key. 1.272 + * Reject params: (none) 1.273 + * 1.274 + * @param aKey 1.275 + * A string. 1.276 + * 1.277 + * @return A deferred promise. 1.278 + */ 1.279 +function getSettings(aKey) { 1.280 + let deferred = Promise.defer(); 1.281 + 1.282 + let request = navigator.mozSettings.createLock().get(aKey); 1.283 + request.addEventListener("success", function(aEvent) { 1.284 + ok(true, "getSettings(" + aKey + ")"); 1.285 + deferred.resolve(aEvent.target.result[aKey]); 1.286 + }); 1.287 + request.addEventListener("error", function() { 1.288 + ok(false, "getSettings(" + aKey + ")"); 1.289 + deferred.reject(); 1.290 + }); 1.291 + 1.292 + return deferred.promise; 1.293 +} 1.294 + 1.295 +/** 1.296 + * Set mozSettings values. 1.297 + * 1.298 + * Resolve if that mozSettings value is set successfully, reject otherwise. 1.299 + * 1.300 + * Fulfill params: (none) 1.301 + * Reject params: (none) 1.302 + * 1.303 + * @param aSettings 1.304 + * An object of format |{key1: value1, key2: value2, ...}|. 1.305 + * 1.306 + * @return A deferred promise. 1.307 + */ 1.308 +function setSettings(aSettings) { 1.309 + let deferred = Promise.defer(); 1.310 + 1.311 + let request = navigator.mozSettings.createLock().set(aSettings); 1.312 + request.addEventListener("success", function() { 1.313 + ok(true, "setSettings(" + JSON.stringify(aSettings) + ")"); 1.314 + deferred.resolve(); 1.315 + }); 1.316 + request.addEventListener("error", function() { 1.317 + ok(false, "setSettings(" + JSON.stringify(aSettings) + ")"); 1.318 + deferred.reject(); 1.319 + }); 1.320 + 1.321 + return deferred.promise; 1.322 +} 1.323 + 1.324 +/** 1.325 + * Get mozSettings value of 'bluetooth.enabled'. 1.326 + * 1.327 + * Resolve if that mozSettings value is retrieved successfully, reject 1.328 + * otherwise. 1.329 + * 1.330 + * Fulfill params: 1.331 + * A boolean value. 1.332 + * Reject params: (none) 1.333 + * 1.334 + * @return A deferred promise. 1.335 + */ 1.336 +function getBluetoothEnabled() { 1.337 + return getSettings("bluetooth.enabled"); 1.338 +} 1.339 + 1.340 +/** 1.341 + * Set mozSettings value of 'bluetooth.enabled'. 1.342 + * 1.343 + * Resolve if that mozSettings value is set successfully, reject otherwise. 1.344 + * 1.345 + * Fulfill params: (none) 1.346 + * Reject params: (none) 1.347 + * 1.348 + * @param aEnabled 1.349 + * A boolean value. 1.350 + * 1.351 + * @return A deferred promise. 1.352 + */ 1.353 +function setBluetoothEnabled(aEnabled) { 1.354 + let obj = {}; 1.355 + obj["bluetooth.enabled"] = aEnabled; 1.356 + return setSettings(obj); 1.357 +} 1.358 + 1.359 +/** 1.360 + * Push required permissions and test if |navigator.mozBluetooth| exists. 1.361 + * Resolve if it does, reject otherwise. 1.362 + * 1.363 + * Fulfill params: 1.364 + * bluetoothManager -- an reference to navigator.mozBluetooth. 1.365 + * Reject params: (none) 1.366 + * 1.367 + * @param aPermissions 1.368 + * Additional permissions to push before any test cases. Could be either 1.369 + * a string or an array of strings. 1.370 + * 1.371 + * @return A deferred promise. 1.372 + */ 1.373 +function ensureBluetoothManager(aPermissions) { 1.374 + let deferred = Promise.defer(); 1.375 + 1.376 + let permissions = ["bluetooth"]; 1.377 + if (aPermissions) { 1.378 + if (Array.isArray(aPermissions)) { 1.379 + permissions = permissions.concat(aPermissions); 1.380 + } else if (typeof aPermissions == "string") { 1.381 + permissions.push(aPermissions); 1.382 + } 1.383 + } 1.384 + 1.385 + let obj = []; 1.386 + for (let perm of permissions) { 1.387 + obj.push({ 1.388 + "type": perm, 1.389 + "allow": 1, 1.390 + "context": document, 1.391 + }); 1.392 + } 1.393 + 1.394 + SpecialPowers.pushPermissions(obj, function() { 1.395 + ok(true, "permissions pushed: " + JSON.stringify(permissions)); 1.396 + 1.397 + bluetoothManager = window.navigator.mozBluetooth; 1.398 + log("navigator.mozBluetooth is " + 1.399 + (bluetoothManager ? "available" : "unavailable")); 1.400 + 1.401 + if (bluetoothManager instanceof BluetoothManager) { 1.402 + deferred.resolve(bluetoothManager); 1.403 + } else { 1.404 + deferred.reject(); 1.405 + } 1.406 + }); 1.407 + 1.408 + return deferred.promise; 1.409 +} 1.410 + 1.411 +/** 1.412 + * Wait for one named BluetoothManager event. 1.413 + * 1.414 + * Resolve if that named event occurs. Never reject. 1.415 + * 1.416 + * Fulfill params: the DOMEvent passed. 1.417 + * 1.418 + * @param aEventName 1.419 + * The name of the EventHandler. 1.420 + * 1.421 + * @return A deferred promise. 1.422 + */ 1.423 +function waitForManagerEvent(aEventName) { 1.424 + let deferred = Promise.defer(); 1.425 + 1.426 + bluetoothManager.addEventListener(aEventName, function onevent(aEvent) { 1.427 + bluetoothManager.removeEventListener(aEventName, onevent); 1.428 + 1.429 + ok(true, "BluetoothManager event '" + aEventName + "' got."); 1.430 + deferred.resolve(aEvent); 1.431 + }); 1.432 + 1.433 + return deferred.promise; 1.434 +} 1.435 + 1.436 +/** 1.437 + * Wait for one named BluetoothAdapter event. 1.438 + * 1.439 + * Resolve if that named event occurs. Never reject. 1.440 + * 1.441 + * Fulfill params: the DOMEvent passed. 1.442 + * 1.443 + * @param aAdapter 1.444 + * The BluetoothAdapter you want to use. 1.445 + * @param aEventName 1.446 + * The name of the EventHandler. 1.447 + * 1.448 + * @return A deferred promise. 1.449 + */ 1.450 +function waitForAdapterEvent(aAdapter, aEventName) { 1.451 + let deferred = Promise.defer(); 1.452 + 1.453 + aAdapter.addEventListener(aEventName, function onevent(aEvent) { 1.454 + aAdapter.removeEventListener(aEventName, onevent); 1.455 + 1.456 + ok(true, "BluetoothAdapter event '" + aEventName + "' got."); 1.457 + deferred.resolve(aEvent); 1.458 + }); 1.459 + 1.460 + return deferred.promise; 1.461 +} 1.462 + 1.463 +/** 1.464 + * Convenient function for setBluetoothEnabled and waitForManagerEvent 1.465 + * combined. 1.466 + * 1.467 + * Resolve if that named event occurs. Reject if we can't set settings. 1.468 + * 1.469 + * Fulfill params: the DOMEvent passed. 1.470 + * Reject params: (none) 1.471 + * 1.472 + * @return A deferred promise. 1.473 + */ 1.474 +function setBluetoothEnabledAndWait(aEnabled) { 1.475 + let promises = []; 1.476 + 1.477 + // Bug 969109 - Intermittent test_dom_BluetoothManager_adapteradded.js 1.478 + // 1.479 + // Here we want to wait for two events coming up -- Bluetooth "settings-set" 1.480 + // event and one of "enabled"/"disabled" events. Special care is taken here 1.481 + // to ensure that we can always receive that "enabled"/"disabled" event by 1.482 + // installing the event handler *before* we ever enable/disable Bluetooth. Or 1.483 + // we might just miss those events and get a timeout error. 1.484 + promises.push(waitForManagerEvent(aEnabled ? "enabled" : "disabled")); 1.485 + promises.push(setBluetoothEnabled(aEnabled)); 1.486 + 1.487 + return Promise.all(promises); 1.488 +} 1.489 + 1.490 +/** 1.491 + * Get default adapter. 1.492 + * 1.493 + * Resolve if that default adapter is got, reject otherwise. 1.494 + * 1.495 + * Fulfill params: a BluetoothAdapter instance. 1.496 + * Reject params: a DOMError, or null if if there is no adapter ready yet. 1.497 + * 1.498 + * @return A deferred promise. 1.499 + */ 1.500 +function getDefaultAdapter() { 1.501 + let deferred = Promise.defer(); 1.502 + 1.503 + let request = bluetoothManager.getDefaultAdapter(); 1.504 + request.onsuccess = function(aEvent) { 1.505 + let adapter = aEvent.target.result; 1.506 + if (!(adapter instanceof BluetoothAdapter)) { 1.507 + ok(false, "no BluetoothAdapter ready yet."); 1.508 + deferred.reject(null); 1.509 + return; 1.510 + } 1.511 + 1.512 + ok(true, "BluetoothAdapter got."); 1.513 + // TODO: We have an adapter instance now, but some of its attributes may 1.514 + // still remain unassigned/out-dated. Here we waste a few seconds to 1.515 + // wait for the property changed events. 1.516 + // 1.517 + // See https://bugzilla.mozilla.org/show_bug.cgi?id=932914 1.518 + window.setTimeout(function() { 1.519 + deferred.resolve(adapter); 1.520 + }, 3000); 1.521 + }; 1.522 + request.onerror = function(aEvent) { 1.523 + ok(false, "Failed to get default adapter."); 1.524 + deferred.reject(aEvent.target.error); 1.525 + }; 1.526 + 1.527 + return deferred.promise; 1.528 +} 1.529 + 1.530 +/** 1.531 + * Flush permission settings and call |finish()|. 1.532 + */ 1.533 +function cleanUp() { 1.534 + waitFor(function() { 1.535 + SpecialPowers.flushPermissions(function() { 1.536 + // Use ok here so that we have at least one test run. 1.537 + ok(true, "permissions flushed"); 1.538 + 1.539 + finish(); 1.540 + }); 1.541 + }, function() { 1.542 + return pendingEmulatorCmdCount === 0; 1.543 + }); 1.544 +} 1.545 + 1.546 +function startBluetoothTestBase(aPermissions, aTestCaseMain) { 1.547 + ensureBluetoothManager(aPermissions) 1.548 + .then(aTestCaseMain) 1.549 + .then(cleanUp, function() { 1.550 + ok(false, "Unhandled rejected promise."); 1.551 + cleanUp(); 1.552 + }); 1.553 +} 1.554 + 1.555 +function startBluetoothTest(aReenable, aTestCaseMain) { 1.556 + startBluetoothTestBase(["settings-read", "settings-write"], function() { 1.557 + let origEnabled, needEnable; 1.558 + 1.559 + return getBluetoothEnabled() 1.560 + .then(function(aEnabled) { 1.561 + origEnabled = aEnabled; 1.562 + needEnable = !aEnabled; 1.563 + log("Original 'bluetooth.enabled' is " + origEnabled); 1.564 + 1.565 + if (aEnabled && aReenable) { 1.566 + log(" Disable 'bluetooth.enabled' ..."); 1.567 + needEnable = true; 1.568 + return setBluetoothEnabledAndWait(false); 1.569 + } 1.570 + }) 1.571 + .then(function() { 1.572 + if (needEnable) { 1.573 + log(" Enable 'bluetooth.enabled' ..."); 1.574 + 1.575 + // See setBluetoothEnabledAndWait(). We must install all event 1.576 + // handlers *before* enabling Bluetooth. 1.577 + let promises = []; 1.578 + promises.push(waitForManagerEvent("adapteradded")); 1.579 + promises.push(setBluetoothEnabledAndWait(true)); 1.580 + return Promise.all(promises); 1.581 + } 1.582 + }) 1.583 + .then(getDefaultAdapter) 1.584 + .then(aTestCaseMain) 1.585 + .then(function() { 1.586 + if (!origEnabled) { 1.587 + return setBluetoothEnabledAndWait(false); 1.588 + } 1.589 + }); 1.590 + }); 1.591 +}