- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / options / chromeos / bluetooth_pair_device_overlay.js
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.
4
5 cr.define('options', function() {
6   /** @const */ var OptionsPage = options.OptionsPage;
7
8   /**
9    * Enumeration of possible states during pairing.  The value associated with
10    * each state maps to a localized string in the global variable
11    * |loadTimeData|.
12    * @enum {string}
13    */
14   var PAIRING = {
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
24                                             // canceled).
25   };
26
27   /**
28    * List of IDs for conditionally visible elements in the dialog.
29    * @type {Array.<string>}
30    * @const
31    */
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'];
40
41   /**
42    * Encapsulated handling of the Bluetooth device pairing page.
43    * @constructor
44    */
45   function BluetoothPairing() {
46     OptionsPage.call(this,
47                      'bluetoothPairing',
48                      loadTimeData.getString('bluetoothOptionsPageTabTitle'),
49                      'bluetooth-pairing');
50   }
51
52   cr.addSingletonGetter(BluetoothPairing);
53
54   BluetoothPairing.prototype = {
55     __proto__: OptionsPage.prototype,
56
57     /**
58      * Description of the bluetooth device.
59      * @type {{name: string,
60      *         address: string,
61      *         paired: boolean,
62      *         connected: boolean,
63      *         connecting: boolean,
64      *         connectable: boolean,
65      *         pairing: string|undefined,
66      *         passkey: number|undefined,
67      *         pincode: string|undefined,
68      *         entered: number|undefined}}
69      * @private.
70      */
71     device_: null,
72
73     /**
74      * Can the dialog be programmatically dismissed.
75      * @type {boolean}
76      */
77     dismissible_: true,
78
79     /** @override */
80     initializePage: function() {
81       OptionsPage.prototype.initializePage.call(this);
82       var self = this;
83       $('bluetooth-pair-device-cancel-button').onclick = function() {
84         OptionsPage.closeOverlay();
85       };
86       $('bluetooth-pair-device-reject-button').onclick = function() {
87         chrome.send('updateBluetoothDevice',
88                     [self.device_.address, 'reject']);
89         OptionsPage.closeOverlay();
90       };
91       $('bluetooth-pair-device-connect-button').onclick = function() {
92         var args = [self.device_.address, 'connect'];
93         var passkey = self.device_.passkey;
94         if (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;
103       };
104       $('bluetooth-pair-device-accept-button').onclick = function() {
105         chrome.send('updateBluetoothDevice',
106                     [self.device_.address, 'accept']);
107         OptionsPage.closeOverlay();
108       };
109       $('bluetooth-pair-device-dismiss-button').onclick = function() {
110         OptionsPage.closeOverlay();
111       };
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;
124       }
125       $('bluetooth-pincode').oninput = function() {
126         $('bluetooth-pair-device-connect-button').disabled =
127             $('bluetooth-pincode').value.length == 0;
128       }
129       $('bluetooth-passkey').addEventListener('keydown',
130           this.keyDownEventHandler_.bind(this));
131       $('bluetooth-pincode').addEventListener('keydown',
132           this.keyDownEventHandler_.bind(this));
133     },
134
135     /** @override */
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']);
142       }
143     },
144
145     /**
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.
150      */
151     canShowPage: function() {
152       return this.device_ && this.device_.address && this.device_.pairing;
153     },
154
155     /**
156      * Sets input focus on the passkey or pincode field if appropriate.
157      */
158     didShowPage: function() {
159       if (!$('bluetooth-pincode').hidden)
160         $('bluetooth-pincode').focus();
161       else if (!$('bluetooth-passkey').hidden)
162         $('bluetooth-passkey').focus();
163     },
164
165     /**
166      * Configures the overlay for pairing a device.
167      * @param {Object} device Description of the bluetooth device.
168      */
169     update: function(device) {
170       this.device_ = {};
171       for (key in 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;
178
179       var message = loadTimeData.getString(device.pairing);
180       message = message.replace('%1', this.device_.name);
181       instructionsEl.textContent = message;
182
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']);
191         } else {
192           // Remote entering a passkey.
193           this.displayElements_(['bluetooth-pairing-passkey-display',
194                                  'bluetooth-pair-device-cancel-button']);
195         }
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']);
215       } else {
216         // Displaying an error message.
217         this.displayElements_(['bluetooth-pair-device-dismiss-button']);
218       }
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;
223     },
224
225     /**
226      * Handles the ENTER key for the passkey or pincode entry field.
227      * @return {Event} a keydown event.
228      * @private
229      */
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');
234         if (!button.hidden)
235           button.click();
236       }
237     },
238
239     /**
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.
243      * @private
244      */
245     displayElements_: function(list) {
246       var enabled = {};
247       for (var i = 0; i < list.length; i++) {
248         var key = list[i];
249         enabled[key] = true;
250       }
251       for (var i = 0; i < ELEMENTS.length; i++) {
252         var key = ELEMENTS[i];
253         $(key).hidden = !enabled[key];
254       }
255     },
256
257     /**
258      * Removes all children from an element.
259      * @param {!Element} element Target element to clear.
260      */
261     clearElement_: function(element) {
262       var child = element.firstChild;
263       while (child) {
264         element.removeChild(child);
265         child = element.firstChild;
266       }
267     },
268
269     /**
270      * Formats an element for displaying the passkey.
271      */
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);
290       }
291       if (this.device_.pairing == PAIRING.REMOTE_PASSKEY) {
292         // Add enter key.
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);
303       }
304       passkeyEl.hidden = false;
305     },
306
307     /**
308      * Formats an element for displaying the PIN code.
309      */
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);
322       }
323       if (this.device_.pairing == PAIRING.REMOTE_PIN_CODE) {
324         // Add enter key.
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);
332       }
333       passkeyEl.hidden = false;
334     },
335   };
336
337   /**
338    * Configures the device pairing instructions and displays the pairing
339    * overlay.
340    * @param {Object} device Description of the Bluetooth device.
341    */
342   BluetoothPairing.showDialog = function(device) {
343     BluetoothPairing.getInstance().update(device);
344     OptionsPage.showPageByName('bluetoothPairing', false);
345   };
346
347   /**
348    * Displays a message from the Bluetooth adapter.
349    * @param {{string: label,
350    *          string: address} data  Data for constructing the message.
351    */
352   BluetoothPairing.showMessage = function(data) {
353     var name = data.address;
354     if (name.length == 0)
355       return;
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.
360       return;
361     }
362
363     var list = $('bluetooth-paired-devices-list');
364     if (list) {
365       var index = list.find(name);
366       if (index == undefined) {
367         list = $('bluetooth-unpaired-devices-list');
368         index = list.find(name);
369       }
370       if (index != undefined) {
371         var entry = list.dataModel.item(index);
372         if (entry && entry.name)
373           name = entry.name;
374       }
375     }
376     BluetoothPairing.showDialog({name: name,
377                                  address: data.address,
378                                  pairing: data.label,
379                                  dismissible: false});
380   };
381
382   /**
383    * Closes the Bluetooth pairing dialog.
384    */
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();
391     }
392   };
393
394   // Export
395   return {
396     BluetoothPairing: BluetoothPairing
397   };
398 });