Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(113)

Side by Side Diff: chrome/browser/resources/settings/bluetooth_page/bluetooth_page.js

Issue 2655043005: MD Settings: Bluetooth: Move device list to subpage (Closed)
Patch Set: Fix clang, ES6 Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2015 The Chromium Authors. All rights reserved. 1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 /** 5 /**
6 * @fileoverview 6 * @fileoverview
7 * 'settings-bluetooth-page' is the settings page for managing bluetooth 7 * 'Settings page for managing bluetooth properties and devices. This page
8 * properties and devices. 8 * just provodes a summary and link to the subpage.
9 *
10 * Example:
11 * <core-animated-pages>
12 * <settings-bluetooth-page>
13 * </settings-bluetooth-page>
14 * ... other pages ...
15 * </core-animated-pages>
16 */ 9 */
17 10
18 var bluetoothPage = bluetoothPage || { 11 var bluetoothApis = bluetoothApis || {
19 /** 12 /**
20 * Set this to provide a fake implementation for testing. 13 * Set this to provide a fake implementation for testing.
21 * @type {Bluetooth} 14 * @type {Bluetooth}
22 */ 15 */
23 bluetoothApiForTest: null, 16 bluetoothApiForTest: null,
24 17
25 /** 18 /**
26 * Set this to provide a fake implementation for testing. 19 * Set this to provide a fake implementation for testing.
27 * @type {BluetoothPrivate} 20 * @type {BluetoothPrivate}
28 */ 21 */
29 bluetoothPrivateApiForTest: null, 22 bluetoothPrivateApiForTest: null,
30 }; 23 };
31 24
32 Polymer({ 25 Polymer({
33 is: 'settings-bluetooth-page', 26 is: 'settings-bluetooth-page',
34 27
35 behaviors: [I18nBehavior, CrScrollableBehavior], 28 behaviors: [I18nBehavior],
36 29
37 properties: { 30 properties: {
38 /** Preferences state. */ 31 /** Preferences state. */
39 prefs: { 32 prefs: {
40 type: Object, 33 type: Object,
41 notify: true, 34 notify: true,
42 }, 35 },
43 36
44 /** @private */ 37 /** @private */
45 bluetoothEnabled_: { 38 bluetoothEnabled_: {
46 type: Boolean, 39 type: Boolean,
47 value: false,
48 observer: 'bluetoothEnabledChanged_', 40 observer: 'bluetoothEnabledChanged_',
41 notify: true,
49 }, 42 },
50 43
51 /** @private */ 44 /**
52 deviceListExpanded_: { 45 * Set by bluetooth-subpage to controll spinner visibilit in the header.
46 * @private
47 */
48 showSpinner_: {
53 type: Boolean, 49 type: Boolean,
50 notify: true,
54 value: false, 51 value: false,
55 }, 52 },
56 53
57 /** 54 /**
58 * The cached bluetooth adapter state. 55 * The cached bluetooth adapter state.
59 * @type {!chrome.bluetooth.AdapterState|undefined} 56 * @type {!chrome.bluetooth.AdapterState|undefined}
60 * @private 57 * @private
61 */ 58 */
62 adapterState_: Object, 59 adapterState_: {
63 60 type: Object,
64 /** 61 notify: true,
65 * Whether or not a bluetooth device is connected.
66 * @private
67 */
68 deviceConnected_: Boolean,
69
70 /**
71 * The ordered list of bluetooth devices.
72 * @type {!Array<!chrome.bluetooth.Device>}
73 * @private
74 */
75 deviceList_: {
76 type: Array,
77 value: function() {
78 return [];
79 },
80 }, 62 },
81 63
82 /** 64 /**
83 * Reflects the iron-list selecteditem property.
84 * @type {!chrome.bluetooth.Device}
85 * @private
86 */
87 selectedItem_: {
88 type: Object,
89 observer: 'selectedItemChanged_',
90 },
91
92 /**
93 * Set to the name of the dialog to show. This page uses a single
94 * paper-dialog to host one of three dialog elements, 'addDevice',
95 * 'pairDevice', or 'connectError'. This allows a seamless transition
96 * between dialogs. Note: This property should be set before opening the
97 * dialog and setting the property will not itself cause the dialog to open.
98 * @private
99 */
100 dialogId_: String,
101
102 /**
103 * Current Pairing device.
104 * @type {?chrome.bluetooth.Device|undefined}
105 * @private
106 */
107 pairingDevice_: Object,
108
109 /**
110 * Current Pairing event.
111 * @type {?chrome.bluetoothPrivate.PairingEvent|undefined}
112 * @private
113 */
114 pairingEvent_: Object,
115
116 /**
117 * The translated error message to show when a connect error occurs.
118 * @private
119 */
120 errorMessage_: String,
121
122 /**
123 * Interface for bluetooth calls. May be overriden by tests. 65 * Interface for bluetooth calls. May be overriden by tests.
124 * @type {Bluetooth} 66 * @type {Bluetooth}
125 * @private 67 * @private
126 */ 68 */
127 bluetooth: { 69 bluetooth: {
128 type: Object, 70 type: Object,
129 value: chrome.bluetooth, 71 value: chrome.bluetooth,
130 }, 72 },
131 73
132 /** 74 /**
133 * Interface for bluetoothPrivate calls. May be overriden by tests. 75 * Interface for bluetoothPrivate calls. May be overriden by tests.
134 * @type {BluetoothPrivate} 76 * @type {BluetoothPrivate}
135 * @private 77 * @private
136 */ 78 */
137 bluetoothPrivate: { 79 bluetoothPrivate: {
138 type: Object, 80 type: Object,
139 value: chrome.bluetoothPrivate, 81 value: chrome.bluetoothPrivate,
140 }, 82 },
141 }, 83 },
142 84
143 observers: ['deviceListChanged_(deviceList_.*)'], 85 observers: ['deviceListChanged_(deviceList_.*)'],
144 86
145 /** 87 /**
146 * Listener for chrome.bluetooth.onAdapterStateChanged events. 88 * Listener for chrome.bluetooth.onAdapterStateChanged events.
147 * @type {function(!chrome.bluetooth.AdapterState)|undefined} 89 * @type {function(!chrome.bluetooth.AdapterState)|undefined}
148 * @private 90 * @private
149 */ 91 */
150 bluetoothAdapterStateChangedListener_: undefined, 92 bluetoothAdapterStateChangedListener_: undefined,
151 93
152 /**
153 * Listener for chrome.bluetooth.onBluetoothDeviceAdded/Changed events.
154 * @type {function(!chrome.bluetooth.Device)|undefined}
155 * @private
156 */
157 bluetoothDeviceUpdatedListener_: undefined,
158
159 /**
160 * Listener for chrome.bluetooth.onBluetoothDeviceRemoved events.
161 * @type {function(!chrome.bluetooth.Device)|undefined}
162 * @private
163 */
164 bluetoothDeviceRemovedListener_: undefined,
165
166 /**
167 * Listener for chrome.bluetoothPrivate.onPairing events.
168 * @type {function(!chrome.bluetoothPrivate.PairingEvent)|undefined}
169 * @private
170 */
171 bluetoothPrivateOnPairingListener_: undefined,
172
173 /** @override */ 94 /** @override */
174 ready: function() { 95 ready: function() {
175 if (bluetoothPage.bluetoothApiForTest) 96 if (bluetoothApis.bluetoothApiForTest)
176 this.bluetooth = bluetoothPage.bluetoothApiForTest; 97 this.bluetooth = bluetoothApis.bluetoothApiForTest;
177 if (bluetoothPage.bluetoothPrivateApiForTest) 98 if (bluetoothApis.bluetoothPrivateApiForTest)
178 this.bluetoothPrivate = bluetoothPage.bluetoothPrivateApiForTest; 99 this.bluetoothPrivate = bluetoothApis.bluetoothPrivateApiForTest;
179 }, 100 },
180 101
181 /** @override */ 102 /** @override */
182 attached: function() { 103 attached: function() {
183 this.bluetoothAdapterStateChangedListener_ = 104 this.bluetoothAdapterStateChangedListener_ =
184 this.onBluetoothAdapterStateChanged_.bind(this); 105 this.onBluetoothAdapterStateChanged_.bind(this);
185 this.bluetooth.onAdapterStateChanged.addListener( 106 this.bluetooth.onAdapterStateChanged.addListener(
186 this.bluetoothAdapterStateChangedListener_); 107 this.bluetoothAdapterStateChangedListener_);
187 108
188 this.bluetoothDeviceUpdatedListener_ =
189 this.onBluetoothDeviceUpdated_.bind(this);
190 this.bluetooth.onDeviceAdded.addListener(
191 this.bluetoothDeviceUpdatedListener_);
192 this.bluetooth.onDeviceChanged.addListener(
193 this.bluetoothDeviceUpdatedListener_);
194
195 this.bluetoothDeviceRemovedListener_ =
196 this.onBluetoothDeviceRemoved_.bind(this);
197 this.bluetooth.onDeviceRemoved.addListener(
198 this.bluetoothDeviceRemovedListener_);
199
200 // Request the inital adapter state. 109 // Request the inital adapter state.
201 this.bluetooth.getAdapterState(this.bluetoothAdapterStateChangedListener_); 110 this.bluetooth.getAdapterState(this.bluetoothAdapterStateChangedListener_);
202 }, 111 },
203 112
204 /** @override */ 113 /** @override */
205 detached: function() { 114 detached: function() {
206 if (this.bluetoothAdapterStateChangedListener_) { 115 if (this.bluetoothAdapterStateChangedListener_) {
207 this.bluetooth.onAdapterStateChanged.removeListener( 116 this.bluetooth.onAdapterStateChanged.removeListener(
208 this.bluetoothAdapterStateChangedListener_); 117 this.bluetoothAdapterStateChangedListener_);
209 } 118 }
210 if (this.bluetoothDeviceUpdatedListener_) {
211 this.bluetooth.onDeviceAdded.removeListener(
212 this.bluetoothDeviceUpdatedListener_);
213 this.bluetooth.onDeviceChanged.removeListener(
214 this.bluetoothDeviceUpdatedListener_);
215 }
216 if (this.bluetoothDeviceRemovedListener_) {
217 this.bluetooth.onDeviceRemoved.removeListener(
218 this.bluetoothDeviceRemovedListener_);
219 }
220 },
221
222 /** @private */
223 bluetoothEnabledChanged_: function() {
224 // When bluetooth is enabled, auto-expand the device list.
225 if (this.bluetoothEnabled_)
226 this.deviceListExpanded_ = true;
227 },
228
229 /** @private */
230 deviceListChanged_: function() {
231 for (var i = 0; i < this.deviceList_.length; ++i) {
232 if (this.deviceList_[i].connected) {
233 this.deviceConnected_ = true;
234 return;
235 }
236 }
237 this.deviceConnected_ = false;
238 },
239
240 /** @private */
241 selectedItemChanged_: function() {
242 if (this.selectedItem_)
243 this.connectDevice_(this.selectedItem_);
244 }, 119 },
245 120
246 /** 121 /**
247 * @return {string} 122 * @return {string}
248 * @private 123 * @private
249 */ 124 */
250 getIcon_: function() { 125 getIcon_: function() {
251 if (!this.bluetoothEnabled_) 126 if (!this.bluetoothEnabled_)
252 return 'settings:bluetooth-disabled'; 127 return 'settings:bluetooth-disabled';
253 if (this.deviceConnected_)
254 return 'settings:bluetooth-connected';
255 return 'settings:bluetooth'; 128 return 'settings:bluetooth';
256 }, 129 },
257 130
258 /** 131 /**
259 * @return {string} 132 * @return {string}
260 * @private 133 * @private
261 */ 134 */
262 getTitle_: function() { 135 getTitle_: function() {
263 return this.i18n( 136 return this.i18n(
264 this.bluetoothEnabled_ ? 'bluetoothEnabled' : 'bluetoothDisabled'); 137 this.bluetoothEnabled_ ? 'bluetoothEnabled' : 'bluetoothDisabled');
265 }, 138 },
266 139
267 /** @private */
268 toggleDeviceListExpanded_: function() {
269 this.deviceListExpanded_ = !this.deviceListExpanded_;
270 },
271
272 /**
273 * @return {boolean} Whether the <iron-collapse> can be shown.
274 * @private
275 */
276 canShowDeviceList_: function() {
277 return this.bluetoothEnabled_ && this.deviceListExpanded_;
278 },
279
280 /**
281 * If bluetooth is enabled, request the complete list of devices and update
282 * this.deviceList_.
283 * @private
284 */
285 updateDeviceList_: function() {
286 if (!this.bluetoothEnabled_) {
287 this.deviceList_ = [];
288 return;
289 }
290 this.bluetooth.getDevices(function(devices) {
291 this.deviceList_ = devices;
292 this.updateScrollableContents();
293 }.bind(this));
294 },
295
296 /**
297 * Event called when a user action changes the bluetoothEnabled state.
298 * @private
299 */
300 onBluetoothEnabledChange_: function() {
301 this.bluetoothPrivate.setAdapterState(
302 {powered: this.bluetoothEnabled_}, function() {
303 if (chrome.runtime.lastError) {
304 console.error(
305 'Error enabling bluetooth: ' +
306 chrome.runtime.lastError.message);
307 }
308 });
309 },
310
311 /** 140 /**
312 * Process bluetooth.onAdapterStateChanged events. 141 * Process bluetooth.onAdapterStateChanged events.
313 * @param {!chrome.bluetooth.AdapterState} state 142 * @param {!chrome.bluetooth.AdapterState} state
314 * @private 143 * @private
315 */ 144 */
316 onBluetoothAdapterStateChanged_: function(state) { 145 onBluetoothAdapterStateChanged_: function(state) {
317 this.adapterState_ = state; 146 this.adapterState_ = state;
318 this.bluetoothEnabled_ = state.powered; 147 this.bluetoothEnabled_ = state.powered;
319 this.updateDeviceList_();
320 },
321
322 /**
323 * Process bluetooth.onDeviceAdded and onDeviceChanged events.
324 * @param {!chrome.bluetooth.Device} device
325 * @private
326 */
327 onBluetoothDeviceUpdated_: function(device) {
328 var address = device.address;
329 if (this.dialogId_ && this.pairingDevice_ &&
330 this.pairingDevice_.address == address) {
331 this.pairingDevice_ = device;
332 }
333 var index = this.getDeviceIndex_(address);
334 if (index >= 0) {
335 this.set('deviceList_.' + index, device);
336 return;
337 }
338 this.push('deviceList_', device);
339 },
340
341 /**
342 * Process bluetooth.onDeviceRemoved events.
343 * @param {!chrome.bluetooth.Device} device
344 * @private
345 */
346 onBluetoothDeviceRemoved_: function(device) {
347 var address = device.address;
348 var index = this.getDeviceIndex_(address);
349 if (index < 0)
350 return;
351 this.splice('deviceList_', index, 1);
352 }, 148 },
353 149
354 /** @private */ 150 /** @private */
355 startDiscovery_: function() { 151 onTap_: function() {
356 if (!this.adapterState_ || this.adapterState_.discovering) 152 if (this.adapterState_.available === false)
357 return; 153 return;
358 154 if (!this.bluetoothEnabled_)
359 if (!this.bluetoothPrivateOnPairingListener_) { 155 this.bluetoothEnabled_ = true;
360 this.bluetoothPrivateOnPairingListener_ =
361 this.onBluetoothPrivateOnPairing_.bind(this);
362 this.bluetoothPrivate.onPairing.addListener(
363 this.bluetoothPrivateOnPairingListener_);
364 }
365
366 this.bluetooth.startDiscovery(function() {
367 if (chrome.runtime.lastError) {
368 if (chrome.runtime.lastError.message == 'Failed to stop discovery') {
369 // May happen if also started elsewhere; ignore.
370 return;
371 }
372 console.error(
373 'startDiscovery Error: ' + chrome.runtime.lastError.message);
374 }
375 });
376 },
377
378 /** @private */
379 stopDiscovery_: function() {
380 if (!this.get('adapterState_.discovering'))
381 return;
382
383 if (this.bluetoothPrivateOnPairingListener_) {
384 this.bluetoothPrivate.onPairing.removeListener(
385 this.bluetoothPrivateOnPairingListener_);
386 this.bluetoothPrivateOnPairingListener_ = undefined;
387 }
388
389 this.bluetooth.stopDiscovery(function() {
390 if (chrome.runtime.lastError) {
391 console.error(
392 'Error stopping bluetooth discovery: ' +
393 chrome.runtime.lastError.message);
394 }
395 });
396 },
397
398 /**
399 * Process bluetoothPrivate.onPairing events.
400 * @param {!chrome.bluetoothPrivate.PairingEvent} e
401 * @private
402 */
403 onBluetoothPrivateOnPairing_: function(e) {
404 if (!this.dialogId_ || !this.pairingDevice_ ||
405 e.device.address != this.pairingDevice_.address) {
406 return;
407 }
408 if (e.pairing == chrome.bluetoothPrivate.PairingEventType.KEYS_ENTERED &&
409 e.passkey === undefined && this.pairingEvent_) {
410 // 'keysEntered' event might not include the updated passkey so preserve
411 // the current one.
412 e.passkey = this.pairingEvent_.passkey;
413 }
414 this.pairingEvent_ = e;
415 },
416
417 /**
418 * @param {!Event} e
419 * @private
420 */
421 onAddDeviceTap_: function(e) {
422 e.preventDefault();
423 this.openDialog_('addDevice');
424 },
425
426 /**
427 * @param {!{detail: {action: string, device: !chrome.bluetooth.Device}}} e
428 * @private
429 */
430 onDeviceEvent_: function(e) {
431 var action = e.detail.action;
432 var device = e.detail.device;
433 if (action == 'connect')
434 this.connectDevice_(device);
435 else if (action == 'disconnect')
436 this.disconnectDevice_(device);
437 else if (action == 'remove')
438 this.forgetDevice_(device);
439 else 156 else
440 console.error('Unexected action: ' + action); 157 this.openSubpage_();
441 },
442
443 /**
444 * Handle a response sent from the pairing dialog and pass it to the
445 * bluetoothPrivate API.
446 * @param {Event} e
447 * @private
448 */
449 onResponse_: function(e) {
450 var options =
451 /** @type {!chrome.bluetoothPrivate.SetPairingResponseOptions} */ (
452 e.detail);
453 this.bluetoothPrivate.setPairingResponse(options, function() {
454 if (chrome.runtime.lastError) {
455 // TODO(stevenjb): Show error.
456 console.error(
457 'Error setting pairing response: ' + options.device.name +
458 ': Response: ' + options.response +
459 ': Error: ' + chrome.runtime.lastError.message);
460 }
461 this.$$('#deviceDialog').close();
462 }.bind(this));
463 },
464
465 /**
466 * @param {string} address
467 * @return {number} The index of the device associated with |address| or -1.
468 * @private
469 */
470 getDeviceIndex_: function(address) {
471 var len = this.deviceList_.length;
472 for (var i = 0; i < len; ++i) {
473 if (this.deviceList_[i].address == address)
474 return i;
475 }
476 return -1;
477 },
478
479 /**
480 * @param {!chrome.bluetooth.Device} device
481 * @return {string} The text to display for |device| in the device list.
482 * @private
483 */
484 getDeviceName_: function(device) {
485 return device.name || device.address;
486 },
487
488 /**
489 * @return {!Array<!chrome.bluetooth.Device>}
490 * @private
491 */
492 getPairedOrConnecting_: function() {
493 return this.deviceList_.filter(function(device) {
494 return !!device.paired || !!device.connecting;
495 });
496 },
497
498 /**
499 * @return {boolean} True if deviceList contains any paired devices.
500 * @private
501 */
502 haveDevices_: function() {
503 return this.deviceList_.findIndex(function(d) {
504 return !!d.paired;
505 }) != -1;
506 },
507
508 /**
509 * @param {!chrome.bluetooth.Device} device
510 * @private
511 */
512 connectDevice_: function(device) {
513 // If the device is not paired, show the pairing dialog.
514 if (!device.paired) {
515 // Set the pairing device and clear any pairing event.
516 this.pairingDevice_ = device;
517 this.pairingEvent_ = null;
518
519 this.openDialog_('pairDevice');
520 }
521
522 this.bluetoothPrivate.connect(device.address, function(result) {
523 var error;
524 if (chrome.runtime.lastError) {
525 error = chrome.runtime.lastError.message;
526 } else {
527 switch (result) {
528 case chrome.bluetoothPrivate.ConnectResultType.ALREADY_CONNECTED:
529 case chrome.bluetoothPrivate.ConnectResultType.AUTH_CANCELED:
530 case chrome.bluetoothPrivate.ConnectResultType.IN_PROGRESS:
531 case chrome.bluetoothPrivate.ConnectResultType.SUCCESS:
532 break;
533 default:
534 error = result;
535 }
536 }
537
538 if (!error) {
539 this.$$('#deviceDialog').close();
540 return;
541 }
542
543 var name = this.getDeviceName_(device);
544 var id = 'bluetooth_connect_' + error;
545 if (this.i18nExists(id)) {
546 this.errorMessage_ = this.i18n(id, name);
547 } else {
548 this.errorMessage_ = error;
549 console.error('Unexpected error connecting to: ' + name + ': ' + error);
550 }
551 this.openDialog_('connectError');
552 }.bind(this));
553 },
554
555 /**
556 * @param {!chrome.bluetooth.Device} device
557 * @private
558 */
559 disconnectDevice_: function(device) {
560 this.bluetoothPrivate.disconnectAll(device.address, function() {
561 if (chrome.runtime.lastError) {
562 console.error(
563 'Error disconnecting: ' + device.address +
564 chrome.runtime.lastError.message);
565 }
566 });
567 },
568
569 /**
570 * @param {!chrome.bluetooth.Device} device
571 * @private
572 */
573 forgetDevice_: function(device) {
574 this.bluetoothPrivate.forgetDevice(device.address, function() {
575 if (chrome.runtime.lastError) {
576 console.error(
577 'Error forgetting: ' + device.name + ': ' +
578 chrome.runtime.lastError.message);
579 }
580 this.updateDeviceList_();
581 }.bind(this));
582 },
583
584 /**
585 * @param {string} dialogId
586 * @param {string} dialogToShow The name of the dialog.
587 * @return {boolean}
588 * @private
589 */
590 dialogIsVisible_: function(dialogId, dialogToShow) {
591 return dialogToShow == dialogId;
592 },
593
594 /**
595 * @param {string} dialogId
596 * @private
597 */
598 openDialog_: function(dialogId) {
599 if (this.dialogId_) {
600 // Dialog already opened, just update the contents.
601 this.dialogId_ = dialogId;
602 return;
603 }
604 this.dialogId_ = dialogId;
605 // Call flush so that the dialog gets sized correctly before it is opened.
606 Polymer.dom.flush();
607 var dialog = this.$$('#deviceDialog');
608 dialog.open();
609 this.startDiscovery_();
610 },
611
612 /** @private */
613 onDialogClosed_: function() {
614 this.stopDiscovery_();
615 this.dialogId_ = '';
616 this.pairingDevice_ = null;
617 this.pairingEvent_ = null;
618 }, 158 },
619 159
620 /** 160 /**
621 * @param {Event} e 161 * @param {Event} e
622 * @private 162 * @private
623 */ 163 */
624 stopTap_: function(e) { 164 onSubpageArrowTap_: function(e) {
165 this.openSubpage_();
625 e.stopPropagation(); 166 e.stopPropagation();
626 }, 167 },
168
169 /** @private */
170 bluetoothEnabledChanged_: function() {
171 this.bluetoothPrivate.setAdapterState(
172 {powered: this.bluetoothEnabled_}, function() {
173 if (chrome.runtime.lastError) {
174 console.error(
175 'Error enabling bluetooth: ' +
176 chrome.runtime.lastError.message);
177 }
178 });
179 },
180
181 /** @private */
182 openSubpage_: function() {
183 settings.navigateTo(settings.Route.BLUETOOTH_DEVICES);
184 }
627 }); 185 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698