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 OptionsPage.closeOverlay();
91 $('bluetooth-pair-device-connect-button').onclick = function() {
92 var args = [self.device_.address, 'connect'];
93 var passkey = self.device_.passkey;
95 args.push(String(passkey));
96 else if (!$('bluetooth-pairing-passkey-entry').hidden)
97 args.push($('bluetooth-passkey').value);
98 else if (!$('bluetooth-pairing-pincode-entry').hidden)
99 args.push($('bluetooth-pincode').value);
100 chrome.send('updateBluetoothDevice', args);
101 // Prevent sending a 'connect' command twice.
102 $('bluetooth-pair-device-connect-button').disabled = true;
104 $('bluetooth-pair-device-accept-button').onclick = function() {
105 chrome.send('updateBluetoothDevice',
106 [self.device_.address, 'accept']);
107 OptionsPage.closeOverlay();
109 $('bluetooth-pair-device-dismiss-button').onclick = function() {
110 OptionsPage.closeOverlay();
112 $('bluetooth-passkey').oninput = function() {
113 var inputField = $('bluetooth-passkey');
114 var value = inputField.value;
115 // Note that using <input type="number"> is insufficient to restrict
116 // the input as it allows negative numbers and does not limit the
117 // number of charactes typed even if a range is set. Furthermore,
118 // it sometimes produces strange repaint artifacts.
119 var filtered = value.replace(/[^0-9]/g, '');
120 if (filtered != value)
121 inputField.value = filtered;
122 $('bluetooth-pair-device-connect-button').disabled =
123 inputField.value.length == 0;
125 $('bluetooth-pincode').oninput = function() {
126 $('bluetooth-pair-device-connect-button').disabled =
127 $('bluetooth-pincode').value.length == 0;
129 $('bluetooth-passkey').addEventListener('keydown',
130 this.keyDownEventHandler_.bind(this));
131 $('bluetooth-pincode').addEventListener('keydown',
132 this.keyDownEventHandler_.bind(this));
136 didClosePage: function() {
137 if (this.device_.pairing != PAIRING.DISMISSED &&
138 this.device_.pairing != PAIRING.CONNECT_FAILED) {
139 this.device_.pairing = PAIRING.CANCELED;
140 chrome.send('updateBluetoothDevice',
141 [this.device_.address, 'cancel']);
146 * Override to prevent showing the overlay if the Bluetooth device details
147 * have not been specified. Prevents showing an empty dialog if the user
148 * quits and restarts Chrome while in the process of pairing with a device.
149 * @return {boolean} True if the overlay can be displayed.
151 canShowPage: function() {
152 return this.device_ && this.device_.address && this.device_.pairing;
156 * Sets input focus on the passkey or pincode field if appropriate.
158 didShowPage: function() {
159 if (!$('bluetooth-pincode').hidden)
160 $('bluetooth-pincode').focus();
161 else if (!$('bluetooth-passkey').hidden)
162 $('bluetooth-passkey').focus();
166 * Configures the overlay for pairing a device.
167 * @param {Object} device Description of the bluetooth device.
169 update: function(device) {
172 this.device_[key] = device[key];
173 // Update the pairing instructions.
174 var instructionsEl = $('bluetooth-pairing-instructions');
175 this.clearElement_(instructionsEl);
176 this.dismissible_ = ('dismissible' in device) ?
177 device.dismissible : true;
179 var message = loadTimeData.getString(device.pairing);
180 message = message.replace('%1', this.device_.name);
181 instructionsEl.textContent = message;
183 // Update visibility of dialog elements.
184 if (this.device_.passkey) {
185 this.updatePasskey_();
186 if (this.device_.pairing == PAIRING.CONFIRM_PASSKEY) {
187 // Confirming a match between displayed passkeys.
188 this.displayElements_(['bluetooth-pairing-passkey-display',
189 'bluetooth-pair-device-accept-button',
190 'bluetooth-pair-device-reject-button']);
192 // Remote entering a passkey.
193 this.displayElements_(['bluetooth-pairing-passkey-display',
194 'bluetooth-pair-device-cancel-button']);
196 } else if (this.device_.pincode) {
197 this.updatePinCode_();
198 this.displayElements_(['bluetooth-pairing-passkey-display',
199 'bluetooth-pair-device-cancel-button']);
200 } else if (this.device_.pairing == PAIRING.ENTER_PIN_CODE) {
201 // Prompting the user to enter a PIN code.
202 this.displayElements_(['bluetooth-pairing-pincode-entry',
203 'bluetooth-pair-device-connect-button',
204 'bluetooth-pair-device-cancel-button']);
205 $('bluetooth-pincode').value = '';
206 } else if (this.device_.pairing == PAIRING.ENTER_PASSKEY) {
207 // Prompting the user to enter a passkey.
208 this.displayElements_(['bluetooth-pairing-passkey-entry',
209 'bluetooth-pair-device-connect-button',
210 'bluetooth-pair-device-cancel-button']);
211 $('bluetooth-passkey').value = '';
212 } else if (this.device_.pairing == PAIRING.STARTUP) {
213 // Starting the pairing process.
214 this.displayElements_(['bluetooth-pair-device-cancel-button']);
216 // Displaying an error message.
217 this.displayElements_(['bluetooth-pair-device-dismiss-button']);
219 // User is required to enter a passkey or pincode before the connect
220 // button can be enabled. The 'oninput' methods for the input fields
221 // determine when the connect button becomes active.
222 $('bluetooth-pair-device-connect-button').disabled = true;
226 * Handles the ENTER key for the passkey or pincode entry field.
227 * @return {Event} a keydown event.
230 keyDownEventHandler_: function(event) {
231 /** @const */ var ENTER_KEY_CODE = 13;
232 if (event.keyCode == ENTER_KEY_CODE) {
233 var button = $('bluetooth-pair-device-connect-button');
240 * Updates the visibility of elements in the dialog.
241 * @param {Array.<string>} list List of conditionally visible elements that
242 * are to be made visible.
245 displayElements_: function(list) {
247 for (var i = 0; i < list.length; i++) {
251 for (var i = 0; i < ELEMENTS.length; i++) {
252 var key = ELEMENTS[i];
253 $(key).hidden = !enabled[key];
258 * Removes all children from an element.
259 * @param {!Element} element Target element to clear.
261 clearElement_: function(element) {
262 var child = element.firstChild;
264 element.removeChild(child);
265 child = element.firstChild;
270 * Formats an element for displaying the passkey.
272 updatePasskey_: function() {
273 var passkeyEl = $('bluetooth-pairing-passkey-display');
274 var keyClass = this.device_.pairing == PAIRING.REMOTE_PASSKEY ?
275 'bluetooth-keyboard-button' : 'bluetooth-passkey-char';
276 this.clearElement_(passkeyEl);
277 var key = String(this.device_.passkey);
278 // Passkey should always have 6 digits.
279 key = '000000'.substring(0, 6 - key.length) + key;
280 var progress = this.device_.entered;
281 for (var i = 0; i < key.length; i++) {
282 var keyEl = document.createElement('span');
283 keyEl.textContent = key.charAt(i);
284 keyEl.className = keyClass;
285 if (progress == undefined)
286 keyEl.classList.add('key-pin');
287 else if (i < progress)
288 keyEl.classList.add('key-typed');
289 passkeyEl.appendChild(keyEl);
291 if (this.device_.pairing == PAIRING.REMOTE_PASSKEY) {
293 var label = loadTimeData.getString('bluetoothEnterKey');
294 var keyEl = document.createElement('span');
295 keyEl.textContent = label;
296 keyEl.className = keyClass;
297 keyEl.id = 'bluetooth-enter-key';
298 if (progress == undefined)
299 keyEl.classList.add('key-pin');
300 else if (progress > key.length)
301 keyEl.classList.add('key-typed');
302 passkeyEl.appendChild(keyEl);
304 passkeyEl.hidden = false;
308 * Formats an element for displaying the PIN code.
310 updatePinCode_: function() {
311 var passkeyEl = $('bluetooth-pairing-passkey-display');
312 var keyClass = this.device_.pairing == PAIRING.REMOTE_PIN_CODE ?
313 'bluetooth-keyboard-button' : 'bluetooth-passkey-char';
314 this.clearElement_(passkeyEl);
315 var key = String(this.device_.pincode);
316 for (var i = 0; i < key.length; i++) {
317 var keyEl = document.createElement('span');
318 keyEl.textContent = key.charAt(i);
319 keyEl.className = keyClass;
320 keyEl.classList.add('key-pin');
321 passkeyEl.appendChild(keyEl);
323 if (this.device_.pairing == PAIRING.REMOTE_PIN_CODE) {
325 var label = loadTimeData.getString('bluetoothEnterKey');
326 var keyEl = document.createElement('span');
327 keyEl.textContent = label;
328 keyEl.className = keyClass;
329 keyEl.classList.add('key-pin');
330 keyEl.id = 'bluetooth-enter-key';
331 passkeyEl.appendChild(keyEl);
333 passkeyEl.hidden = false;
338 * Configures the device pairing instructions and displays the pairing
340 * @param {Object} device Description of the Bluetooth device.
342 BluetoothPairing.showDialog = function(device) {
343 BluetoothPairing.getInstance().update(device);
344 OptionsPage.showPageByName('bluetoothPairing', false);
348 * Displays a message from the Bluetooth adapter.
349 * @param {{string: label,
350 * string: address} data Data for constructing the message.
352 BluetoothPairing.showMessage = function(data) {
353 var name = data.address;
354 if (name.length == 0)
356 var dialog = BluetoothPairing.getInstance();
357 if (dialog.device_ && name == dialog.device_.address &&
358 dialog.device_.pairing == PAIRING.CANCELED) {
359 // Do not show any error message after cancelation of the pairing.
363 var list = $('bluetooth-paired-devices-list');
365 var index = list.find(name);
366 if (index == undefined) {
367 list = $('bluetooth-unpaired-devices-list');
368 index = list.find(name);
370 if (index != undefined) {
371 var entry = list.dataModel.item(index);
372 if (entry && entry.name)
376 BluetoothPairing.showDialog({name: name,
377 address: data.address,
379 dismissible: false});
383 * Closes the Bluetooth pairing dialog.
385 BluetoothPairing.dismissDialog = function() {
386 var overlay = OptionsPage.getTopmostVisiblePage();
387 var dialog = BluetoothPairing.getInstance();
388 if (overlay == dialog && dialog.dismissible_) {
389 dialog.device_.pairing = PAIRING.DISMISSED;
390 OptionsPage.closeOverlay();
396 BluetoothPairing: BluetoothPairing