Commit 66f0edf4 authored by David Tseng's avatar David Tseng Committed by Commit Bot

BTBrl: adds UI in ChromeVox options for bluetooth braille

Bug: 882261
Change-Id: I8226d70905146da97e9d1e1846a4e6a8e2cd01be
Reviewed-on: https://chromium-review.googlesource.com/c/1336656
Commit-Queue: David Tseng <dtseng@chromium.org>
Reviewed-by: default avatarYuki Awano <yawano@chromium.org>
Cr-Commit-Position: refs/heads/master@{#612345}
parent 0b4b79d2
...@@ -4,9 +4,9 @@ ...@@ -4,9 +4,9 @@
import("//build/config/features.gni") import("//build/config/features.gni")
import("//chrome/common/features.gni") import("//chrome/common/features.gni")
import("//chrome/test/base/js2gtest.gni")
import("//components/nacl/features.gni") import("//components/nacl/features.gni")
import("//testing/test.gni") import("//testing/test.gni")
import("//chrome/test/base/js2gtest.gni")
import("//third_party/closure_compiler/compile_js.gni") import("//third_party/closure_compiler/compile_js.gni")
import("run_jsbundler.gni") import("run_jsbundler.gni")
...@@ -31,6 +31,7 @@ chromevox_modules = [ ...@@ -31,6 +31,7 @@ chromevox_modules = [
"braille/braille_table.js", "braille/braille_table.js",
"braille/braille_translator_manager.js", "braille/braille_translator_manager.js",
"braille/bluetooth_braille_display_manager.js", "braille/bluetooth_braille_display_manager.js",
"braille/bluetooth_braille_display_ui.js",
"braille/expanding_braille_translator.js", "braille/expanding_braille_translator.js",
"braille/liblouis.js", "braille/liblouis.js",
"braille/nav_braille.js", "braille/nav_braille.js",
...@@ -559,6 +560,7 @@ js2gtest("chromevox_unitjs_tests") { ...@@ -559,6 +560,7 @@ js2gtest("chromevox_unitjs_tests") {
test_type = "webui" test_type = "webui"
sources = [ sources = [
"braille/bluetooth_braille_display_manager_test.unitjs", "braille/bluetooth_braille_display_manager_test.unitjs",
"braille/bluetooth_braille_display_ui_test.unitjs",
"braille/braille_display_manager_test.unitjs", "braille/braille_display_manager_test.unitjs",
"braille/braille_input_handler_test.unitjs", "braille/braille_input_handler_test.unitjs",
"braille/expanding_braille_translator_test.unitjs", "braille/expanding_braille_translator_test.unitjs",
......
...@@ -24,8 +24,9 @@ BluetoothBrailleDisplayListener.prototype = { ...@@ -24,8 +24,9 @@ BluetoothBrailleDisplayListener.prototype = {
/** /**
* Called when a pincode is requested and a response can be made by calling * Called when a pincode is requested and a response can be made by calling
* BluetoothBrailleDisplayManager.finishPairing. * BluetoothBrailleDisplayManager.finishPairing.
* @param {!chrome.bluetooth.Device} display
*/ */
onPincodeRequested: function() {}, onPincodeRequested: function(display) {},
}; };
/** /**
...@@ -47,8 +48,22 @@ BluetoothBrailleDisplayManager = function() { ...@@ -47,8 +48,22 @@ BluetoothBrailleDisplayManager = function() {
/** @private {!Array<BluetoothBrailleDisplayListener>} */ /** @private {!Array<BluetoothBrailleDisplayListener>} */
this.listeners_ = []; this.listeners_ = [];
/** @private {!Array<string>} */ /**
this.displayNames_ = ['Focus 40 BT']; * This list of braille display names was taken from other services that
* utilize Brltty (e.g. BrailleBack).
* @private {!Array<string|RegExp>}
*/
this.displayNames_ = [
'"EL12-', 'Esys-', 'Focus 14 BT', 'Focus 40 BT', 'Brailliant BI',
/Hansone|HansoneXL|BrailleSense|BrailleEDGE|SmartBeetle/, 'Refreshabraille',
'Orbit', 'VarioConnect', 'VarioUltra', 'HWG Brailliant', 'braillex trio',
/Alva BC/i, 'TSM', 'TS5',
new RegExp(
'(Actilino.*|Active Star.*|Braille Wave( BRW)?|Braillino( BL2)?' +
'|Braille Star 40( BS4)?|Easy Braille( EBR)?|Active Braille( AB4)?' +
'|Basic Braille BB[3,4,6]?)\\/[a-zA-Z][0-9]-[0-9]{5}'),
new RegExp('(BRW|BL2|BS4|EBR|AB4|BB(3|4|6)?)\\/[a-zA-Z][0-9]-[0-9]{5}')
];
/** /**
* The display explicitly preferred by a caller via connect. Only one such * The display explicitly preferred by a caller via connect. Only one such
...@@ -87,6 +102,10 @@ BluetoothBrailleDisplayManager.prototype = { ...@@ -87,6 +102,10 @@ BluetoothBrailleDisplayManager.prototype = {
*/ */
start: function() { start: function() {
chrome.bluetooth.startDiscovery(); chrome.bluetooth.startDiscovery();
// Pick up any devices already in the system including previously paired,
// but out of range displays.
this.handleDevicesChanged();
}, },
/** /**
...@@ -168,7 +187,7 @@ BluetoothBrailleDisplayManager.prototype = { ...@@ -168,7 +187,7 @@ BluetoothBrailleDisplayManager.prototype = {
chrome.bluetooth.getDevices((devices) => { chrome.bluetooth.getDevices((devices) => {
var displayList = devices.filter((device) => { var displayList = devices.filter((device) => {
return this.displayNames_.some((name) => { return this.displayNames_.some((name) => {
return device.name && device.name.indexOf(name) == 0; return device.name && device.name.search(name) == 0;
}); });
}); });
if (displayList.length == 0) if (displayList.length == 0)
...@@ -193,7 +212,8 @@ BluetoothBrailleDisplayManager.prototype = { ...@@ -193,7 +212,8 @@ BluetoothBrailleDisplayManager.prototype = {
handlePairing: function(pairingEvent) { handlePairing: function(pairingEvent) {
if (pairingEvent.pairing == if (pairingEvent.pairing ==
chrome.bluetoothPrivate.PairingEventType.REQUEST_PINCODE) chrome.bluetoothPrivate.PairingEventType.REQUEST_PINCODE)
this.listeners_.forEach((listener) => listener.onPincodeRequested()); this.listeners_.forEach(
(listener) => listener.onPincodeRequested(pairingEvent.device));
}, },
/** /**
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview A widget that exposes UI for interacting with a list of braille
* displays.
*/
goog.provide('BluetoothBrailleDisplayUI');
goog.require('BluetoothBrailleDisplayListener');
goog.require('BluetoothBrailleDisplayManager');
goog.require('Msgs');
/**
* A widget used for interacting with bluetooth braille displays.
* @constructor
* @implements {BluetoothBrailleDisplayListener}
*/
BluetoothBrailleDisplayUI = function() {
/** @private {!BluetoothBrailleDisplayManager} */
this.manager_ = new BluetoothBrailleDisplayManager();
this.manager_.addListener(this);
/** @private {Element} */
this.root_;
/** @private {Element} */
this.displaySelect_;
/** @private {Element} */
this.controls_;
};
BluetoothBrailleDisplayUI.prototype = {
/**
* Attaches this widget to |element|.
* @param {!Element} element
*/
attach: function(element) {
this.manager_.start();
var container = document.createElement('div');
element.appendChild(container);
this.root_ = container;
var title = document.createElement('h2');
title.textContent = Msgs.getMsg('options_bluetooth_braille_display_title');
container.appendChild(title);
var controls = document.createElement('div');
container.appendChild(controls);
this.controls_ = controls;
controls.className = 'option';
var selectLabel = document.createElement('span');
controls.appendChild(selectLabel);
selectLabel.id = 'bluetoothBrailleSelectLabel';
selectLabel.textContent =
Msgs.getMsg('options_bluetooth_braille_display_select_label');
var displaySelect = document.createElement('select');
this.displaySelect_ = displaySelect;
controls.appendChild(displaySelect);
displaySelect.setAttribute(
'aria-labelledby', 'bluetoothBrailleSelectLabel');
displaySelect.addEventListener('change', (evt) => {
this.updateControls_();
});
var connectOrDisconnect = document.createElement('button');
controls.appendChild(connectOrDisconnect);
connectOrDisconnect.id = 'connectOrDisconnect';
connectOrDisconnect.disabled = true;
var forget = document.createElement('button');
controls.appendChild(forget);
forget.id = 'forget';
forget.textContent =
Msgs.getMsg('options_bluetooth_braille_display_forget');
forget.disabled = true;
},
/**
* Detaches the rendered widget.
*/
detach: function() {
this.manager_.stop();
if (this.root_) {
this.root_.remove();
this.root_ = null;
}
},
/** @override */
onDisplayListChanged: function(displays) {
if (!this.displaySelect_)
throw 'Expected attach to have been called.';
// Remove any displays that were removed.
for (var i = 0; i < this.displaySelect_.children.length; i++) {
var domDisplay = this.displaySelect_.children[i];
if (!displays.find((display) => domDisplay.id == display.address))
domDisplay.remove();
}
displays.forEach((display) => {
// Check if the element already exists.
var displayContainer =
this.displaySelect_.querySelector('#' + CSS.escape(display.address));
// If the display already exists, no further processing is needed.
if (displayContainer)
return;
displayContainer = document.createElement('option');
this.displaySelect_.appendChild(displayContainer);
displayContainer.id = display.address;
var name = document.createElement('span');
displayContainer.appendChild(name);
name.textContent = display.name;
});
this.updateControls_();
},
/** @override */
onPincodeRequested: function(display) {
this.controls_.hidden = true;
var form = document.createElement('form');
this.controls_.parentElement.insertBefore(form, this.controls_);
// Create the text field and its label.
var label = document.createElement('label');
form.appendChild(label);
label.id = 'pincodeLabel';
label.textContent =
Msgs.getMsg('options_bluetooth_braille_display_pincode_label');
label.for = 'pincode';
var pincodeField = document.createElement('input');
pincodeField.id = 'pincode';
pincodeField.type = 'text';
pincodeField.setAttribute('aria-labelledby', 'pincodeLabel');
form.appendChild(pincodeField);
var timeoutId;
form.addEventListener('submit', (evt) => {
if (timeoutId)
clearTimeout(timeoutId);
if (pincodeField.value)
this.manager_.finishPairing(display, pincodeField.value);
this.controls_.hidden = false;
form.remove();
form = null;
evt.preventDefault();
evt.stopPropagation();
this.displaySelect_.focus();
});
// Also, schedule a 60 second timeout for pincode entry.
timeoutId = setTimeout(() => {
form.remove();
this.controls_.hidden = false;
this.displaySelect_.focus();
}, 60000);
document.body.blur();
pincodeField.focus();
},
/**
* @private
*/
updateControls_: function() {
// Only update controls if there is a selected display.
var sel = this.displaySelect_.options[this.displaySelect_.selectedIndex];
if (!sel)
return;
chrome.bluetooth.getDevice(sel.id, (display) => {
var connectOrDisconnect =
this.controls_.querySelector('#connectOrDisconnect');
connectOrDisconnect.disabled = display.connecting;
this.displaySelect_.disabled = display.connecting;
connectOrDisconnect.textContent = Msgs.getMsg(
display.connecting ?
'options_bluetooth_braille_display_connecting' :
(display.connected ?
'options_bluetooth_braille_display_disconnect' :
'options_bluetooth_braille_display_connect'));
connectOrDisconnect.onclick = function(savedDisplay, evt) {
if (savedDisplay.connected)
this.manager_.disconnect(savedDisplay);
else
this.manager_.connect(savedDisplay);
}.bind(this, display);
var forget = this.controls_.querySelector('#forget');
forget.disabled = !display.paired || display.connecting;
forget.onclick = function(savedDisplay) {
this.manager_.forget(savedDisplay);
}.bind(this, display);
});
}
};
...@@ -104,6 +104,8 @@ ...@@ -104,6 +104,8 @@
</label> </label>
</div> </div>
<div id="bluetoothBraille"></div>
<h2 class="i18n" msgid="options_virtual_braille_display"> <h2 class="i18n" msgid="options_virtual_braille_display">
Virtual Braille Display Virtual Braille Display
</h2> </h2>
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
goog.provide('cvox.OptionsPage'); goog.provide('cvox.OptionsPage');
goog.require('BluetoothBrailleDisplayManager'); goog.require('BluetoothBrailleDisplayUI');
goog.require('ConsoleTts'); goog.require('ConsoleTts');
goog.require('EventStreamLogger'); goog.require('EventStreamLogger');
goog.require('Msgs'); goog.require('Msgs');
...@@ -47,6 +47,7 @@ cvox.OptionsPage.consoleTts; ...@@ -47,6 +47,7 @@ cvox.OptionsPage.consoleTts;
* Initialize the options page by setting the current value of all prefs, and * Initialize the options page by setting the current value of all prefs, and
* adding event listeners. * adding event listeners.
* @suppress {missingProperties} Property prefs never defined on Window * @suppress {missingProperties} Property prefs never defined on Window
* @this {cvox.OptionsPage}
*/ */
cvox.OptionsPage.init = function() { cvox.OptionsPage.init = function() {
cvox.OptionsPage.prefs = chrome.extension.getBackgroundPage().prefs; cvox.OptionsPage.prefs = chrome.extension.getBackgroundPage().prefs;
...@@ -184,6 +185,13 @@ cvox.OptionsPage.init = function() { ...@@ -184,6 +185,13 @@ cvox.OptionsPage.init = function() {
'virtual_braille_display_rows_input', 'virtualBrailleRows'); 'virtual_braille_display_rows_input', 'virtualBrailleRows');
handleNumericalInputPref( handleNumericalInputPref(
'virtual_braille_display_columns_input', 'virtualBrailleColumns'); 'virtual_braille_display_columns_input', 'virtualBrailleColumns');
/** @type {!BluetoothBrailleDisplayUI} */
cvox.OptionsPage.bluetoothBrailleDisplayUI = new BluetoothBrailleDisplayUI();
var bluetoothBraille = $('bluetoothBraille');
if (bluetoothBraille)
cvox.OptionsPage.bluetoothBrailleDisplayUI.attach(bluetoothBraille);
}; };
/** /**
...@@ -457,3 +465,7 @@ cvox.OptionsPage.getBrailleTranslatorManager = function() { ...@@ -457,3 +465,7 @@ cvox.OptionsPage.getBrailleTranslatorManager = function() {
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
cvox.OptionsPage.init(); cvox.OptionsPage.init();
}, false); }, false);
window.addEventListener('beforeunload', function(e) {
cvox.OptionsPage.bluetoothBrailleDisplayUI.detach();
});
...@@ -3126,6 +3126,27 @@ If you're done with the tutorial, use ChromeVox to navigate to the Close button ...@@ -3126,6 +3126,27 @@ If you're done with the tutorial, use ChromeVox to navigate to the Close button
<message desc="Part of the ChromeVox touch tutorial page. Concludes this page." name="IDS_CHROMEVOX_TUTORIAL_TOUCH_LEARN_MORE"> <message desc="Part of the ChromeVox touch tutorial page. Concludes this page." name="IDS_CHROMEVOX_TUTORIAL_TOUCH_LEARN_MORE">
Explore more gestures in Learn Mode and the Chromebook Help Center Explore more gestures in Learn Mode and the Chromebook Help Center
</message> </message>
<message desc="Title of the bluetooth braille display section in ChromeVox options." name="IDS_CHROMEVOX_OPTIONS_BLUETOOTH_BRAILLE_DISPLAY_TITLE">
Bluetooth Braille Display
</message>
<message desc="Labels a button which when pressed, connects to a selected braille display." name="IDS_CHROMEVOX_OPTIONS_BLUETOOTH_BRAILLE_DISPLAY_CONNECT">
Connect
</message>
<message desc="Labels a button which when pressed, disconnects from a selected braille display." name="IDS_CHROMEVOX_OPTIONS_BLUETOOTH_BRAILLE_DISPLAY_DISCONNECT">
Disconnect
</message>
<message desc="Labels a button which is disabled and indicates the system is connecting to a braille display." name="IDS_CHROMEVOX_OPTIONS_BLUETOOTH_BRAILLE_DISPLAY_CONNECTING">
Connecting
</message>
<message desc="Labels a button which when pressed, forgets the selected braille display." name="IDS_CHROMEVOX_OPTIONS_BLUETOOTH_BRAILLE_DISPLAY_FORGET">
Forget
</message>
<message desc="Labels a text field which prompts the user for a pincode when pairing a braille display." name="IDS_CHROMEVOX_OPTIONS_BLUETOOTH_BRAILLE_DISPLAY_PINCODE_LABEL">
Please enter a pin
</message>
<message desc="Labels a select control which lists all bluetooth braille displays." name="IDS_CHROMEVOX_OPTIONS_BLUETOOTH_BRAILLE_DISPLAY_SELECT_LABEL">
Select a bluetooth braille display
</message>
</messages> </messages>
</release> </release>
</grit> </grit>
...@@ -110,5 +110,22 @@ function assertNotReached() { ...@@ -110,5 +110,22 @@ function assertNotReached() {
assertFalse(true); assertFalse(true);
} }
/**
* Asserts an actual DOM equals an expected stringified DOM.
* @param {string} expected
* @param {Node} actual
*/
function assertEqualsDOM(expected, actual) {
expected = expected.replace(/>\s+</gm, '><').trim(/\s/gm);
var actualStr = actual.outerHTML;
actualStr = actualStr.replace(/>\s+</gm, '><').trim(/\s/gm);
for (var i = 0; i < expected.length; i++)
assertEquals(
expected[i], actualStr[i],
'Mismatch at index ' + i + ' in expected:\n' + expected +
'\nactual:\n' + actualStr + '\n');
}
assertSame = assertEquals; assertSame = assertEquals;
assertNotSame = assertNotEquals; assertNotSame = assertNotEquals;
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment