OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011 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.system.bluetooth', function() { | |
6 /** | |
7 * Bluetooth settings constants. | |
8 */ | |
9 function Constants() {} | |
10 | |
11 /** | |
12 * Enumeration of supported device types. Each device type has an | |
13 * associated icon and CSS style. | |
14 * @enum {string} | |
15 */ | |
16 Constants.DEVICE_TYPE = { | |
17 COMPUTER: 'computer', | |
18 HEADSET: 'headset', | |
19 KEYBOARD: 'input-keyboard', | |
20 MOUSE: 'input-mouse', | |
21 PHONE: 'phone', | |
22 }; | |
23 | |
24 /** | |
25 * Enumeration of possible states for a bluetooth device. The value | |
26 * associated with each state maps to a localized string in the global | |
27 * variable 'templateData'. | |
28 * @enum {string} | |
29 */ | |
30 Constants.DEVICE_STATUS = { | |
31 CONNECTED: 'bluetoothDeviceConnected', | |
32 CONNECTING: 'bluetoothDeviceConnecting', | |
33 FAILED_PAIRING: 'bluetoothDeviceFailedPairing', | |
34 NOT_PAIRED: 'bluetoothDeviceNotPaired', | |
35 PAIRED: 'bluetoothDevicePaired' | |
36 }; | |
37 | |
38 /** | |
39 * Enumeration of possible states during pairing. The value associated | |
40 * with each state maps to a loalized string in the global variable | |
41 * 'tempateData'. | |
42 * @enum {string} | |
43 */ | |
44 Constants.PAIRING = { | |
45 CONFIRM_PASSKEY: 'bluetoothConfirmPasskey', | |
46 ENTER_PASSKEY: 'bluetoothEnterPasskey', | |
47 FAILED_CONNECT_INSTRUCTIONS: 'bluetoothFailedPairingInstructions', | |
48 REMOTE_PASSKEY: 'bluetoothRemotePasskey' | |
49 }; | |
50 | |
51 /** | |
52 * Creates an element for storing a list of bluetooth devices. | |
53 * @param {Object=} opt_propertyBag Optional properties. | |
54 * @constructor | |
55 * @extends {HTMLDivElement} | |
56 */ | |
57 var BluetoothListElement = cr.ui.define('div'); | |
58 | |
59 BluetoothListElement.prototype = { | |
60 __proto__: HTMLDivElement.prototype, | |
61 | |
62 /** @inheritDoc */ | |
63 decorate: function() { | |
64 }, | |
65 | |
66 /** | |
67 * Loads given list of bluetooth devices. This list will comprise of | |
68 * devices that are currently connected. New devices are discovered | |
69 * via the 'Find devices' button. | |
70 * @param {Array} devices An array of bluetooth devices. | |
71 */ | |
72 load: function(devices) { | |
73 this.textContent = ''; | |
74 for (var i = 0; i < devices.length; i++) { | |
75 if (this.isSupported_(devices[i])) | |
76 this.appendChild(new BluetoothItem(devices[i])); | |
77 } | |
78 }, | |
79 | |
80 /** | |
81 * Adds a bluetooth device to the list of available devices. A check is | |
82 * made to see if the device is already in the list, in which case the | |
83 * existing device is updated. | |
84 * @param {Object.<string,string>} device Description of the bluetooth | |
85 * device. | |
86 * @return {boolean} True if the devies was successfully added or updated. | |
87 */ | |
88 appendDevice: function(device) { | |
89 if (!this.isSupported_(device)) | |
90 return false; | |
91 var item = new BluetoothItem(device); | |
92 var existing = this.findDevice(device.address); | |
93 if (existing) | |
94 this.replaceChild(item, existing); | |
95 else | |
96 this.appendChild(item); | |
97 return true; | |
98 }, | |
99 | |
100 /** | |
101 * Scans the list of elements corresponding to discovered Bluetooth | |
102 * devices for one with a matching address. | |
103 * @param {string} address The address of the device. | |
104 * @return {Element|undefined} Element corresponding to the device address | |
105 * or undefined if no corresponding element is found. | |
106 */ | |
107 findDevice: function(address) { | |
108 var candidate = this.firstChild; | |
109 while (candidate) { | |
110 if (candidate.data.address == address) | |
111 return candidate; | |
112 candidate = candidate.nextSibling; | |
113 } | |
114 }, | |
115 | |
116 /** | |
117 * Tests if the bluetooth device is supported based on the type of device. | |
118 * @param {Object.<string,string>} device Desription of the device. | |
119 * @return {boolean} true if the device is supported. | |
120 * @private | |
121 */ | |
122 isSupported_: function(device) { | |
123 var target = device.icon; | |
124 for (var key in Constants.DEVICE_TYPE) { | |
125 if (Constants.DEVICE_TYPE[key] == target) | |
126 return true; | |
127 } | |
128 return false; | |
129 } | |
130 }; | |
131 | |
132 /** | |
133 * Creates an element in the list of bluetooth devices. | |
134 * @param {{name: string, | |
135 * address: string, | |
136 * icon: Constants.DEVICE_TYPE, | |
137 * paired: boolean, | |
138 * connected: boolean, | |
139 * pairing: string|undefined, | |
140 * passkey: number|undefined, | |
141 * entered: number|undefined}} device | |
142 * Decription of the bluetooth device. | |
143 * @constructor | |
144 */ | |
145 function BluetoothItem(device) { | |
146 var el = $('bluetooth-item-template').cloneNode(true); | |
147 el.__proto__ = BluetoothItem.prototype; | |
148 el.removeAttribute('id'); | |
149 el.hidden = false; | |
150 el.data = {}; | |
151 for (var key in device) | |
152 el.data[key] = device[key]; | |
153 el.decorate(); | |
154 return el; | |
155 } | |
156 | |
157 BluetoothItem.prototype = { | |
158 __proto__: HTMLDivElement.prototype, | |
159 | |
160 /** @inheritDoc */ | |
161 decorate: function() { | |
162 this.className = 'bluetooth-item'; | |
163 this.connected = this.data.connected; | |
164 // Though strictly speaking, a connected device will also be paired, | |
165 // we are interested in tracking paired devices that are not connected. | |
166 this.paired = this.data.paired && !this.data.connected; | |
167 this.connecting = !!this.data.pairing; | |
168 this.addLabels_(); | |
169 this.addButtons_(); | |
170 }, | |
171 | |
172 /** | |
173 * Retrieves the descendent element with the matching class name. | |
174 * @param {string} className The class name for the target element. | |
175 * @return {Element|undefined} Returns the matching element if | |
176 * found and unique. | |
177 * @private | |
178 */ | |
179 getNodeByClass_:function(className) { | |
180 var elements = this.getElementsByClassName(className); | |
181 if (elements && elements.length == 1) | |
182 return elements[0]; | |
183 }, | |
184 | |
185 /** | |
186 * Sets the text content for an element. | |
187 * @param {string} className The class name of the target element. | |
188 * @param {string} label Text content for the element. | |
189 * @private | |
190 */ | |
191 setLabel_: function(className, label) { | |
192 var el = this.getNodeByClass_(className); | |
193 el.textContent = label; | |
194 }, | |
195 | |
196 /** | |
197 * Adds an element containing the display name, status and device pairing | |
198 * instructions. | |
199 * @private | |
200 */ | |
201 addLabels_: function() { | |
202 this.setLabel_('network-name-label', this.data.name); | |
203 var status; | |
204 if (this.data.connected) | |
205 status = Constants.DEVICE_STATUS.CONNECTED; | |
206 else if (this.data.pairing) | |
207 status = Constants.DEVICE_STATUS.CONNECTING; | |
208 if (status) { | |
209 var statusMessage = templateData[status]; | |
210 if (statusMessage) | |
211 this.setLabel_('network-status-label', statusMessage); | |
212 if (this.connecting) { | |
213 var spinner = this.getNodeByClass_('inline-spinner'); | |
214 spinner.hidden = false; | |
215 } | |
216 } | |
217 if (this.data.pairing) | |
218 this.addPairingInstructions_(); | |
219 }, | |
220 | |
221 /** | |
222 * Adds instructions on how to complete the pairing process. | |
223 * @param {!Element} textDiv Target element for inserting the instructions. | |
224 * @private | |
225 */ | |
226 addPairingInstructions_: function() { | |
227 var instructionsEl = this.getNodeByClass_('bluetooth-instructions'); | |
228 var message = templateData[this.data.pairing]; | |
229 var array = this.formatInstructions_(message); | |
230 for (var i = 0; i < array.length; i++) { | |
231 instructionsEl.appendChild(array[i]); | |
232 } | |
233 if (this.data.pairing == Constants.PAIRING.ENTER_PASSKEY) { | |
234 var input = this.ownerDocument.createElement('input'); | |
235 input.type = 'text'; | |
236 input.className = 'bluetooth-passkey-field'; | |
237 instructionsEl.appendChild(input); | |
238 } | |
239 }, | |
240 | |
241 /** | |
242 * Formats the pairing instruction, which may contain labels for | |
243 * substitution. The label '%1' is replaced with the passkey, and '%2' | |
244 * is replaced with the name of the bluetooth device. Formatting of the | |
245 * passkey depends on the type of validation. | |
246 * @param {string} instructions The source instructions to format. | |
247 * @return {Array.<Element>} Array of formatted elements. | |
248 */ | |
249 formatInstructions_: function(instructions) { | |
250 var array = []; | |
251 var index = instructions.indexOf('%'); | |
252 if (index >= 0) { | |
253 array.push(this.createTextElement_(instructions.substring(0, index))); | |
254 var labelPlaceholder = instructions.charAt(index + 1); | |
255 // ... handle the placeholder | |
256 switch (labelPlaceholder) { | |
257 case '1': | |
258 array.push(this.createPasskeyElement_()); | |
259 break; | |
260 case '2': | |
261 array.push(this.createTextElement_(this.data.name)); | |
262 } | |
263 array = array.concat(this.formatInstructions_(instructions.substring( | |
264 index + 2))); | |
265 } else { | |
266 array.push(this.createTextElement_(instructions)); | |
267 } | |
268 return array; | |
269 }, | |
270 | |
271 /** | |
272 * Formats an element for displaying the passkey. | |
273 * @return {Element} Element containing the passkey. | |
274 */ | |
275 createPasskeyElement_: function() { | |
276 var passkeyEl = document.createElement('div'); | |
277 if (this.data.pairing == Constants.PAIRING.REMOTE_PASSKEY) { | |
278 passkeyEl.className = 'bluetooth-remote-passkey'; | |
279 var key = String(this.data.passkey); | |
280 var progress = this.data.entered; | |
281 for (var i = 0; i < key.length; i++) { | |
282 var keyEl = document.createElement('div'); | |
283 keyEl.textContent = key.charAt(i); | |
284 keyEl.className = 'bluetooth-passkey-char'; | |
285 if (i < progress) | |
286 keyEl.classList.add('key-typed'); | |
287 passkeyEl.appendChild(keyEl); | |
288 } | |
289 // Add return key symbol. | |
290 var keyEl = document.createElement('div'); | |
291 keyEl.className = 'bluetooth-passkey-char'; | |
292 keyEl.textContent = '\u23ce'; | |
293 passkeyEl.appendChild(keyEl); | |
294 } else { | |
295 passkeyEl.className = 'bluetooth-confirm-passkey'; | |
296 passkeyEl.textContent = this.data.passkey; | |
297 } | |
298 return passkeyEl; | |
299 }, | |
300 | |
301 /** | |
302 * Adds a text element. | |
303 * @param {string} text The text content of the new element. | |
304 * @param {string=} opt_style Optional parameter for the CSS class for | |
305 * formatting the text element. | |
306 * @return {Element} Element containing the text. | |
307 */ | |
308 createTextElement_: function(text, array, opt_style) { | |
309 var el = this.ownerDocument.createElement('span'); | |
310 el.textContent = text; | |
311 if (opt_style) | |
312 el.className = opt_style; | |
313 return el; | |
314 }, | |
315 | |
316 /** | |
317 * Adds buttons for updating the connectivity of a device. | |
318 * @private. | |
319 */ | |
320 addButtons_: function() { | |
321 var buttonsDiv = this.getNodeByClass_('bluetooth-button-group'); | |
322 var buttonLabelKey = null; | |
323 var callbackType = null; | |
324 if (this.connected) { | |
325 buttonLabelKey = 'bluetoothDisconnectDevice'; | |
326 callbackType = 'disconnect'; | |
327 } else if (this.paired) { | |
328 buttonLabelKey = 'bluetoothForgetDevice'; | |
329 callbackType = 'forget'; | |
330 } else if (this.connecting) { | |
331 if (this.data.pairing == Constants.PAIRING.CONFIRM_PASSKEY) { | |
332 buttonLabelKey = 'bluetoothRejectPasskey'; | |
333 callbackType = 'reject'; | |
334 } else { | |
335 buttonLabelKey = 'bluetoothCancel'; | |
336 callbackType = 'cancel'; | |
337 } | |
338 } else { | |
339 buttonLabelKey = 'bluetoothConnectDevice'; | |
340 callbackType = 'connect'; | |
341 } | |
342 if (buttonLabelKey && callbackType) { | |
343 var buttonEl = this.ownerDocument.createElement('button'); | |
344 buttonEl.textContent = localStrings.getString(buttonLabelKey); | |
345 var self = this; | |
346 var callback = function(e) { | |
347 chrome.send('updateBluetoothDevice', | |
348 [self.data.address, callbackType]); | |
349 } | |
350 buttonEl.addEventListener('click', callback); | |
351 buttonsDiv.appendChild(buttonEl); | |
352 } | |
353 if (this.data.pairing == Constants.PAIRING.CONFIRM_PASSKEY || | |
354 this.data.pairing == Constants.PAIRING.ENTER_PASSKEY) { | |
355 var buttonEl = this.ownerDocument.createElement('button'); | |
356 buttonEl.className = 'accept-pairing-button'; | |
357 var msg = this.data.pairing == Constants.PAIRING.CONFIRM_PASSKEY ? | |
358 'bluetoothAcceptPasskey' : 'bluetoothConnectDevice'; | |
359 buttonEl.textContent = localStrings.getString(msg); | |
360 var self = this; | |
361 var callback = function(e) { | |
362 var passkey = self.data.passkey; | |
363 if (self.data.pairing == Constants.PAIRING.ENTER_PASSKEY) { | |
364 var passkeyField = self.getNodeByClass_('bluetooth-passkey-field'); | |
365 passkey = passkeyField.value; | |
366 } | |
367 chrome.send('updateBluetoothDevice', | |
368 [self.data.address, 'connect', String(passkey)]); | |
369 } | |
370 buttonEl.addEventListener('click', callback); | |
371 buttonsDiv.insertBefore(buttonEl, buttonsDiv.firstChild); | |
372 } | |
373 this.appendChild(buttonsDiv); | |
374 } | |
375 }; | |
376 | |
377 cr.defineProperty(BluetoothItem, 'connected', cr.PropertyKind.BOOL_ATTR); | |
378 | |
379 cr.defineProperty(BluetoothItem, 'paired', cr.PropertyKind.BOOL_ATTR); | |
380 | |
381 cr.defineProperty(BluetoothItem, 'connecting', cr.PropertyKind.BOOL_ATTR); | |
382 | |
383 return { | |
384 Constants: Constants, | |
385 BluetoothListElement: BluetoothListElement | |
386 }; | |
387 }); | |
OLD | NEW |