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 Page = cr.ui.pageManager.Page;
7 /** @const */ var PageManager = cr.ui.pageManager.PageManager;
10 * Enumeration of possible states during pairing. The value associated with
11 * each state maps to a localized string in the global variable
16 STARTUP: 'bluetoothStartConnecting',
17 ENTER_PIN_CODE: 'bluetoothEnterPinCode',
18 ENTER_PASSKEY: 'bluetoothEnterPasskey',
19 REMOTE_PIN_CODE: 'bluetoothRemotePinCode',
20 REMOTE_PASSKEY: 'bluetoothRemotePasskey',
21 CONFIRM_PASSKEY: 'bluetoothConfirmPasskey',
22 CONNECT_FAILED: 'bluetoothConnectFailed',
23 CANCELED: 'bluetoothPairingCanceled',
24 DISMISSED: 'bluetoothPairingDismissed', // pairing dismissed(succeeded or
29 * List of IDs for conditionally visible elements in the dialog.
30 * @type {Array<string>}
33 var ELEMENTS = ['bluetooth-pairing-passkey-display',
34 'bluetooth-pairing-passkey-entry',
35 'bluetooth-pairing-pincode-entry',
36 'bluetooth-pair-device-connect-button',
37 'bluetooth-pair-device-cancel-button',
38 'bluetooth-pair-device-accept-button',
39 'bluetooth-pair-device-reject-button',
40 'bluetooth-pair-device-dismiss-button'];
43 * Encapsulated handling of the Bluetooth device pairing page.
45 * @extends {cr.ui.pageManager.Page}
47 function BluetoothPairing() {
48 Page.call(this, 'bluetoothPairing',
49 loadTimeData.getString('bluetoothOptionsPageTabTitle'),
53 cr.addSingletonGetter(BluetoothPairing);
55 BluetoothPairing.prototype = {
56 __proto__: Page.prototype,
59 * Description of the bluetooth device.
60 * @type {?BluetoothDevice}
66 * Can the dialog be programmatically dismissed.
72 initializePage: function() {
73 Page.prototype.initializePage.call(this);
75 $('bluetooth-pair-device-cancel-button').onclick = function() {
76 PageManager.closeOverlay();
78 $('bluetooth-pair-device-reject-button').onclick = function() {
79 chrome.send('updateBluetoothDevice',
80 [self.device_.address, 'reject']);
81 self.device_.pairing = PAIRING.DISMISSED;
82 PageManager.closeOverlay();
84 $('bluetooth-pair-device-connect-button').onclick = function() {
85 var args = [self.device_.address, 'connect'];
86 var passkey = self.device_.passkey;
88 args.push(String(passkey));
89 else if (!$('bluetooth-pairing-passkey-entry').hidden)
90 args.push($('bluetooth-passkey').value);
91 else if (!$('bluetooth-pairing-pincode-entry').hidden)
92 args.push($('bluetooth-pincode').value);
93 chrome.send('updateBluetoothDevice', args);
94 // Prevent sending a 'connect' command twice.
95 $('bluetooth-pair-device-connect-button').disabled = true;
97 $('bluetooth-pair-device-accept-button').onclick = function() {
98 chrome.send('updateBluetoothDevice',
99 [self.device_.address, 'accept']);
100 // Prevent sending a 'accept' command twice.
101 $('bluetooth-pair-device-accept-button').disabled = true;
103 $('bluetooth-pair-device-dismiss-button').onclick = function() {
104 PageManager.closeOverlay();
106 $('bluetooth-passkey').oninput = function() {
107 var inputField = $('bluetooth-passkey');
108 var value = inputField.value;
109 // Note that using <input type="number"> is insufficient to restrict
110 // the input as it allows negative numbers and does not limit the
111 // number of charactes typed even if a range is set. Furthermore,
112 // it sometimes produces strange repaint artifacts.
113 var filtered = value.replace(/[^0-9]/g, '');
114 if (filtered != value)
115 inputField.value = filtered;
116 $('bluetooth-pair-device-connect-button').disabled =
117 inputField.value.length == 0;
119 $('bluetooth-pincode').oninput = function() {
120 $('bluetooth-pair-device-connect-button').disabled =
121 $('bluetooth-pincode').value.length == 0;
123 $('bluetooth-passkey').addEventListener('keydown',
124 this.keyDownEventHandler_.bind(this));
125 $('bluetooth-pincode').addEventListener('keydown',
126 this.keyDownEventHandler_.bind(this));
130 didClosePage: function() {
131 if (this.device_.pairing != PAIRING.DISMISSED &&
132 this.device_.pairing != PAIRING.CONNECT_FAILED) {
133 this.device_.pairing = PAIRING.CANCELED;
134 chrome.send('updateBluetoothDevice',
135 [this.device_.address, 'cancel']);
140 * Override to prevent showing the overlay if the Bluetooth device details
141 * have not been specified. Prevents showing an empty dialog if the user
142 * quits and restarts Chrome while in the process of pairing with a device.
143 * @return {boolean} True if the overlay can be displayed.
145 canShowPage: function() {
146 return !!(this.device_ && this.device_.address && this.device_.pairing);
150 * Sets input focus on the passkey or pincode field if appropriate.
152 didShowPage: function() {
153 if (!$('bluetooth-pincode').hidden)
154 $('bluetooth-pincode').focus();
155 else if (!$('bluetooth-passkey').hidden)
156 $('bluetooth-passkey').focus();
160 * Configures the overlay for pairing a device.
161 * @param {Object} device Description of the bluetooth device.
163 update: function(device) {
164 this.device_ = /** @type {BluetoothDevice} */({});
165 for (var key in device)
166 this.device_[key] = device[key];
167 // Update the pairing instructions.
168 var instructionsEl = assert($('bluetooth-pairing-instructions'));
169 this.clearElement_(instructionsEl);
170 this.dismissible_ = ('dismissible' in device) ?
171 device.dismissible : true;
173 var message = loadTimeData.getString(device.pairing);
174 message = message.replace('%1', this.device_.name);
175 instructionsEl.textContent = message;
177 // Update visibility of dialog elements.
178 if (this.device_.passkey) {
179 this.updatePasskey_(String(this.device_.passkey));
180 if (this.device_.pairing == PAIRING.CONFIRM_PASSKEY) {
181 // Confirming a match between displayed passkeys.
182 this.displayElements_(['bluetooth-pairing-passkey-display',
183 'bluetooth-pair-device-accept-button',
184 'bluetooth-pair-device-reject-button']);
185 $('bluetooth-pair-device-accept-button').disabled = false;
187 // Remote entering a passkey.
188 this.displayElements_(['bluetooth-pairing-passkey-display',
189 'bluetooth-pair-device-cancel-button']);
191 } else if (this.device_.pincode) {
192 this.updatePasskey_(String(this.device_.pincode));
193 this.displayElements_(['bluetooth-pairing-passkey-display',
194 'bluetooth-pair-device-cancel-button']);
195 } else if (this.device_.pairing == PAIRING.ENTER_PIN_CODE) {
196 // Prompting the user to enter a PIN code.
197 this.displayElements_(['bluetooth-pairing-pincode-entry',
198 'bluetooth-pair-device-connect-button',
199 'bluetooth-pair-device-cancel-button']);
200 $('bluetooth-pincode').value = '';
201 } else if (this.device_.pairing == PAIRING.ENTER_PASSKEY) {
202 // Prompting the user to enter a passkey.
203 this.displayElements_(['bluetooth-pairing-passkey-entry',
204 'bluetooth-pair-device-connect-button',
205 'bluetooth-pair-device-cancel-button']);
206 $('bluetooth-passkey').value = '';
207 } else if (this.device_.pairing == PAIRING.STARTUP) {
208 // Starting the pairing process.
209 this.displayElements_(['bluetooth-pair-device-cancel-button']);
211 // Displaying an error message.
212 this.displayElements_(['bluetooth-pair-device-dismiss-button']);
214 // User is required to enter a passkey or pincode before the connect
215 // button can be enabled. The 'oninput' methods for the input fields
216 // determine when the connect button becomes active.
217 $('bluetooth-pair-device-connect-button').disabled = true;
221 * Handles the ENTER key for the passkey or pincode entry field.
222 * @param {Event} event A keydown event.
225 keyDownEventHandler_: function(event) {
226 /** @const */ var ENTER_KEY_CODE = 13;
227 if (event.keyCode == ENTER_KEY_CODE) {
228 var button = $('bluetooth-pair-device-connect-button');
235 * Updates the visibility of elements in the dialog.
236 * @param {Array<string>} list List of conditionally visible elements that
237 * are to be made visible.
240 displayElements_: function(list) {
242 for (var i = 0; i < list.length; i++) {
246 for (var i = 0; i < ELEMENTS.length; i++) {
247 var key = ELEMENTS[i];
248 $(key).hidden = !enabled[key];
253 * Removes all children from an element.
254 * @param {!Element} element Target element to clear.
256 clearElement_: function(element) {
257 var child = element.firstChild;
259 element.removeChild(child);
260 child = element.firstChild;
265 * Formats an element for displaying the passkey or PIN code.
266 * @param {string} key Passkey or PIN to display.
268 updatePasskey_: function(key) {
269 var passkeyEl = assert($('bluetooth-pairing-passkey-display'));
270 var keyClass = (this.device_.pairing == PAIRING.REMOTE_PASSKEY ||
271 this.device_.pairing == PAIRING.REMOTE_PIN_CODE) ?
272 'bluetooth-keyboard-button' : 'bluetooth-passkey-char';
273 this.clearElement_(passkeyEl);
274 // Passkey should always have 6 digits.
275 key = '000000'.substring(0, 6 - key.length) + key;
276 var progress = this.device_.entered;
277 for (var i = 0; i < key.length; i++) {
278 var keyEl = document.createElement('span');
279 keyEl.textContent = key.charAt(i);
280 keyEl.className = keyClass;
281 if (progress != undefined) {
283 keyEl.classList.add('key-typed');
284 else if (i == progress)
285 keyEl.classList.add('key-next');
287 keyEl.classList.add('key-untyped');
289 passkeyEl.appendChild(keyEl);
291 if (this.device_.pairing == PAIRING.REMOTE_PASSKEY ||
292 this.device_.pairing == PAIRING.REMOTE_PIN_CODE) {
294 var label = loadTimeData.getString('bluetoothEnterKey');
295 var keyEl = document.createElement('span');
296 keyEl.textContent = label;
297 keyEl.className = keyClass;
298 keyEl.id = 'bluetooth-enter-key';
299 if (progress != undefined) {
300 if (progress > key.length)
301 keyEl.classList.add('key-typed');
302 else if (progress == key.length)
303 keyEl.classList.add('key-next');
305 keyEl.classList.add('key-untyped');
307 passkeyEl.appendChild(keyEl);
309 passkeyEl.hidden = false;
314 * Configures the device pairing instructions and displays the pairing
316 * @param {Object} device Description of the Bluetooth device.
318 BluetoothPairing.showDialog = function(device) {
319 BluetoothPairing.getInstance().update(device);
320 PageManager.showPageByName('bluetoothPairing', false);
324 * Displays a message from the Bluetooth adapter.
325 * @param {{message: string, address: string}} data Data for constructing the
326 * message. |data.message| is the name of message to show. |data.address|
327 * is the device address.
329 BluetoothPairing.showMessage = function(data) {
330 var name = data.address;
331 if (name.length == 0)
333 var dialog = BluetoothPairing.getInstance();
334 if (dialog.device_ && name == dialog.device_.address &&
335 dialog.device_.pairing == PAIRING.CANCELED) {
336 // Do not show any error message after cancelation of the pairing.
340 var list = $('bluetooth-paired-devices-list');
342 var index = list.find(name);
343 if (index == undefined) {
344 list = $('bluetooth-unpaired-devices-list');
345 index = list.find(name);
347 if (index != undefined) {
348 var entry = list.dataModel.item(index);
349 if (entry && entry.name)
353 BluetoothPairing.showDialog({name: name,
354 address: data.address,
355 pairing: data.message,
356 dismissible: false});
360 * Closes the Bluetooth pairing dialog.
362 BluetoothPairing.dismissDialog = function() {
363 var overlay = PageManager.getTopmostVisiblePage();
364 var dialog = BluetoothPairing.getInstance();
365 if (overlay == dialog && dialog.dismissible_) {
366 dialog.device_.pairing = PAIRING.DISMISSED;
367 PageManager.closeOverlay();
373 BluetoothPairing: BluetoothPairing