1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 cr.define('options', function() {
6 /** @const */ var OptionsPage = options.OptionsPage;
9 * Enumeration of possible states during pairing. The value associated with
10 * each state maps to a localized string in the global variable
15 STARTUP: 'bluetoothStartConnecting',
16 ENTER_PIN_CODE: 'bluetoothEnterPinCode',
17 ENTER_PASSKEY: 'bluetoothEnterPasskey',
18 REMOTE_PIN_CODE: 'bluetoothRemotePinCode',
19 REMOTE_PASSKEY: 'bluetoothRemotePasskey',
20 CONFIRM_PASSKEY: 'bluetoothConfirmPasskey',
21 CONNECT_FAILED: 'bluetoothConnectFailed',
22 CANCELED: 'bluetoothPairingCanceled',
23 DISMISSED: 'bluetoothPairingDismissed', // pairing dismissed(succeeded or
28 * List of IDs for conditionally visible elements in the dialog.
29 * @type {Array.<string>}
32 var ELEMENTS = ['bluetooth-pairing-passkey-display',
33 'bluetooth-pairing-passkey-entry',
34 'bluetooth-pairing-pincode-entry',
35 'bluetooth-pair-device-connect-button',
36 'bluetooth-pair-device-cancel-button',
37 'bluetooth-pair-device-accept-button',
38 'bluetooth-pair-device-reject-button',
39 'bluetooth-pair-device-dismiss-button'];
42 * Encapsulated handling of the Bluetooth device pairing page.
45 function BluetoothPairing() {
46 OptionsPage.call(this,
48 loadTimeData.getString('bluetoothOptionsPageTabTitle'),
52 cr.addSingletonGetter(BluetoothPairing);
54 BluetoothPairing.prototype = {
55 __proto__: OptionsPage.prototype,
58 * Description of the bluetooth device.
59 * @type {{name: string,
63 * connecting: boolean,
64 * connectable: boolean,
65 * pairing: string|undefined,
66 * passkey: number|undefined,
67 * pincode: string|undefined,
68 * entered: number|undefined}}
74 * Can the dialog be programmatically dismissed.
80 initializePage: function() {
81 OptionsPage.prototype.initializePage.call(this);
83 $('bluetooth-pair-device-cancel-button').onclick = function() {
84 OptionsPage.closeOverlay();
86 $('bluetooth-pair-device-reject-button').onclick = function() {
87 chrome.send('updateBluetoothDevice',
88 [self.device_.address, 'reject']);
89 self.device_.pairing = PAIRING.DISMISSED;
90 OptionsPage.closeOverlay();
92 $('bluetooth-pair-device-connect-button').onclick = function() {
93 var args = [self.device_.address, 'connect'];
94 var passkey = self.device_.passkey;
96 args.push(String(passkey));
97 else if (!$('bluetooth-pairing-passkey-entry').hidden)
98 args.push($('bluetooth-passkey').value);
99 else if (!$('bluetooth-pairing-pincode-entry').hidden)
100 args.push($('bluetooth-pincode').value);
101 chrome.send('updateBluetoothDevice', args);
102 // Prevent sending a 'connect' command twice.
103 $('bluetooth-pair-device-connect-button').disabled = true;
105 $('bluetooth-pair-device-accept-button').onclick = function() {
106 chrome.send('updateBluetoothDevice',
107 [self.device_.address, 'accept']);
108 // Prevent sending a 'accept' command twice.
109 $('bluetooth-pair-device-accept-button').disabled = true;
111 $('bluetooth-pair-device-dismiss-button').onclick = function() {
112 OptionsPage.closeOverlay();
114 $('bluetooth-passkey').oninput = function() {
115 var inputField = $('bluetooth-passkey');
116 var value = inputField.value;
117 // Note that using <input type="number"> is insufficient to restrict
118 // the input as it allows negative numbers and does not limit the
119 // number of charactes typed even if a range is set. Furthermore,
120 // it sometimes produces strange repaint artifacts.
121 var filtered = value.replace(/[^0-9]/g, '');
122 if (filtered != value)
123 inputField.value = filtered;
124 $('bluetooth-pair-device-connect-button').disabled =
125 inputField.value.length == 0;
127 $('bluetooth-pincode').oninput = function() {
128 $('bluetooth-pair-device-connect-button').disabled =
129 $('bluetooth-pincode').value.length == 0;
131 $('bluetooth-passkey').addEventListener('keydown',
132 this.keyDownEventHandler_.bind(this));
133 $('bluetooth-pincode').addEventListener('keydown',
134 this.keyDownEventHandler_.bind(this));
138 didClosePage: function() {
139 if (this.device_.pairing != PAIRING.DISMISSED &&
140 this.device_.pairing != PAIRING.CONNECT_FAILED) {
141 this.device_.pairing = PAIRING.CANCELED;
142 chrome.send('updateBluetoothDevice',
143 [this.device_.address, 'cancel']);
148 * Override to prevent showing the overlay if the Bluetooth device details
149 * have not been specified. Prevents showing an empty dialog if the user
150 * quits and restarts Chrome while in the process of pairing with a device.
151 * @return {boolean} True if the overlay can be displayed.
153 canShowPage: function() {
154 return this.device_ && this.device_.address && this.device_.pairing;
158 * Sets input focus on the passkey or pincode field if appropriate.
160 didShowPage: function() {
161 if (!$('bluetooth-pincode').hidden)
162 $('bluetooth-pincode').focus();
163 else if (!$('bluetooth-passkey').hidden)
164 $('bluetooth-passkey').focus();
168 * Configures the overlay for pairing a device.
169 * @param {Object} device Description of the bluetooth device.
171 update: function(device) {
174 this.device_[key] = device[key];
175 // Update the pairing instructions.
176 var instructionsEl = $('bluetooth-pairing-instructions');
177 this.clearElement_(instructionsEl);
178 this.dismissible_ = ('dismissible' in device) ?
179 device.dismissible : true;
181 var message = loadTimeData.getString(device.pairing);
182 message = message.replace('%1', this.device_.name);
183 instructionsEl.textContent = message;
185 // Update visibility of dialog elements.
186 if (this.device_.passkey) {
187 this.updatePasskey_(String(this.device_.passkey));
188 if (this.device_.pairing == PAIRING.CONFIRM_PASSKEY) {
189 // Confirming a match between displayed passkeys.
190 this.displayElements_(['bluetooth-pairing-passkey-display',
191 'bluetooth-pair-device-accept-button',
192 'bluetooth-pair-device-reject-button']);
193 $('bluetooth-pair-device-accept-button').disabled = false;
195 // Remote entering a passkey.
196 this.displayElements_(['bluetooth-pairing-passkey-display',
197 'bluetooth-pair-device-cancel-button']);
199 } else if (this.device_.pincode) {
200 this.updatePasskey_(String(this.device_.pincode));
201 this.displayElements_(['bluetooth-pairing-passkey-display',
202 'bluetooth-pair-device-cancel-button']);
203 } else if (this.device_.pairing == PAIRING.ENTER_PIN_CODE) {
204 // Prompting the user to enter a PIN code.
205 this.displayElements_(['bluetooth-pairing-pincode-entry',
206 'bluetooth-pair-device-connect-button',
207 'bluetooth-pair-device-cancel-button']);
208 $('bluetooth-pincode').value = '';
209 } else if (this.device_.pairing == PAIRING.ENTER_PASSKEY) {
210 // Prompting the user to enter a passkey.
211 this.displayElements_(['bluetooth-pairing-passkey-entry',
212 'bluetooth-pair-device-connect-button',
213 'bluetooth-pair-device-cancel-button']);
214 $('bluetooth-passkey').value = '';
215 } else if (this.device_.pairing == PAIRING.STARTUP) {
216 // Starting the pairing process.
217 this.displayElements_(['bluetooth-pair-device-cancel-button']);
219 // Displaying an error message.
220 this.displayElements_(['bluetooth-pair-device-dismiss-button']);
222 // User is required to enter a passkey or pincode before the connect
223 // button can be enabled. The 'oninput' methods for the input fields
224 // determine when the connect button becomes active.
225 $('bluetooth-pair-device-connect-button').disabled = true;
229 * Handles the ENTER key for the passkey or pincode entry field.
230 * @return {Event} a keydown event.
233 keyDownEventHandler_: function(event) {
234 /** @const */ var ENTER_KEY_CODE = 13;
235 if (event.keyCode == ENTER_KEY_CODE) {
236 var button = $('bluetooth-pair-device-connect-button');
243 * Updates the visibility of elements in the dialog.
244 * @param {Array.<string>} list List of conditionally visible elements that
245 * are to be made visible.
248 displayElements_: function(list) {
250 for (var i = 0; i < list.length; i++) {
254 for (var i = 0; i < ELEMENTS.length; i++) {
255 var key = ELEMENTS[i];
256 $(key).hidden = !enabled[key];
261 * Removes all children from an element.
262 * @param {!Element} element Target element to clear.
264 clearElement_: function(element) {
265 var child = element.firstChild;
267 element.removeChild(child);
268 child = element.firstChild;
273 * Formats an element for displaying the passkey or PIN code.
274 * @param {string} key Passkey or PIN to display.
276 updatePasskey_: function(key) {
277 var passkeyEl = $('bluetooth-pairing-passkey-display');
278 var keyClass = (this.device_.pairing == PAIRING.REMOTE_PASSKEY ||
279 this.device_.pairing == PAIRING.REMOTE_PIN_CODE) ?
280 'bluetooth-keyboard-button' : 'bluetooth-passkey-char';
281 this.clearElement_(passkeyEl);
282 // Passkey should always have 6 digits.
283 key = '000000'.substring(0, 6 - key.length) + key;
284 var progress = this.device_.entered;
285 for (var i = 0; i < key.length; i++) {
286 var keyEl = document.createElement('span');
287 keyEl.textContent = key.charAt(i);
288 keyEl.className = keyClass;
289 if (progress != undefined) {
291 keyEl.classList.add('key-typed');
292 else if (i == progress)
293 keyEl.classList.add('key-next');
295 keyEl.classList.add('key-untyped');
297 passkeyEl.appendChild(keyEl);
299 if (this.device_.pairing == PAIRING.REMOTE_PASSKEY ||
300 this.device_.pairing == PAIRING.REMOTE_PIN_CODE) {
302 var label = loadTimeData.getString('bluetoothEnterKey');
303 var keyEl = document.createElement('span');
304 keyEl.textContent = label;
305 keyEl.className = keyClass;
306 keyEl.id = 'bluetooth-enter-key';
307 if (progress != undefined) {
308 if (progress > key.length)
309 keyEl.classList.add('key-typed');
310 else if (progress == key.length)
311 keyEl.classList.add('key-next');
313 keyEl.classList.add('key-untyped');
315 passkeyEl.appendChild(keyEl);
317 passkeyEl.hidden = false;
322 * Configures the device pairing instructions and displays the pairing
324 * @param {Object} device Description of the Bluetooth device.
326 BluetoothPairing.showDialog = function(device) {
327 BluetoothPairing.getInstance().update(device);
328 OptionsPage.showPageByName('bluetoothPairing', false);
332 * Displays a message from the Bluetooth adapter.
333 * @param {{string: label,
334 * string: address} data Data for constructing the message.
336 BluetoothPairing.showMessage = function(data) {
337 var name = data.address;
338 if (name.length == 0)
340 var dialog = BluetoothPairing.getInstance();
341 if (dialog.device_ && name == dialog.device_.address &&
342 dialog.device_.pairing == PAIRING.CANCELED) {
343 // Do not show any error message after cancelation of the pairing.
347 var list = $('bluetooth-paired-devices-list');
349 var index = list.find(name);
350 if (index == undefined) {
351 list = $('bluetooth-unpaired-devices-list');
352 index = list.find(name);
354 if (index != undefined) {
355 var entry = list.dataModel.item(index);
356 if (entry && entry.name)
360 BluetoothPairing.showDialog({name: name,
361 address: data.address,
363 dismissible: false});
367 * Closes the Bluetooth pairing dialog.
369 BluetoothPairing.dismissDialog = function() {
370 var overlay = OptionsPage.getTopmostVisiblePage();
371 var dialog = BluetoothPairing.getInstance();
372 if (overlay == dialog && dialog.dismissible_) {
373 dialog.device_.pairing = PAIRING.DISMISSED;
374 OptionsPage.closeOverlay();
380 BluetoothPairing: BluetoothPairing