Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
575 lines
16 KiB
JavaScript
575 lines
16 KiB
JavaScript
'use strict';
|
|
/**
|
|
* Bluetooth2 is compatible with Bluetooth APIv2 and used to enable/
|
|
* disable bluetooth hardware, get bluetooth adapter, and check target
|
|
* bluetooth profile is connected.
|
|
*
|
|
* Bluetooth api v2 is the two level structure.
|
|
* We use _btManagerHandler to handle BluetoothManager defaultAdapter
|
|
* change event and _btAdapterHandler to handle status change event.
|
|
*
|
|
* Current supported Bluetooth profiles are:
|
|
* - hfp: Hands-Free Profile
|
|
* - opp: Object Push Profile
|
|
* - a2dp: Advenced Audio Distribution Profile
|
|
* - sco: Synchronous Connection-Oriented Profile
|
|
*/
|
|
/* global Service, LazyLoader, BluetoothIcon, BluetoothTransferIcon,
|
|
BluetoothHeadphoneIcon, BluetoothTransfer */
|
|
/* exported Bluetooth2 */
|
|
(function(exports) {
|
|
var Bluetooth = function() {};
|
|
|
|
Bluetooth.prototype = {
|
|
name: 'Bluetooth',
|
|
|
|
/**
|
|
* Debug message.
|
|
*
|
|
* @public
|
|
* @type {Boolean} turn on/off the console log
|
|
*/
|
|
onDebug: false,
|
|
|
|
/**
|
|
* Store a reference of the default adapter.
|
|
* If the adapter is not availble, no bluetooth operation could be
|
|
* executed.
|
|
*
|
|
* @private
|
|
*/
|
|
_adapter: null,
|
|
|
|
/**
|
|
* Hold instance of bluetooth adapter state change handler.
|
|
*
|
|
* @private
|
|
*/
|
|
_bindBtAdapterHandler: null,
|
|
|
|
/**
|
|
* Hold instance of HFP status changed handler.
|
|
*
|
|
* @private
|
|
*/
|
|
_bindHfpStatusChangedHandler: null,
|
|
|
|
/**
|
|
* Hold instance of A2DP status changed handler.
|
|
*
|
|
* @private
|
|
*/
|
|
_bindA2dpStatusChangedHandler: null,
|
|
|
|
/**
|
|
* Hold instance of SCO status changed handler.
|
|
*
|
|
* @private
|
|
*/
|
|
_bindScoStatusChangedHandler: null,
|
|
|
|
/**
|
|
* Store a reference for mozBluetooth.
|
|
*
|
|
* @private
|
|
*/
|
|
_bluetooth: null,
|
|
|
|
/**
|
|
* State of Bluetooth default adapter.
|
|
*
|
|
* @private
|
|
*/
|
|
_isEnabled: false,
|
|
|
|
/**
|
|
* Set profile connect state.
|
|
*
|
|
* @private
|
|
* @param {String} profile profile id
|
|
* @param {Boolean} connected connect status
|
|
*/
|
|
_setProfileConnected: function bt_setProfileConnected(profile, connected) {
|
|
var value = this['_' + profile + 'Connected'];
|
|
if (value !== connected) {
|
|
this['_' + profile + 'Connected'] = connected;
|
|
|
|
// Raise an event for the profile connection changes.
|
|
window.dispatchEvent(new CustomEvent('bluetoothprofileconnectionchange',
|
|
{
|
|
detail: {
|
|
name: profile,
|
|
connected: connected
|
|
}
|
|
}
|
|
));
|
|
// Update Bluetooth and profile icons
|
|
this.icon && this.icon.update();
|
|
if (profile === 'opp' && this.transferIcon) {
|
|
this.transferIcon.update();
|
|
}
|
|
if (profile === 'a2dp' && this.headphoneIcon) {
|
|
this.headphoneIcon.update();
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Check if bluetooth profile is connected.
|
|
*
|
|
* @private
|
|
* @param {String} profile profile name
|
|
* @return {Boolean} connected state
|
|
*/
|
|
_isProfileConnected: function bt__isProfileConnected(profile) {
|
|
var isConnected = this['_' + profile + 'Connected'];
|
|
if (isConnected === undefined) {
|
|
return false;
|
|
} else {
|
|
return isConnected;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Initialize bluetooth module.
|
|
*
|
|
* @public
|
|
*/
|
|
start: function bt_start() {
|
|
if (!window.navigator.mozSettings || !window.navigator.mozBluetooth) {
|
|
return;
|
|
}
|
|
|
|
this._bluetooth = window.navigator.mozBluetooth;
|
|
|
|
// hold the instances so we can remove it when needed
|
|
this._bindBtAdapterHandler =
|
|
this._btAdapterHandler.bind(this);
|
|
|
|
this._bindHfpStatusChangedHandler = function(evt) {
|
|
this._setProfileConnected('hfp', evt.status);
|
|
}.bind(this);
|
|
this._bindA2dpStatusChangedHandler = function(evt) {
|
|
this._setProfileConnected('a2dp', evt.status);
|
|
}.bind(this);
|
|
this._bindScoStatusChangedHandler = function(evt) {
|
|
this._setProfileConnected('sco', evt.status);
|
|
}.bind(this);
|
|
|
|
this._initDefaultAdapter();
|
|
|
|
this._bluetooth.addEventListener('attributechanged',
|
|
this._btManagerHandler.bind(this));
|
|
|
|
// decouple enable/disable bluetooth function from other system part
|
|
window.addEventListener('request-enable-bluetooth',
|
|
this._requestEnableHandler.bind(this));
|
|
window.addEventListener('request-disable-bluetooth',
|
|
this._requestDisableHandler.bind(this));
|
|
|
|
// relay bluetooth transfer system messages
|
|
navigator.mozSetMessageHandler('bluetooth-opp-transfer-start',
|
|
this._oppTransferStartHandler.bind(this));
|
|
navigator.mozSetMessageHandler('bluetooth-opp-transfer-complete',
|
|
this._oppTransferCompleteHandler.bind(this));
|
|
|
|
// expose functions to Service.request
|
|
Service.register('adapter', this);
|
|
Service.register('pair', this);
|
|
Service.register('getPairedDevices', this);
|
|
// expose functions to Service.query
|
|
Service.registerState('isEnabled', this);
|
|
Service.registerState('getAdapter', this);
|
|
Service.registerState('isOPPProfileConnected', this);
|
|
Service.registerState('isA2DPProfileConnected', this);
|
|
Service.registerState('isSCOProfileConnected', this);
|
|
|
|
// handle statusbar icons
|
|
LazyLoader.load(['js/bluetooth_transfer.js',
|
|
'js/bluetooth_icon.js',
|
|
'js/bluetooth_transfer_icon.js',
|
|
'js/bluetooth_headphone_icon.js']).then(() => {
|
|
BluetoothTransfer.start();
|
|
this.icon = new BluetoothIcon(this);
|
|
this.icon.start();
|
|
this.transferIcon = new BluetoothTransferIcon(this);
|
|
this.transferIcon.start();
|
|
this.headphoneIcon = new BluetoothHeadphoneIcon(this);
|
|
this.headphoneIcon.start();
|
|
}).catch(function(err) {
|
|
console.error(err);
|
|
});
|
|
},
|
|
|
|
stop: function() {
|
|
Service.unregister('adapter', this);
|
|
Service.unregister('pair', this);
|
|
Service.unregister('getPairedDevices', this);
|
|
Service.unregisterState('isEnabled', this);
|
|
Service.unregisterState('getAdapter', this);
|
|
Service.unregisterState('isOPPProfileConnected', this);
|
|
Service.unregisterState('isA2DPProfileConnected', this);
|
|
Service.unregisterState('isSCOProfileConnected', this);
|
|
},
|
|
|
|
/**
|
|
* Generally bluetooth adapter is already available when start
|
|
* this module and no related event will be received.
|
|
* so we try to get the adapter then listen to mozBluetooth change
|
|
* to prevent race condition
|
|
*
|
|
* @private
|
|
*/
|
|
_initDefaultAdapter: function() {
|
|
this.debug('init bluetooth adapter');
|
|
this._adapter = this._bluetooth.defaultAdapter;
|
|
if (this._adapter) {
|
|
this.debug('adapter is available');
|
|
// listen states when the adapter is available
|
|
this._adapterAvailableHandler();
|
|
|
|
// dispatch default bluetooth enable state
|
|
this._isEnabled = (this._adapter.state === 'enabled');
|
|
this._dispatchEnableState();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Once adapter is available, listen to bluetooth state and
|
|
* the connect stat of supported profiles.
|
|
*
|
|
* we only support two use cases for bluetooth connection:
|
|
* 1. connecting with a headset
|
|
* 2. transfering a file to/from another device
|
|
* So we need to listen to corresponding events to know we are (aren't)
|
|
* connected, then summarize to an event and dispatch to Statusbar
|
|
*
|
|
* @private
|
|
*/
|
|
_adapterAvailableHandler: function bt__adapterAvailableHandler() {
|
|
// listen to adapter state
|
|
this._adapter.addEventListener('attributechanged',
|
|
this._bindBtAdapterHandler);
|
|
|
|
// headset connected cases
|
|
this._adapter.addEventListener('hfpstatuschanged',
|
|
this._bindHfpStatusChangedHandler);
|
|
this._adapter.addEventListener('a2dpstatuschanged',
|
|
this._bindA2dpStatusChangedHandler);
|
|
this._adapter.addEventListener('scostatuschanged',
|
|
this._bindScoStatusChangedHandler);
|
|
},
|
|
|
|
/**
|
|
* Remove all EventListeners when default adapter is changed.
|
|
*/
|
|
_adapterUnavailableHandler: function bt__adapterUnavailableHandler() {
|
|
this.debug('default adapter is removed');
|
|
// unbind headset connected cases
|
|
this._adapter.removeEventListener('hfpstatuschanged',
|
|
this._bindHfpStatusChangedHandler);
|
|
this._adapter.removeEventListener('a2dpstatuschanged',
|
|
this._bindA2dpStatusChangedHandler);
|
|
this._adapter.removeEventListener('scostatuschanged',
|
|
this._bindScoStatusChangedHandler);
|
|
|
|
// unbind adapter
|
|
this._adapter.removeEventListener('attributechanged',
|
|
this._bindBtAdapterHandler);
|
|
},
|
|
|
|
/**
|
|
* Update settings value and enable bluetooth hardware.
|
|
*
|
|
* @private
|
|
*/
|
|
_requestEnableHandler: function bt__requestEnableHandler() {
|
|
this.debug('enabling bluetooth');
|
|
if (this._adapter) {
|
|
// the state will be dispatched in _btAdapterHandler
|
|
this._adapter.enable().then(() => { //resolve
|
|
this.debug('bluetooth enabled');
|
|
}, () => { //reject
|
|
this.debug('can not get bluetooth adapter');
|
|
});
|
|
} else { // return current state since we can't process the request
|
|
this.debug('adapter is not available');
|
|
this._dispatchEnableState();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Update settings value and disable bluetooth hardware.
|
|
*
|
|
* @private
|
|
*/
|
|
_requestDisableHandler: function bt__requestDisableHandler() {
|
|
this.debug('disabling bluetooth');
|
|
if (this._adapter) {
|
|
// the state will be dispatched in _btAdapterHandler
|
|
this._adapter.disable().then(() => { //resolve
|
|
this.debug('bluetooth disabled');
|
|
}, () => { //reject
|
|
this.debug('can not get bluetooth adapter');
|
|
});
|
|
} else { // return current state since we can't process the request
|
|
this.debug('adapter is not available');
|
|
this._dispatchEnableState();
|
|
}
|
|
},
|
|
|
|
/*
|
|
* In file transfering case:
|
|
* since System Message can't be listened in two js files within a app,
|
|
* so we listen here but dispatch events to bluetooth_transfer.js
|
|
* while getting bluetooth file transfer start system messages
|
|
*
|
|
* @private
|
|
*/
|
|
_oppTransferStartHandler: function(transferInfo) {
|
|
this._setProfileConnected('opp', true);
|
|
window.dispatchEvent(new CustomEvent('bluetooth-opp-transfer-start',
|
|
{
|
|
detail: { transferInfo: transferInfo }
|
|
}
|
|
));
|
|
},
|
|
|
|
/*
|
|
* In file transfering case:
|
|
* since System Message can't be listened in two js files within a app,
|
|
* so we listen here but dispatch events to bluetooth_transfer.js
|
|
* while getting bluetooth file transfer complete system messages
|
|
*
|
|
* @private
|
|
*/
|
|
_oppTransferCompleteHandler: function(transferInfo) {
|
|
this._setProfileConnected('opp', false);
|
|
window.dispatchEvent(new CustomEvent('bluetooth-opp-transfer-complete',
|
|
{
|
|
detail: { transferInfo: transferInfo }
|
|
}
|
|
));
|
|
},
|
|
|
|
/**
|
|
* BT APIv2: Watch 'onattributechanged' event from
|
|
* mozBluetooth.defaultAdapter for updating state information.
|
|
*
|
|
* @private
|
|
* @param {Object} evt event object
|
|
*/
|
|
_btAdapterHandler:
|
|
function bt__btAdapterHandler(evt) {
|
|
for (var i in evt.attrs) {
|
|
switch (evt.attrs[i]) {
|
|
case 'state':
|
|
if (this._adapter.state === 'enabled') {
|
|
this.debug('state true');
|
|
this._isEnabled = true;
|
|
navigator.mozSettings.createLock()
|
|
.set({'bluetooth.enabled': true});
|
|
window.dispatchEvent(new CustomEvent('bluetooth-enabled'));
|
|
this.icon && this.icon.update();
|
|
} else if (this._adapter.state === 'disabled') {
|
|
this.debug('state false');
|
|
this._isEnabled = false;
|
|
navigator.mozSettings.createLock()
|
|
.set({'bluetooth.enabled': false});
|
|
window.dispatchEvent(new CustomEvent('bluetooth-disabled'));
|
|
this.icon && this.icon.update();
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Watch 'onattributechanged' event from mozBluetooth for updating default
|
|
* adapter information.
|
|
*
|
|
* 'onattributechanged' event description:
|
|
* A handler to trigger when bluetooth manager's only property
|
|
* defaultAdapter has changed.
|
|
*
|
|
* Here are three adapter change cases:
|
|
*
|
|
* null -> adapter: set new adapter and dispatch adapter state.
|
|
* adapter -> null: clear defaultAdapter and listener.
|
|
* adapterA -> adapterB: clean defaultAdapter and listener,
|
|
* then set new adapter and dispatch adapter state.
|
|
*
|
|
* @private
|
|
* @param {Object} evt event object
|
|
*/
|
|
_btManagerHandler:
|
|
function bt__btManagerHandler(evt) {
|
|
for (var i in evt.attrs) {
|
|
switch (evt.attrs[i]) {
|
|
// Default adapter attribute change.
|
|
// Usually, it means that we reach new default adapter.
|
|
case 'defaultAdapter':
|
|
this.debug('defaultAdapter changed.');
|
|
if (this._bluetooth.defaultAdapter) {
|
|
if (this._adapter !== this._bluetooth.defaultAdapter) {
|
|
if (this._adapter !== null) { // adapter A -> adapter B
|
|
this.debug('origin adapter is removed');
|
|
this._adapterUnavailableHandler();
|
|
this._adapter = null;
|
|
}
|
|
// set new adapter and dispatch adapter state
|
|
this.debug('new adapter is added');
|
|
this._adapter = this._bluetooth.defaultAdapter;
|
|
this._isEnabled = (this._adapter.state === 'enabled');
|
|
// listen states when the adapter is available
|
|
this._adapterAvailableHandler();
|
|
}
|
|
} else { // adapter -> null
|
|
this.debug('default adapter is removed');
|
|
this._adapterUnavailableHandler();
|
|
this._adapter = null;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Dispatch bluetooth enable/disable state to system.
|
|
*
|
|
* @private
|
|
*/
|
|
_dispatchEnableState: function bt__dispatchEnableState() {
|
|
if (this._isEnabled) {
|
|
window.dispatchEvent(new CustomEvent('bluetooth-enabled'));
|
|
} else {
|
|
window.dispatchEvent(new CustomEvent('bluetooth-disabled'));
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Get adapter from bluetooth through promise interface.
|
|
* XXX: the function abstract the Bluetooth API difference.
|
|
* We can remove it and use service query once BTv1 is deprecated.
|
|
*
|
|
* @public
|
|
* @return {Promise} A promise that resolve the Bluetooth Adapter
|
|
*/
|
|
adapter: function bt__adapter() {
|
|
return new Promise((resolve, reject) => {
|
|
if (this._adapter !== null) {
|
|
resolve(this._adapter);
|
|
} else {
|
|
this.debug('No BT adapter retrieved');
|
|
reject();
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Return device pairing result.
|
|
* XXX: the function abstract the Bluetooth API difference.
|
|
*
|
|
* @public
|
|
* @param {string} mac target device address
|
|
* @return {Promise} A promise that resolve when pair successfully,
|
|
* reject when pair failed
|
|
*/
|
|
pair: function bt__pair(mac) {
|
|
return this._adapter.pair(mac);
|
|
},
|
|
|
|
/**
|
|
* Return paired devices list.
|
|
* XXX: the function abstract the Bluetooth API difference.
|
|
* We can use service query to return list once BTv1 is deprecated.
|
|
*
|
|
* @public
|
|
* @returns {Object[]} sequence of BluetoothDevice
|
|
*/
|
|
getPairedDevices: function bt__getPairedDevices() {
|
|
return this._adapter.getPairedDevices();
|
|
},
|
|
|
|
/**
|
|
* Return bluetooth adapter.
|
|
*
|
|
* @public
|
|
*/
|
|
get getAdapter() {
|
|
return this._adapter;
|
|
},
|
|
|
|
/**
|
|
* Maintain bluetooth enable/disable state.
|
|
*
|
|
* @public
|
|
*/
|
|
get isEnabled() {
|
|
return this._isEnabled;
|
|
},
|
|
|
|
/**
|
|
* Check if bluetooth OPP profile is connected.
|
|
*
|
|
* @public
|
|
* @return {Boolean} connected state
|
|
*/
|
|
get isOPPProfileConnected() {
|
|
return this._isProfileConnected('opp');
|
|
},
|
|
|
|
/**
|
|
* Check if bluetooth A2DP profile is connected.
|
|
*
|
|
* @public
|
|
* @return {Boolean} connected state
|
|
*/
|
|
get isA2DPProfileConnected() {
|
|
return this._isProfileConnected('a2dp');
|
|
},
|
|
|
|
/**
|
|
* Check if bluetooth SCO profile is connected.
|
|
*
|
|
* @public
|
|
* @return {Boolean} connected state
|
|
*/
|
|
get isSCOProfileConnected() {
|
|
return this._isProfileConnected('sco');
|
|
},
|
|
|
|
/**
|
|
* Check if any of bluetooth profiles is connected.
|
|
* Referenced by Bluetooth icon update
|
|
*
|
|
* @public
|
|
* @return {Boolean} connected state
|
|
*/
|
|
get connected() {
|
|
return this._isProfileConnected('hfp') ||
|
|
this._isProfileConnected('a2dp') ||
|
|
this._isProfileConnected('opp');
|
|
},
|
|
|
|
/**
|
|
* Console log.
|
|
*
|
|
* @private
|
|
* @param {Object} msg debug message
|
|
*/
|
|
debug: function bt_debug(msg) {
|
|
if (!this.onDebug) {
|
|
return;
|
|
}
|
|
|
|
console.log('[System Bluetooth]: ' + msg);
|
|
}
|
|
};
|
|
|
|
exports.Bluetooth2 = Bluetooth;
|
|
})(window);
|