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 |