| Index: chrome/browser/resources/bluetooth_internals/value_control.js
|
| diff --git a/chrome/browser/resources/bluetooth_internals/value_control.js b/chrome/browser/resources/bluetooth_internals/value_control.js
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..2fccbb8ca667035ac62ed33ce6ca3aa50c156b4a
|
| --- /dev/null
|
| +++ b/chrome/browser/resources/bluetooth_internals/value_control.js
|
| @@ -0,0 +1,382 @@
|
| +// Copyright 2017 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.
|
| +
|
| +/**
|
| + * Javascript for ValueControl, served from chrome://bluetooth-internals/.
|
| + */
|
| +
|
| +cr.define('value_control', function() {
|
| + /** @const */ var Snackbar = snackbar.Snackbar;
|
| + /** @const */ var SnackbarType = snackbar.SnackbarType;
|
| +
|
| + /** @enum {string} */
|
| + var ValueDataType = {
|
| + HEXADECIMAL: 'Hexadecimal',
|
| + UTF8: 'UTF-8',
|
| + DECIMAL: 'Decimal',
|
| + };
|
| +
|
| + /**
|
| + * A container for an array value that needs to be converted to multiple
|
| + * display formats. Internally, the value is stored as an array and converted
|
| + * to the needed display type at runtime.
|
| + * @constructor
|
| + * @param {!Array<number>} initialValue
|
| + */
|
| + function Value(initialValue) {
|
| + /** @private {!Array<number>} */
|
| + this.value_ = initialValue;
|
| + }
|
| +
|
| + Value.prototype = {
|
| + /**
|
| + * Gets the backing array value.
|
| + * @return {!Array<number>}
|
| + */
|
| + getArray: function() {
|
| + return this.value_;
|
| + },
|
| +
|
| + /**
|
| + * Sets the backing array value.
|
| + * @param {!Array<number>} newValue
|
| + */
|
| + setArray: function(newValue) {
|
| + this.value_ = newValue;
|
| + },
|
| +
|
| + /**
|
| + * Sets the value by converting the |newValue| string using the formatting
|
| + * specified by |valueDataType|.
|
| + * @param {!ValueDataType} valueDataType
|
| + * @param {string} newValue
|
| + */
|
| + setAs: function(valueDataType, newValue) {
|
| + switch (valueDataType) {
|
| + case ValueDataType.HEXADECIMAL:
|
| + this.setValueFromHex_(newValue);
|
| + break;
|
| +
|
| + case ValueDataType.UTF8:
|
| + this.setValueFromUTF8_(newValue);
|
| + break;
|
| +
|
| + case ValueDataType.DECIMAL:
|
| + this.setValueFromDecimal_(newValue);
|
| + break;
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Gets the value as a string representing the given |valueDataType|.
|
| + * @param {!ValueDataType} valueDataType
|
| + * @return {string}
|
| + */
|
| + getAs: function(valueDataType) {
|
| + switch (valueDataType) {
|
| + case ValueDataType.HEXADECIMAL:
|
| + return this.toHex_();
|
| +
|
| + case ValueDataType.UTF8:
|
| + return this.toUTF8_();
|
| +
|
| + case ValueDataType.DECIMAL:
|
| + return this.toDecimal_();
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Converts the value to a hex string.
|
| + * @return {string}
|
| + * @private
|
| + */
|
| + toHex_: function() {
|
| + if (this.value_.length == 0)
|
| + return '';
|
| +
|
| + return this.value_.reduce(function(result, value, index) {
|
| + return result + ('0' + value.toString(16)).substr(-2);
|
| + }, '0x');
|
| + },
|
| +
|
| + /**
|
| + * Sets the value from a hex string.
|
| + * @return {string}
|
| + * @private
|
| + */
|
| + setValueFromHex_: function(newValue) {
|
| + if (!newValue) {
|
| + this.value_ = [];
|
| + return;
|
| + }
|
| +
|
| + if (!newValue.startsWith('0x'))
|
| + throw new Error('Expected new value to start with "0x".');
|
| +
|
| + var result = [];
|
| + for (var i = 2; i < newValue.length; i += 2) {
|
| + result.push(parseInt(newValue.substr(i, 2), 16));
|
| + }
|
| +
|
| + this.value_ = result;
|
| + },
|
| +
|
| + /**
|
| + * Converts the value to a UTF-8 encoded text string.
|
| + * @return {string}
|
| + * @private
|
| + */
|
| + toUTF8_: function() {
|
| + return this.value_.reduce(function(result, value) {
|
| + return result + String.fromCharCode(value);
|
| + }, '');
|
| + },
|
| +
|
| + /**
|
| + * Sets the value from a UTF-8 encoded text string.
|
| + * @return {string}
|
| + * @private
|
| + */
|
| + setValueFromUTF8_: function(newValue) {
|
| + if (!newValue) {
|
| + this.value_ = [];
|
| + return;
|
| + }
|
| +
|
| + this.value_ = Array.from(newValue).map(function(char) {
|
| + return char.charCodeAt(0);
|
| + });
|
| + },
|
| +
|
| + /**
|
| + * Converts the value to a decimal string with numbers delimited by '-'.
|
| + * @return {string}
|
| + * @private
|
| + */
|
| + toDecimal_: function() {
|
| + return this.value_.join('-');
|
| + },
|
| +
|
| + /**
|
| + * Sets the value from a decimal string delimited by '-'.
|
| + * @return {string}
|
| + * @private
|
| + */
|
| + setValueFromDecimal_: function(newValue) {
|
| + if (!newValue) {
|
| + this.value_ = [];
|
| + return;
|
| + }
|
| +
|
| + if (!/^[0-9\-]*$/.test(newValue))
|
| + throw new Error('New value can only contain numbers and hyphens.');
|
| +
|
| + this.value_ = newValue.split('-').map(function(val) {
|
| + return parseInt(val, 10);
|
| + });
|
| + },
|
| + };
|
| +
|
| + /**
|
| + * A set of inputs that allow a user to request reads and writes of values.
|
| + * This control allows the value to be displayed in multiple forms
|
| + * as defined by the |ValueDataType| array. Values must be written
|
| + * in these formats. Read and write capability is controlled by a
|
| + * 'properties' bitfield provided by the characteristic.
|
| + * @constructor
|
| + */
|
| + var ValueControl = cr.ui.define('div');
|
| +
|
| + ValueControl.prototype = {
|
| + __proto__: HTMLDivElement.prototype,
|
| +
|
| + /**
|
| + * Decorates the element as a ValueControl. Creates the layout for the value
|
| + * control by creating a text input, select element, and two buttons for
|
| + * read/write requests. Event handlers are attached and references to these
|
| + * elements are stored for later use.
|
| + * @override
|
| + */
|
| + decorate: function() {
|
| + this.classList.add('value-control');
|
| +
|
| + /** @private {!Value} */
|
| + this.value_ = new Value([]);
|
| + /** @private {?string} */
|
| + this.deviceAddress_ = null;
|
| + /** @private {?string} */
|
| + this.serviceId_ = null;
|
| + /** @private {?interfaces.BluetoothDevice.CharacteristicInfo} */
|
| + this.characteristicInfo_ = null;
|
| +
|
| + this.unavailableMessage_ = document.createElement('h3');
|
| + this.unavailableMessage_.textContent = 'Value cannot be read or written.';
|
| +
|
| + this.valueInput_ = document.createElement('input');
|
| + this.valueInput_.addEventListener('change', function() {
|
| + try {
|
| + this.value_.setAs(this.typeSelect_.value, this.valueInput_.value);
|
| + } catch (e) {
|
| + Snackbar.show(e.message, SnackbarType.ERROR);
|
| + }
|
| + }.bind(this));
|
| +
|
| + this.typeSelect_ = document.createElement('select');
|
| +
|
| + Object.keys(ValueDataType).forEach(function(key) {
|
| + var type = ValueDataType[key];
|
| + var option = document.createElement('option');
|
| + option.value = type;
|
| + option.text = type;
|
| + this.typeSelect_.add(option);
|
| + }, this);
|
| +
|
| + this.typeSelect_.addEventListener('change', this.redraw.bind(this));
|
| +
|
| + var inputDiv = document.createElement('div');
|
| + inputDiv.appendChild(this.valueInput_);
|
| + inputDiv.appendChild(this.typeSelect_);
|
| +
|
| + this.readBtn_ = document.createElement('button');
|
| + this.readBtn_.textContent = 'Read';
|
| + this.readBtn_.addEventListener('click', this.readValue_.bind(this));
|
| +
|
| + this.writeBtn_ = document.createElement('button');
|
| + this.writeBtn_.textContent = 'Write';
|
| + this.writeBtn_.addEventListener('click', this.writeValue_.bind(this));
|
| +
|
| + var buttonsDiv = document.createElement('div');
|
| + buttonsDiv.appendChild(this.readBtn_);
|
| + buttonsDiv.appendChild(this.writeBtn_);
|
| +
|
| + this.appendChild(this.unavailableMessage_);
|
| + this.appendChild(inputDiv);
|
| + this.appendChild(buttonsDiv);
|
| + },
|
| +
|
| + /**
|
| + * Sets the settings used by the value control and redraws the control to
|
| + * match the read/write settings provided in
|
| + * |characteristicInfo.properties|.
|
| + * @param {string} deviceAddress
|
| + * @param {string} serviceId
|
| + * @param {!interfaces.BluetoothDevice.CharacteristicInfo}
|
| + * characteristicInfo
|
| + */
|
| + load: function(deviceAddress, serviceId, characteristicInfo) {
|
| + this.deviceAddress_ = deviceAddress;
|
| + this.serviceId_ = serviceId;
|
| + this.characteristicInfo_ = characteristicInfo;
|
| +
|
| + this.redraw();
|
| + },
|
| +
|
| + /**
|
| + * Redraws the value control with updated layout depending on the
|
| + * availability of reads and writes and the current cached value.
|
| + */
|
| + redraw: function() {
|
| + this.readBtn_.hidden = (this.characteristicInfo_.properties &
|
| + interfaces.BluetoothDevice.Property.READ) === 0;
|
| + this.writeBtn_.hidden = (this.characteristicInfo_.properties &
|
| + interfaces.BluetoothDevice.Property.WRITE) === 0;
|
| +
|
| + var isAvailable = !this.readBtn_.hidden || !this.writeBtn_.hidden;
|
| + this.unavailableMessage_.hidden = isAvailable;
|
| + this.valueInput_.hidden = !isAvailable;
|
| + this.typeSelect_.hidden = !isAvailable;
|
| +
|
| + if (!isAvailable)
|
| + return;
|
| +
|
| + this.valueInput_.value = this.value_.getAs(this.typeSelect_.value);
|
| + },
|
| +
|
| + /**
|
| + * Sets the value of the control.
|
| + * @param {!Array<number>} value
|
| + */
|
| + setValue: function(value) {
|
| + this.value_.setArray(value);
|
| + this.redraw();
|
| + },
|
| +
|
| + /**
|
| + * Gets an error string describing the given |result| code.
|
| + * @param {!interfaces.BluetoothDevice.GattResult} result
|
| + * @private
|
| + */
|
| + getErrorString_: function(result) {
|
| + // TODO(crbug.com/663394): Replace with more descriptive error
|
| + // messages.
|
| + var GattResult = interfaces.BluetoothDevice.GattResult;
|
| + return Object.keys(GattResult).find(function(key) {
|
| + return GattResult[key] === result;
|
| + });
|
| + },
|
| +
|
| + /**
|
| + * Called when the read button is pressed. Connects to the device and
|
| + * retrieves the current value of the characteristic in the |service_id|
|
| + * with id |characteristic_id|
|
| + * @private
|
| + */
|
| + readValue_: function() {
|
| + this.readBtn_.disabled = true;
|
| +
|
| + device_broker.connectToDevice(this.deviceAddress_).then(function(device) {
|
| + return device.readValueForCharacteristic(
|
| + this.serviceId_, this.characteristicInfo_.id);
|
| + }.bind(this)).then(function(response) {
|
| + this.readBtn_.disabled = false;
|
| +
|
| + if (response.result === interfaces.BluetoothDevice.GattResult.SUCCESS) {
|
| + this.setValue(response.value);
|
| + Snackbar.show(
|
| + this.deviceAddress_ + ': Read succeeded', SnackbarType.SUCCESS);
|
| + return;
|
| + }
|
| +
|
| + var errorString = this.getErrorString_(response.result);
|
| + Snackbar.show(
|
| + this.deviceAddress_ + ': ' + errorString, SnackbarType.ERROR,
|
| + 'Retry', this.readValue_.bind(this));
|
| + }.bind(this));
|
| + },
|
| +
|
| + /**
|
| + * Called when the write button is pressed. Connects to the device and
|
| + * retrieves the current value of the characteristic in the |service_id|
|
| + * with id |characteristic_id|
|
| + * @private
|
| + */
|
| + writeValue_: function() {
|
| + this.writeBtn_.disabled = true;
|
| +
|
| + device_broker.connectToDevice(this.deviceAddress_).then(function(device) {
|
| + return device.writeValueForCharacteristic(
|
| + this.serviceId_, this.characteristicInfo_.id,
|
| + this.value_.getArray());
|
| + }.bind(this)).then(function(response) {
|
| + this.writeBtn_.disabled = false;
|
| +
|
| + if (response.result === interfaces.BluetoothDevice.GattResult.SUCCESS) {
|
| + Snackbar.show(
|
| + this.deviceAddress_ + ': Write succeeded', SnackbarType.SUCCESS);
|
| + return;
|
| + }
|
| +
|
| + var errorString = this.getErrorString_(response.result);
|
| + Snackbar.show(
|
| + this.deviceAddress_ + ': ' + errorString, SnackbarType.ERROR,
|
| + 'Retry', this.writeValue_.bind(this));
|
| + }.bind(this));
|
| + },
|
| + }
|
| +
|
| + return {
|
| + ValueControl: ValueControl,
|
| + ValueDataType: ValueDataType,
|
| + };
|
| +});
|
|
|