diff options
Diffstat (limited to 'chromium/chrome/browser/resources/settings/bluetooth_page')
12 files changed, 1404 insertions, 0 deletions
diff --git a/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_add_device_dialog.html b/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_add_device_dialog.html new file mode 100644 index 00000000000..c6f747c04f2 --- /dev/null +++ b/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_add_device_dialog.html @@ -0,0 +1,49 @@ +<link rel="import" href="chrome://resources/polymer/v1_0/polymer/polymer.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-selector/iron-selector.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-spinner/paper-spinner.html"> + +<dom-module id="settings-bluetooth-add-device-dialog"> + <link rel="import" type="css" href="chrome://md-settings/settings_shared.css"> + <link rel="import" type="css" href="bluetooth_page.css"> + <link rel="import" type="css" href="bluetooth_dialog.css"> + <template> + <div id="dialogOuterDiv" class="layout vertical flex"> + <div id="dialogHeaderDiv" class="settings-box layout horizontal"> + <span id="dialogTitle" class="flex" + i18n-content="bluetoothAddDevicePageTitle"> + </span> + <paper-icon-button icon="close" on-tap="onCancelTap_" id="close"> + </paper-icon-button> + </div> + <div class="settings-box flex"> + <div id="dialogDeviceList" class="settings-box layout vertical" + on-device-event="onDeviceEvent_"> + <span class="no-devices" hidden$="[[haveDevices_(deviceList)]]" + i18n-content="bluetoothNoDevices"> + </span> + <iron-selector class="flex"> + <template is="dom-repeat" items="[[deviceList]]" + filter="deviceNotPaired_" observe="paired"> + <bluetooth-device-list-item device="[[item]]"> + </bluetooth-device-list-item> + </template> + </iron-selector> + </div> + </div> + <div id="dialogFooterDiv" class="layout horizontal center"> + <div id="scanning" class="layout horizontal center flex" + hidden$="[[!adapterState.discovering]]"> + <paper-spinner active="[[adapterState.discovering]]"> + </paper-spinner> + <span i18n-content="bluetoothScanning"></span> + </div> + <paper-button id="cancel" class="end-justified" + i18n-content="bluetoothCancel" on-tap="onCancelTap_"> + </paper-button> + </div> + </div> + </template> + <script src="bluetooth_add_device_dialog.js"></script> +</dom-module> diff --git a/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_add_device_dialog.js b/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_add_device_dialog.js new file mode 100644 index 00000000000..189711f310f --- /dev/null +++ b/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_add_device_dialog.js @@ -0,0 +1,71 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview + * 'settings-bluetooth-add-device-dialog' is the settings subpage for adding + * bluetooth devices. + * + * @group Chrome Settings Elements + * @element settings-bluetooth-add-device-dialog + */ +Polymer({ + is: 'settings-bluetooth-add-device-dialog', + + properties: { + /** + * The cached bluetooth adapter state. + * @type {!chrome.bluetooth.AdapterState|undefined} + */ + adapterState: { + type: Object, + observer: 'adapterStateChanged_', + }, + + /** + * The ordered list of bluetooth devices. + * @type {!Array<!chrome.bluetooth.Device>} + */ + deviceList: { + type: Array, + value: function() { return []; }, + }, + }, + + /** @private */ + adapterStateChanged_: function() { + if (!this.adapterState.powered) + this.fire('close-dialog'); + }, + + /** + * @param {!chrome.bluetooth.Device} device + * @return {boolean} + * @private + */ + deviceNotPaired_: function(device) { + return !device.paired; + }, + + /** + * @param {!Array<!chrome.bluetooth.Device>} deviceList + * @return {boolean} True if deviceList contains any unpaired devices. + * @private + */ + haveDevices_: function(deviceList) { + return this.deviceList.findIndex(function(d) { return !d.paired; }) != -1; + }, + + /** + * @param {!{detail: {action: string, device: !chrome.bluetooth.Device}}} e + * @private + */ + onDeviceEvent_: function(e) { + this.fire('device-event', e.detail); + /** @type {Event} */(e).stopPropagation(); + }, + + /** @private */ + onCancelTap_: function() { this.fire('close-dialog'); }, +}); diff --git a/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_device_list_item.css b/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_device_list_item.css new file mode 100644 index 00000000000..de43201be75 --- /dev/null +++ b/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_device_list_item.css @@ -0,0 +1,33 @@ +/* Copyright 2015 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. */ + +#outer { + padding: 5px 5px 5px 15px; +} + +#outer:hover:not([dropdown]) { + background-color: #f0f0f0; +} + +iron-icon { + -webkit-padding-start: 10px; + color: green; +} + +paper-item:hover { + background-color: #f0f0f0; +} + +span.name { + padding: 10px 0; +} + +span.name[connected] { + font-weight: bold; +} + +.dropdown-content { + background: white; + box-shadow: 0 2px 6px grey; +} diff --git a/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_device_list_item.html b/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_device_list_item.html new file mode 100644 index 00000000000..a868a559356 --- /dev/null +++ b/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_device_list_item.html @@ -0,0 +1,37 @@ +<link rel="import" href="chrome://resources/polymer/v1_0/polymer/polymer.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-dropdown/iron-dropdown.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-icons/iron-icons.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-item/paper-item.html"> + +<dom-module id="bluetooth-device-list-item"> + <link rel="import" type="css" href="bluetooth_device_list_item.css"> + <template> + <div id="outer" class="layout horizontal center" + dropdown$="[[dropdownOpened]]" on-tap="itemTapped_"> + <span class="name" connected$="[[device.connected]]"> + [[getDeviceName_(device)]] + </span> + <iron-icon icon="check" hidden$="[[!device.connected]]"></iron-icon> + <span class="flex"></span> + <span hidden$="[[!device.connecting]]" + i18n-content="bluetoothConnecting"></span> + <div hidden$="[[!device.paired]]" on-tap="doNothing_"> + <paper-icon-button icon="more-vert" toggles active="{{dropdownOpened}}"> + </paper-icon-button> + <iron-dropdown opened="{{dropdownOpened}}" on-tap="menuSelected_"> + <div class="dropdown-content"> + <paper-item id="connect" i18n-content="bluetoothConnect" + hidden$="[[device.connected]]"></paper-item> + <paper-item id="disconnect" i18n-content="bluetoothDisconnect" + hidden$="[[!device.connected]]"></paper-item> + <paper-item id="remove" i18n-content="bluetoothRemove"></paper-item> + </div> + </iron-dropdown> + </div> + </div> + </template> + <script src="bluetooth_device_list_item.js"></script> +</dom-module> diff --git a/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_device_list_item.js b/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_device_list_item.js new file mode 100644 index 00000000000..dd2378b8f11 --- /dev/null +++ b/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_device_list_item.js @@ -0,0 +1,71 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Polymer element for displaying a bluetooth device in a list. + */ + +Polymer({ + is: 'bluetooth-device-list-item', + + properties: { + /** + * The bluetooth device. + * @type {!chrome.bluetooth.Device} + */ + device: { + type: Object, + }, + }, + + /** + * @param {Event} e + * @private + */ + itemTapped_: function(e) { + this.fire('device-event', { + action: 'connect', + device: this.device, + }); + }, + + /** + * @param {Event} e + * @private + */ + menuSelected_: function(e) { + e.currentTarget.opened = false; + this.fire('device-event', { + action: e.target.id, + device: this.device, + }); + }, + + /** + * @param {Event} e + * @private + */ + doNothing_: function(e) { + // Avoid triggering itemTapped_. + e.stopPropagation(); + }, + + /** + * @param {!chrome.bluetooth.Device} device + * @return {string} The text to display for |device| in the device list. + * @private + */ + getDeviceName_: function(device) { + return device.name || device.address; + }, + + /** + * @param {!chrome.bluetooth.Device} device + * @return {boolean} + * @private + */ + isDisconnected_: function(device) { + return !device.connected && !device.connecting; + }, +}); diff --git a/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_dialog.css b/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_dialog.css new file mode 100644 index 00000000000..67646335f0b --- /dev/null +++ b/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_dialog.css @@ -0,0 +1,91 @@ +/* Copyright 2015 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. */ + +#dialogOuterDiv { + margin-bottom: 16px; +} + +#dialogHeaderDiv { + height: 40px; + margin: 0 5px 10px; +} + +#dialogFooterDiv { + height: 40px; + margin: 0 20px; +} + +#dialogMessage { + margin-bottom: 10px; +} + +#dialogTitle { + font-size: 125%; + margin: 0 10px; +} + +#dialogDeviceList { + height: 210px; + margin-bottom: 20px; + margin-left: 4px; + overflow-y: auto; +} + +#pairing { + margin-bottom: 10px; +} + +#pairing paper-input { + text-align: center; +} + +#pinDiv { + margin-top: 10px; +} + +iron-selector { + width: 100%; +} + +paper-spinner { + height: 20px; + margin: 0 10px; + width: 20px; +} + +/* .display indicates a displayed pin code or passkey. */ +span.display { + border: 1px solid #ccc; + border-radius: 4px; + box-shadow: 0 0 0 1px #222; + color: #222; + font-size: 16px; + height: 38px; + line-height: 38px; + margin: 0 5px; + padding: 0 15px; + text-align: center; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); +} + +span.display.next { + background: rgb(77, 144, 254); + border: 2px solid rgb(77, 144, 254); + box-shadow: none; + color: #fff; +} + +span.display.untyped { + border: 1px solid #d4d4d4; + box-shadow: 0 0 0 1px #888; + color: #666; +} + +/* .confirm indicates a confirmation passkey. */ +span.confirm { + color: #999; + font-size: 20px; + font-weight: 600; /* semibold */ + margin: 0 20px; +} diff --git a/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_page.css b/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_page.css new file mode 100644 index 00000000000..2eb7bd8b15d --- /dev/null +++ b/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_page.css @@ -0,0 +1,32 @@ +/* Copyright 2015 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. */ + +#addDevice { + color: blue; +} + +#deviceList { + -webkit-margin-start: 15px; + max-height: 300px; + overflow-y: auto; +} + +cr-expand-button { + -webkit-margin-end: 10px; +} + +iron-icon { + -webkit-margin-end: 10px; +} + +settings-bluetooth-add-device-dialog, +settings-bluetooth-pair-device-dialog { + height: 400px; + padding: 0; + width: 500px; +} + +span.no-devices { + margin: 10px 20px; +} diff --git a/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_page.html b/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_page.html new file mode 100644 index 00000000000..d6e576e17b1 --- /dev/null +++ b/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_page.html @@ -0,0 +1,86 @@ +<link rel="import" href="chrome://resources/polymer/v1_0/polymer/polymer.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-collapse/iron-collapse.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-icons/device-icons.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/neon-animation/neon-animatable.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-toggle-button/paper-toggle-button.html"> +<link rel="import" href="chrome://resources/html/i18n_behavior.html"> +<link rel="import" href="chrome://md-settings/settings_page/settings_animated_pages.html"> +<link rel="import" href="bluetooth_device_list_item.html"> +<link rel="import" href="bluetooth_add_device_dialog.html"> +<link rel="import" href="bluetooth_pair_device_dialog.html"> + +<dom-module id="settings-bluetooth-page"> + <link rel="import" type="css" href="chrome://md-settings/settings_shared.css"> + <link rel="import" type="css" href="bluetooth_page.css"> + <template> + <settings-animated-pages id="pages" current-route="{{currentRoute}}" + section="bluetooth"> + <neon-animatable id="main"> + <div class="settings-box"> + <div class="layout horizontal center"> + <iron-icon icon="device:bluetooth"></iron-icon> + <span class="flex" i18n-content="bluetoothEnable"></span> + <cr-expand-button id="expandListButton" + hidden$="[[!bluetoothEnabled]]" + expanded="{{deviceListExpanded}}"> + </cr-expand-button> + <paper-toggle-button id="enableBluetooth" + checked="{{bluetoothEnabled}}" + on-change="onBluetoothEnabledChange_"> + </paper-toggle-button> + </div> + <iron-collapse opened="[[deviceListExpanded]]"> + <div id="deviceList" class="layout vertical" + on-device-event="onDeviceEvent_"> + <span class="no-devices" + hidden$="[[haveDevices_(deviceList.splices)]]" + i18n-content="bluetoothNoDevices"> + </span> + <template is="dom-repeat" items="[[deviceList]]" + filter="deviceIsPairedOrConnecting_"> + <bluetooth-device-list-item device="[[item]]"> + </bluetooth-device-list-item> + </template> + </div> + <div class="settings-box" hidden$="[[!bluetoothEnabled]]"> + <paper-button id="addDevice" i18n-content="bluetoothAddDevice" + on-tap="onAddDeviceTap_"> + </paper-button> + </div> + </iron-collapse> + </div> + </neon-animatable> + </settings-animated-pages> + + <paper-dialog modal id="deviceDialog" class="layout vertical" + on-iron-overlay-opened="onDialogOpened_" + on-iron-overlay-closed="onDialogClosed_"> + <template is="dom-if" if="[[dialogIsVisible_(dialog, 'addDevice')]]" + restamp> + <settings-bluetooth-add-device-dialog + class="layout vertical flex" + adapter-state="[[adapterState]]" + device-list="[[deviceList]]" + on-device-event="onDeviceEvent_" + on-close-dialog="onCloseDialog_"> + </settings-bluetooth-add-device-dialog> + </template> + <template is="dom-if" if="[[dialogIsVisible_(dialog, 'pairDevice')]]" + restamp> + <settings-bluetooth-pair-device-dialog + class="layout vertical flex" + pairing-device="[[pairingDevice]]" + pairing-event="[[pairingEvent]]" + on-response="onResponse_" + on-close-dialog="onCloseDialog_"> + </settings-bluetooth-pair-device-dialog> + </template> + </paper-dialog> + + </template> + <script src="bluetooth_page.js"></script> +</dom-module> diff --git a/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_page.js b/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_page.js new file mode 100644 index 00000000000..c8babf426cc --- /dev/null +++ b/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_page.js @@ -0,0 +1,523 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview + * 'settings-bluetooth-page' is the settings page for managing bluetooth + * properties and devices. + * + * Example: + * <core-animated-pages> + * <settings-bluetooth-page> + * </settings-bluetooth-page> + * ... other pages ... + * </core-animated-pages> + * + * @group Chrome Settings Elements + * @element settings-bluetooth-page + */ + +var bluetoothPage = bluetoothPage || { + /** + * Set this to provide a fake implementation for testing. + * @type {Bluetooth} + */ + bluetoothApiForTest: null, + + /** + * Set this to provide a fake implementation for testing. + * @type {BluetoothPrivate} + */ + bluetoothPrivateApiForTest: null, +}; + +Polymer({ + is: 'settings-bluetooth-page', + + behaviors: [ + I18nBehavior, + ], + + properties: { + /** The current active route. */ + currentRoute: { + type: Object, + notify: true, + }, + + /** Whether bluetooth is enabled. */ + bluetoothEnabled: { + type: Boolean, + value: false, + observer: 'bluetoothEnabledChanged_', + }, + + /** Whether the device list is expanded. */ + deviceListExpanded: { + type: Boolean, + value: false, + }, + + /** + * The cached bluetooth adapter state. + * @type {!chrome.bluetooth.AdapterState|undefined} + */ + adapterState: Object, + + /** + * The ordered list of bluetooth devices. + * @type {!Array<!chrome.bluetooth.Device>} + */ + deviceList: { + type: Array, + value: function() { return []; }, + }, + + /** + * Set to the name of the dialog to show. This page uses a single + * paper-dialog to host one of two dialog elements, 'addDevice' or + * 'pairDevice'. This allows a seamless transition between adding and + * pairing dialogs. Note: This property should be set before opening the + * dialog, and setting the property will not itself cause the dialog to + * open. + */ + dialog: String, + + /** + * Current Pairing device. + * @type {?chrome.bluetooth.Device|undefined} + */ + pairingDevice: Object, + + /** + * Current Pairing event. + * @type {?chrome.bluetoothPrivate.PairingEvent|undefined} + */ + pairingEvent: Object, + + /** + * Interface for bluetooth calls. May be overriden by tests. + * @type {Bluetooth} + */ + bluetooth: { + type: Object, + value: chrome.bluetooth, + }, + + /** + * Interface for bluetoothPrivate calls. May be overriden by tests. + * @type {BluetoothPrivate} + */ + bluetoothPrivate: { + type: Object, + value: chrome.bluetoothPrivate, + }, + }, + + /** + * Listener for chrome.bluetooth.onAdapterStateChanged events. + * @type {function(!chrome.bluetooth.AdapterState)|undefined} + * @private + */ + bluetoothAdapterStateChangedListener_: undefined, + + /** + * Listener for chrome.bluetooth.onBluetoothDeviceAdded/Changed events. + * @type {function(!chrome.bluetooth.Device)|undefined} + * @private + */ + bluetoothDeviceUpdatedListener_: undefined, + + /** + * Listener for chrome.bluetooth.onBluetoothDeviceRemoved events. + * @type {function(!chrome.bluetooth.Device)|undefined} + * @private + */ + bluetoothDeviceRemovedListener_: undefined, + + /** + * Listener for chrome.bluetoothPrivate.onPairing events. + * @type {function(!chrome.bluetoothPrivate.PairingEvent)|undefined} + * @private + */ + bluetoothPrivateOnPairingListener_: undefined, + + /** @override */ + ready: function() { + if (bluetoothPage.bluetoothApiForTest) + this.bluetooth = bluetoothPage.bluetoothApiForTest; + if (bluetoothPage.bluetoothPrivateApiForTest) + this.bluetoothPrivate = bluetoothPage.bluetoothPrivateApiForTest; + }, + + /** @override */ + attached: function() { + this.bluetoothAdapterStateChangedListener_ = + this.onBluetoothAdapterStateChanged_.bind(this); + this.bluetooth.onAdapterStateChanged.addListener( + this.bluetoothAdapterStateChangedListener_); + + this.bluetoothDeviceUpdatedListener_ = + this.onBluetoothDeviceUpdated_.bind(this); + this.bluetooth.onDeviceAdded.addListener( + this.bluetoothDeviceUpdatedListener_); + this.bluetooth.onDeviceChanged.addListener( + this.bluetoothDeviceUpdatedListener_); + + this.bluetoothDeviceRemovedListener_ = + this.onBluetoothDeviceRemoved_.bind(this); + this.bluetooth.onDeviceRemoved.addListener( + this.bluetoothDeviceRemovedListener_); + + // Request the inital adapter state. + this.bluetooth.getAdapterState( + this.bluetoothAdapterStateChangedListener_); + }, + + /** @override */ + detached: function() { + if (this.bluetoothAdapterStateChangedListener_) { + this.bluetooth.onAdapterStateChanged.removeListener( + this.bluetoothAdapterStateChangedListener_); + } + if (this.bluetoothDeviceUpdatedListener_) { + this.bluetooth.onDeviceAdded.removeListener( + this.bluetoothDeviceUpdatedListener_); + this.bluetooth.onDeviceChanged.removeListener( + this.bluetoothDeviceUpdatedListener_); + } + if (this.bluetoothDeviceRemovedListener_) { + this.bluetooth.onDeviceRemoved.removeListener( + this.bluetoothDeviceRemovedListener_); + } + }, + + bluetoothEnabledChanged_: function() { + // When bluetooth is enabled, auto-expand the device list. + if (this.bluetoothEnabled) + this.deviceListExpanded = true; + }, + + /** + * If bluetooth is enabled, request the complete list of devices and update + * |deviceList|. + * @private + */ + updateDeviceList_: function() { + if (!this.bluetoothEnabled) { + this.deviceList = []; + return; + } + this.bluetooth.getDevices(function(devices) { + this.deviceList = devices; + }.bind(this)); + }, + + /** + * Event called when a user action changes the bluetoothEnabled state. + * @private + */ + onBluetoothEnabledChange_: function() { + this.bluetoothPrivate.setAdapterState( + {powered: this.bluetoothEnabled}, function() { + if (chrome.runtime.lastError) { + console.error( + 'Error enabling bluetooth: ' + + chrome.runtime.lastError.message); + } + }); + }, + + /** + * Process bluetooth.onAdapterStateChanged events. + * @param {!chrome.bluetooth.AdapterState} state + * @private + */ + onBluetoothAdapterStateChanged_: function(state) { + this.adapterState = state; + this.bluetoothEnabled = state.powered; + this.updateDeviceList_(); + }, + + /** + * Process bluetooth.onDeviceAdded and onDeviceChanged events. + * @param {!chrome.bluetooth.Device} device + * @private + */ + onBluetoothDeviceUpdated_: function(device) { + var address = device.address; + if (this.dialog && this.pairingDevice && + this.pairingDevice.address == address) { + this.pairingDevice = device; + } + var index = this.getDeviceIndex_(address); + if (index >= 0) { + // Use splice to update the item in order to update the dom-repeat lists. + // See https://github.com/Polymer/polymer/issues/3254. + this.splice('deviceList', index, 1, device); + return; + } + this.push('deviceList', device); + }, + + /** + * Process bluetooth.onDeviceRemoved events. + * @param {!chrome.bluetooth.Device} device + * @private + */ + onBluetoothDeviceRemoved_: function(device) { + var address = device.address; + var index = this.getDeviceIndex_(address); + if (index < 0) + return; + this.splice('deviceList', index, 1); + }, + + /** @private */ + startDiscovery_: function() { + if (!this.adapterState || this.adapterState.discovering) + return; + + if (!this.bluetoothPrivateOnPairingListener_) { + this.bluetoothPrivateOnPairingListener_ = + this.onBluetoothPrivateOnPairing_.bind(this); + this.bluetoothPrivate.onPairing.addListener( + this.bluetoothPrivateOnPairingListener_); + } + + this.bluetooth.startDiscovery(function() { + if (chrome.runtime.lastError) { + if (chrome.runtime.lastError.message == 'Failed to stop discovery') { + // May happen if also started elsewhere; ignore. + return; + } + console.error('startDsicovery Error: ' + + chrome.runtime.lastError.message); + } + }); + }, + + /** @private */ + stopDiscovery_: function() { + if (!this.get('adapterState.discovering')) + return; + + if (this.bluetoothPrivateOnPairingListener_) { + this.bluetoothPrivate.onPairing.removeListener( + this.bluetoothPrivateOnPairingListener_); + this.bluetoothPrivateOnPairingListener_ = undefined; + } + + this.bluetooth.stopDiscovery(function() { + if (chrome.runtime.lastError) { + console.error('Error stopping bluetooth discovery: ' + + chrome.runtime.lastError.message); + } + }); + }, + + /** + * Process bluetoothPrivate.onPairing events. + * @param {!chrome.bluetoothPrivate.PairingEvent} e + * @private + */ + onBluetoothPrivateOnPairing_: function(e) { + if (!this.dialog || !this.pairingDevice || + e.device.address != this.pairingDevice.address) { + return; + } + if (e.pairing == chrome.bluetoothPrivate.PairingEventType.KEYS_ENTERED && + e.passkey === undefined && this.pairingEvent) { + // 'keysEntered' event might not include the updated passkey so preserve + // the current one. + e.passkey = this.pairingEvent.passkey; + } + this.pairingEvent = e; + }, + + /** @private */ + onAddDeviceTap_: function() { this.openDialog_('addDevice'); }, + + /** + * @param {!{detail: {action: string, device: !chrome.bluetooth.Device}}} e + * @private + */ + onDeviceEvent_: function(e) { + var action = e.detail.action; + var device = e.detail.device; + if (action == 'connect') + this.connectDevice_(device); + else if (action == 'disconnect') + this.disconnectDevice_(device); + else if (action == 'remove') + this.forgetDevice_(device); + else + console.error('Unexected action: ' + action); + }, + + /** + * Handle a response sent from the pairing dialog and pass it to the + * bluetoothPrivate API. + * @param {Event} e + * @private + */ + onResponse_: function(e) { + var options = + /** @type {!chrome.bluetoothPrivate.SetPairingResponseOptions} */ ( + e.detail); + this.bluetoothPrivate.setPairingResponse(options, function() { + if (chrome.runtime.lastError) { + // TODO(stevenjb): Show error. + console.error( + 'Error setting pairing response: ' + options.device.name + + ': Response: ' + options.response + ': Error: ' + + chrome.runtime.lastError.message); + } + this.closeDialog_(); + }.bind(this)); + }, + + /** + * @param {string} address + * @return {number} The index of the device associated with |address| or -1. + * @private + */ + getDeviceIndex_: function(address) { + var len = this.deviceList.length; + for (var i = 0; i < len; ++i) { + if (this.deviceList[i].address == address) + return i; + } + return -1; + }, + + /** + * @param {!chrome.bluetooth.Device} device + * @return {string} The text to display for |device| in the device list. + * @private + */ + getDeviceName_: function(device) { + return device.name || device.address; + }, + + /** + * @param {!chrome.bluetooth.Device} device + * @return {boolean} + * @private + */ + deviceIsPairedOrConnecting_: function(device) { + return !!device.paired || !!device.connecting; + }, + + /** + * @param {Object} deviceListChanges Changes to the deviceList Array. + * @return {boolean} True if deviceList contains any paired devices. + * @private + */ + haveDevices_: function(deviceListChanges) { + return this.deviceList.findIndex(function(d) { return d.paired; }) != -1; + }, + + /** + * @param {!chrome.bluetooth.Device} device + * @private + */ + connectDevice_: function(device) { + // If the device is not paired, show the pairing dialog. + if (!device.paired) { + // Set the pairing device and clear any pairing event. + this.pairingDevice = device; + this.pairingEvent = null; + + this.openDialog_('pairDevice'); + } + + this.bluetoothPrivate.connect(device.address, function(result) { + if (chrome.runtime.lastError) { + console.error( + 'Error connecting: ' + device.address + + chrome.runtime.lastError.message); + // TODO(stevenjb): Show error message insead. + this.closeDialog_(); + } + }.bind(this)); + }, + + /** + * @param {!chrome.bluetooth.Device} device + * @private + */ + disconnectDevice_: function(device) { + this.bluetoothPrivate.disconnectAll(device.address, function() { + if (chrome.runtime.lastError) { + console.error( + 'Error disconnecting: ' + device.address + + chrome.runtime.lastError.message); + } + }); + }, + + /** + * @param {!chrome.bluetooth.Device} device + * @private + */ + forgetDevice_: function(device) { + this.bluetoothPrivate.forgetDevice(device.address, function() { + if (chrome.runtime.lastError) { + console.error( + 'Error forgetting: ' + device.name + ': ' + + chrome.runtime.lastError.message); + } + this.updateDeviceList_(); + }.bind(this)); + }, + + /** + * @param {string} dialog + * @param {string} dialogToShow The name of the dialog. + * @return {boolean} + * @private + */ + dialogIsVisible_(dialog, dialogToShow) { + return dialogToShow == dialog; + }, + + /** + * @param {string} dialogId + * @private + */ + openDialog_: function(dialogId) { + if (this.dialog) { + // Dialog already opened, just update the contents. + this.dialog = dialogId; + return; + } + this.dialog = dialogId; + // Call flush so that the dialog gets sized correctly before it is opened. + Polymer.dom.flush(); + var dialog = this.$$('#deviceDialog'); + dialog.open(); + dialog.focus(); + }, + + /** @private */ + closeDialog_: function() { + if (!this.dialog) + return; + var dialog = this.$$('#deviceDialog'); + dialog.close(); + this.dialog = ''; + this.pairingDevice = null; + this.pairingEvent = null; + }, + + /** @private */ + onCloseDialog_: function(event) { this.closeDialog_(); }, + + /** @private */ + onDialogOpened_: function() { this.startDiscovery_(); }, + + /** @private */ + onDialogClosed_: function() { this.stopDiscovery_(); }, +}); diff --git a/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_pair_device_dialog.html b/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_pair_device_dialog.html new file mode 100644 index 00000000000..05ed236ecd1 --- /dev/null +++ b/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_pair_device_dialog.html @@ -0,0 +1,68 @@ +<link rel="import" href="chrome://resources/polymer/v1_0/polymer/polymer.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/paper-input/paper-input.html"> + +<dom-module id="settings-bluetooth-pair-device-dialog"> + <link rel="import" type="css" href="chrome://md-settings/settings_shared.css"> + <link rel="import" type="css" href="bluetooth_page.css"> + <link rel="import" type="css" href="bluetooth_dialog.css"> + <template> + <div id="dialogOuterDiv" class="layout vertical flex"> + <div id="dialogHeaderDiv" class="settings-box layout horizontal center"> + <span id="dialogTitle" class="flex" + i18n-content="bluetoothPairDevicePageTitle"> + </span> + <paper-icon-button icon="close" on-tap="onCancelTap_" id="close"> + </paper-icon-button> + </div> + <div id="pairing" + class="settings-blox layout vertical center center-justified flex"> + <div id="dialogMessage"> + [[getMessage_(pairingDevice, pairingEvent)]] + </div> + <div hidden$="[[!showEnterPincode_(pairingEvent)]]"> + <paper-input id="pincode" minlength="1" maxlength="16" type="text"> + </div> + <div hidden$="[[!showEnterPasskey_(pairingEvent)]]"> + <paper-input id="passkey" minlength="6" maxlength="6" type="text"> + </div> + <div id="pinDiv" class="layout horizontal center center-justified" + hidden="[[!showDisplayPassOrPin_(pairingEvent)]]"> + <template is="dom-repeat" items="[[digits]]"> + <span class$="[[getPinClass_(pairingEvent, index)]]"> + [[getPinDigit_(pairingEvent, index)]] + </span> + </template> + <span class$="[[getPinClass_(pairingEvent, -1)]]" + hidden="[[showAcceptReject_(pairingEvent)]]"> + [[i18n('bluetoothEnterKey')]] + </span> + </div> + </div> + <div id="dialogFooterDiv" class="layout horizontal center end-justified"> + <paper-button i18n-content="bluetoothAccept" + hidden$="[[!showAcceptReject_(pairingEvent)]]" + on-tap="onAcceptTap_"> + </paper-button> + <paper-button i18n-content="bluetoothReject" + hidden$="[[!showAcceptReject_(pairingEvent)]]" + on-tap="onRejectTap_"> + </paper-button> + <paper-button i18n-content="bluetoothConnect" + hidden$="[[!showConnect_(pairingEvent)]]" + on-tap="onConnectTap_"> + </paper-button> + <paper-button i18n-content="bluetoothDismiss" + hidden$="[[!showDismiss_(pairingDevice, pairingEvent)]]" + on-tap="onDismissTap_"> + </paper-button> + <paper-button i18n-content="bluetoothCancel" on-tap="onCancelTap_" + hidden$="[[showDismiss_(pairingDevice, pairingEvent)]]" + </paper-button> + </div> + </div> + </template> + <script src="bluetooth_pair_device_dialog.js"></script> +</dom-module> diff --git a/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_pair_device_dialog.js b/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_pair_device_dialog.js new file mode 100644 index 00000000000..fe286744b2b --- /dev/null +++ b/chromium/chrome/browser/resources/settings/bluetooth_page/bluetooth_pair_device_dialog.js @@ -0,0 +1,271 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview + * 'settings-bluetooth-pair-device-dialog' is the settings dialog for pairing + * a bluetooth device. + * + * @group Chrome Settings Elements + * @element settings-bluetooth-pair-device-dialog + */ + +(function() { + +var PairingEventType = chrome.bluetoothPrivate.PairingEventType; + +Polymer({ + is: 'settings-bluetooth-pair-device-dialog', + + behaviors: [I18nBehavior], + + properties: { + /** + * Current Pairing device. + * @type {?chrome.bluetooth.Device|undefined} + */ + pairingDevice: Object, + + /** + * Current Pairing event. + * @type {?chrome.bluetoothPrivate.PairingEvent|undefined} + */ + pairingEvent: Object, + + /** + * @const + * @type {!Array<number>} + */ + digits: { + type: Array, + readonly: true, + value: [0, 1, 2, 3, 4, 5], + }, + }, + + observers: [ + 'pairingChanged_(pairingDevice, pairingEvent)', + ], + + /** + * @param {?chrome.bluetooth.Device} pairingDevice + * @param {?chrome.bluetoothPrivate.PairingEvent} pairingEvent + * @private + */ + pairingChanged_: function(pairingDevice, pairingEvent) { + // Auto-close the dialog when pairing completes. + if (pairingDevice && pairingDevice.connected) { + this.fire('close-dialog', ''); + return; + } + }, + + /** + * @param {?chrome.bluetooth.Device} device + * @param {?chrome.bluetoothPrivate.PairingEvent} pairingEvent + * @return {string} + * @private + */ + getMessage_: function(device, pairingEvent) { + if (!device) + return ''; + var message; + if (!pairingEvent) + message = 'bluetoothStartConnecting'; + else + message = this.getEventDesc_(pairingEvent.pairing); + return this.i18n(message, device.name); + }, + + /** + * @param {?chrome.bluetoothPrivate.PairingEvent} pairingEvent + * @return {boolean} + * @private + */ + showEnterPincode_: function(pairingEvent) { + return !!pairingEvent && + pairingEvent.pairing == PairingEventType.REQUEST_PINCODE; + }, + + /** + * @param {?chrome.bluetoothPrivate.PairingEvent} pairingEvent + * @return {boolean} + * @private + */ + showEnterPasskey_: function(pairingEvent) { + return !!pairingEvent && + pairingEvent.pairing == PairingEventType.REQUEST_PASSKEY; + }, + + /** + * @param {?chrome.bluetoothPrivate.PairingEvent} pairingEvent + * @return {boolean} + * @private + */ + showDisplayPassOrPin_: function(pairingEvent) { + if (!pairingEvent) + return false; + var pairing = pairingEvent.pairing; + return ( + pairing == PairingEventType.DISPLAY_PINCODE || + pairing == PairingEventType.DISPLAY_PASSKEY || + pairing == PairingEventType.CONFIRM_PASSKEY || + pairing == PairingEventType.KEYS_ENTERED); + }, + + /** + * @param {?chrome.bluetoothPrivate.PairingEvent} pairingEvent + * @return {boolean} + * @private + */ + showAcceptReject_: function(pairingEvent) { + return !!pairingEvent && + pairingEvent.pairing == PairingEventType.CONFIRM_PASSKEY; + }, + + /** + * @param {?chrome.bluetoothPrivate.PairingEvent} pairingEvent + * @return {boolean} + * @private + */ + showConnect_: function(pairingEvent) { + if (!pairingEvent) + return false; + var pairing = pairingEvent.pairing; + if (pairing == PairingEventType.REQUEST_PINCODE) { + var pincode = /** @type {{invalid: boolean}} */(this.$.pincode); + return !pincode.invalid; + } else if (pairing == PairingEventType.REQUEST_PASSKEY) { + var passkey = /** @type {{invalid: boolean}} */(this.$.passkey); + return !passkey.invalid; + } + return false; + }, + + /** + * @param {?chrome.bluetooth.Device} device + * @param {?chrome.bluetoothPrivate.PairingEvent} pairingEvent + * @return {boolean} + * @private + */ + showDismiss_: function(device, pairingEvent) { + return (!!device && device.paired) || + (!!pairingEvent && pairingEvent.pairing == PairingEventType.COMPLETE); + }, + + /** @private */ + onAcceptTap_: function() { + this.sendResponse_(chrome.bluetoothPrivate.PairingResponse.CONFIRM); + }, + + /** @private */ + onConnectTap_: function() { + this.sendResponse_(chrome.bluetoothPrivate.PairingResponse.CONFIRM); + }, + + /** @private */ + onRejectTap_: function() { + this.sendResponse_(chrome.bluetoothPrivate.PairingResponse.REJECT); + }, + + /** @private */ + onCancelTap_: function() { + this.sendResponse_(chrome.bluetoothPrivate.PairingResponse.CANCEL); + // Close the dialog immediately. + this.fire('close-dialog', ''); + }, + + /** @private */ + onDismissTap_: function() { this.fire('close-dialog', ''); }, + + /** @private */ + sendResponse_: function(response) { + if (!this.pairingDevice) + return; + var options = + /** @type {!chrome.bluetoothPrivate.SetPairingResponseOptions} */ { + device: this.pairingDevice, + response: response + }; + if (response == chrome.bluetoothPrivate.PairingResponse.CONFIRM) { + var pairing = this.pairingEvent.pairing; + if (pairing == PairingEventType.REQUEST_PINCODE) + options.pincode = this.$.pincode.value; + else if (pairing == PairingEventType.REQUEST_PASSKEY) + options.passkey = parseInt(this.$.passkey.value, 10); + } + this.fire('response', options); + }, + + /** + * @param {!PairingEventType} eventType + * @return {string} + * @private + */ + getEventDesc_: function(eventType) { + assert(eventType); + if (eventType == PairingEventType.COMPLETE || + eventType == PairingEventType.KEYS_ENTERED || + eventType == PairingEventType.REQUEST_AUTHORIZATION) { + return 'bluetoothStartConnecting'; + } + return 'bluetooth_' + /** @type {string} */(eventType); + }, + + /** + * @param {?chrome.bluetoothPrivate.PairingEvent} pairingEvent + * @param {number} index + * @return {string} + * @private + */ + getPinDigit_: function(pairingEvent, index) { + if (!pairingEvent) + return ''; + var digit = '0'; + var pairing = pairingEvent.pairing; + if (pairing == PairingEventType.DISPLAY_PINCODE && pairingEvent.pincode && + index < pairingEvent.pincode.length) { + digit = pairingEvent.pincode[index]; + } else if (pairingEvent.passkey && + (pairing == PairingEventType.DISPLAY_PASSKEY || + pairing == PairingEventType.KEYS_ENTERED || + pairing == PairingEventType.CONFIRM_PASSKEY)) { + var passkeyString = String(pairingEvent.passkey); + if (index < passkeyString.length) + digit = passkeyString[index]; + } + return digit; + }, + + /** + * @param {?chrome.bluetoothPrivate.PairingEvent} pairingEvent + * @param {number} index + * @return {string} + * @private + */ + getPinClass_: function(pairingEvent, index) { + if (!pairingEvent) + return ''; + if (pairingEvent.pairing == PairingEventType.CONFIRM_PASSKEY) + return 'confirm'; + var cssClass = 'display'; + if (pairingEvent.pairing == PairingEventType.DISPLAY_PASSKEY) { + if (index == 0) + cssClass += ' next'; + else + cssClass += ' untyped'; + } else if ( + pairingEvent.pairing == PairingEventType.KEYS_ENTERED && + pairingEvent.enteredKey) { + var enteredKey = pairingEvent.enteredKey; // 1-7 + var lastKey = this.digits.length; // 6 + if ((index == -1 && enteredKey > lastKey) || (index + 1 == enteredKey)) + cssClass += ' next'; + else if (index > enteredKey) + cssClass += ' untyped'; + } + return cssClass; + }, +}); +})(); diff --git a/chromium/chrome/browser/resources/settings/bluetooth_page/compiled_resources.gyp b/chromium/chrome/browser/resources/settings/bluetooth_page/compiled_resources.gyp new file mode 100644 index 00000000000..17032053dd6 --- /dev/null +++ b/chromium/chrome/browser/resources/settings/bluetooth_page/compiled_resources.gyp @@ -0,0 +1,72 @@ +# Copyright 2015 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +{ + 'targets': [ + { + 'target_name': 'bluetooth_page', + 'variables': { + 'depends': [ + '../../../../../third_party/closure_compiler/externs/bluetooth_interface.js', + '../../../../../third_party/closure_compiler/externs/bluetooth_private_interface.js', + '../../../../../ui/webui/resources/js/compiled_resources.gyp:assert', + '../../../../../ui/webui/resources/js/compiled_resources.gyp:load_time_data', + '../../../../../ui/webui/resources/js/i18n_behavior.js', + '../settings_page/settings_animated_pages.js' + ], + 'externs': [ + '../../../../../third_party/closure_compiler/externs/bluetooth.js', + '../../../../../third_party/closure_compiler/externs/bluetooth_private.js' + ], + }, + 'includes': ['../../../../../third_party/closure_compiler/compile_js.gypi'], + }, + { + 'target_name': 'bluetooth_device_list_item', + 'variables': { + 'depends': [ + '../../../../../ui/webui/resources/js/compiled_resources.gyp:assert', + ], + 'externs': [ + '../../../../../third_party/closure_compiler/externs/bluetooth.js', + '../../../../../third_party/closure_compiler/externs/bluetooth_private.js' + ], + }, + 'includes': ['../../../../../third_party/closure_compiler/compile_js.gypi'], + }, + { + 'target_name': 'bluetooth_add_device_dialog', + 'variables': { + 'depends': [ + '../../../../../third_party/closure_compiler/externs/bluetooth_interface.js', + '../../../../../third_party/closure_compiler/externs/bluetooth_private_interface.js', + '../../../../../ui/webui/resources/js/compiled_resources.gyp:assert', + '../../../../../ui/webui/resources/js/compiled_resources.gyp:load_time_data', + '../../../../../ui/webui/resources/js/i18n_behavior.js', + ], + 'externs': [ + '../../../../../third_party/closure_compiler/externs/bluetooth.js', + '../../../../../third_party/closure_compiler/externs/bluetooth_private.js' + ], + }, + 'includes': ['../../../../../third_party/closure_compiler/compile_js.gypi'], + }, + { + 'target_name': 'bluetooth_pair_device_dialog', + 'variables': { + 'depends': [ + '../../../../../third_party/closure_compiler/externs/bluetooth_interface.js', + '../../../../../third_party/closure_compiler/externs/bluetooth_private_interface.js', + '../../../../../ui/webui/resources/js/compiled_resources.gyp:assert', + '../../../../../ui/webui/resources/js/compiled_resources.gyp:load_time_data', + '../../../../../ui/webui/resources/js/i18n_behavior.js', + ], + 'externs': [ + '../../../../../third_party/closure_compiler/externs/bluetooth.js', + '../../../../../third_party/closure_compiler/externs/bluetooth_private.js' + ], + }, + 'includes': ['../../../../../third_party/closure_compiler/compile_js.gypi'], + }, + ], +} |