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