Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
13
gaia/apps/bluetooth/js/config/require.js
Executable file
13
gaia/apps/bluetooth/js/config/require.js
Executable file
|
|
@ -0,0 +1,13 @@
|
|||
require.config({
|
||||
baseUrl: '/js',
|
||||
paths: {
|
||||
'modules': 'modules',
|
||||
'views': 'views',
|
||||
'shared': '../shared/js'
|
||||
},
|
||||
shim: {
|
||||
'shared/async_storage': {
|
||||
exports: 'asyncStorage'
|
||||
}
|
||||
}
|
||||
});
|
||||
398
gaia/apps/bluetooth/js/deviceList.js
Executable file
398
gaia/apps/bluetooth/js/deviceList.js
Executable file
|
|
@ -0,0 +1,398 @@
|
|||
/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil -*- */
|
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
||||
|
||||
'use strict';
|
||||
|
||||
var gDeviceList = null;
|
||||
|
||||
// handle Bluetooth settings
|
||||
navigator.mozL10n.once(function deviceList() {
|
||||
var _ = navigator.mozL10n.get;
|
||||
var settings = window.navigator.mozSettings;
|
||||
var bluetooth = window.navigator.mozBluetooth;
|
||||
var defaultAdapter = null;
|
||||
var _debug = false;
|
||||
|
||||
if (!settings || !bluetooth) {
|
||||
return;
|
||||
}
|
||||
|
||||
function debug(msg) {
|
||||
if (!_debug) {
|
||||
return;
|
||||
}
|
||||
console.log('[Bluetooth APP Device List]: ' + msg);
|
||||
}
|
||||
|
||||
// device list
|
||||
gDeviceList = (function devicesList() {
|
||||
var deviceList = document.getElementById('devices-list-view');
|
||||
var bluetoothSearch = document.getElementById('bluetooth-search');
|
||||
var searchAgainBtn = document.getElementById('search-device');
|
||||
var searchingItem = document.getElementById('bluetooth-searching');
|
||||
var header = document.getElementById('devices-list-header');
|
||||
|
||||
var pairingAddress = null;
|
||||
var connectingAddress = null;
|
||||
var connectedAddress = null;
|
||||
// stop discover other device after 60 seconds
|
||||
var discoverTimeoutTime = 60000;
|
||||
var discoverTimeout = null;
|
||||
// Register handler from share activity
|
||||
var onDeviceSelectedHandler = null;
|
||||
var onExitBtnClickedHandler = null;
|
||||
var isSendingFile = false;
|
||||
|
||||
var pairList = {
|
||||
title: document.getElementById('bluetooth-paired-title'),
|
||||
list: document.getElementById('bluetooth-paired-devices'),
|
||||
index: [],
|
||||
clear: function emptyList() {
|
||||
while (this.list.hasChildNodes()) {
|
||||
this.list.removeChild(this.list.lastChild);
|
||||
}
|
||||
this.index = [];
|
||||
},
|
||||
show: function showArea(isShown) {
|
||||
if (!isShown) {
|
||||
this.clear();
|
||||
}
|
||||
this.title.hidden = !isShown;
|
||||
this.list.hidden = !isShown;
|
||||
}
|
||||
};
|
||||
|
||||
var openList = {
|
||||
title: document.getElementById('bluetooth-found-title'),
|
||||
list: document.getElementById('bluetooth-devices'),
|
||||
index: [],
|
||||
clear: function emptyList() {
|
||||
while (this.list.hasChildNodes()) {
|
||||
this.list.removeChild(this.list.lastChild);
|
||||
}
|
||||
this.index = [];
|
||||
},
|
||||
show: function showArea(isShown) {
|
||||
if (!isShown) {
|
||||
this.clear();
|
||||
}
|
||||
this.title.hidden = !isShown;
|
||||
this.list.hidden = !isShown;
|
||||
}
|
||||
};
|
||||
|
||||
searchAgainBtn.onclick = function searchAgainClicked() {
|
||||
if (isSendingFile || pairingAddress) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateDeviceList(true); // reset network list
|
||||
openList.clear();
|
||||
startDiscovery();
|
||||
};
|
||||
|
||||
// private DOM helper: create a device list item
|
||||
function newListItem(device, descL10nId) {
|
||||
var deviceName = document.createElement('span');
|
||||
var aName = (device.name === '') ? _('unnamed-device') : device.name;
|
||||
var aL10nId = (device.name === '') ? 'unnamed-device' : '';
|
||||
deviceName.textContent = aName;
|
||||
deviceName.dataset.l10nId = aL10nId;
|
||||
|
||||
var deviceDesc = document.createElement('small');
|
||||
deviceDesc.textContent = (descL10nId === '') ? '' : _(descL10nId);
|
||||
deviceDesc.dataset.l10nId = descL10nId;
|
||||
|
||||
var pairingProgress = document.createElement('progress');
|
||||
pairingProgress.classList.add('overlapping-icon');
|
||||
pairingProgress.classList.add('hidden');
|
||||
|
||||
var a = document.createElement('a');
|
||||
a.appendChild(deviceName);
|
||||
a.appendChild(deviceDesc);
|
||||
var li = document.createElement('li');
|
||||
li.classList.add('bluetooth-device');
|
||||
li.classList.add('bluetooth-type-' + device.icon);
|
||||
li.appendChild(a);
|
||||
li.appendChild(pairingProgress);
|
||||
|
||||
return li;
|
||||
}
|
||||
|
||||
// immediatly UI update, DOM element manipulation.
|
||||
function updateDeviceList(show) {
|
||||
bluetoothSearch.hidden = !show;
|
||||
if (show) {
|
||||
openList.show(true);
|
||||
searchingItem.hidden = false;
|
||||
} else {
|
||||
openList.show(false);
|
||||
pairList.show(false);
|
||||
searchingItem.hidden = true;
|
||||
pairingAddress = null;
|
||||
connectingAddress = null;
|
||||
connectedAddress = null;
|
||||
// clear discoverTimeout
|
||||
clearTimeout(discoverTimeout);
|
||||
discoverTimeout = null;
|
||||
}
|
||||
}
|
||||
|
||||
// do default actions (start discover avaliable devices)
|
||||
// when DefaultAdapter is ready.
|
||||
function initial(adapter, deviceSelectedCallback, exitBtnClickedCallback) {
|
||||
deviceList.hidden = false;
|
||||
header.addEventListener('action', cancelActivity);
|
||||
defaultAdapter = adapter;
|
||||
defaultAdapter.onpairedstatuschanged = function bt_getPairedMessage(evt) {
|
||||
showDevicePaired(evt.status, 'Authentication Failed', evt.address);
|
||||
};
|
||||
|
||||
defaultAdapter.ondiscoverystatechanged =
|
||||
function bt_discoveryStateChanged(evt) {
|
||||
if (!evt.discovering) {
|
||||
searchAgainBtn.disabled = false;
|
||||
searchingItem.hidden = true;
|
||||
|
||||
clearTimeout(discoverTimeout);
|
||||
discoverTimeout = null;
|
||||
} else {
|
||||
searchAgainBtn.disabled = true;
|
||||
searchingItem.hidden = false;
|
||||
}
|
||||
};
|
||||
|
||||
onDeviceSelectedHandler = deviceSelectedCallback;
|
||||
onExitBtnClickedHandler = exitBtnClickedCallback;
|
||||
|
||||
// get paired device and restore connection
|
||||
// if we have one device connected before.
|
||||
getPairedDevice();
|
||||
startDiscovery();
|
||||
}
|
||||
|
||||
function uninit() {
|
||||
deviceList.hidden = true;
|
||||
defaultAdapter = null;
|
||||
header.removeEventListener('action', cancelActivity);
|
||||
}
|
||||
|
||||
function getPairedDevice() {
|
||||
if (!bluetooth.enabled || !defaultAdapter) {
|
||||
return;
|
||||
}
|
||||
var req = defaultAdapter.getPairedDevices();
|
||||
req.onsuccess = function bt_getPairedSuccess() {
|
||||
// copy for sorting
|
||||
var paired = req.result.slice();
|
||||
var length = paired.length;
|
||||
if (length === 0) {
|
||||
pairList.show(false);
|
||||
return;
|
||||
}
|
||||
pairList.clear();
|
||||
paired.sort(function(a, b) {
|
||||
return a.name > b.name;
|
||||
});
|
||||
|
||||
function deviceStatus(){
|
||||
return (function(device) {
|
||||
var stateL10nId = (device.address === connectedAddress) ?
|
||||
'device-status-connected' : '';
|
||||
var aItem = newListItem(device, stateL10nId);
|
||||
aItem.onclick = function() {
|
||||
if (isSendingFile || pairingAddress) {
|
||||
return;
|
||||
}
|
||||
|
||||
stopDiscovery();
|
||||
|
||||
// show the description to be Connecting...
|
||||
// since we do connection and send file to the device
|
||||
var small = aItem.querySelector('small');
|
||||
small.textContent = _('device-status-connecting');
|
||||
small.dataset.l10nId = 'device-status-connecting';
|
||||
readyToSendFile(device);
|
||||
};
|
||||
pairList.list.appendChild(aItem);
|
||||
pairList.index[device.address] = [device, aItem];
|
||||
});
|
||||
}
|
||||
|
||||
for (var i = 0; i < length; i++) {
|
||||
deviceStatus()(paired[i]);
|
||||
}
|
||||
pairList.show(true);
|
||||
};
|
||||
}
|
||||
|
||||
// callback function when an avaliable device found
|
||||
function onDeviceFound(evt) {
|
||||
var device = evt.device;
|
||||
// Ignore duplicate and paired device. Update the name if needed.
|
||||
var existingDevice = openList.index[device.address] ||
|
||||
pairList.index[device.address];
|
||||
if (existingDevice) {
|
||||
var existingItem = existingDevice[1];
|
||||
if (device.name && existingItem) {
|
||||
var deviceName = existingItem.querySelector('a > span');
|
||||
if (deviceName) {
|
||||
deviceName.dataset.l10nId = '';
|
||||
deviceName.textContent = device.name;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var aItem = newListItem(device, 'device-status-tap-connect');
|
||||
|
||||
// bind paired callback
|
||||
aItem.onclick = function() {
|
||||
stopDiscovery();
|
||||
if (isSendingFile || pairingAddress) {
|
||||
return;
|
||||
}
|
||||
var small = aItem.querySelector('small');
|
||||
small.textContent = _('device-status-pairing');
|
||||
small.dataset.l10nId = 'device-status-pairing';
|
||||
// hide the device icon
|
||||
aItem.classList.add('icon-hidden');
|
||||
// show the pairing progress
|
||||
var progress = aItem.querySelector('progress');
|
||||
progress.classList.remove('hidden');
|
||||
|
||||
var req = defaultAdapter.pair(device.address);
|
||||
pairingAddress = device.address;
|
||||
var msg = 'pairing with address = ' + pairingAddress;
|
||||
debug(msg);
|
||||
req.onerror = function bt_pairError(error) {
|
||||
showDevicePaired(false, req.error.name);
|
||||
};
|
||||
};
|
||||
openList.list.appendChild(aItem);
|
||||
openList.index[device.address] = [device, aItem];
|
||||
}
|
||||
|
||||
function showDevicePaired(paired, errorMessage,
|
||||
addressFromOnpairedstatuschangedEvent) {
|
||||
// If we don't know the pairing device address,
|
||||
// it means the pair request is handled by interface level.
|
||||
// So we need to do:
|
||||
// 1. update paired list
|
||||
// 2. remove the paired device from found device list
|
||||
if (!pairingAddress) {
|
||||
getPairedDevice();
|
||||
// remove the paired device from found device list.
|
||||
if (addressFromOnpairedstatuschangedEvent && paired) {
|
||||
// If the device is on the list, remove it.
|
||||
// The situation is coming while Settings app pairing with the device.
|
||||
// And Bluetooth app is running in-line pairing in the background.
|
||||
if (openList.index[addressFromOnpairedstatuschangedEvent]) {
|
||||
var wantoRemovedItem =
|
||||
openList.index[addressFromOnpairedstatuschangedEvent][1];
|
||||
openList.list.removeChild(wantoRemovedItem);
|
||||
delete openList.index[addressFromOnpairedstatuschangedEvent];
|
||||
}
|
||||
}
|
||||
// Early return here since the event is not caused via Bluetooth app.
|
||||
// Skip over following UI updating flow.
|
||||
return;
|
||||
}
|
||||
// clear pairingAddress first to prevent execute
|
||||
// the same status update twice.
|
||||
var workingAddress = pairingAddress;
|
||||
pairingAddress = null;
|
||||
if (paired) {
|
||||
// if the device is on the list, remove it.
|
||||
// it will show on paired list later.
|
||||
if (openList.index[workingAddress]) {
|
||||
var device = openList.index[workingAddress][0];
|
||||
var item = openList.index[workingAddress][1];
|
||||
openList.list.removeChild(item);
|
||||
delete openList.index[workingAddress];
|
||||
connectingAddress = workingAddress;
|
||||
// Already paired with target device. Then, it's able to send files.
|
||||
readyToSendFile(device);
|
||||
}
|
||||
} else {
|
||||
// show pair process fail.
|
||||
var msg = _('error-pair-title');
|
||||
if (errorMessage === 'Repeated Attempts') {
|
||||
msg = msg + '\n' + _('error-pair-toofast');
|
||||
} else if (errorMessage === 'Authentication Failed') {
|
||||
msg = msg + '\n' + _('error-pair-pincode');
|
||||
}
|
||||
debug(msg);
|
||||
window.alert(msg);
|
||||
|
||||
// rollback device status
|
||||
if (openList.index[workingAddress]) {
|
||||
var small = openList.index[workingAddress][1].querySelector('small');
|
||||
small.textContent = _('device-status-tap-connect');
|
||||
small.dataset.l10nId = 'device-status-tap-connect';
|
||||
// show the device icon
|
||||
var li = openList.index[workingAddress][1];
|
||||
li.classList.remove('icon-hidden');
|
||||
// hide the pairing progress
|
||||
var progress =
|
||||
openList.index[workingAddress][1].querySelector('progress');
|
||||
progress.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
// acquire a new paired list no matter paired or unpaired
|
||||
getPairedDevice();
|
||||
}
|
||||
|
||||
function startDiscovery() {
|
||||
if (!bluetooth.enabled || !defaultAdapter || discoverTimeout) {
|
||||
return;
|
||||
}
|
||||
|
||||
var req = defaultAdapter.startDiscovery();
|
||||
req.onsuccess = function bt_discoveryStart() {
|
||||
if (!discoverTimeout) {
|
||||
discoverTimeout = setTimeout(stopDiscovery, discoverTimeoutTime);
|
||||
}
|
||||
};
|
||||
req.onerror = function bt_discoveryFailed() {
|
||||
console.error('Can not discover nearby device');
|
||||
};
|
||||
}
|
||||
|
||||
function stopDiscovery() {
|
||||
if (!bluetooth.enabled || !defaultAdapter || !discoverTimeout) {
|
||||
return;
|
||||
}
|
||||
|
||||
var req = defaultAdapter.stopDiscovery();
|
||||
req.onerror = function bt_discoveryStopFailed() {
|
||||
console.error('Failed to stop discovery of nearby devices');
|
||||
};
|
||||
|
||||
clearTimeout(discoverTimeout);
|
||||
discoverTimeout = null;
|
||||
}
|
||||
|
||||
function readyToSendFile(targetDevice) {
|
||||
isSendingFile = true;
|
||||
if (onDeviceSelectedHandler) {
|
||||
onDeviceSelectedHandler(targetDevice);
|
||||
}
|
||||
}
|
||||
|
||||
function cancelActivity() {
|
||||
if (onExitBtnClickedHandler) {
|
||||
onExitBtnClickedHandler();
|
||||
}
|
||||
}
|
||||
|
||||
// API
|
||||
return {
|
||||
update: updateDeviceList,
|
||||
initWithAdapter: initial,
|
||||
uninit: uninit,
|
||||
startDiscovery: startDiscovery,
|
||||
onDeviceFound: onDeviceFound
|
||||
};
|
||||
})();
|
||||
});
|
||||
111
gaia/apps/bluetooth/js/modules/bluetooth/bluetooth_adapter_manager.js
Executable file
111
gaia/apps/bluetooth/js/modules/bluetooth/bluetooth_adapter_manager.js
Executable file
|
|
@ -0,0 +1,111 @@
|
|||
/**
|
||||
* BluetoothAdapterManager:
|
||||
* - BluetoothAdapterManager is an Observable that wraps the platform
|
||||
* Bluetooth object for construct default adapter.
|
||||
* - It has only one observable properties: defaultAdapter
|
||||
* BluetoothAdapterManager only update state and does not involve in any UI
|
||||
* logic.
|
||||
*
|
||||
* @module BluetoothAdapterManager
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var NavigatorBluetooth = require('modules/navigator/mozBluetooth');
|
||||
var Observable = require('modules/mvvm/observable');
|
||||
|
||||
var _debug = false;
|
||||
var debug = function() {};
|
||||
if (_debug) {
|
||||
debug = function btam_debug(msg) {
|
||||
console.log('--> [BluetoothAdapterManager]: ' + msg);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @alias module:modules/bluetooth/BluetoothAdapterManager
|
||||
* @requires module:modules/navigator/mozBluetooth
|
||||
* @requires module:modules/mvvm/observable
|
||||
* @return {BluetoothAdapterManager}
|
||||
*/
|
||||
var BluetoothAdapterManager = {
|
||||
/**
|
||||
* The default adapter used to connect to the remote bluetooth devices.
|
||||
*
|
||||
* @readonly
|
||||
* @memberOf BluetoothAdapterManager
|
||||
* @type {Object}
|
||||
*/
|
||||
defaultAdapter: null,
|
||||
|
||||
/**
|
||||
* Init Bluetooth module.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothAdapterManager
|
||||
*/
|
||||
_init: function btam__init() {
|
||||
// Early return while there is no navigator.mozBluetooth module.
|
||||
if (!NavigatorBluetooth) {
|
||||
return;
|
||||
}
|
||||
|
||||
// watch BluetoothManager event
|
||||
this._watchMozBluetoothAttributechanged();
|
||||
|
||||
// init default adapter
|
||||
this._initDefaultAdapter();
|
||||
},
|
||||
|
||||
/**
|
||||
* Watch 'attributechanged' event from mozBluetooth for updating default
|
||||
* adapter information.
|
||||
*
|
||||
* 'attributechanged' event description:
|
||||
* A handler to trigger when bluetooth manager's only property
|
||||
* defaultAdapter has changed.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothAdapterManager
|
||||
*/
|
||||
_watchMozBluetoothAttributechanged:
|
||||
function btam__watchMozBluetoothAttributechanged() {
|
||||
NavigatorBluetooth.addEventListener('attributechanged', (evt) => {
|
||||
for (var i in evt.attrs) {
|
||||
switch (evt.attrs[i]) {
|
||||
case 'defaultAdapter':
|
||||
// Default adapter attribute change.
|
||||
// Usually, it means that we reach new default adapter.
|
||||
this.defaultAdapter = NavigatorBluetooth.defaultAdapter;
|
||||
debug('_watchMozBluetoothAttributechanged(): ' +
|
||||
'this.defaultAdapter = ' + this.defaultAdapter);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/* Reach default adapter from platform Bluetooth.
|
||||
*
|
||||
* If the property is accessed on platform in the first time, it will
|
||||
* trigger Gecko::Bluetooth to get default adapter from the chipset.
|
||||
* So the value would be null.
|
||||
* Then we will receive 'attributechanged' event later.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothAdapterManager
|
||||
*/
|
||||
_initDefaultAdapter: function btam__initDefaultAdapter() {
|
||||
this.defaultAdapter = NavigatorBluetooth.defaultAdapter;
|
||||
debug('_initDefaultAdapter(): this.defaultAdapter = ' +
|
||||
this.defaultAdapter);
|
||||
}
|
||||
};
|
||||
|
||||
// Create the observable object using the prototype.
|
||||
var bluetoothAdapterManager = Observable(BluetoothAdapterManager);
|
||||
bluetoothAdapterManager._init();
|
||||
return bluetoothAdapterManager;
|
||||
});
|
||||
198
gaia/apps/bluetooth/js/modules/bluetooth/bluetooth_cod_mapper.js
Executable file
198
gaia/apps/bluetooth/js/modules/bluetooth/bluetooth_cod_mapper.js
Executable file
|
|
@ -0,0 +1,198 @@
|
|||
/**
|
||||
* BluetoothClassOfDeviceMapper:
|
||||
* - BluetoothClassOfDeviceMapper is a mapper that helps settings/bluetooth
|
||||
* apps to decide the icon type. Since platform exposed the 'number' type
|
||||
* for class of device(Major Service Classes, Major Device Classes,
|
||||
* Minor Device Classes), the helper will map the input number to specific
|
||||
* device type. And the decision is referenced from Baseband.
|
||||
* (Assigned numbers for Baseband identifies the Inquiry Access codes
|
||||
* and Class of Device/Service (CoD) fields.)[3]
|
||||
* According to the specifications described, we define the device type in
|
||||
* Gaia side entirely. And the naming of device type we given is also
|
||||
* mapping to CSS style sheet.
|
||||
*
|
||||
* - [1] Bluetooth API v1, BluetoothDevice icon property:
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/BluetoothDevice.icon
|
||||
* - [2] Bluetooth API v2, BluetoothClassOfDevice object:
|
||||
* https://wiki.mozilla.org/B2G/Bluetooth/WebBluetooth-v2/
|
||||
* BluetoothClassOfDevice
|
||||
* - [3] Bluetooth Class of Device Reference:
|
||||
* https://www.bluetooth.org/en-us/specification/assigned-numbers/baseband
|
||||
* - We can update the mapper if the specifications Baseband is updated.
|
||||
*
|
||||
* @module BluetoothClassOfDeviceMapper
|
||||
*/
|
||||
define(function() {
|
||||
'use strict';
|
||||
|
||||
var _debug = false;
|
||||
var debug = function() {};
|
||||
if (_debug) {
|
||||
debug = function btcodm_debug(msg) {
|
||||
console.log('--> [BluetoothCODMapper]: ' + msg);
|
||||
};
|
||||
}
|
||||
|
||||
var MinorDeviceClasses = {
|
||||
Computer: {
|
||||
0: 'computer', // Uncategorized, code for device not assigned
|
||||
1: 'computer', // Desktop workstation
|
||||
2: 'computer', // Server-class computer
|
||||
3: 'computer', // Laptop
|
||||
4: 'pda', // Handheld PC/PDA (clamshell)
|
||||
5: 'pda', // Palm-size PC/PDA
|
||||
6: 'computer', // Wearable computer (watch size)
|
||||
7: 'computer', // Tablet
|
||||
reserved: 'computer' // All other values reserved. Default type
|
||||
// 'computer' given for reserved case.
|
||||
},
|
||||
|
||||
Phone: {
|
||||
0: 'phone', // Uncategorized, code for device not assigned
|
||||
1: 'phone', // Cellular
|
||||
2: 'phone', // Cordless
|
||||
3: 'phone', // Smartphone
|
||||
4: 'modem', // Wired modem or voice gateway
|
||||
5: 'phone', // Common ISDN access
|
||||
reserved: 'phone' // All other values reserved. Default type
|
||||
// 'phone' given for reserved case.
|
||||
},
|
||||
|
||||
AudioVideo: {
|
||||
0: 'audio-card', // Uncategorized, code not assigned
|
||||
1: 'audio-card', // Wearable Headset Device
|
||||
2: 'audio-card', // Hands-free Device
|
||||
4: 'audio-input-microphone', // Microphone
|
||||
5: 'audio-card', // Loudspeaker
|
||||
6: 'audio-card', // Headphones
|
||||
7: 'audio-card', // Portable Audio
|
||||
8: 'audio-card', // Car audio
|
||||
9: 'audio-card', // Set-top box
|
||||
10: 'audio-card', // HiFi Audio Device
|
||||
11: 'camera-video', // VCR
|
||||
12: 'camera-video', // Video Camera
|
||||
13: 'camera-video', // Camcorder
|
||||
14: 'video-display', // Video Monitor
|
||||
15: 'video-display', // Video Display and Loudspeaker
|
||||
16: 'video-display', // Video Conferencing
|
||||
18: 'audio-card', // Gameing/Toy
|
||||
reserved: 'audio-card' // All other values reserved. Default type
|
||||
// 'audio-card' given for reserved case.
|
||||
},
|
||||
|
||||
Peripheral: {
|
||||
1: 'input-gaming', // Joystick
|
||||
2: 'input-gaming', // Gamepad
|
||||
16: 'input-keyboard', // Keyboard
|
||||
17: 'input-keyboard', // Keyboard
|
||||
18: 'input-keyboard', // Keyboard
|
||||
19: 'input-keyboard', // Keyboard
|
||||
20: 'input-keyboard', // Keyboard
|
||||
21: 'input-keyboard', // Keyboard
|
||||
22: 'input-keyboard', // Keyboard
|
||||
23: 'input-keyboard', // Keyboard
|
||||
24: 'input-keyboard', // Keyboard
|
||||
25: 'input-keyboard', // Keyboard
|
||||
26: 'input-keyboard', // Keyboard
|
||||
27: 'input-keyboard', // Keyboard
|
||||
28: 'input-keyboard', // Keyboard
|
||||
29: 'input-keyboard', // Keyboard
|
||||
30: 'input-keyboard', // Keyboard
|
||||
31: 'input-keyboard', // Keyboard
|
||||
32: 'input-mouse', // Pointing device
|
||||
33: 'input-mouse', // Pointing device
|
||||
34: 'input-mouse', // Pointing device
|
||||
35: 'input-mouse', // Pointing device
|
||||
36: 'input-mouse', // Pointing device
|
||||
37: 'input-tablet', // Digitizer tablet
|
||||
38: 'input-mouse', // Pointing device
|
||||
39: 'input-mouse', // Pointing device
|
||||
40: 'input-mouse', // Pointing device
|
||||
41: 'input-mouse', // Pointing device
|
||||
42: 'input-mouse', // Pointing device
|
||||
43: 'input-mouse', // Pointing device
|
||||
44: 'input-mouse', // Pointing device
|
||||
45: 'input-mouse', // Pointing device
|
||||
46: 'input-mouse', // Pointing device
|
||||
47: 'input-mouse' // Pointing device
|
||||
},
|
||||
|
||||
Imaging: {
|
||||
4: 'video-display', // Display, bit: XXX1XX
|
||||
8: 'camera-photo', // Camera, bit: XX1XXX
|
||||
16: 'scanner', // Scanner, bit: X1XXXX
|
||||
32: 'printer' // Printer, bit: 1XXXXX
|
||||
}
|
||||
};
|
||||
|
||||
var MajorDeviceClasses = {
|
||||
1: MinorDeviceClasses.Computer,
|
||||
2: MinorDeviceClasses.Phone,
|
||||
3: 'network-wireless', // LAN/Network Access Point Major Class
|
||||
4: MinorDeviceClasses.AudioVideo,
|
||||
5: MinorDeviceClasses.Peripheral,
|
||||
6: MinorDeviceClasses.Imaging
|
||||
};
|
||||
|
||||
var BluetoothClassOfDeviceMapper = {
|
||||
/**
|
||||
* The string indicating which kind of icon could be used to represent
|
||||
* the device.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf BluetoothClassOfDeviceMapper
|
||||
* @param {BluetoothClassOfDevice} cod
|
||||
* @return {String}
|
||||
*/
|
||||
getDeviceType: function btcodm_getDeviceType(cod) {
|
||||
debug('cod.majorDeviceClass = ' + cod.majorDeviceClass);
|
||||
debug('cod.majorServiceClass = ' + cod.majorServiceClass);
|
||||
debug('cod.minorDeviceClass = ' + cod.minorDeviceClass);
|
||||
// Given an empty string to be default type.
|
||||
// Then, we won't show any icon for empty type.
|
||||
var deviceType = '';
|
||||
var majorDeviceClass = MajorDeviceClasses[cod.majorDeviceClass];
|
||||
debug('majorDeviceClass = ' + majorDeviceClass);
|
||||
if (typeof(majorDeviceClass) === 'object') {
|
||||
// drop in other Major Class
|
||||
deviceType = majorDeviceClass[cod.minorDeviceClass] ||
|
||||
majorDeviceClass.reserved || '';
|
||||
debug('return in minor class, type = ' + deviceType);
|
||||
return deviceType;
|
||||
} else if (typeof(majorDeviceClass) === 'string') {
|
||||
// drop in LAN/Network Access Point Major Class
|
||||
debug('return in major class, type = network-wireless');
|
||||
return majorDeviceClass;
|
||||
} else if (cod.majorServiceClass & 0x100) {
|
||||
// Not in any Major Device Class which is defined in Gaia.
|
||||
// Ex: Wearable, Toy, Health.
|
||||
|
||||
// Since there is no icon to display wearable, toy, health devices,
|
||||
// file a bug 1163479[2] to define them for specification needed.
|
||||
// But some of these devices probably service 'Audio' per Bluetooth
|
||||
// specification[1].
|
||||
// Property 'type' may be missed due to CoD of major class is TOY(0x08).
|
||||
// But we need to assign 'type' as 'audio-card' if service class
|
||||
// is 'Audio'. This is for PTS test case TC_AG_COD_BV_02_I.
|
||||
// As HFP specification defines that service class is 'Audio' can
|
||||
// be considered as HFP HF.
|
||||
// [1]: HFP_SPEC_V16.pdf: A device implementing the HF role of HFP shall
|
||||
// set the "Audio" bit in the Service Class field.
|
||||
// [2]: Bug 1163479 - [Gaia][Bluetooth] Device icon definition for
|
||||
// wearable, toy, health(Major Device Class) devices.
|
||||
// (https://bugzilla.mozilla.org/show_bug.cgi?id=1163479)
|
||||
|
||||
// Major Service Class: Audio(Speaker, Microphone, Headset service, ...)
|
||||
deviceType = 'audio-card';
|
||||
debug('return in major service class, type = audio-card');
|
||||
return deviceType;
|
||||
} else {
|
||||
// Not in any Class which is defined in Gaia.
|
||||
debug('not mapping in any class, return type = ' + deviceType);
|
||||
return deviceType;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return BluetoothClassOfDeviceMapper;
|
||||
});
|
||||
953
gaia/apps/bluetooth/js/modules/bluetooth/bluetooth_connection_manager.js
Executable file
953
gaia/apps/bluetooth/js/modules/bluetooth/bluetooth_connection_manager.js
Executable file
|
|
@ -0,0 +1,953 @@
|
|||
/**
|
||||
* BluetoothConnectionManager:
|
||||
* BluetoothConnectionManager only update state and does not involve in any UI
|
||||
* logic.
|
||||
*
|
||||
* @module BluetoothConnectionManager
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var AdapterManager = require('modules/bluetooth/bluetooth_adapter_manager');
|
||||
var AsyncStorage = require('shared/async_storage');
|
||||
|
||||
var _debug = false;
|
||||
var debug = function() {};
|
||||
if (_debug) {
|
||||
debug = function btam_debug(msg) {
|
||||
console.log('--> [BluetoothConnectionManager]: ' + msg);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @alias module:modules/bluetooth/BluetoothConnectionManager
|
||||
* @requires module:modules/bluetooth/bluetooth_adapter_manager
|
||||
* @requires module:shared/async_storage
|
||||
* @return {BluetoothConnectionManager}
|
||||
*/
|
||||
var BluetoothConnectionManager = {
|
||||
/**
|
||||
* The profiles of connected device that we are defined here.
|
||||
*
|
||||
* @public
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @type {Object}
|
||||
*/
|
||||
Profiles: {
|
||||
'hfp': 0x111E, // Handsfree
|
||||
'a2dp': 0x110D // Advanced Audio Distribution Devices
|
||||
},
|
||||
|
||||
/**
|
||||
* The address of device that we are trying to connect.
|
||||
*
|
||||
* @public
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @type {String}
|
||||
*/
|
||||
connectingAddress: null,
|
||||
|
||||
/**
|
||||
* A object that we cache the connected devices information(address, device,
|
||||
* connectedProfiles). It will be inited while default adapter is ready.
|
||||
* And these information is coming from profile events.
|
||||
* Each connected device is hashed by device address.
|
||||
*
|
||||
* EX:
|
||||
* _connectedDevicesInfo = {
|
||||
* 'AA:BB:CC:00:11:22': {
|
||||
* 'device': DeviceObject,
|
||||
* 'connectedProfiles': {
|
||||
* 'hfp': true,
|
||||
* 'a2dp': false
|
||||
* }
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* @private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @type {Object}
|
||||
*/
|
||||
_connectedDevicesInfo: {},
|
||||
|
||||
/**
|
||||
* An instance to maintain that we have created a promise to get connected
|
||||
* devices.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @return {Promise}
|
||||
*/
|
||||
_getConnectedDevicesPromise: null,
|
||||
|
||||
/**
|
||||
* The object maintains listeners' callback per property name.
|
||||
* Each listener would be called as following definition.
|
||||
* 'connecting' - be called when device is connecting.
|
||||
* 'connected': - be called when device is connected.
|
||||
* 'disconnected': - be called when device is disconnected.
|
||||
* 'profileChanged': - be called when profile is changed.
|
||||
*
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @type {Object}
|
||||
*/
|
||||
_listeners: {
|
||||
'connecting': [],
|
||||
'connected': [],
|
||||
'disconnected': [],
|
||||
'profileChanged': []
|
||||
},
|
||||
|
||||
/**
|
||||
* Default adapter of Bluetooth.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @type {Object} BluetoothAdapter
|
||||
*/
|
||||
_defaultAdapter: null,
|
||||
|
||||
/**
|
||||
* Init BluetoothConnectionManager module.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
*/
|
||||
_init: function btcm__init() {
|
||||
// Observe 'defaultAdapter' property for reaching default adapter.
|
||||
AdapterManager.observe('defaultAdapter',
|
||||
this._onDefaultAdapterChanged.bind(this));
|
||||
this._onDefaultAdapterChanged(AdapterManager.defaultAdapter);
|
||||
},
|
||||
|
||||
/**
|
||||
* 'defaultAdapter' change event handler from adapter manager for
|
||||
* updating adapter immediately.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {Object} BluetoothAdapter newAdapter
|
||||
* @param {Object} BluetoothAdapter oldAdapter
|
||||
*/
|
||||
_onDefaultAdapterChanged:
|
||||
function btcm__onDefaultAdapterChanged(newAdapter, oldAdapter) {
|
||||
debug('_onDefaultAdapterChanged(): newAdapter = ' + newAdapter);
|
||||
debug('_onDefaultAdapterChanged(): oldAdapter = ' + oldAdapter);
|
||||
|
||||
// save default adapter
|
||||
this._defaultAdapter = newAdapter;
|
||||
|
||||
if (oldAdapter) {
|
||||
// unwatch event since the old adapter is no longer usefull
|
||||
this._unwatchProfilesStatuschanged(oldAdapter);
|
||||
this._unwatchDefaultAdapterOnattributechanged(oldAdapter);
|
||||
}
|
||||
|
||||
if (newAdapter) {
|
||||
// watch event since the new adapter is ready to access
|
||||
this._watchProfilesStatuschanged(newAdapter);
|
||||
this._watchDefaultAdapterOnattributechanged(newAdapter);
|
||||
// restore connection
|
||||
if (newAdapter.state === 'enabled') {
|
||||
this._restoreConnection();
|
||||
}
|
||||
} else {
|
||||
// reset properties only
|
||||
this._resetConnectionInfo();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the cache of connected devices in ConnectionManager.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @return {Promise}
|
||||
*/
|
||||
getConnectedDevices: function btcm_getConnectedDevices() {
|
||||
if (!this._getConnectedDevicesPromise) {
|
||||
this._getConnectedDevicesPromise =
|
||||
this._initConnectedDevicesInfo().then(() => {
|
||||
debug('getConnectedDevices(): resolved with latest cache = ' +
|
||||
JSON.stringify(this._connectedDevicesInfo));
|
||||
return this._connectedDevicesInfo;
|
||||
}, (reason) => {
|
||||
debug('getConnectedDevices(): rejected with reason = ' + reason);
|
||||
this._getConnectedDevicesPromise = null;
|
||||
});
|
||||
}
|
||||
return this._getConnectedDevicesPromise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Init cache of connected device and save it in cache.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @return {Promise}
|
||||
*/
|
||||
_initConnectedDevicesInfo: function btcm__initConnectedDevicesInfo() {
|
||||
if (!this._defaultAdapter) {
|
||||
return Promise.reject('default adapter is not existed!!');
|
||||
}
|
||||
// Init connection status and profile from platform.
|
||||
// Then, save these connected device information in cache.
|
||||
return this._getConnectedDevicesFromPlatform().then(
|
||||
(connectedDevicesByProfile) => {
|
||||
this._constructDeviceItemsMap(connectedDevicesByProfile);
|
||||
}, (reason) => {
|
||||
debug('_initConnectedDevicesInfo(): rejected in ' +
|
||||
'_getConnectedDevicesFromPlatform');
|
||||
return Promise.reject(reason);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* The method will update each device item in maintaining map.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {Object} connectedDevices
|
||||
*/
|
||||
_constructDeviceItemsMap:
|
||||
function btcm__constructDeviceItemsMap(connectedDevices) {
|
||||
debug('_constructDeviceItemsMap(): connectedDevices = ' +
|
||||
JSON.stringify(connectedDevices));
|
||||
if (!connectedDevices) {
|
||||
// Return empty object while there is no any connected devices.
|
||||
debug('_constructDeviceItemsMap(): early return with empty object');
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(this.Profiles).map((profileID) => {
|
||||
connectedDevices[profileID].forEach((connectedDevice) => {
|
||||
var connectionDeviceInfo = {
|
||||
address: connectedDevice.address,
|
||||
connected: true,
|
||||
profileID: profileID,
|
||||
device: connectedDevice
|
||||
};
|
||||
debug('_constructDeviceItemsMap(): connectionDeviceInfo = ' +
|
||||
JSON.stringify(connectionDeviceInfo));
|
||||
this._initConnectedDevicesCache(connectionDeviceInfo);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Init the cache which is maintained for connection devices.
|
||||
* And the input is gotten from platform API adapter.getConnectedDevices().
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {Object} options
|
||||
* @param {String} options.address - address of the device
|
||||
* @param {Boolean} options.connected - is connected or not
|
||||
* @param {Object} options.profileID - profile ID of the connection type
|
||||
* @param {Object} options.device - connect device, Bluetooth Object
|
||||
*/
|
||||
_initConnectedDevicesCache:
|
||||
function btcm__initConnectedDevicesCache(options) {
|
||||
debug('_initConnectedDevicesCache(): options = ' +
|
||||
JSON.stringify(options));
|
||||
// hash by device address
|
||||
var info = this._connectedDevicesInfo[options.address];
|
||||
if (info) {
|
||||
// Already have profiles, update it for other profile.
|
||||
info.connectedProfiles[options.profileID] = options.connected;
|
||||
} else {
|
||||
// Not have profiles yet, create for it.
|
||||
// If options.device is existed, save the connected device.
|
||||
// Otherwise, given null in this property.
|
||||
var connectedDevice = (options.device) ? options.device : null;
|
||||
info = {
|
||||
'device': connectedDevice,
|
||||
'connectedProfiles': {}
|
||||
};
|
||||
info.connectedProfiles[options.profileID] = options.connected;
|
||||
}
|
||||
// Save the device/profile in map.
|
||||
this._connectedDevicesInfo[options.address] = info;
|
||||
|
||||
// If there is no profile connected,
|
||||
// remove the device item from cache since it is already disconnected.
|
||||
var dataToCheckConnectedProfile = {
|
||||
address: options.address,
|
||||
connectedDevices: this._connectedDevicesInfo
|
||||
};
|
||||
if (!this._hasConnectedProfileByAddress(dataToCheckConnectedProfile)) {
|
||||
delete this._connectedDevicesInfo[options.address];
|
||||
}
|
||||
// Return the latest cache which is just updated here.
|
||||
debug('_initConnectedDevicesCache(): this._connectedDevicesInfo = ' +
|
||||
JSON.stringify(this._connectedDevicesInfo));
|
||||
return this._connectedDevicesInfo;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the cache which is maintained for connection devices.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {Object} options
|
||||
* @param {String} options.address - address of the device
|
||||
* @param {Boolean} options.connected - is connected or not
|
||||
* @param {Object} options.profileID - profile ID of the connection type
|
||||
* @param {Object} options.device - connect device, Bluetooth Object
|
||||
*/
|
||||
_updateConnectedDevices:
|
||||
function btcm__updateConnectedDevices(options) {
|
||||
return this.getConnectedDevices().then((connectedDevicesInfo) => {
|
||||
debug('_updateConnectedDevices(): connectedDevicesInfo = ' +
|
||||
JSON.stringify(connectedDevicesInfo));
|
||||
|
||||
// hash by device address
|
||||
var info =
|
||||
(connectedDevicesInfo) ? connectedDevicesInfo[options.address] : null;
|
||||
if (info) {
|
||||
// Already have profiles, update it for other profile.
|
||||
info.connectedProfiles[options.profileID] = options.connected;
|
||||
} else {
|
||||
// Not have profiles yet, create for it.
|
||||
// If options.device is existed, save the connected device.
|
||||
// Otherwise, given null in this property.
|
||||
var connectedDevice = (options.device) ? options.device : null;
|
||||
info = {
|
||||
'device': connectedDevice,
|
||||
'connectedProfiles': {}
|
||||
};
|
||||
info.connectedProfiles[options.profileID] = options.connected;
|
||||
}
|
||||
// Save the device/profile in map.
|
||||
this._connectedDevicesInfo[options.address] = info;
|
||||
|
||||
// If there is no profile connected,
|
||||
// remove the device item from cache since it is already disconnected.
|
||||
var dataToCheckConnectedProfile = {
|
||||
address: options.address,
|
||||
connectedDevices: this._connectedDevicesInfo
|
||||
};
|
||||
if (!this._hasConnectedProfileByAddress(dataToCheckConnectedProfile)) {
|
||||
delete this._connectedDevicesInfo[options.address];
|
||||
}
|
||||
// Return the latest cache which is just updated here.
|
||||
debug('_updateConnectedDevices(): this._connectedDevicesInfo = ' +
|
||||
JSON.stringify(this._connectedDevicesInfo));
|
||||
return Promise.resolve(this._connectedDevicesInfo);
|
||||
}, () => {
|
||||
debug('_updateConnectedDevices(): rejected with some exception');
|
||||
return Promise.reject('rejected with some exception');
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Only reset properties since there is no available default adapter.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
*/
|
||||
_resetConnectionInfo: function btcm__resetConnectionInfo() {
|
||||
// Reset connection status.
|
||||
this.connectingAddress = null;
|
||||
// Clean up the instance to get connected devices
|
||||
// while new adapter is ready.
|
||||
this._getConnectedDevicesPromise = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Watch 'onattributechanged' event from default adapter for watching
|
||||
* adapter enabled/disabled status.
|
||||
*
|
||||
* Description of 'onattributechanged' event:
|
||||
* A handler to trigger when one of the local bluetooth adapter's properties
|
||||
* has changed. Note access to the changed property in this event handler
|
||||
* would get the updated value.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {Object} BluetoothAdapter adapter
|
||||
*/
|
||||
_watchDefaultAdapterOnattributechanged:
|
||||
function btcm__watchDefaultAdapterOnattributechanged(adapter) {
|
||||
adapter.addEventListener('attributechanged',
|
||||
this._onAdapterAttributeChanged.bind(this, adapter));
|
||||
},
|
||||
|
||||
/**
|
||||
* Unwatch 'onattributechanged' event from default adapter since adapter is
|
||||
* removed.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {Object} BluetoothAdapter adapter
|
||||
*/
|
||||
_unwatchDefaultAdapterOnattributechanged:
|
||||
function btcm__unwatchDefaultAdapterOnattributechanged(adapter) {
|
||||
adapter.removeEventListener('attributechanged',
|
||||
this._onAdapterAttributeChanged);
|
||||
},
|
||||
|
||||
/**
|
||||
* 'onattributechanged' event handler from default adapter for reaching
|
||||
* adapter enabled/disabled status.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {Object} BluetoothAdapter adapter
|
||||
* @param {event} evt
|
||||
*/
|
||||
_onAdapterAttributeChanged:
|
||||
function btcm__onAdapterAttributeChanged(adapter, evt) {
|
||||
for (var i in evt.attrs) {
|
||||
debug('_onAdapterAttributeChanged(): ' + evt.attrs[i]);
|
||||
switch (evt.attrs[i]) {
|
||||
case 'state':
|
||||
if (adapter.state === 'enabled') {
|
||||
// Restore connection while default adapter state is enabled.
|
||||
this._restoreConnection();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Watch every of profile events('onhfpstatuschanged','ona2dpstatuschanged')
|
||||
* from default adapter for updating device connected status immediately.
|
||||
*
|
||||
* Description of 'onhfpstatuschanged' event:
|
||||
* Specifies an event listener to receive hfpstatuschanged events.
|
||||
* Those events occur when an HFP connection status changes.
|
||||
*
|
||||
* Description of 'ona2dpstatuschanged' event:
|
||||
* Specifies an event listener to receive a2dpstatuschanged events.
|
||||
* Those events occur when an A2DP connection status changes.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {Object} BluetoothAdapter adapter
|
||||
*/
|
||||
_watchProfilesStatuschanged:
|
||||
function btcm__watchProfilesStatuschanged(adapter) {
|
||||
var eventName;
|
||||
for (var profileID in this.Profiles) {
|
||||
eventName = 'on' + profileID + 'statuschanged';
|
||||
adapter[eventName] =
|
||||
this._onProfileStatuschangeHandler.bind(this, profileID);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Unwatch every of profile events('onhfpstatuschanged',
|
||||
* 'ona2dpstatuschanged') from default adapter since adapter is removed.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {Object} BluetoothAdapter adapter
|
||||
*/
|
||||
_unwatchProfilesStatuschanged:
|
||||
function btc__unwatchProfilesStatuschanged(adapter) {
|
||||
var eventName;
|
||||
for (var profileID in this.Profiles) {
|
||||
eventName = 'on' + profileID + 'statuschanged';
|
||||
adapter[eventName] = null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 'onhfpstatuschanged', 'ona2dpstatuschanged' events handler from
|
||||
* default adapter for updating device connected status.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {String} profileID
|
||||
* @param {event} evt
|
||||
*/
|
||||
_onProfileStatuschangeHandler:
|
||||
function btcm__onProfileStatuschangeHandler(profileID, evt) {
|
||||
debug('_onProfileStatuschangeHandler(): profileID = ' + profileID +
|
||||
', evt.address = ' + evt.address + ', evt.status = ' + evt.status);
|
||||
var options = {
|
||||
address: evt.address,
|
||||
connected: evt.status,
|
||||
profileID: profileID
|
||||
};
|
||||
// Update connection status.
|
||||
this._updateConnectionStatus(options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Query async storage to restore connection.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
*/
|
||||
_restoreConnection: function btcm__restoreConnection() {
|
||||
if (!this._defaultAdapter) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reconnect the one kept in the async storage.
|
||||
AsyncStorage.getItem('device.connected', (address) => {
|
||||
if (!address) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the restore device is already connected or not.
|
||||
this.getConnectedDevices().then((connectedDevices) => {
|
||||
if (connectedDevices && connectedDevices[address]) {
|
||||
// Do an early return since the restore device is connected already.
|
||||
debug('_restoreConnection(): early return cause connected already');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the device which is wanted to connect from paired devices.
|
||||
var restoreDevice = this._getPairedDeviceByAddress(address);
|
||||
if (restoreDevice) {
|
||||
this.connectingAddress = restoreDevice.address;
|
||||
// Fire 'connecting' event.
|
||||
var event = {
|
||||
type: 'connecting',
|
||||
detail: {
|
||||
address: restoreDevice.address
|
||||
}
|
||||
};
|
||||
this._emitEvent(event);
|
||||
this._connect(restoreDevice).then(() => {
|
||||
debug('_restoreConnection(): restore connection successfully');
|
||||
}, (reason) => {
|
||||
debug('_restoreConnection(): restore connection failed, ' +
|
||||
'reason = ' + reason);
|
||||
// No available profiles are connected. Reset connecting address.
|
||||
this.connectingAddress = null;
|
||||
// Then, fire 'disconnected' event.
|
||||
event = {
|
||||
type: 'disconnected',
|
||||
detail: {
|
||||
address: restoreDevice.address
|
||||
}
|
||||
};
|
||||
this._emitEvent(event);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Record connected device so if Bluetooth is turned off and then on
|
||||
* we can restore the connection.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {String} action - to set or remove item for recording connection
|
||||
* @param {String} address - the address of connected device
|
||||
*/
|
||||
_recordConnection: function btcm__recordConnection(action, address) {
|
||||
if (action === 'set') {
|
||||
// record connected device so if Bluetooth is turned off and then on
|
||||
// we can restore the connection
|
||||
AsyncStorage.setItem('device.connected', address);
|
||||
debug('_recordConnection(): set item');
|
||||
} else if ((action === 'remove') &&
|
||||
(this._defaultAdapter.state === 'enabled')) {
|
||||
// Clean up the connected device from async storage
|
||||
// which is recorded before.
|
||||
// Only remove the record while Bluetooth state is enabled.
|
||||
// Because the request also comes while Bluetooth is turned off.
|
||||
AsyncStorage.removeItem('device.connected');
|
||||
debug('_recordConnection(): remove item');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update connection status.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {Object} options
|
||||
* @param {String} options.address - address of the device
|
||||
* @param {Boolean} options.connected - is connected or not
|
||||
* @param {Object} options.profileID - profile ID of the connection type
|
||||
*/
|
||||
_updateConnectionStatus: function btcm___updateConnectionStatus(options) {
|
||||
debug('_updateConnectionStatus(): address = ' + options.address +
|
||||
', connected = ' + options.connected +
|
||||
', profileID = ' + options.profileID);
|
||||
this.connectingAddress = null;
|
||||
|
||||
// Update the profile in the cache of connected device info.
|
||||
this._updateConnectedDevices(options).then((connectedDevicesInfo) => {
|
||||
debug('_updateConnectionStatus(): _updateConnectedDevices() ' +
|
||||
'resolved with connectedDevicesInfo = ' +
|
||||
JSON.stringify(connectedDevicesInfo));
|
||||
// Prepare latest data to check connected profile.
|
||||
var dataToCheckConnectedProfile = {
|
||||
address: options.address,
|
||||
connectedDevices: connectedDevicesInfo
|
||||
};
|
||||
// Fire 'connected'/'disconnected' event according to
|
||||
// the connection profile. Then, record connection.
|
||||
var event;
|
||||
if (options.connected) {
|
||||
// Fire 'connected' event.
|
||||
event = {
|
||||
type: 'connected',
|
||||
detail: {
|
||||
address: options.address
|
||||
}
|
||||
};
|
||||
this._emitEvent(event);
|
||||
// Record connection.
|
||||
this._recordConnection('set', options.address);
|
||||
} else {
|
||||
// If there is no profile connected,
|
||||
// we have to remove the record connection.
|
||||
// And fire 'disconnected' event for outer modules.
|
||||
if (!this._hasConnectedProfileByAddress(
|
||||
dataToCheckConnectedProfile)) {
|
||||
// Record connection. Only remove the record while Bluetooth state
|
||||
// is enabled. Because the event also comes while Bluetooth is
|
||||
// turned off.
|
||||
if (this._defaultAdapter.state === 'enabled') {
|
||||
this._recordConnection('remove');
|
||||
}
|
||||
// Fire 'disconnected' event.
|
||||
event = {
|
||||
type: 'disconnected',
|
||||
detail: {
|
||||
address: options.address
|
||||
}
|
||||
};
|
||||
this._emitEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
// Fire 'profileChanged' event.
|
||||
var newProfiles;
|
||||
if (!this._hasConnectedProfileByAddress(dataToCheckConnectedProfile)) {
|
||||
newProfiles = null;
|
||||
} else {
|
||||
newProfiles = connectedDevicesInfo[options.address].connectedProfiles;
|
||||
}
|
||||
event = {
|
||||
type: 'profileChanged',
|
||||
detail: {
|
||||
address: options.address,
|
||||
profiles: newProfiles
|
||||
}
|
||||
};
|
||||
this._emitEvent(event);
|
||||
}, (reason) => {
|
||||
debug('_updateConnectionStatus(): miss to update in rejected case, ' +
|
||||
'reason = ' + reason);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* It provides a convenient function for panel to connect with a device.
|
||||
* And the panel no need to care about connected device currently.
|
||||
* The method will disconnect current connected device first.
|
||||
* Then, it will start connecting a paired device with the device's adapter.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {BluetoothDevice} device
|
||||
* @return {Promise}
|
||||
*/
|
||||
connect: function btcm_connect(device) {
|
||||
if (!this._defaultAdapter) {
|
||||
return Promise.reject('default adapter is not existed!!');
|
||||
}
|
||||
|
||||
// Disconnect current connected device first.
|
||||
debug('connect(): Want to connect device address = ' + device.address +
|
||||
', name = ' + device.name);
|
||||
|
||||
var connectedDevices = [];
|
||||
return this.getConnectedDevices().then((connectedDevicesInfo) => {
|
||||
for (var address in connectedDevicesInfo) {
|
||||
if (connectedDevicesInfo[address].device !== null) {
|
||||
connectedDevices.push(connectedDevicesInfo[address].device);
|
||||
debug('connect(): push device cache in queue = ' +
|
||||
JSON.stringify(connectedDevices));
|
||||
} else {
|
||||
var regetPairedDevice = this._getPairedDeviceByAddress(address);
|
||||
connectedDevices.push(regetPairedDevice);
|
||||
debug('connect(): push _getPairedDeviceByAddress in queue = ' +
|
||||
JSON.stringify(regetPairedDevice));
|
||||
}
|
||||
}
|
||||
|
||||
debug('connect(): Will disconnect these connected devices = ' +
|
||||
JSON.stringify(connectedDevices));
|
||||
|
||||
// Disconnect these connected device before
|
||||
// service to connect with new device.
|
||||
return Promise.all(connectedDevices.map((connectedDevice) => {
|
||||
return this.disconnect(connectedDevice);
|
||||
})).then(() => {
|
||||
// All connected devices is disconnected.
|
||||
// We can start to connect the new request.
|
||||
debug('connect(): Start to connect with wanted device address = ' +
|
||||
device.address);
|
||||
return this._connect(device).then(() => {
|
||||
debug('connect(): Resolved');
|
||||
}, (reason) => {
|
||||
debug('connect(): reason = ' + reason);
|
||||
return Promise.reject(reason);
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* The method will connect the input device with the device's adapter.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {BluetoothDevice} device
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_connect: function btcm__connect(device) {
|
||||
if (!this._defaultAdapter) {
|
||||
return Promise.reject('default adapter is not existed!!');
|
||||
}
|
||||
|
||||
// Save the connecting address
|
||||
this.connectingAddress = device.address;
|
||||
// Fire 'connecting' event.
|
||||
var event = {
|
||||
type: 'connecting',
|
||||
detail: {
|
||||
address: device.address
|
||||
}
|
||||
};
|
||||
this._emitEvent(event);
|
||||
debug('_connect(): before start to connect, stop discovery first');
|
||||
|
||||
// Note on Bluedroid stack, discovery has to be stopped before connect
|
||||
// (i.e., call stopDiscovery() before connect())
|
||||
// otherwise stack callbacks with connect failure.
|
||||
return this._defaultAdapter.stopDiscovery().then(() => {
|
||||
debug('_connect(): start connecting device, ' +
|
||||
'address = ' + device.address);
|
||||
return this._defaultAdapter.connect(device).then(() => {
|
||||
debug('_connect(): resolve, already connected with address = ' +
|
||||
device.address);
|
||||
}, () => {
|
||||
// No available profiles are connected. Reset connecting address.
|
||||
this.connectingAddress = null;
|
||||
// Fire 'disconnected' event.
|
||||
event = {
|
||||
type: 'disconnected',
|
||||
detail: {
|
||||
address: device.address
|
||||
}
|
||||
};
|
||||
this._emitEvent(event);
|
||||
debug('_connect(): reject with connection failed');
|
||||
return Promise.reject('connection failed');
|
||||
});
|
||||
}, () => {
|
||||
// Cannot connect with the device since stopDiscovery failed.
|
||||
this.connectingAddress = null;
|
||||
// Fire 'disconnected' event.
|
||||
event = {
|
||||
type: 'disconnected',
|
||||
detail: {
|
||||
address: device.address
|
||||
}
|
||||
};
|
||||
this._emitEvent(event);
|
||||
debug('_connect(): reject with stop discovery failed');
|
||||
return Promise.reject('stop discovery failed');
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* The method will disconnect the input device with the device's adapter.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {BluetoothDevice} device
|
||||
* @returns {Promise}
|
||||
*/
|
||||
disconnect: function btcm_disconnect(device) {
|
||||
if (!this._defaultAdapter) {
|
||||
return Promise.reject('default adapter is not existed!!');
|
||||
}
|
||||
|
||||
return this._defaultAdapter.disconnect(device).then(() => {
|
||||
debug('disconnect(): onsuccess(): resolve');
|
||||
}, () => {
|
||||
debug('disconnect(): onerror(): reject with disconnect failed');
|
||||
return Promise.reject('disconnect failed');
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* The method will get all connected devices profiles
|
||||
* which we are interested in. Profile: HFP, A2DP.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @returns {Promise} resolve: connectedDevices
|
||||
* @returns {Promise} reject: reason
|
||||
*/
|
||||
_getConnectedDevicesFromPlatform:
|
||||
function btcm__getConnectedDevicesFromPlatform() {
|
||||
// Get connected device via profiles HFP, A2DP
|
||||
return Promise.all(Object.keys(this.Profiles).map((profile) => {
|
||||
return this._getConnectedDevicesByProfile(this.Profiles[profile]);
|
||||
})).then((connectedDevices) => {
|
||||
// Update each connected devices in map.
|
||||
var collectedConnectedDevicesByProfile = {};
|
||||
Object.keys(this.Profiles).forEach((profile, index) => {
|
||||
collectedConnectedDevicesByProfile[profile] = connectedDevices[index];
|
||||
});
|
||||
debug('_getConnectedDevicesFromPlatform(): ' +
|
||||
'collectedConnectedDevicesByProfile = ' +
|
||||
JSON.stringify(collectedConnectedDevicesByProfile));
|
||||
return Promise.resolve(collectedConnectedDevicesByProfile);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* The method will get connected device by inputed profile.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {String} profileID
|
||||
* @returns {Promise} resolve: the connected devices in array
|
||||
* @returns {Promise} reject: reason
|
||||
*/
|
||||
_getConnectedDevicesByProfile:
|
||||
function btcm__getConnectedDevicesByProfile(profileID) {
|
||||
if (!this._defaultAdapter) {
|
||||
debug('_getConnectedDevicesByProfile(): reject with no adapter');
|
||||
return Promise.reject('default adapter is not existed!!');
|
||||
}
|
||||
|
||||
if (this._defaultAdapter.state === 'disabled') {
|
||||
debug('_getConnectedDevicesByProfile(): resolve with empty array ' +
|
||||
'since it is impossible to connect with any device');
|
||||
return Promise.reject('getConnectedDevices in disabled state');
|
||||
}
|
||||
|
||||
return this._defaultAdapter.getConnectedDevices(profileID).then(
|
||||
(connectedDevice) => {
|
||||
debug('_getConnectedDevicesByProfile(): resolved with ' +
|
||||
'connectedDevice = ' + JSON.stringify(connectedDevice));
|
||||
return Promise.resolve(connectedDevice || []);
|
||||
}, (reason) => {
|
||||
debug('_getConnectedDevicesByProfile(): rejected with ' +
|
||||
'reason = ' + reason);
|
||||
return Promise.reject(reason);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get device from paired devices by address.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {String} address
|
||||
*/
|
||||
_getPairedDeviceByAddress:
|
||||
function btcm__getPairedDeviceByAddress(address) {
|
||||
if (!this._defaultAdapter) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var pairedDevices = this._defaultAdapter.getPairedDevices();
|
||||
if (pairedDevices.length === 0) {
|
||||
return null;
|
||||
} else {
|
||||
for (var i in pairedDevices) {
|
||||
if (pairedDevices[i].address === address) {
|
||||
return pairedDevices[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Find out there is any profile still connected.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {Object} options
|
||||
* @param {String} options.connectedDevices - connected devices info
|
||||
* @param {String} options.address - the address of device
|
||||
*/
|
||||
_hasConnectedProfileByAddress:
|
||||
function btcm__hasConnectedProfileByAddress(options) {
|
||||
if (!options.connectedDevices[options.address]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var hasConnectedProfile = false;
|
||||
for (var profileID in this.Profiles) {
|
||||
var connectedProfiles =
|
||||
options.connectedDevices[options.address].connectedProfiles;
|
||||
if (connectedProfiles && (connectedProfiles[profileID] === true)) {
|
||||
hasConnectedProfile = true;
|
||||
}
|
||||
}
|
||||
return hasConnectedProfile;
|
||||
},
|
||||
|
||||
/**
|
||||
* A function to emit event to each registered listener by the input type.
|
||||
*
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {Object} options
|
||||
* @param {String} options.type - type of event name
|
||||
* @param {Object} options.detail - the object pass to the listener
|
||||
*/
|
||||
_emitEvent: function btcm__emitEvent(options) {
|
||||
this._listeners[options.type].forEach(function(listener) {
|
||||
listener(options);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* The method will provide event listener for outer modules to regist.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {String} eventName
|
||||
* @param {Function} callback
|
||||
*/
|
||||
addEventListener: function btcm_addEventListener(eventName, callback) {
|
||||
if (callback && (this._listeners.hasOwnProperty(eventName))) {
|
||||
this._listeners[eventName].push(callback);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The method will provide event listener for outer modules to un-regist.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf BluetoothConnectionManager
|
||||
* @param {String} eventName
|
||||
* @param {Function} callback
|
||||
*/
|
||||
removeEventListener:
|
||||
function btcm_removeEventListener(eventName, callback) {
|
||||
if (callback && (this._listeners.hasOwnProperty(eventName))) {
|
||||
var index = this._listeners[eventName].indexOf(callback);
|
||||
if (index >= 0) {
|
||||
this._listeners[eventName].splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BluetoothConnectionManager._init();
|
||||
return BluetoothConnectionManager;
|
||||
});
|
||||
1026
gaia/apps/bluetooth/js/modules/bluetooth/bluetooth_context.js
Executable file
1026
gaia/apps/bluetooth/js/modules/bluetooth/bluetooth_context.js
Executable file
File diff suppressed because it is too large
Load diff
158
gaia/apps/bluetooth/js/modules/bluetooth/bluetooth_device.js
Executable file
158
gaia/apps/bluetooth/js/modules/bluetooth/bluetooth_device.js
Executable file
|
|
@ -0,0 +1,158 @@
|
|||
/**
|
||||
* BluetoothDevice:
|
||||
* - BluetoothDevice is an Observable that wraps the platform
|
||||
* BluetoothDevice object.
|
||||
* - It has some observable properties: name, paired, cod
|
||||
* BluetoothDevice only update device information and does not involve in any
|
||||
* UI logic.
|
||||
*
|
||||
* @module BluetoothDevice
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var BtClassOfDeviceMapper = require('modules/bluetooth/bluetooth_cod_mapper');
|
||||
var Observable = require('modules/mvvm/observable');
|
||||
|
||||
var _debug = false;
|
||||
var debug = function() {};
|
||||
if (_debug) {
|
||||
debug = function btd_debug(msg) {
|
||||
console.log('--> [BluetoothDevice]: ' + msg);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a function to update connection info.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf BluetoothDevice
|
||||
* @param {Object} options
|
||||
* @param {Boolean} options.connectionStatus - is connected or not
|
||||
* @param {Object} options.profiles - profiles of the connection type
|
||||
*/
|
||||
var updateConnectionInfo = function(options) {
|
||||
debug('updateConnectionInfo(): this.address = ' + this.address +
|
||||
', this.name = ' + this.name +
|
||||
', options = ' + JSON.stringify(options));
|
||||
if (options.connectionStatus) {
|
||||
this.connectionStatus = options.connectionStatus;
|
||||
}
|
||||
|
||||
if (options.profiles) {
|
||||
this.profiles = Object.create(options.profiles);
|
||||
}
|
||||
|
||||
debug('updateConnectionInfo(): this.connectionStatus = ' +
|
||||
this.connectionStatus + ', this.profiles = ' +
|
||||
JSON.stringify(this.profiles));
|
||||
};
|
||||
|
||||
/**
|
||||
* Provide a function to update description text.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf BluetoothDevice
|
||||
*/
|
||||
var updateDescriptionText = function() {
|
||||
debug('updateDescriptionText():');
|
||||
// Define description for found device.
|
||||
if (this.paired === false) {
|
||||
this.descriptionText = 'tapToConnect';
|
||||
} else if (this.paired === 'pairing') {
|
||||
this.descriptionText = 'pairing';
|
||||
} else if (this.paired === true) {
|
||||
this.descriptionText = 'paired';
|
||||
// Define description for paired device.
|
||||
if (this.connectionStatus === 'connecting') {
|
||||
this.descriptionText = 'connecting';
|
||||
} else if (this.connectionStatus === 'connected') {
|
||||
if (this.profiles) {
|
||||
var hfpConnected = this.profiles.hfp;
|
||||
var a2dpConnected = this.profiles.a2dp;
|
||||
if (hfpConnected && a2dpConnected) {
|
||||
this.descriptionText = 'connectedWithDeviceMedia';
|
||||
} else if (hfpConnected) {
|
||||
this.descriptionText = 'connectedWithDevice';
|
||||
} else if (a2dpConnected) {
|
||||
this.descriptionText = 'connectedWithMedia';
|
||||
} else {
|
||||
this.descriptionText = 'connectedWithNoProfileInfo';
|
||||
}
|
||||
} else {
|
||||
this.descriptionText = 'connectedWithNoProfileInfo';
|
||||
}
|
||||
} else if (this.connectionStatus === 'disconnected') {
|
||||
this.descriptionText = 'disconnected';
|
||||
}
|
||||
}
|
||||
debug('updateDescriptionText(): this.descriptionText = ' +
|
||||
this.descriptionText);
|
||||
};
|
||||
|
||||
/**
|
||||
* @class BluetoothDevice
|
||||
* @requires module:modules/mvvm/observable
|
||||
* @param {BluetoothDevice} device
|
||||
* @return {Observable} observableBluetoothDevice
|
||||
*/
|
||||
return function ctor_bluetooth_device(device) {
|
||||
var type = BtClassOfDeviceMapper.getDeviceType(device.cod);
|
||||
var connectionStatus = device.connectionStatus || 'disconnected';
|
||||
var observableBluetoothDevice = Observable({
|
||||
name: device.name,
|
||||
paired: device.paired,
|
||||
address: device.address,
|
||||
type: type,
|
||||
connectionStatus: connectionStatus,
|
||||
profiles: null,
|
||||
descriptionText: '',
|
||||
get data() {return device;},
|
||||
updateConnectionInfo: updateConnectionInfo,
|
||||
updateDescriptionText: updateDescriptionText
|
||||
});
|
||||
|
||||
/**
|
||||
* Observe 'paired', 'connectionStatus', and 'profiles' properties changed
|
||||
* event in init function. Once these properties changed, we can update
|
||||
* corrected description for the device.
|
||||
*/
|
||||
observableBluetoothDevice._init = function btd__init() {
|
||||
this.observe('paired', this.updateDescriptionText.bind(this));
|
||||
this.observe('connectionStatus', this.updateDescriptionText.bind(this));
|
||||
this.observe('profiles', this.updateDescriptionText.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* watch 'onattributechanged' event
|
||||
* A function to receive device which is just found via discovery handler.
|
||||
* And set a function to handle 'onattributechanged' event.
|
||||
*/
|
||||
device.onattributechanged = function btd_onDeviceAttributeChanged(evt) {
|
||||
for (var i in evt.attrs) {
|
||||
debug('onDeviceAttributeChanged(): ' + evt.attrs[i]);
|
||||
switch (evt.attrs[i]) {
|
||||
case 'name':
|
||||
observableBluetoothDevice.name = device.name;
|
||||
break;
|
||||
case 'paired':
|
||||
debug('onDeviceAttributeChanged(): ' +
|
||||
'device.paired = ' + device.paired);
|
||||
observableBluetoothDevice.paired = device.paired;
|
||||
break;
|
||||
case 'cod':
|
||||
debug('onDeviceAttributeChanged(): ' +
|
||||
'device.cod = ' + device.cod);
|
||||
observableBluetoothDevice.type =
|
||||
BtClassOfDeviceMapper.getDeviceType(device.cod);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
observableBluetoothDevice._init();
|
||||
return observableBluetoothDevice;
|
||||
};
|
||||
});
|
||||
242
gaia/apps/bluetooth/js/modules/mvvm/list_view.js
Executable file
242
gaia/apps/bluetooth/js/modules/mvvm/list_view.js
Executable file
|
|
@ -0,0 +1,242 @@
|
|||
'use strict';
|
||||
|
||||
define(function(require) {
|
||||
var elements = new WeakMap();
|
||||
var ObservableArray = require('modules/mvvm/observable_array');
|
||||
|
||||
/*
|
||||
* A ListView takes an ObservableArray or an ordinary array, and generate/
|
||||
* manipulate the corresponding DOM elements of the content in the array using
|
||||
* the given template function. If the array is an ObservableArray, ListView
|
||||
* updates the DOM elements accordingly when the array is manipulated.
|
||||
*/
|
||||
var ListView = function(root, observableArray, templateFunc) {
|
||||
var _observableArray = null;
|
||||
var _root = root;
|
||||
var _templateFunc = templateFunc;
|
||||
var _enabled = true;
|
||||
|
||||
// keep track of observeables so we can unobserve
|
||||
var observerMap = new WeakMap();
|
||||
|
||||
function releaseObservers(element) {
|
||||
var observable = observerMap.get(element);
|
||||
if (!observable || typeof observable !== 'object') {
|
||||
return;
|
||||
}
|
||||
var observers = observerMap.get(observable);
|
||||
if (observers) {
|
||||
observers.forEach(function eachObserver(observer) {
|
||||
observable.unobserve(observer);
|
||||
});
|
||||
observerMap.delete(observable);
|
||||
observerMap.delete(element);
|
||||
}
|
||||
}
|
||||
|
||||
// helper to observe something and automatically remove old observers
|
||||
function observeAndCall(observable, map) {
|
||||
var observers = observerMap.get(observable) || [];
|
||||
if (observerMap.has(observable)) {
|
||||
observers = observerMap.get(observable);
|
||||
}
|
||||
observers = Object.keys(map).map(function eachProperty(property) {
|
||||
observable.observe(property, map[property]);
|
||||
map[property]();
|
||||
return map[property];
|
||||
});
|
||||
observerMap.set(observable, observers);
|
||||
}
|
||||
|
||||
var _helper = {
|
||||
observeAndCall: observeAndCall
|
||||
};
|
||||
|
||||
var _handleEvent = function(event) {
|
||||
// Ignore the change event when the list view is not enabled.
|
||||
if (!_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
var data = event.data;
|
||||
switch (event.type) {
|
||||
case 'insert':
|
||||
_insert(data.index, data.items);
|
||||
break;
|
||||
case 'remove':
|
||||
_remove(data.index, data.count);
|
||||
break;
|
||||
case 'replace':
|
||||
_replace(data.index, data.newValue);
|
||||
break;
|
||||
case 'reset':
|
||||
_reset(data.items || []);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
var _insert = function(index, items) {
|
||||
if (items.length <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// add DOM elements
|
||||
var referenceElement = _root.children[index];
|
||||
for (var i = items.length - 1; i >= 0; i--) {
|
||||
var curElement = _templateFunc(items[i], undefined, _helper);
|
||||
observerMap.set(curElement, items[i]);
|
||||
_root.insertBefore(curElement, referenceElement);
|
||||
referenceElement = curElement;
|
||||
}
|
||||
};
|
||||
|
||||
var _remove = function(index, count) {
|
||||
if (count === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// remove DOM elements
|
||||
if (count >= _root.childElementCount) {
|
||||
// clear all
|
||||
while (_root.firstElementChild) {
|
||||
releaseObservers(_root.firstElementChild);
|
||||
_root.removeChild(_root.firstElementChild);
|
||||
}
|
||||
} else {
|
||||
var nextElement = _root.children[index];
|
||||
for (var i = 0; i < count; i++) {
|
||||
if (nextElement) {
|
||||
var temp = nextElement.nextElementSibling;
|
||||
releaseObservers(nextElement);
|
||||
_root.removeChild(nextElement);
|
||||
nextElement = temp;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var _replace = function(index, value) {
|
||||
var element = _root.children[index];
|
||||
if (element) {
|
||||
releaseObservers(element);
|
||||
var newElement = _templateFunc(value, element, _helper);
|
||||
observerMap.set(element, value);
|
||||
if (newElement !== element) {
|
||||
_root.insertBefore(newElement, element);
|
||||
_root.removeChild(element);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var _reset = function(items) {
|
||||
var itemCount = items.length;
|
||||
var elementCount = _root.childElementCount;
|
||||
|
||||
if (itemCount === 0) {
|
||||
_remove(0, elementCount);
|
||||
} else if (itemCount <= elementCount) {
|
||||
items.forEach(function(item, index) {
|
||||
_replace(index, item);
|
||||
});
|
||||
// remove extra elements
|
||||
_remove(itemCount, elementCount - itemCount);
|
||||
} else {
|
||||
var slicedItems = items.slice(0, elementCount);
|
||||
var remainingItems = items.slice(elementCount);
|
||||
|
||||
slicedItems.forEach(function(item, index) {
|
||||
_replace(index, item);
|
||||
});
|
||||
// add extra elements
|
||||
_insert(elementCount, remainingItems);
|
||||
}
|
||||
};
|
||||
|
||||
var _enabledChanged = function() {
|
||||
if (_enabled) {
|
||||
_reset(_observableArray.array);
|
||||
}
|
||||
};
|
||||
|
||||
var view = {
|
||||
set: function lv_set(newArray) {
|
||||
if (_observableArray) {
|
||||
_observableArray.unobserve(_handleEvent);
|
||||
}
|
||||
|
||||
if (!newArray) {
|
||||
// clear all existing items
|
||||
if (_observableArray) {
|
||||
_remove(0, _observableArray.length);
|
||||
_observableArray = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If new array is an ordinary array, convert it to an ObservableArray.
|
||||
if (Array.isArray(newArray)) {
|
||||
newArray = ObservableArray(newArray);
|
||||
}
|
||||
_observableArray = newArray;
|
||||
|
||||
// An ObservableArray fires the four events for notifying its changes.
|
||||
// Register to these events for updating UI.
|
||||
_observableArray.observe('insert', _handleEvent);
|
||||
_observableArray.observe('remove', _handleEvent);
|
||||
_observableArray.observe('replace', _handleEvent);
|
||||
_observableArray.observe('reset', _handleEvent);
|
||||
|
||||
if (this.enabled) {
|
||||
_reset(_observableArray.array);
|
||||
}
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
// unobserve from array and null everything out
|
||||
if (_observableArray) {
|
||||
_observableArray.unobserve(_handleEvent);
|
||||
}
|
||||
_remove(0, _observableArray.length);
|
||||
elements.delete(_root);
|
||||
_root = null;
|
||||
_observableArray = null;
|
||||
_templateFunc = null;
|
||||
_enabled = false;
|
||||
},
|
||||
|
||||
get element() {
|
||||
return _root;
|
||||
},
|
||||
|
||||
set enabled(value) {
|
||||
if (_enabled !== value) {
|
||||
_enabled = value;
|
||||
_enabledChanged();
|
||||
}
|
||||
},
|
||||
|
||||
get enabled() {
|
||||
return _enabled;
|
||||
}
|
||||
};
|
||||
|
||||
if (elements.has(_root)) {
|
||||
// destroy old ListView if we setup a second one
|
||||
elements.get(_root).destroy();
|
||||
}
|
||||
|
||||
elements.set(_root, view);
|
||||
|
||||
// emtpy element at creation time
|
||||
_root.innerHTML = '';
|
||||
|
||||
view.set(observableArray);
|
||||
return view;
|
||||
};
|
||||
|
||||
return ListView;
|
||||
});
|
||||
102
gaia/apps/bluetooth/js/modules/mvvm/observable.js
Executable file
102
gaia/apps/bluetooth/js/modules/mvvm/observable.js
Executable file
|
|
@ -0,0 +1,102 @@
|
|||
'use strict';
|
||||
|
||||
define(function() {
|
||||
function unobserve(_eventHandlers, prop, handler) {
|
||||
// arguments in reverse order to support .bind(handler) for the
|
||||
// unbind from all case
|
||||
function removeHandler(handler, prop) {
|
||||
var handlers = _eventHandlers[prop];
|
||||
if (!handlers) {
|
||||
return;
|
||||
}
|
||||
var index = handlers.indexOf(handler);
|
||||
if (index >= 0) {
|
||||
handlers.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof prop === 'function') {
|
||||
// (handler) -- remove from every key in _eventHandlers
|
||||
Object.keys(_eventHandlers).forEach(removeHandler.bind(null, prop));
|
||||
} else if (handler) {
|
||||
// (prop, handler) -- remove handler from the specific prop
|
||||
removeHandler(handler, prop);
|
||||
} else if (prop in _eventHandlers) {
|
||||
// (prop) -- otherwise remove all handlers for property
|
||||
_eventHandlers[prop] = [];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* An Observable is able to notify its property change. It is initialized by
|
||||
* an ordinary object.
|
||||
*/
|
||||
function Observable(obj) {
|
||||
var _eventHandlers = {};
|
||||
var _observable = {
|
||||
observe: function o_observe(p, handler) {
|
||||
/*
|
||||
* We should check if _observable[_p] exists. Since _observable[_p] is
|
||||
* created along with _eventHandlers[p], here we simply check
|
||||
* _eventHandlers[p].
|
||||
*/
|
||||
var handlers = _eventHandlers[p];
|
||||
if (handlers) {
|
||||
handlers.push(handler);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* unobserve([prop], handler) - remove handler from observeable callbacks
|
||||
*/
|
||||
unobserve: unobserve.bind(null, _eventHandlers)
|
||||
};
|
||||
|
||||
var _getFunctionTemplate = function(p) {
|
||||
return function() {
|
||||
return _observable['_' + p];
|
||||
};
|
||||
};
|
||||
|
||||
var _setFunctionTemplate = function(p) {
|
||||
return function(value) {
|
||||
var oldValue = _observable['_' + p];
|
||||
if (oldValue !== value) {
|
||||
_observable['_' + p] = value;
|
||||
var handlers = _eventHandlers[p];
|
||||
handlers.forEach(function(handler) {
|
||||
handler(value, oldValue);
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
* Iterate all properties in the object and create corresponding getter and
|
||||
* setter for them.
|
||||
*/
|
||||
for (var p in obj) {
|
||||
// If p is a function, simply add it to the observable.
|
||||
if (typeof obj[p] === 'function') {
|
||||
_observable[p] = obj[p];
|
||||
continue;
|
||||
}
|
||||
|
||||
_eventHandlers[p] = [];
|
||||
|
||||
Object.defineProperty(_observable, '_' + p, {
|
||||
value: obj[p],
|
||||
writable: true
|
||||
});
|
||||
|
||||
Object.defineProperty(_observable, p, {
|
||||
enumerable: true,
|
||||
get: _getFunctionTemplate(p),
|
||||
set: _setFunctionTemplate(p)
|
||||
});
|
||||
}
|
||||
|
||||
return _observable;
|
||||
}
|
||||
|
||||
return Observable;
|
||||
});
|
||||
153
gaia/apps/bluetooth/js/modules/mvvm/observable_array.js
Executable file
153
gaia/apps/bluetooth/js/modules/mvvm/observable_array.js
Executable file
|
|
@ -0,0 +1,153 @@
|
|||
'use strict';
|
||||
|
||||
define(function() {
|
||||
function unobserve(_eventHandlers, prop, handler) {
|
||||
// arguments in reverse order to support .bind(handler) for the
|
||||
// unbind from all case
|
||||
function removeHandler(handler, prop) {
|
||||
var handlers = _eventHandlers[prop];
|
||||
if (!handlers) {
|
||||
return;
|
||||
}
|
||||
var index = handlers.indexOf(handler);
|
||||
if (index >= 0) {
|
||||
handlers.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof prop === 'function') {
|
||||
// (handler) -- remove from every key in _eventHandlers
|
||||
Object.keys(_eventHandlers).forEach(removeHandler.bind(null, prop));
|
||||
} else if (handler) {
|
||||
// (prop, handler) -- remove handler from the specific prop
|
||||
removeHandler(handler, prop);
|
||||
} else if (prop in _eventHandlers) {
|
||||
// (prop) -- otherwise remove all handlers for property
|
||||
_eventHandlers[prop] = [];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* An ObservableArray is able to notify its change through four basic
|
||||
* operations including 'insert', 'remove', 'replace', 'reset'. It is
|
||||
* initialized by an ordinary array.
|
||||
*/
|
||||
function ObservableArray(array) {
|
||||
var _array = array || [];
|
||||
var _eventHandlers = {
|
||||
'insert': [],
|
||||
'remove': [],
|
||||
'replace': [],
|
||||
'reset': []
|
||||
};
|
||||
|
||||
var _notify = function(eventType, data) {
|
||||
var handlers = _eventHandlers[eventType];
|
||||
handlers.forEach(function(handler) {
|
||||
handler({
|
||||
type: eventType,
|
||||
data: data
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
get length() {
|
||||
return _array.length;
|
||||
},
|
||||
|
||||
get array() {
|
||||
return _array;
|
||||
},
|
||||
|
||||
forEach: function oa_foreach(func) {
|
||||
_array.forEach(func);
|
||||
},
|
||||
|
||||
observe: function oa_observe(eventType, handler) {
|
||||
var handlers = _eventHandlers[eventType];
|
||||
if (handlers) {
|
||||
handlers.push(handler);
|
||||
}
|
||||
},
|
||||
|
||||
unobserve: unobserve.bind(null, _eventHandlers),
|
||||
|
||||
push: function oa_push(item) {
|
||||
_array.push(item);
|
||||
|
||||
_notify('insert', {
|
||||
index: _array.length - 1,
|
||||
count: 1,
|
||||
items: [item]
|
||||
});
|
||||
},
|
||||
|
||||
pop: function oa_pop() {
|
||||
if (!_array.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var item = _array.pop();
|
||||
|
||||
_notify('remove', {
|
||||
index: _array.length,
|
||||
count: 1
|
||||
});
|
||||
|
||||
return item;
|
||||
},
|
||||
|
||||
splice: function oa_splice(index, count) {
|
||||
if (arguments.length < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
var addedItems = Array.prototype.slice.call(arguments, 2);
|
||||
_array.splice.apply(_array, arguments);
|
||||
|
||||
if (count) {
|
||||
_notify('remove', {
|
||||
index: index,
|
||||
count: count
|
||||
});
|
||||
}
|
||||
|
||||
if (addedItems.length) {
|
||||
_notify('insert', {
|
||||
index: index,
|
||||
count: addedItems.length,
|
||||
items: addedItems
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
set: function oa_set(index, value) {
|
||||
if (index < 0 || index >= _array.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var oldValue = _array[index];
|
||||
_array[index] = value;
|
||||
_notify('replace', {
|
||||
index: index,
|
||||
oldValue: oldValue,
|
||||
newValue: value
|
||||
});
|
||||
},
|
||||
|
||||
get: function oa_get(index) {
|
||||
return _array[index];
|
||||
},
|
||||
|
||||
reset: function oa_reset(array) {
|
||||
_array = array;
|
||||
_notify('reset', {
|
||||
items: _array
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return ObservableArray;
|
||||
});
|
||||
12
gaia/apps/bluetooth/js/modules/navigator/mozBluetooth.js
Executable file
12
gaia/apps/bluetooth/js/modules/navigator/mozBluetooth.js
Executable file
|
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
* Wraps navigator.bluetooth for replacing it in unit tests more easily.
|
||||
*/
|
||||
define(function() {
|
||||
'use strict';
|
||||
|
||||
if (navigator.mozBluetooth) {
|
||||
return navigator.mozBluetooth;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
430
gaia/apps/bluetooth/js/modules/pair_manager.js
Executable file
430
gaia/apps/bluetooth/js/modules/pair_manager.js
Executable file
|
|
@ -0,0 +1,430 @@
|
|||
/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
|
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
||||
|
||||
/* global NotificationHelper */
|
||||
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var AdapterManager = require('modules/bluetooth/bluetooth_adapter_manager');
|
||||
var BtContext = require('modules/bluetooth/bluetooth_context');
|
||||
var PairExpiredDialog = require('views/pair_expired_dialog');
|
||||
|
||||
var _debug = false;
|
||||
var debug = function() {};
|
||||
if (_debug) {
|
||||
debug = function btam_debug(msg) {
|
||||
console.log('--> [PairManager]: ' + msg);
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* PairManager is responsible for:
|
||||
* 1. Handling system message 'bluetooth-pairing-request' while there is an
|
||||
* incoming/outgoing pairing request.
|
||||
* 2. Handling dom event from BluetoothPairingListener while there is an
|
||||
* incoming/outgoing pairing request.
|
||||
* 3. Handling dom event 'ondeviceunpaired' while some remote devices
|
||||
* request for canceling an overdue pairing request. The reason could be
|
||||
* authentication fails, remote device down, and internal error happens.
|
||||
*/
|
||||
var PairManager = {
|
||||
/**
|
||||
* Default adapter of Bluetooth.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf PairManger
|
||||
* @type {Object} BluetoothAdapter
|
||||
*/
|
||||
_defaultAdapter: null,
|
||||
/**
|
||||
* Indicate if the pair request is expired
|
||||
* @type {Boolean}
|
||||
*/
|
||||
_isExpired: false,
|
||||
|
||||
init: function() {
|
||||
// Observe 'defaultAdapter' property for reaching default adapter.
|
||||
AdapterManager.observe('defaultAdapter',
|
||||
this._onDefaultAdapterChanged.bind(this));
|
||||
this._onDefaultAdapterChanged(AdapterManager.defaultAdapter);
|
||||
|
||||
// Watch pairing events.
|
||||
this._watchOndisplaypasskeyreq();
|
||||
this._watchOnenterpincodereq();
|
||||
this._watchOnpairingconfirmationreq();
|
||||
this._watchOnpairingconsentreq();
|
||||
this._watchOnpairingaborted();
|
||||
|
||||
navigator.mozSetMessageHandler('bluetooth-pairing-request',
|
||||
this._onRequestPairingFromSystemMessage.bind(this)
|
||||
);
|
||||
|
||||
// Observe screen lockscreen locked/unlocked state for show pending
|
||||
// pairing request immediately.
|
||||
navigator.mozSettings.addObserver('lockscreen.locked',
|
||||
function gotScreenLockedChanged(event) {
|
||||
this.showPendingPairing(event.settingValue);
|
||||
}.bind(this));
|
||||
|
||||
// Observe Bluetooth 'enabled' property from hardware side.
|
||||
// Then, close pairing dialog immediately.
|
||||
BtContext.observe('enabled', (enabled) => {
|
||||
if (!enabled) {
|
||||
this.onBluetoothDisabled();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 'defaultAdapter' change event handler from adapter manager for
|
||||
* updating it immediately.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf PairManager
|
||||
* @param {Object} BluetoothAdapter newAdapter
|
||||
*/
|
||||
_onDefaultAdapterChanged: function(newAdapter) {
|
||||
// save default adapter
|
||||
this._defaultAdapter = newAdapter;
|
||||
},
|
||||
|
||||
/**
|
||||
* Watch 'ondisplaypasskeyreq' dom event for pairing.
|
||||
* A handler to trigger when a remote bluetooth device requests to display
|
||||
* passkey on the screen during pairing process.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf PairManager
|
||||
*/
|
||||
_watchOndisplaypasskeyreq: function() {
|
||||
if (!this._defaultAdapter || !this._defaultAdapter.pairingReqs) {
|
||||
debug('_watchOndisplaypasskeyreq() no adapter or pairingReqs');
|
||||
return;
|
||||
}
|
||||
|
||||
this._defaultAdapter.pairingReqs.addEventListener('displaypasskeyreq',
|
||||
(evt) => {
|
||||
debug('_watchOndisplaypasskeyreq()');
|
||||
this._handlePairingRequest({
|
||||
method: 'displaypasskey',
|
||||
evt: evt
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Watch 'onenterpincodereq' dom event for pairing.
|
||||
* A handler to trigger when a remote bluetooth device requests user enter
|
||||
* PIN code during pairing process.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf PairManager
|
||||
*/
|
||||
_watchOnenterpincodereq: function() {
|
||||
if (!this._defaultAdapter || !this._defaultAdapter.pairingReqs) {
|
||||
debug('_watchOnenterpincodereq() no adapter or pairingReqs');
|
||||
return;
|
||||
}
|
||||
|
||||
this._defaultAdapter.pairingReqs.addEventListener('enterpincodereq',
|
||||
(evt) => {
|
||||
debug('_watchOnenterpincodereq()');
|
||||
this._handlePairingRequest({
|
||||
method: 'enterpincode',
|
||||
evt: evt
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Watch 'onpairingconfirmationreq' dom event for pairing.
|
||||
* A handler to trigger when a remote bluetooth device requests user
|
||||
* confirm passkey during pairing process. Applications may prompt passkey
|
||||
* to user for confirmation, or confirm the passkey for user proactively.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf PairManager
|
||||
*/
|
||||
_watchOnpairingconfirmationreq: function() {
|
||||
if (!this._defaultAdapter || !this._defaultAdapter.pairingReqs) {
|
||||
debug('_watchOnpairingconfirmationreq() no adapter or pairingReqs');
|
||||
return;
|
||||
}
|
||||
|
||||
this._defaultAdapter.pairingReqs.addEventListener(
|
||||
'pairingconfirmationreq', (evt) => {
|
||||
debug('_watchOnpairingconfirmationreq: evt = ' + JSON.stringify(evt));
|
||||
this._handlePairingRequest({
|
||||
method: 'confirmation',
|
||||
evt: evt
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Watch 'onpairingconsentreq' dom event for pairing.
|
||||
* A handler to trigger when a remote bluetooth device requests user
|
||||
* confirm pairing during pairing process. Applications may prompt user
|
||||
* for confirmation or confirm for user proactively.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf PairManager
|
||||
*/
|
||||
_watchOnpairingconsentreq: function() {
|
||||
if (!this._defaultAdapter || !this._defaultAdapter.pairingReqs) {
|
||||
debug('_watchOnpairingconsentreq() no adapter or pairingReqs');
|
||||
return;
|
||||
}
|
||||
|
||||
this._defaultAdapter.pairingReqs.addEventListener('pairingconsentreq',
|
||||
(evt) => {
|
||||
debug('_watchOnpairingconsentreq: evt = ' + JSON.stringify(evt));
|
||||
this._handlePairingRequest({
|
||||
method: 'consent',
|
||||
evt: evt
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Watch 'onpairingaborted' dom event for pairing aborted.
|
||||
* A handler to trigger when pairing fails due to one of
|
||||
* following conditions:
|
||||
* - authentication fails
|
||||
* - remote device down (bluetooth ACL becomes disconnected)
|
||||
* - internal error happens
|
||||
*
|
||||
* @access private
|
||||
* @memberOf PairManager
|
||||
*/
|
||||
_watchOnpairingaborted: function() {
|
||||
if (!this._defaultAdapter) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._defaultAdapter.addEventListener('pairingaborted',
|
||||
this._onPairingAborted.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* A handler to handle 'onpairingaborted' event while it's coming.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf PairManager
|
||||
*/
|
||||
_onPairingAborted: function(evt) {
|
||||
debug('_onPairingAborted(): evt = ' + JSON.stringify(evt));
|
||||
// if the attention screen still open, close it
|
||||
if (this.childWindow) {
|
||||
this.childWindow.Pairview.closeInput();
|
||||
this.childWindow.close();
|
||||
}
|
||||
|
||||
// If "onpairingaborted" event is coming, and there is a pending pairing
|
||||
// request, we have reset the object. Because the callback is useless
|
||||
// since the older pairing request is expired.
|
||||
// The moment is fit to notify user that the later pairing request
|
||||
// is timeout or canceled. According to user story, do nothing here.
|
||||
// Clear up the pending pairing request only.
|
||||
if (this.pendingPairing) {
|
||||
this.pendingPairing = null;
|
||||
}
|
||||
|
||||
// Close pairing expired dialog if it is showing.
|
||||
if (PairExpiredDialog.isVisible) {
|
||||
PairExpiredDialog.close();
|
||||
// Have to close Bluetooth app after the dialog is closed.
|
||||
window.close();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Receive the system message event for launch Bluetooth app here.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf PairManager
|
||||
*/
|
||||
_onRequestPairingFromSystemMessage: function() {
|
||||
debug('onRequestPairingFromSystemMessage():');
|
||||
},
|
||||
|
||||
/**
|
||||
* It is used to handle each pairing request from different pairing methods.
|
||||
*
|
||||
* @memberOf PairManager
|
||||
* @access private
|
||||
* @param {Object} pairingInfo
|
||||
* @param {Object} pairingInfo.method - method of this pairing request
|
||||
* @param {Object} pairingInfo.evt - DOM evt of this pairing request
|
||||
*/
|
||||
_handlePairingRequest: function(pairingInfo) {
|
||||
debug('_onRequestPairing():' +
|
||||
' pairingInfo.method = ' + pairingInfo.method +
|
||||
' pairingInfo.evt = ' + pairingInfo.evt);
|
||||
|
||||
var req = navigator.mozSettings.createLock().get('lockscreen.locked');
|
||||
req.onsuccess = () => {
|
||||
if (req.result['lockscreen.locked']) {
|
||||
// notify user that we are receiving pairing request
|
||||
this.fireNotification(pairingInfo);
|
||||
} else {
|
||||
// We have clear up pending one before show the new pairing requst.
|
||||
// Becasue the pending pairing request is no longer usefull.
|
||||
this.cleanPendingPairing();
|
||||
|
||||
// show pair view directly while lock screen is unlocked
|
||||
this.showPairview(pairingInfo);
|
||||
}
|
||||
};
|
||||
req.onerror = () => {
|
||||
// We have clear up pending one before show the new pairing requst.
|
||||
// Becasue the pending pairing request is no longer usefull.
|
||||
this.cleanPendingPairing();
|
||||
|
||||
// fallback to default value 'unlocked'
|
||||
this.showPairview(pairingInfo);
|
||||
};
|
||||
},
|
||||
|
||||
fireNotification: function(pairingInfo) {
|
||||
// Once we received a pairing request in screen locked mode,
|
||||
// overwrite the object with the latest pairing request. Because the
|
||||
// older pairing request might be timeout and useless now.
|
||||
this.pendingPairing = {
|
||||
showPairviewCallback: this.showPairview.bind(this, pairingInfo)
|
||||
};
|
||||
// Prepare notification toast.
|
||||
var titleL10n = 'bluetooth-pairing-request-now-title';
|
||||
var bodyL10n = pairingInfo.evt.deviceName ?
|
||||
{ raw: pairingInfo.evt.deviceName } :
|
||||
'unnamed-device';
|
||||
var iconUrl =
|
||||
'app://bluetooth.gaiamobile.org/style/images/icon_bluetooth.png';
|
||||
// We always use tag "pairing-request" to manage these notifications.
|
||||
var notificationId = 'pairing-request';
|
||||
return NotificationHelper.send(titleL10n, {
|
||||
bodyL10n: bodyL10n,
|
||||
icon: iconUrl,
|
||||
tag: notificationId,
|
||||
closeOnClick: false
|
||||
}).then(notification => {
|
||||
// set onclick handler for the notification
|
||||
notification.onclick =
|
||||
this.pairingRequestExpiredNotificationHandler.bind(this,
|
||||
notification);
|
||||
});
|
||||
},
|
||||
|
||||
// According to user story, it won't notify user again
|
||||
// while the pending pairing request is just timeout or canceled.
|
||||
// So we have to set onclick handler in this moment.
|
||||
// The handler will pop out a pairing request expired prompt only.
|
||||
pairingRequestExpiredNotificationHandler: function(notification) {
|
||||
var req = navigator.mozSettings.createLock().get('lockscreen.locked');
|
||||
req.onsuccess = () => {
|
||||
// Avoid to do nothting while the notification toast is showing
|
||||
// and a user is able to trigger onclick event. Make sure screen
|
||||
// is unlocked, then show the prompt in bluetooth app.
|
||||
if (!req.result['lockscreen.locked']) {
|
||||
// Clean the pairing request notficiation which is expired.
|
||||
notification.close();
|
||||
|
||||
navigator.mozApps.getSelf().onsuccess = (evt) => {
|
||||
var app = evt.target.result;
|
||||
|
||||
// launch bluetooth app to foreground for showing the prompt
|
||||
if (PairExpiredDialog.isVisible) {
|
||||
app.launch();
|
||||
}
|
||||
|
||||
// show an alert with the overdue message
|
||||
if (!PairExpiredDialog.isVisible && this._isExpired) {
|
||||
debug('show expired dialog');
|
||||
PairExpiredDialog.showConfirm(function() {
|
||||
// Have to close Bluetooth app after the dialog is closed.
|
||||
window.close();
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
// If there is a pending pairing request while a user just unlocks screen,
|
||||
// we will show pair view immediately. Then, we clear up the notification.
|
||||
// If pendingPairing object is not exist, it means pair request is expired.
|
||||
showPendingPairing: function(screenLocked) {
|
||||
debug('showPendingPairing when screenLocked:' + screenLocked);
|
||||
if (!screenLocked) {
|
||||
if (this.pendingPairing) {
|
||||
this._isExpired = false;
|
||||
// show pair view from the callback function
|
||||
if (this.pendingPairing.showPairviewCallback) {
|
||||
this.pendingPairing.showPairviewCallback();
|
||||
}
|
||||
|
||||
this.cleanPendingPairing();
|
||||
} else {
|
||||
this._isExpired = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
cleanPendingPairing: function() {
|
||||
debug('cleanPendingPairing(): has pendingPairing = ' +
|
||||
(this.pendingPairing));
|
||||
|
||||
// Clear up the pending pairing request
|
||||
if (this.pendingPairing) {
|
||||
this.pendingPairing = null;
|
||||
}
|
||||
|
||||
// Clear up the pairing request from notification.
|
||||
this.cleanNotifications();
|
||||
},
|
||||
|
||||
// Clean all notifications which are fired with tag 'pairing-request'.
|
||||
cleanNotifications: function() {
|
||||
Notification.get().then(function(notifications) {
|
||||
if (notifications) {
|
||||
notifications.forEach(function(notification) {
|
||||
// Compare tags, as the tag is based on the "pairing-request" and
|
||||
// we only have one notification for the request. Plus, there
|
||||
// is no "id" field on the notification.
|
||||
if (notification.tag === 'pairing-request' &&
|
||||
notification.close) {
|
||||
notification.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
showPairview: function(pairingInfo) {
|
||||
debug('showPairview(): pairingInfo = ' + pairingInfo);
|
||||
var protocol = window.location.protocol;
|
||||
var host = window.location.host;
|
||||
this.childWindow = window.open(protocol + '//' + host + '/onpair.html',
|
||||
'pair_screen', 'attention');
|
||||
this.childWindow.onload = () => {
|
||||
this.childWindow.Pairview.init(pairingInfo.method, pairingInfo.evt);
|
||||
};
|
||||
},
|
||||
|
||||
onBluetoothDisabled: function() {
|
||||
debug('onBluetoothDisabled():');
|
||||
|
||||
// if the attention screen still open, close it
|
||||
if (this.childWindow) {
|
||||
this.childWindow.Pairview.closeInput();
|
||||
this.childWindow.close();
|
||||
}
|
||||
|
||||
// Since Bluetooth is off, close itself.
|
||||
window.close();
|
||||
}
|
||||
};
|
||||
|
||||
return PairManager;
|
||||
});
|
||||
349
gaia/apps/bluetooth/js/modules/transfer_manager.js
Executable file
349
gaia/apps/bluetooth/js/modules/transfer_manager.js
Executable file
|
|
@ -0,0 +1,349 @@
|
|||
/*
|
||||
* TransferManager is responsible for:
|
||||
* - Handling system message 'activity' while there is an activity request
|
||||
* incoming. Then, it will post result/error to requesting app.
|
||||
* - Watching Bluetooth enabled/disabled state, then show/hide turn Bluetooth
|
||||
* on dialog.
|
||||
* - Watching default adapter existed or not. And will post error if we cannot
|
||||
* get default adapter.
|
||||
* - Watching 'devicePicked', 'cancelSelection' events from DevicePickerPanel
|
||||
* module. Then, do corrected activity service flow regarding user decision.
|
||||
*
|
||||
* @module BluetoothContext
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var AdapterManager = require('modules/bluetooth/bluetooth_adapter_manager');
|
||||
var BtContext = require('modules/bluetooth/bluetooth_context');
|
||||
var CannotTransferDialog = require('views/cannot_transfer_dialog');
|
||||
var DevicePickerPanel = require('views/device_picker_panel');
|
||||
var TurnBluetoothOnDialog = require('views/turn_bluetooth_on_dialog');
|
||||
|
||||
var _debug = false;
|
||||
var debug = function() {};
|
||||
if (_debug) {
|
||||
debug = function bttm_debug(msg) {
|
||||
console.log('--> [TransferManager]: ' + msg);
|
||||
};
|
||||
}
|
||||
|
||||
var TransferManager = {
|
||||
/**
|
||||
* An instance to access activity source.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf TransferManager
|
||||
* @type {Object}
|
||||
*/
|
||||
_activity: null,
|
||||
|
||||
/**
|
||||
* The init function only watches the 'activity' event from system message.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf TransferManager
|
||||
*/
|
||||
init: function bttm_init() {
|
||||
// Watch 'activity' event from system message.
|
||||
this._watchActivityRequest();
|
||||
},
|
||||
|
||||
/**
|
||||
* Watch 'activity' event with callback function from system message.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf TransferManager
|
||||
*/
|
||||
_watchActivityRequest: function bttm__watchActivityRequest() {
|
||||
navigator.mozSetMessageHandler('activity',
|
||||
this._activityHandler.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle 'activity' request. Then make decision to service the request or
|
||||
* reject it.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf TransferManager
|
||||
* @param {Object} activityRequest
|
||||
*/
|
||||
_activityHandler: function bttm__activityHandler(activityRequest) {
|
||||
debug('_activityHandler(): activityRequest = ' +
|
||||
JSON.stringify(activityRequest));
|
||||
if (activityRequest.source.data.blobs &&
|
||||
activityRequest.source.data.blobs.length > 0) {
|
||||
this._activity = activityRequest;
|
||||
// Source info is enough to servie file transfer.
|
||||
// Start to init and observe Bluetooth relative modules.
|
||||
this._observeDefaultAdapter();
|
||||
this._observeBluetoothEnabled();
|
||||
this._watchEventFromDevicePicker();
|
||||
DevicePickerPanel.visible = true;
|
||||
} else {
|
||||
debug('_activityHandler(): Cannot transfer without blobs data!');
|
||||
this._cannotTransfer();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Show alert dialog. End the activity service while a user confirm to close
|
||||
* the dialog.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf TransferManager
|
||||
*/
|
||||
_cannotTransfer: function bttm__cannotTransfer() {
|
||||
debug('_cannotTransfer(): show alert dialog!!');
|
||||
// show an alert with the overdue message
|
||||
if (!CannotTransferDialog.isVisible) {
|
||||
CannotTransferDialog.showConfirm().then(() => {
|
||||
// Post error with reason then end the activity service.
|
||||
this._endTransferWithReason('cancelled');
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Post result/error to end the activity service.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf TransferManager
|
||||
*/
|
||||
_endTransferWithReason: function bttm__endTransferWithReason(reason) {
|
||||
debug('_endTransferWithReason(): reason = ' + reason);
|
||||
if (reason === 'transferred') {
|
||||
this._activity.postResult(reason);
|
||||
} else if (reason === 'cancelled') {
|
||||
this._activity.postError(reason);
|
||||
}
|
||||
|
||||
this._activity = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Observe and get default adapter from bluetooth adapter manager.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf TransferManager
|
||||
*/
|
||||
_observeDefaultAdapter: function bttm__observeDefaultAdapter() {
|
||||
// Observe 'defaultAdapter' property for reaching default adapter.
|
||||
AdapterManager.observe('defaultAdapter',
|
||||
this._onDefaultAdapterChanged.bind(this));
|
||||
this._onDefaultAdapterChanged(AdapterManager.defaultAdapter);
|
||||
},
|
||||
|
||||
/**
|
||||
* Observe and get 'enabled' property from bluetooth context.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf TransferManager
|
||||
*/
|
||||
_observeBluetoothEnabled: function bttm__observeBluetoothEnabled() {
|
||||
// Observe Bluetooth 'enabled' property from hardware side.
|
||||
BtContext.observe('enabled', this._onBluetoothEnabledChanged.bind(this));
|
||||
this._onBluetoothEnabledChanged(BtContext.enabled);
|
||||
},
|
||||
|
||||
/**
|
||||
* 'defaultAdapter' change event handler from adapter manager for
|
||||
* updating it immediately.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf TransferManager
|
||||
* @param {Object} BluetoothAdapter newAdapter
|
||||
* @param {Object} BluetoothAdapter oldAdapter
|
||||
*/
|
||||
_onDefaultAdapterChanged:
|
||||
function bttm__onDefaultAdapterChanged(newAdapter, oldAdapter) {
|
||||
if (oldAdapter && (newAdapter === null)) {
|
||||
debug('_onDefaultAdapterChanged(): Can not get bluetooth adapter!');
|
||||
// Post error with reason then end the activity service.
|
||||
this._endTransferWithReason('cancelled');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 'enabled' change event handler from bluetooth context for
|
||||
* updating UI flow immediately.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf TransferManager
|
||||
* @param {Boolean} enabled
|
||||
*/
|
||||
_onBluetoothEnabledChanged:
|
||||
function bttm__onBluetoothEnabledChanged(enabled) {
|
||||
debug('_onBluetoothEnabledChanged(): enabled = ' + enabled);
|
||||
switch(enabled) {
|
||||
case undefined:
|
||||
break;
|
||||
case true:
|
||||
// Close the confirmation dialog
|
||||
TurnBluetoothOnDialog.close();
|
||||
break;
|
||||
default:
|
||||
TurnBluetoothOnDialog.showConfirm().then((result) => {
|
||||
if (result === 'confirm') {
|
||||
// Turn Bluetooth on.
|
||||
BtContext.setEnabled(true);
|
||||
} else if (result === 'cancel') {
|
||||
// Post error with reason then end the activity service.
|
||||
this._endTransferWithReason('cancelled');
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Watch 'devicePicked', 'cancelSelection' event from device picker panel.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf TransferManager
|
||||
*/
|
||||
_watchEventFromDevicePicker: function bttm__watchEventFromDevicePicker() {
|
||||
debug('_watchEventFromDevicePicker():');
|
||||
// Watch 'devicePicked' event for reaching device picked from user.
|
||||
DevicePickerPanel.addEventListener('devicePicked',
|
||||
this._onDevicePicked.bind(this));
|
||||
|
||||
// Watch 'cancelSelection' event for reaching cancel selection from user.
|
||||
DevicePickerPanel.addEventListener('cancelSelection',
|
||||
this._onCancelSelection.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* While the device is picked from user, let's send file(s) to the device.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf TransferManager
|
||||
* @param {Object} event
|
||||
* @param {String} event.type - type of event name
|
||||
* @param {Object} event.detail - device info in this object
|
||||
* @param {Object} event.detail.address - address of device
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_onDevicePicked: function bttm__onDevicePicked(event) {
|
||||
// Send file to the device via address
|
||||
debug('_onDevicePicked(): address of target device = ' +
|
||||
event.detail.address);
|
||||
var targetDeviceAddress = event.detail.address;
|
||||
var blobs = this._activity.source.data.blobs;
|
||||
|
||||
// Produce sending files schedule.
|
||||
// Then, post message to system app for sending files in queue.
|
||||
var schedule = this._produceSendingFilesSchedule(blobs.length);
|
||||
this._postMessageToSystemApp(schedule);
|
||||
|
||||
// Send each file via Bluetooth sendFile API
|
||||
return Promise.all(blobs.map((blob, index) => {
|
||||
/**
|
||||
* Check blob.name because sendFile() API needs a "file"
|
||||
* object and a filaname before sending it.
|
||||
* If there is no filename in the blob, Bluetooth API will give a
|
||||
* default name "Unknown.jpeg".
|
||||
* So Bluetooth app has to find out the name via device storage.
|
||||
*/
|
||||
if (blob.name) {
|
||||
// The blob has name, send the blob directly.
|
||||
debug('blob is sending with name...');
|
||||
return BtContext.sendFile(targetDeviceAddress, blob);
|
||||
} else if (this._activity.source.data.filepaths) {
|
||||
// The blob does not have name,
|
||||
// browse the file via filepath from storage again.
|
||||
var filepath = this._activity.source.data.filepaths[index];
|
||||
var storage = navigator.getDeviceStorage('sdcard');
|
||||
return storage.get(filepath).then((file) => {
|
||||
debug('getFile succeed & file is sending... file = ' + file);
|
||||
return BtContext.sendFile(targetDeviceAddress, file);
|
||||
}, (error) => {
|
||||
debug('getFile failed so that blob is sending without filename ' +
|
||||
error && error.name);
|
||||
return BtContext.sendFile(targetDeviceAddress, blob);
|
||||
});
|
||||
} else {
|
||||
// The blob does not have name and filepath,
|
||||
// pass the blob without name to send file directly.
|
||||
debug('no filepath to get from device storage ' +
|
||||
'so that blob is sending without filename');
|
||||
return BtContext.sendFile(targetDeviceAddress, blob);
|
||||
}
|
||||
})).then(() => {
|
||||
debug('all files are already sent out..');
|
||||
// Post result with reason then end the activity service.
|
||||
this._endTransferWithReason('transferred');
|
||||
return Promise.resolve();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* While a user cancel selection, let's post result then end the activity
|
||||
* service.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf TransferManager
|
||||
* @param {Object} event
|
||||
* @param {String} event.type - type of event name
|
||||
*/
|
||||
_onCancelSelection: function bttm__onCancelSelection(event) {
|
||||
debug('_onCancelSelection(): cancel from user decision');
|
||||
// Post error with reason then end the activity service.
|
||||
this._endTransferWithReason('cancelled');
|
||||
},
|
||||
|
||||
/**
|
||||
* A function is a role producer.
|
||||
* Bluetooth app produce one message for each sending file request.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf TransferManager
|
||||
* @param {Number} numberOfTasks - number of tasks
|
||||
* @returns {Object}
|
||||
*/
|
||||
_produceSendingFilesSchedule:
|
||||
function bttm__produceSendingFilesSchedule(numberOfTasks) {
|
||||
// Construct a object to contain info of sending files schedule.
|
||||
// And the info will be posted message to system app.
|
||||
// The result of send files will be displayed on notification.
|
||||
var sendingFilesSchedule = {
|
||||
numberOfFiles: numberOfTasks,
|
||||
numSuccessful: 0,
|
||||
numUnsuccessful: 0
|
||||
};
|
||||
return sendingFilesSchedule;
|
||||
},
|
||||
|
||||
/**
|
||||
* A function to do inner app communcation.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf TransferManager
|
||||
* @param {Object} message - the info of sending files schedule
|
||||
*/
|
||||
_postMessageToSystemApp:
|
||||
function bttm__postMessageToSystemApp(message) {
|
||||
// Set up Inter-App Communications
|
||||
var getRequest = navigator.mozApps.getSelf();
|
||||
getRequest.onsuccess = (evt) => {
|
||||
var app = evt.target.result;
|
||||
// If IAC doesn't exist, just bail out.
|
||||
if (!app.connect) {
|
||||
debug('_postMessageToSystemApp(): Cannot post message since no ' +
|
||||
'connect function, message = ' + JSON.stringify(message));
|
||||
return;
|
||||
}
|
||||
// Post message object to system app.
|
||||
app.connect('bluetoothTransfercomms').then((ports) => {
|
||||
ports.forEach((port) => {
|
||||
port.postMessage(message);
|
||||
debug('_postMessageToSystemApp(): sending files schedule = ' +
|
||||
JSON.stringify(message));
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return TransferManager;
|
||||
});
|
||||
901
gaia/apps/bluetooth/js/settings.js
Executable file
901
gaia/apps/bluetooth/js/settings.js
Executable file
|
|
@ -0,0 +1,901 @@
|
|||
'use strict';
|
||||
|
||||
// handle Bluetooth settings
|
||||
navigator.mozL10n.once(function bluetoothSettings() {
|
||||
// Service ID for profiles
|
||||
var Profiles = {
|
||||
'HFP': 0x111E,
|
||||
'A2DP': 0x110D
|
||||
};
|
||||
|
||||
var _ = navigator.mozL10n.get;
|
||||
var l10n = navigator.mozL10n;
|
||||
var settings = navigator.mozSettings;
|
||||
var bluetooth = navigator.mozBluetooth;
|
||||
var defaultAdapter = null;
|
||||
|
||||
const DEVICE_CONNECTED_KEY = 'bluetooth.device.connected';
|
||||
|
||||
var MAX_DEVICE_NAME_LENGTH = 20;
|
||||
|
||||
if (!settings || !bluetooth) {
|
||||
return;
|
||||
}
|
||||
|
||||
var header = document.querySelector('gaia-header');
|
||||
header.addEventListener('action', function backToSettings() {
|
||||
window.close();
|
||||
});
|
||||
|
||||
var gBluetoothCheckBox = document.querySelector('.bluetooth-status gaia-switch');
|
||||
|
||||
// display Bluetooth power state
|
||||
function updateBluetoothState(value) {
|
||||
gBluetoothCheckBox.checked = value;
|
||||
}
|
||||
|
||||
// activate main button
|
||||
gBluetoothCheckBox.addEventListener('change', function() {
|
||||
console.log("GOT CHANGE EVENT!, setting disabled");
|
||||
var req = settings.createLock().set({'bluetooth.enabled': this.checked});
|
||||
gBluetoothCheckBox.setAttribute('disabled', true);
|
||||
req.onerror = function() {
|
||||
gBluetoothCheckBox.removeAttribute('disabled');
|
||||
};
|
||||
});
|
||||
|
||||
function initialDefaultAdapter() {
|
||||
if (!bluetooth.enabled)
|
||||
return;
|
||||
var req = bluetooth.getDefaultAdapter();
|
||||
req.onsuccess = function bt_getAdapterSuccess() {
|
||||
defaultAdapter = req.result;
|
||||
if (defaultAdapter == null) {
|
||||
// we can do nothing without DefaultAdapter, so set bluetooth disabled
|
||||
settings.createLock().set({'bluetooth.enabled': false});
|
||||
return;
|
||||
}
|
||||
defaultAdapter.ondevicefound = gDeviceList.onDeviceFound;
|
||||
|
||||
// initial related components that need defaultAdapter.
|
||||
gMyDeviceInfo.initWithAdapter();
|
||||
gDeviceList.initWithAdapter();
|
||||
};
|
||||
req.onerror = function bt_getAdapterFailed() {
|
||||
// we can do nothing without DefaultAdapter, so set bluetooth disabled
|
||||
settings.createLock().set({'bluetooth.enabled': false});
|
||||
};
|
||||
}
|
||||
|
||||
// device information
|
||||
var gMyDeviceInfo = (function deviceInfo() {
|
||||
var visibleItem = document.querySelector('.device-visible');
|
||||
var visibleName = document.querySelector('.bluetooth-device-name');
|
||||
var visibleCheckBox = document.querySelector('.device-visible gaia-switch');
|
||||
var bluetoothRename = document.getElementById('bluetooth-rename');
|
||||
var renameButton = document.getElementById('rename-device');
|
||||
var updateNameDialog = document.getElementById('update-device-name');
|
||||
var updateNameInput = document.getElementById('update-device-name-input');
|
||||
var updateNameCancelButton =
|
||||
document.getElementById('update-device-name-cancel');
|
||||
var updateNameConfirmButton =
|
||||
document.getElementById('update-device-name-confirm');
|
||||
|
||||
var visibleTimeout = null;
|
||||
var visibleTimeoutTime = 120000; // visibility will timeout after 2 minutes
|
||||
var myName = '';
|
||||
|
||||
visibleCheckBox.addEventListener('change', function() {
|
||||
setDiscoverable(visibleCheckBox.checked);
|
||||
});
|
||||
|
||||
renameButton.onclick = function renameBtnClicked() {
|
||||
if (myName === '') {
|
||||
myName = visibleName.textContent = defaultAdapter.name;
|
||||
}
|
||||
updateNameInput.value = myName;
|
||||
updateNameDialog.hidden = false;
|
||||
// Focus the input field to trigger showing the keyboard
|
||||
updateNameInput.focus();
|
||||
var cursorPos = updateNameInput.value.length;
|
||||
updateNameInput.setSelectionRange(0, cursorPos);
|
||||
};
|
||||
|
||||
updateNameCancelButton.onclick = function updateNameCancelClicked(evt) {
|
||||
if (evt)
|
||||
evt.preventDefault();
|
||||
|
||||
updateNameDialog.hidden = true;
|
||||
};
|
||||
|
||||
updateNameConfirmButton.onclick = function updateNameConfirmClicked(evt) {
|
||||
if (evt)
|
||||
evt.preventDefault();
|
||||
|
||||
var nameEntered = updateNameInput.value;
|
||||
nameEntered = nameEntered.replace(/^\s+|\s+$/g, '');
|
||||
|
||||
if (nameEntered.length > MAX_DEVICE_NAME_LENGTH) {
|
||||
var wantToRetry = window.confirm(_('bluetooth-name-maxlength-alert',
|
||||
{ length: MAX_DEVICE_NAME_LENGTH }));
|
||||
|
||||
if (!wantToRetry) {
|
||||
updateNameDialog.hidden = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (nameEntered === myName || !bluetooth.enabled || !defaultAdapter) {
|
||||
updateNameDialog.hidden = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (nameEntered !== '') {
|
||||
updateDeviceName(nameEntered);
|
||||
}
|
||||
else {
|
||||
// Bug 847459: Default name of the bluetooth device is set by bluetoothd
|
||||
// to the value of the Android ro.product.model property upon first
|
||||
// start. In case the user gives an empty bluetooth device name, we want
|
||||
// to revert to the original ro.product.model. Gecko exposes it under
|
||||
// the deviceinfo.product_model setting.
|
||||
var deviceInfo = settings.createLock().get('deviceinfo.product_model');
|
||||
deviceInfo.onsuccess = function bt_getProductModel() {
|
||||
var productModel = deviceInfo.result['deviceinfo.product_model'];
|
||||
updateDeviceName(productModel);
|
||||
};
|
||||
}
|
||||
|
||||
updateNameDialog.hidden = true;
|
||||
};
|
||||
|
||||
function updateDeviceName(nameEntered) {
|
||||
var req = defaultAdapter.setName(nameEntered);
|
||||
|
||||
req.onsuccess = function bt_renameSuccess() {
|
||||
myName = visibleName.textContent = defaultAdapter.name;
|
||||
};
|
||||
}
|
||||
|
||||
// immediatly UI update, DOM element manipulation.
|
||||
function updateDeviceInfo(show) {
|
||||
bluetoothRename.hidden = !show;
|
||||
if (show) {
|
||||
visibleItem.hidden = false;
|
||||
// get last user setting for device visible
|
||||
var req = settings.createLock().get('bluetooth.visible');
|
||||
req.onsuccess = function bt_getVisibleSuccess() {
|
||||
var visible = req.result['bluetooth.visible'];
|
||||
if (typeof visible === 'undefined') {
|
||||
visible = true;
|
||||
}
|
||||
setDiscoverable(visible);
|
||||
};
|
||||
req.onerror = function bt_getVisibleError() {
|
||||
visibleCheckBox.checked = true;
|
||||
};
|
||||
} else {
|
||||
visibleItem.hidden = true;
|
||||
renameButton.disabled = true;
|
||||
if (visibleTimeout) {
|
||||
clearTimeout(visibleTimeout);
|
||||
visibleTimeout = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// initial this device information and do default actions
|
||||
// when DefaultAdapter is ready.
|
||||
function initial() {
|
||||
visibleCheckBox.checked = defaultAdapter.discoverable;
|
||||
setDiscoverable(visibleCheckBox.checked);
|
||||
// we can't get device name immediately, wait a while
|
||||
setTimeout(function() {
|
||||
myName = visibleName.textContent = defaultAdapter.name;
|
||||
renameButton.disabled = false;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function setDiscoverable(visible) {
|
||||
if (!bluetooth.enabled || !defaultAdapter)
|
||||
return;
|
||||
|
||||
settings.createLock().set({'bluetooth.visible': visible});
|
||||
|
||||
defaultAdapter.setDiscoverable(visible);
|
||||
// Visibility will time out after 2 mins.
|
||||
if (visible) {
|
||||
if (!visibleTimeout) {
|
||||
visibleTimeout = setTimeout(function() {
|
||||
setDiscoverable(false);
|
||||
}, visibleTimeoutTime);
|
||||
}
|
||||
} else {
|
||||
if (visibleTimeout) {
|
||||
clearTimeout(visibleTimeout);
|
||||
visibleTimeout = null;
|
||||
}
|
||||
}
|
||||
visibleCheckBox.checked = visible;
|
||||
}
|
||||
|
||||
// API
|
||||
return {
|
||||
update: updateDeviceInfo,
|
||||
initWithAdapter: initial
|
||||
};
|
||||
})();
|
||||
|
||||
// device list
|
||||
var gDeviceList = (function deviceList() {
|
||||
var bluetoothSearch = document.getElementById('bluetooth-search');
|
||||
var searchAgainBtn = document.getElementById('search-device');
|
||||
var searchingItem = document.getElementById('bluetooth-searching');
|
||||
var enableMsg = document.getElementById('bluetooth-enable-msg');
|
||||
|
||||
var pairingAddress = null;
|
||||
var connectingAddress = null;
|
||||
var connectedAddress = null;
|
||||
// stop discover other device after 60 seconds
|
||||
var discoverTimeoutTime = 60000;
|
||||
var discoverTimeout = null;
|
||||
|
||||
var pairList = {
|
||||
title: document.getElementById('bluetooth-paired-title'),
|
||||
list: document.getElementById('bluetooth-paired-devices'),
|
||||
index: [],
|
||||
clear: function emptyList() {
|
||||
while (this.list.hasChildNodes()) {
|
||||
this.list.removeChild(this.list.lastChild);
|
||||
}
|
||||
this.index = [];
|
||||
},
|
||||
show: function showArea(isShown) {
|
||||
if (!isShown) {
|
||||
this.clear();
|
||||
}
|
||||
this.title.hidden = !isShown;
|
||||
this.list.hidden = !isShown;
|
||||
}
|
||||
};
|
||||
|
||||
var openList = {
|
||||
title: document.getElementById('bluetooth-found-title'),
|
||||
list: document.getElementById('bluetooth-devices'),
|
||||
index: [],
|
||||
clear: function emptyList() {
|
||||
while (this.list.hasChildNodes()) {
|
||||
this.list.removeChild(this.list.lastChild);
|
||||
}
|
||||
this.index = [];
|
||||
},
|
||||
show: function showArea(isShown) {
|
||||
if (!isShown) {
|
||||
this.clear();
|
||||
}
|
||||
this.title.hidden = !isShown;
|
||||
this.list.hidden = !isShown;
|
||||
}
|
||||
};
|
||||
|
||||
var optionMenu = {
|
||||
menu: document.getElementById('paired-device-option'),
|
||||
connectOpt: document.getElementById('connect-option'),
|
||||
disconnectOpt: document.getElementById('disconnect-option'),
|
||||
unpairOpt: document.getElementById('unpair-option'),
|
||||
confirmDlg: document.getElementById('unpair-device'),
|
||||
unpairCancel: document.getElementById('unpair-device-cancel'),
|
||||
confirmOpt: document.getElementById('confirm-option'),
|
||||
|
||||
showActions: function showActions() {
|
||||
var self = this;
|
||||
if (connectedAddress && this.device.address === connectedAddress) {
|
||||
this.connectOpt.style.display = 'none';
|
||||
this.disconnectOpt.style.display = 'block';
|
||||
this.disconnectOpt.onclick = function() {
|
||||
setDeviceDisconnect(self.device);
|
||||
};
|
||||
} else {
|
||||
this.connectOpt.style.display = 'block';
|
||||
this.disconnectOpt.style.display = 'none';
|
||||
this.connectOpt.onclick = function() {
|
||||
setDeviceConnect(self.device);
|
||||
};
|
||||
}
|
||||
this.unpairOpt.onclick = function() {
|
||||
setDeviceUnpair(self.device);
|
||||
};
|
||||
this.menu.addEventListener('gaiamenu-cancel', function closeMenu() {
|
||||
return self.close();
|
||||
});
|
||||
this.menu.show();
|
||||
},
|
||||
|
||||
showConfirm: function showConfirm() {
|
||||
var self = this;
|
||||
this.unpairCancel.onclick = function() {
|
||||
return self.close();
|
||||
};
|
||||
this.confirmOpt.onclick = function() {
|
||||
setDeviceUnpair(self.device);
|
||||
return self.close();
|
||||
};
|
||||
this.confirmDlg.hidden = false;
|
||||
},
|
||||
|
||||
show: function showMenu(device) {
|
||||
this.device = device;
|
||||
// we only support audio-card device to connect atm
|
||||
this[this.device.icon === 'audio-card' ?
|
||||
'showActions' : 'showConfirm']();
|
||||
},
|
||||
|
||||
close: function closeMenu() {
|
||||
this.menu.hide();
|
||||
this.confirmDlg.hidden = true;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
searchAgainBtn.onclick = function searchAgainClicked() {
|
||||
updateDeviceList(true); // reset network list
|
||||
openList.clear();
|
||||
startDiscovery();
|
||||
};
|
||||
|
||||
// private DOM helper: create a device list item
|
||||
function newListItem(device, descL10nId) {
|
||||
var deviceName = document.createElement('span');
|
||||
if (device.name !== '') {
|
||||
deviceName.textContent = device.name;
|
||||
} else {
|
||||
deviceName.setAttribute('data-l10n-id', 'unnamed-device');
|
||||
}
|
||||
|
||||
var deviceDesc = document.createElement('small');
|
||||
if (descL10nId) {
|
||||
deviceDesc.setAttribute('data-l10n-id', descL10nId);
|
||||
}
|
||||
|
||||
var a = document.createElement('a');
|
||||
a.appendChild(deviceName);
|
||||
a.appendChild(deviceDesc);
|
||||
var li = document.createElement('li');
|
||||
li.classList.add('bluetooth-device');
|
||||
li.classList.add('bluetooth-type-' + device.icon);
|
||||
li.appendChild(a);
|
||||
|
||||
return li;
|
||||
}
|
||||
|
||||
// immediatly UI update, DOM element manipulation.
|
||||
function updateDeviceList(show) {
|
||||
bluetoothSearch.hidden = !show;
|
||||
if (show) {
|
||||
enableMsg.hidden = true;
|
||||
openList.show(true);
|
||||
searchingItem.hidden = false;
|
||||
document.addEventListener('visibilitychange',
|
||||
stopDiscoveryWhenLeaveApp);
|
||||
} else {
|
||||
openList.show(false);
|
||||
pairList.show(false);
|
||||
enableMsg.hidden = false;
|
||||
searchingItem.hidden = true;
|
||||
optionMenu.close();
|
||||
pairingAddress = null;
|
||||
connectingAddress = null;
|
||||
connectedAddress = null;
|
||||
// clear discoverTimeout
|
||||
clearTimeout(discoverTimeout);
|
||||
discoverTimeout = null;
|
||||
document.removeEventListener('visibilitychange',
|
||||
stopDiscoveryWhenLeaveApp);
|
||||
}
|
||||
}
|
||||
|
||||
// do default actions (start discover avaliable devices)
|
||||
// when DefaultAdapter is ready.
|
||||
function initial() {
|
||||
defaultAdapter.onpairedstatuschanged = function bt_getPairedMessage(evt) {
|
||||
dispatchEvent(new CustomEvent('bluetooth-pairedstatuschanged'));
|
||||
showDevicePaired(evt.status, 'Authentication Failed');
|
||||
};
|
||||
|
||||
defaultAdapter.ondiscoverystatechanged =
|
||||
function bt_discoveryStateChanged(evt) {
|
||||
if (!evt.discovering) {
|
||||
searchAgainBtn.disabled = false;
|
||||
searchingItem.hidden = true;
|
||||
|
||||
clearTimeout(discoverTimeout);
|
||||
discoverTimeout = null;
|
||||
} else {
|
||||
searchAgainBtn.disabled = true;
|
||||
searchingItem.hidden = false;
|
||||
}
|
||||
};
|
||||
|
||||
defaultAdapter.onhfpstatuschanged = function bt_hfpStatusChanged(evt) {
|
||||
showDeviceConnected(evt.address, evt.status, Profiles.HFP);
|
||||
};
|
||||
|
||||
defaultAdapter.ona2dpstatuschanged = function bt_a2dpStatusChanged(evt) {
|
||||
showDeviceConnected(evt.address, evt.status, Profiles.A2DP);
|
||||
};
|
||||
|
||||
// Get paired device
|
||||
getPairedDevice(function() {
|
||||
for (var address in pairList.index) {
|
||||
var deviceItem = pairList.index[address];
|
||||
if (Object.keys(deviceItem.connectedProfiles).length > 0)
|
||||
return;
|
||||
}
|
||||
|
||||
// If there is no current connection and we have one device connected
|
||||
// before, restore it.
|
||||
restoreConnection();
|
||||
});
|
||||
startDiscovery();
|
||||
}
|
||||
|
||||
function restoreConnection() {
|
||||
// Reconnect the one kept in settings key.
|
||||
var connectedDevice =
|
||||
settings.createLock().get(DEVICE_CONNECTED_KEY);
|
||||
connectedDevice.onsuccess = function bt_getConnectedDevice() {
|
||||
var address = connectedDevice.result[DEVICE_CONNECTED_KEY];
|
||||
if (!address || !pairList.index[address])
|
||||
return;
|
||||
|
||||
var device = pairList.index[address].device;
|
||||
setDeviceConnect(device);
|
||||
};
|
||||
}
|
||||
|
||||
function getPairedDevice(callback) {
|
||||
if (!bluetooth.enabled || !defaultAdapter)
|
||||
return;
|
||||
var req = defaultAdapter.getPairedDevices();
|
||||
req.onsuccess = function bt_getPairedSuccess() {
|
||||
// copy for sorting
|
||||
var paired = req.result.slice();
|
||||
var length = paired.length;
|
||||
if (length == 0) {
|
||||
pairList.show(false);
|
||||
return;
|
||||
}
|
||||
pairList.clear();
|
||||
paired.sort(function(a, b) {
|
||||
return a.name > b.name;
|
||||
});
|
||||
for (var i = 0; i < length; i++) {
|
||||
(function(device) {
|
||||
var aItem = newListItem(device, '');
|
||||
aItem.onclick = function() {
|
||||
optionMenu.show(device);
|
||||
};
|
||||
pairList.list.appendChild(aItem);
|
||||
pairList.index[device.address] = {
|
||||
device: device,
|
||||
item: aItem,
|
||||
connectedProfiles: {}
|
||||
};
|
||||
// if the device has to be connected when it just paired
|
||||
// wait for a while so they can have time to communicate
|
||||
// their connection protocol
|
||||
if (device.address === connectingAddress &&
|
||||
device.icon === 'audio-card') {
|
||||
var small = aItem.querySelector('small');
|
||||
small.setAttribute('data-l10n-id', 'device-status-connecting');
|
||||
setTimeout(function() {
|
||||
setDeviceConnect(device);
|
||||
}, 5000);
|
||||
}
|
||||
})(paired[i]);
|
||||
}
|
||||
|
||||
// update the connection status
|
||||
getConnectedDeviceItems(function(connectedDeviceItems) {
|
||||
if (connectedDeviceItems.length > 0) {
|
||||
connectedDeviceItems.forEach(function(item) {
|
||||
var connectedDevice = item.device;
|
||||
var connectedProfiles = item.connectedProfiles;
|
||||
for (var profile in connectedProfiles) {
|
||||
showDeviceConnected(connectedDevice.address, true, profile);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pairList.show(true);
|
||||
// the callback function now is for restoring the connected device
|
||||
// when the bluetooth is turned on.
|
||||
if (callback)
|
||||
callback();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// In the callback we returns an array of connected device items.
|
||||
// Each device item contains "device" and "connectedProfiles".
|
||||
function getConnectedDeviceItems(callback) {
|
||||
if (!callback)
|
||||
return;
|
||||
|
||||
if (!defaultAdapter) {
|
||||
callback([]);
|
||||
return;
|
||||
}
|
||||
|
||||
var getConnectedDevicesByProfile = function(profileID, gcdCallback) {
|
||||
if (!gcdCallback)
|
||||
return;
|
||||
|
||||
var req = defaultAdapter.getConnectedDevices(profileID);
|
||||
req.onsuccess = function() {
|
||||
gcdCallback(req.result || []);
|
||||
};
|
||||
req.onerror = function() {
|
||||
gcdCallback(null);
|
||||
};
|
||||
};
|
||||
|
||||
var connectedDeviceItemsMap = {}; // hash by device address
|
||||
var updateDeviceItemsMap = function(profileID, connectedDevices) {
|
||||
if (!connectedDevices)
|
||||
return;
|
||||
|
||||
connectedDevices.forEach(function(connectedDevice) {
|
||||
var info = connectedDeviceItemsMap[connectedDevice.address];
|
||||
if (info) {
|
||||
info.connectedProfiles[profileID] = true;
|
||||
} else {
|
||||
info = {
|
||||
'device': connectedDevice,
|
||||
'connectedProfiles': {}
|
||||
};
|
||||
info.connectedProfiles[profileID] = true;
|
||||
}
|
||||
connectedDeviceItemsMap[connectedDevice.address] = info;
|
||||
});
|
||||
};
|
||||
|
||||
// XXX: we should have better ways of doing this.
|
||||
getConnectedDevicesByProfile(Profiles.HFP, function(hfpResult) {
|
||||
updateDeviceItemsMap(Profiles.HFP, hfpResult);
|
||||
getConnectedDevicesByProfile(Profiles.A2DP, function(a2dpResult) {
|
||||
updateDeviceItemsMap(Profiles.A2DP, a2dpResult);
|
||||
|
||||
var connectedDeviceItems = [];
|
||||
for (var i in connectedDeviceItemsMap) {
|
||||
var item = connectedDeviceItemsMap[i];
|
||||
connectedDeviceItems.push(item);
|
||||
}
|
||||
callback(connectedDeviceItems);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// callback function when an avaliable device found
|
||||
function onDeviceFound(evt) {
|
||||
var device = evt.device;
|
||||
// Ignore duplicate and paired device. Update the name if needed.
|
||||
var existingDevice = openList.index[device.address] ||
|
||||
pairList.index[device.address];
|
||||
if (existingDevice) {
|
||||
var existingItem = existingDevice.item;
|
||||
if (device.name && existingItem) {
|
||||
var deviceName = existingItem.querySelector('a > span');
|
||||
if (deviceName) {
|
||||
deviceName.removeAttribute('data-l10n-id');
|
||||
deviceName.textContent = device.name;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var aItem = newListItem(device, 'device-status-tap-connect');
|
||||
|
||||
// bind paired callback
|
||||
aItem.onclick = function() {
|
||||
// block the pairing request if there is already one.
|
||||
if (pairingAddress)
|
||||
return;
|
||||
|
||||
var small = aItem.querySelector('small');
|
||||
small.setAttribute('data-l10n-id', 'device-status-pairing');
|
||||
this.setAttribute('aria-disabled', true);
|
||||
stopDiscovery();
|
||||
|
||||
// pairing dialog is handled and showing via Bluetooth app
|
||||
var req = defaultAdapter.pair(device.address);
|
||||
pairingAddress = device.address;
|
||||
req.onerror = function bt_pairError(error) {
|
||||
showDevicePaired(false, req.error.name);
|
||||
};
|
||||
};
|
||||
|
||||
openList.list.appendChild(aItem);
|
||||
openList.index[device.address] = {
|
||||
device: device,
|
||||
item: aItem,
|
||||
connectedProfiles: {}
|
||||
};
|
||||
}
|
||||
|
||||
function showDevicePaired(paired, errorMessage) {
|
||||
// If we don't know the pairing device address,
|
||||
// it means the pair request is handled by interface level.
|
||||
// So we just need to update paired list.
|
||||
if (!pairingAddress) {
|
||||
getPairedDevice();
|
||||
return;
|
||||
}
|
||||
// clear pairingAddress first to prevent execute
|
||||
// the same status update twice.
|
||||
var workingAddress = pairingAddress;
|
||||
pairingAddress = null;
|
||||
if (paired) {
|
||||
// if the device is on the list, remove it.
|
||||
// it will show on paired list later.
|
||||
if (openList.index[workingAddress]) {
|
||||
var device = openList.index[workingAddress].device;
|
||||
var item = openList.index[workingAddress].item;
|
||||
openList.list.removeChild(item);
|
||||
delete openList.index[workingAddress];
|
||||
connectingAddress = workingAddress;
|
||||
}
|
||||
} else {
|
||||
// show pair process fail.
|
||||
var msg = _('error-pair-title');
|
||||
if (errorMessage === 'Repeated Attempts') {
|
||||
msg = msg + '\n' + _('error-pair-toofast');
|
||||
} else if (errorMessage === 'Authentication Failed') {
|
||||
msg = msg + '\n' + _('error-pair-pincode');
|
||||
}
|
||||
window.alert(msg);
|
||||
|
||||
// rollback device status
|
||||
if (openList.index[workingAddress]) {
|
||||
var item = openList.index[workingAddress].item;
|
||||
var small = item.querySelector('small');
|
||||
item.removeAttribute('aria-disabled');
|
||||
small.setAttribute('data-l10n-id', 'device-status-tap-connect');
|
||||
}
|
||||
}
|
||||
// acquire a new paired list no matter paired or unpaired
|
||||
getPairedDevice();
|
||||
}
|
||||
|
||||
function setDeviceUnpair(device) {
|
||||
if (device.address === connectedAddress) {
|
||||
var msg = _('unpair-title') + '\n' + _('unpair-msg');
|
||||
if (!window.confirm(msg))
|
||||
return;
|
||||
connectedAddress = null;
|
||||
}
|
||||
// backend takes responsibility to disconnect first.
|
||||
var req = defaultAdapter.unpair(device.address);
|
||||
req.onerror = function bt_pairError() {
|
||||
showDevicePaired(true, null);
|
||||
};
|
||||
}
|
||||
|
||||
function setDeviceDisconnect(device, callback) {
|
||||
if (!bluetooth.enabled || !defaultAdapter ||
|
||||
device.address !== connectedAddress) {
|
||||
if (callback)
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
var req = defaultAdapter.disconnect(device);
|
||||
req.onsuccess = req.onerror = function() {
|
||||
if (callback)
|
||||
callback();
|
||||
};
|
||||
}
|
||||
|
||||
function setDeviceConnect(device) {
|
||||
// we only support audio-card device to connect now
|
||||
if (!bluetooth.enabled || !defaultAdapter ||
|
||||
device.icon !== 'audio-card' ||
|
||||
device.address === connectedAddress) {
|
||||
connectingAddress = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var doConnect = function() {
|
||||
var connectSuccess = function bt_connectSuccess() {
|
||||
if (connectingAddress) {
|
||||
connectingAddress = null;
|
||||
}
|
||||
};
|
||||
|
||||
var connectError = function bt_connectError() {
|
||||
// Connection state might be changed before DOM request response.
|
||||
if (connectingAddress) {
|
||||
// Clear the text of connecting status.
|
||||
var small =
|
||||
pairList.index[connectingAddress].item.querySelector('small');
|
||||
small.removeAttribute('data-l10n-id');
|
||||
small.textContent = '';
|
||||
connectingAddress = null;
|
||||
window.alert(_('error-connect-msg'));
|
||||
}
|
||||
};
|
||||
|
||||
stopDiscovery();
|
||||
|
||||
var req = defaultAdapter.connect(device);
|
||||
req.onsuccess = connectSuccess; // At least one profile is connected.
|
||||
req.onerror = connectError; // No available profiles are connected.
|
||||
|
||||
connectingAddress = device.address;
|
||||
if (!pairList.index[connectingAddress]) {
|
||||
return;
|
||||
}
|
||||
|
||||
var small =
|
||||
pairList.index[connectingAddress].item.querySelector('small');
|
||||
small.setAttribute('data-l10n-id', 'device-status-connecting');
|
||||
};
|
||||
|
||||
// disconnect current connected device first
|
||||
if (connectedAddress && pairList.index[connectedAddress]) {
|
||||
setDeviceDisconnect(pairList.index[connectedAddress].device, doConnect);
|
||||
} else {
|
||||
doConnect();
|
||||
}
|
||||
}
|
||||
|
||||
function showDeviceConnected(deviceAddress, connected, profile) {
|
||||
var deviceItem = pairList.index[deviceAddress];
|
||||
if (!deviceItem)
|
||||
return;
|
||||
|
||||
deviceItem.connectedProfiles[profile] = connected;
|
||||
|
||||
var existConnectedProfile = false;
|
||||
if (connected) {
|
||||
existConnectedProfile = true;
|
||||
} else {
|
||||
// Check if there are other connected profiles
|
||||
for (var profile in deviceItem.connectedProfiles) {
|
||||
existConnectedProfile = existConnectedProfile ||
|
||||
deviceItem.connectedProfiles[profile];
|
||||
}
|
||||
}
|
||||
|
||||
if (existConnectedProfile) {
|
||||
connectedAddress = deviceAddress;
|
||||
// record connected device so if Bluetooth is turned off and then on
|
||||
// we can restore the connection
|
||||
settings.createLock().set({
|
||||
'bluetooth.device.connected': connectedAddress
|
||||
});
|
||||
} else {
|
||||
if (connectedAddress === deviceAddress) {
|
||||
connectedAddress = null;
|
||||
settings.createLock().set({'bluetooth.device.connected': null});
|
||||
}
|
||||
}
|
||||
|
||||
var l10nId = '';
|
||||
var hfpConnected = deviceItem.connectedProfiles[Profiles.HFP];
|
||||
var a2dpConnected = deviceItem.connectedProfiles[Profiles.A2DP];
|
||||
if (hfpConnected && a2dpConnected) {
|
||||
l10nId = 'device-status-connected-phone-media';
|
||||
} else if (hfpConnected) {
|
||||
l10nId = 'device-status-connected-phone';
|
||||
} else if (a2dpConnected) {
|
||||
l10nId = 'device-status-connected-media';
|
||||
} else {
|
||||
l10nId = null;
|
||||
}
|
||||
|
||||
var small = pairList.index[deviceAddress].item.querySelector('small');
|
||||
if (l10nId) {
|
||||
small.setAttribute('data-l10n-id', l10nId);
|
||||
} else {
|
||||
small.removeAttribute('data-l10n-id');
|
||||
small.textContent = '';
|
||||
}
|
||||
}
|
||||
|
||||
function startDiscovery() {
|
||||
if (!bluetooth.enabled || !defaultAdapter ||
|
||||
discoverTimeout || document.hidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
var req = defaultAdapter.startDiscovery();
|
||||
req.onsuccess = function bt_discoveryStart() {
|
||||
if (!discoverTimeout)
|
||||
discoverTimeout = setTimeout(stopDiscovery, discoverTimeoutTime);
|
||||
};
|
||||
req.onerror = function bt_discoveryFailed() {
|
||||
console.error('Can not discover nearby device');
|
||||
};
|
||||
}
|
||||
|
||||
function stopDiscoveryWhenLeaveApp() {
|
||||
//only stop discovery when Settings app is hidden
|
||||
if (!document.hidden)
|
||||
return;
|
||||
stopDiscovery();
|
||||
}
|
||||
|
||||
function stopDiscovery() {
|
||||
if (!bluetooth.enabled || !defaultAdapter || !discoverTimeout)
|
||||
return;
|
||||
|
||||
var req = defaultAdapter.stopDiscovery();
|
||||
req.onerror = function bt_discoveryStopFailed() {
|
||||
console.error('Can not stop discover nearby device');
|
||||
};
|
||||
|
||||
clearTimeout(discoverTimeout);
|
||||
discoverTimeout = null;
|
||||
}
|
||||
|
||||
// API
|
||||
return {
|
||||
update: updateDeviceList,
|
||||
initWithAdapter: initial,
|
||||
startDiscovery: startDiscovery,
|
||||
onDeviceFound: onDeviceFound
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
var lastMozSettingValue = false;
|
||||
|
||||
// enable Bluetooth if the related settings says so
|
||||
// register an observer to monitor bluetooth.enabled changes
|
||||
settings.addObserver('bluetooth.enabled', function(event) {
|
||||
var enabled = event.settingValue;
|
||||
if (lastMozSettingValue == enabled)
|
||||
return;
|
||||
|
||||
// lock UI toggle
|
||||
gBluetoothCheckBox.setAttribute('disabled', true);
|
||||
|
||||
lastMozSettingValue = enabled;
|
||||
updateBluetoothState(enabled);
|
||||
|
||||
gDeviceList.update(enabled);
|
||||
gMyDeviceInfo.update(enabled);
|
||||
|
||||
// clear defaultAdapter, we have to acquire it again when enabled.
|
||||
if (!enabled)
|
||||
defaultAdapter = null;
|
||||
});
|
||||
|
||||
// startup, update status
|
||||
var req = settings.createLock().get('bluetooth.enabled');
|
||||
|
||||
req.onsuccess = function bt_getSettingsSuccess() {
|
||||
lastMozSettingValue = req.result['bluetooth.enabled'];
|
||||
|
||||
// if bluetooth is on when booting, the adapter probably is ready.
|
||||
if (lastMozSettingValue)
|
||||
initialDefaultAdapter();
|
||||
|
||||
updateBluetoothState(lastMozSettingValue);
|
||||
|
||||
gDeviceList.update(lastMozSettingValue);
|
||||
gMyDeviceInfo.update(lastMozSettingValue);
|
||||
};
|
||||
|
||||
bluetooth.addEventListener('adapteradded', function() {
|
||||
// enable UI toggle
|
||||
gBluetoothCheckBox.removeAttribute('disabled');
|
||||
initialDefaultAdapter();
|
||||
});
|
||||
|
||||
bluetooth.addEventListener('disabled', function() {
|
||||
gBluetoothCheckBox.removeAttribute('disabled'); // enable UI toggle
|
||||
defaultAdapter = null; // clear defaultAdapter
|
||||
});
|
||||
});
|
||||
16
gaia/apps/bluetooth/js/startup_pair.js
Executable file
16
gaia/apps/bluetooth/js/startup_pair.js
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
'use strict';
|
||||
|
||||
require(['config/require'], function() {
|
||||
var _debug = false;
|
||||
var debug = function() {};
|
||||
if (_debug) {
|
||||
debug = function btsp_debug(msg) {
|
||||
console.log('--> [startup_pair]: ' + msg);
|
||||
};
|
||||
}
|
||||
|
||||
require(['modules/pair_manager'], function(PairManager) {
|
||||
debug('Load pair manager with API version 2. Loaded!!');
|
||||
navigator.mozL10n.once(() => PairManager.init());
|
||||
});
|
||||
});
|
||||
16
gaia/apps/bluetooth/js/startup_transfer.js
Executable file
16
gaia/apps/bluetooth/js/startup_transfer.js
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
'use strict';
|
||||
|
||||
require(['config/require'], function() {
|
||||
var _debug = false;
|
||||
var debug = function() {};
|
||||
if (_debug) {
|
||||
debug = function btst_debug(msg) {
|
||||
console.log('--> [startup_transfer]: ' + msg);
|
||||
};
|
||||
}
|
||||
|
||||
require(['modules/transfer_manager'], function(TransferManager) {
|
||||
debug('Load transfer manager with API version 2. Loaded!!');
|
||||
navigator.mozL10n.once(() => TransferManager.init());
|
||||
});
|
||||
});
|
||||
149
gaia/apps/bluetooth/js/utils.js
Executable file
149
gaia/apps/bluetooth/js/utils.js
Executable file
|
|
@ -0,0 +1,149 @@
|
|||
/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil -*- */
|
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
||||
|
||||
/* exported getTruncated */
|
||||
|
||||
'use strict';
|
||||
|
||||
function getTruncated(oldName, options) {
|
||||
|
||||
// options
|
||||
var maxLine = options.maxLine || 2;
|
||||
var node = options.node;
|
||||
var ellipsisIndex = options.ellipsisIndex || 3;
|
||||
var ellipsisCharacter = options.ellipsisCharacter || '...';
|
||||
|
||||
if (node === null) {
|
||||
return oldName;
|
||||
}
|
||||
|
||||
// used variables and functions
|
||||
function hitsNewline(oldHeight, newHeight) {
|
||||
return oldHeight !== newHeight;
|
||||
}
|
||||
|
||||
var newName = '';
|
||||
var oldHeight;
|
||||
var newHeight;
|
||||
var baseHeight;
|
||||
var currentLine;
|
||||
var ellipsisAt;
|
||||
var hasNewEllipsisPoint = true;
|
||||
var nameBeforeEllipsis = [];
|
||||
var nameBeforeEllipsisString;
|
||||
var nameAfterEllipsis = oldName.slice(-ellipsisIndex);
|
||||
var realVisibility = node.style.visibility;
|
||||
var realWordBreak = node.style.wordBreak;
|
||||
|
||||
/*
|
||||
* Hide UI, because we are manipulating DOM
|
||||
*/
|
||||
node.style.visibility = 'hidden';
|
||||
|
||||
/*
|
||||
* Force breaking on boundaries
|
||||
*/
|
||||
node.style.wordBreak = 'break-all';
|
||||
|
||||
/*
|
||||
* Get the base height to count the currentLine at first
|
||||
*/
|
||||
node.textContent = '.';
|
||||
baseHeight = node.clientHeight;
|
||||
node.textContent = '';
|
||||
|
||||
var needEllipsis = oldName.split('').some(function(character, index) {
|
||||
|
||||
nameBeforeEllipsis.push(character);
|
||||
nameBeforeEllipsisString = nameBeforeEllipsis.join('');
|
||||
|
||||
oldHeight = node.clientHeight;
|
||||
node.textContent = nameBeforeEllipsisString +
|
||||
ellipsisCharacter + nameAfterEllipsis;
|
||||
newHeight = node.clientHeight;
|
||||
|
||||
/*
|
||||
* When index is 0, we have to update currentLine according to
|
||||
* the first assignment (it is possible that at first the currentLine
|
||||
* is not 0 if the width of node is too small)
|
||||
*/
|
||||
if (index === 0) {
|
||||
currentLine = Math.floor(newHeight / baseHeight);
|
||||
}
|
||||
|
||||
if (hitsNewline(oldHeight, newHeight) && index !== 0) {
|
||||
|
||||
/*
|
||||
* The reason why we have to check twice is because there is a
|
||||
* situation that truncated string is overflowed but there is
|
||||
* still room for original string.
|
||||
*
|
||||
* In this way, we have to memorize the ellipsis index and
|
||||
* slice `nameBeforeEllipsis` to the index in the end.
|
||||
*/
|
||||
var testHeight;
|
||||
node.textContent = nameBeforeEllipsisString;
|
||||
testHeight = node.clientHeight;
|
||||
|
||||
if (hitsNewline(oldHeight, testHeight)) {
|
||||
|
||||
/*
|
||||
* We have to make it true again to keep the ellipsisAt
|
||||
* up to date.
|
||||
*/
|
||||
hasNewEllipsisPoint = true;
|
||||
currentLine += 1;
|
||||
} else {
|
||||
/*
|
||||
* This is the situation that we still have room, so we have
|
||||
* to keep the ellipsisAt value for later use.
|
||||
*/
|
||||
if (hasNewEllipsisPoint) {
|
||||
ellipsisAt = index;
|
||||
hasNewEllipsisPoint = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currentLine > maxLine) {
|
||||
if (index === 0) {
|
||||
|
||||
/*
|
||||
* It means that at first, the whole string is already in
|
||||
* an overflowed situation, you have to avoid this situation.
|
||||
* And we will bypass oldName back to you.
|
||||
*
|
||||
* There are some options for you :
|
||||
*
|
||||
* 1. Check options.ellipsisCharacter
|
||||
* 2. Check options.maxLine
|
||||
* 3. Check node's width (maybe too narrow)
|
||||
*/
|
||||
console.log(
|
||||
'Your string is in a overflowed situation, ' +
|
||||
'please check your options');
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove the last character, because it causes the overflow
|
||||
*/
|
||||
nameBeforeEllipsis.pop();
|
||||
node.textContent = '';
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// restore UI
|
||||
node.style.visibility = realVisibility;
|
||||
node.style.wordBreak = realWordBreak;
|
||||
|
||||
if (!needEllipsis) {
|
||||
newName = oldName;
|
||||
} else {
|
||||
newName += nameBeforeEllipsis.join('').slice(0, ellipsisAt);
|
||||
newName += ellipsisCharacter;
|
||||
newName += nameAfterEllipsis;
|
||||
}
|
||||
|
||||
return newName;
|
||||
}
|
||||
1224
gaia/apps/bluetooth/js/vendor/alameda.js
vendored
Executable file
1224
gaia/apps/bluetooth/js/vendor/alameda.js
vendored
Executable file
File diff suppressed because it is too large
Load diff
159
gaia/apps/bluetooth/js/views/bt_template_factory.js
Executable file
159
gaia/apps/bluetooth/js/views/bt_template_factory.js
Executable file
|
|
@ -0,0 +1,159 @@
|
|||
/**
|
||||
* The template function for generating an UI element for an item of Bluetooth
|
||||
* paired/remote device.
|
||||
*
|
||||
* @module bluetooth/bt_template_factory
|
||||
*/
|
||||
define(function() {
|
||||
'use strict';
|
||||
|
||||
var _debug = false;
|
||||
var debug = function() {};
|
||||
if (_debug) {
|
||||
debug = function bttf_debug(msg) {
|
||||
console.log('--> [BluetoothTemplateFactory]: ' + msg);
|
||||
};
|
||||
}
|
||||
|
||||
function btTemplate(deviceType, onItemClick, observableItem) {
|
||||
var device = observableItem;
|
||||
|
||||
var nameSpan = document.createElement('span');
|
||||
_updateItemName(nameSpan, device.name);
|
||||
|
||||
var descSmall = document.createElement('small');
|
||||
if (deviceType === 'remote') {
|
||||
descSmall.setAttribute('data-l10n-id', 'device-status-tap-connect');
|
||||
}
|
||||
|
||||
var li = document.createElement('li');
|
||||
var anchor = document.createElement('a');
|
||||
li.classList.add('bluetooth-device');
|
||||
|
||||
// According to Bluetooth class of device to give icon style.
|
||||
debug('device.type = ' + device.type);
|
||||
li.classList.add('bluetooth-type-' + device.type);
|
||||
|
||||
// According to 'descriptionText' property to give description.
|
||||
_updateItemDescriptionText(li, descSmall, device.descriptionText,
|
||||
deviceType);
|
||||
|
||||
anchor.appendChild(nameSpan);
|
||||
anchor.appendChild(descSmall); // should append this first
|
||||
li.appendChild(anchor);
|
||||
|
||||
// Add pairing progress in remote device.
|
||||
if (deviceType === 'remote') {
|
||||
var pairingProgress = document.createElement('progress');
|
||||
pairingProgress.classList.add('overlapping-icon');
|
||||
pairingProgress.classList.add('hidden');
|
||||
li.appendChild(pairingProgress);
|
||||
}
|
||||
|
||||
// Register the handler for the click event.
|
||||
if (typeof onItemClick === 'function') {
|
||||
li.onclick = function() {
|
||||
onItemClick(observableItem);
|
||||
};
|
||||
}
|
||||
|
||||
// Observe name property for update device name
|
||||
// while device 'onattributechanged' event is coming.
|
||||
device.observe('name', function(newName) {
|
||||
_updateItemName(nameSpan, newName);
|
||||
});
|
||||
|
||||
// Observe descriptionText property for update device description
|
||||
// while the connection status changed.
|
||||
device.observe('descriptionText', function(descriptionText) {
|
||||
_updateItemDescriptionText(li, descSmall, descriptionText, deviceType);
|
||||
});
|
||||
|
||||
return li;
|
||||
}
|
||||
|
||||
function _updateItemName(element, name) {
|
||||
if (name !== '') {
|
||||
element.removeAttribute('data-l10n-id');
|
||||
element.textContent = name;
|
||||
} else {
|
||||
element.setAttribute('data-l10n-id', 'unnamed-device');
|
||||
}
|
||||
}
|
||||
|
||||
function _updateItemDescriptionText(li, element, descriptionText,
|
||||
deviceType) {
|
||||
debug('_updateItemDescriptionText(): descriptionText = ' + descriptionText);
|
||||
switch (descriptionText) {
|
||||
case 'tapToConnect':
|
||||
li.removeAttribute('aria-disabled');
|
||||
element.setAttribute('data-l10n-id', 'device-status-tap-connect');
|
||||
// Rollback icon layout for remote device.
|
||||
if (deviceType === 'remote') {
|
||||
// hide the pairing progress
|
||||
li.querySelector('progress').classList.add('hidden');
|
||||
// show the device icon
|
||||
li.classList.remove('icon-hidden');
|
||||
}
|
||||
break;
|
||||
case 'pairing':
|
||||
li.setAttribute('aria-disabled', true);
|
||||
element.setAttribute('data-l10n-id', 'device-status-pairing');
|
||||
// The remote device has different icon layout
|
||||
// while it's in inline pairing progress.
|
||||
if (deviceType === 'remote') {
|
||||
// hide the device icon
|
||||
li.classList.add('icon-hidden');
|
||||
// show the pairing progress
|
||||
li.querySelector('progress').classList.remove('hidden');
|
||||
}
|
||||
break;
|
||||
case 'paired':
|
||||
li.removeAttribute('aria-disabled');
|
||||
element.setAttribute('data-l10n-id', 'device-status-paired');
|
||||
// Rollback icon layout for remote device.
|
||||
if (deviceType === 'remote') {
|
||||
// hide the pairing progress
|
||||
li.querySelector('progress').classList.add('hidden');
|
||||
// show the device icon
|
||||
li.classList.remove('icon-hidden');
|
||||
}
|
||||
break;
|
||||
case 'connecting':
|
||||
li.setAttribute('aria-disabled', true);
|
||||
element.setAttribute('data-l10n-id', 'device-status-connecting');
|
||||
break;
|
||||
case 'connectedWithDeviceMedia':
|
||||
li.removeAttribute('aria-disabled');
|
||||
element.setAttribute('data-l10n-id',
|
||||
'device-status-connected-device-media');
|
||||
break;
|
||||
case 'connectedWithDevice':
|
||||
li.removeAttribute('aria-disabled');
|
||||
element.setAttribute('data-l10n-id',
|
||||
'device-status-connected-device');
|
||||
break;
|
||||
case 'connectedWithMedia':
|
||||
li.removeAttribute('aria-disabled');
|
||||
element.setAttribute('data-l10n-id',
|
||||
'device-status-connected-media');
|
||||
break;
|
||||
case 'connectedWithNoProfileInfo':
|
||||
li.removeAttribute('aria-disabled');
|
||||
element.setAttribute('data-l10n-id',
|
||||
'device-status-connected');
|
||||
break;
|
||||
case 'disconnected':
|
||||
li.removeAttribute('aria-disabled');
|
||||
element.removeAttribute('data-l10n-id');
|
||||
element.textContent = '';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return function ctor_btTemplate(deviceType, onItemClick) {
|
||||
return btTemplate.bind(null, deviceType, onItemClick);
|
||||
};
|
||||
});
|
||||
37
gaia/apps/bluetooth/js/views/cannot_transfer_dialog.js
Executable file
37
gaia/apps/bluetooth/js/views/cannot_transfer_dialog.js
Executable file
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* CannotTransferDialog is responsible for:
|
||||
* Alert a user that the activity request is not able to transfer via
|
||||
* Bluetooth with some reason. There is an alert handling via here.
|
||||
*/
|
||||
define(function() {
|
||||
'use strict';
|
||||
|
||||
var CannotTransferDialog = {
|
||||
init: function() {
|
||||
this.dialog = document.getElementById('alert-view');
|
||||
this.confirmBtn =
|
||||
document.getElementById('alert-button-ok');
|
||||
},
|
||||
|
||||
showConfirm: function() {
|
||||
return new Promise((resolve) => {
|
||||
this.confirmBtn.onclick = () => {
|
||||
this.close();
|
||||
resolve();
|
||||
};
|
||||
this.dialog.hidden = false;
|
||||
});
|
||||
},
|
||||
|
||||
get isVisible() {
|
||||
return (!this.dialog.hidden);
|
||||
},
|
||||
|
||||
close : function() {
|
||||
this.dialog.hidden = true;
|
||||
}
|
||||
};
|
||||
|
||||
CannotTransferDialog.init();
|
||||
return CannotTransferDialog;
|
||||
});
|
||||
413
gaia/apps/bluetooth/js/views/device_picker_panel.js
Executable file
413
gaia/apps/bluetooth/js/views/device_picker_panel.js
Executable file
|
|
@ -0,0 +1,413 @@
|
|||
/*
|
||||
* DevicePickerPanel is responsible for:
|
||||
* - Render paired/found devices list in this panel.
|
||||
* - Emit 'devicePicked' event with device address while a user click on a
|
||||
* paired device.
|
||||
* - Pair with the picked device while a user click on a found device.
|
||||
* Emit 'devicePicked' event with device address until the picked device
|
||||
* is pared.
|
||||
* - Emit 'cancelSelection' event while a user click close button on the
|
||||
* header for leaving.
|
||||
*
|
||||
* @module DevicePickerPanel
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var BtContext = require('modules/bluetooth/bluetooth_context');
|
||||
var BtTemplateFactory = require('views/bt_template_factory');
|
||||
var ListView = require('modules/mvvm/list_view');
|
||||
|
||||
var _debug = false;
|
||||
var debug = function() {};
|
||||
if (_debug) {
|
||||
debug = function btdpp_debug(msg) {
|
||||
console.log('--> [DevicePickerPanel]: ' + msg);
|
||||
};
|
||||
}
|
||||
|
||||
var DevicePickerPanel = {
|
||||
/**
|
||||
* A instance to maintain paired devices list view.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf DevicePickerPanel
|
||||
* @type {Object}
|
||||
*/
|
||||
_pairedDevicesListView: null,
|
||||
|
||||
/**
|
||||
* A instance to maintain found devices list view.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf DevicePickerPanel
|
||||
* @type {Object}
|
||||
*/
|
||||
_foundDevicesListView: null,
|
||||
|
||||
/**
|
||||
* Construct elements.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf DevicePickerPanel
|
||||
* @type {Object}
|
||||
*/
|
||||
_elements: {
|
||||
view: {
|
||||
viewDevicePicker: document.querySelector('.devices-list-view')
|
||||
},
|
||||
header: {
|
||||
headerActionClose: document.querySelector('.devices-list-header')
|
||||
},
|
||||
paired: {
|
||||
pairedDevicesHeader: document.querySelector('.bluetooth-paired-title'),
|
||||
pairedDevicesList: document.querySelector('.bluetooth-paired-devices')
|
||||
},
|
||||
found: {
|
||||
foundDevicesHeader: document.querySelector('.bluetooth-found-title'),
|
||||
foundDevicesList: document.querySelector('.bluetooth-devices')
|
||||
},
|
||||
search: {
|
||||
searchingItem: document.querySelector('.bluetooth-searching'),
|
||||
searchItem: document.querySelector('.bluetooth-search'),
|
||||
searchButton: document.querySelector('.search-device')
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The object maintains listeners' callback per property name.
|
||||
* Each listener would be called as following definition.
|
||||
* 'devicePicked' - be called when device is picked and paired successfully.
|
||||
* 'cancelSelection': - be called when user click on close button on header.
|
||||
*
|
||||
* @memberOf DevicePickerPanel
|
||||
* @type {Object}
|
||||
*/
|
||||
_listeners: {
|
||||
'devicePicked': [],
|
||||
'cancelSelection': []
|
||||
},
|
||||
|
||||
/**
|
||||
* Default adapter of Bluetooth.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf DevicePickerPanel
|
||||
* @type {Object} BluetoothAdapter
|
||||
*/
|
||||
_defaultAdapter: null,
|
||||
|
||||
/**
|
||||
* The function init the paired/found devices list and search button.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf DevicePickerPanel
|
||||
*/
|
||||
_init: function btdpp__init() {
|
||||
// Bind click event for header.
|
||||
this._elements.header.headerActionClose.addEventListener('action',
|
||||
this._onHeaderActionCloseClick.bind(this));
|
||||
|
||||
// Bind click event for search button.
|
||||
this._elements.search.searchButton.addEventListener('click',
|
||||
this._onSearchButtonClick.bind(this));
|
||||
|
||||
// Bind click event for paired device item.
|
||||
var pairedDeviceTemplate =
|
||||
BtTemplateFactory('paired', this._onPairedDeviceItemClick.bind(this));
|
||||
|
||||
// Create paired devices list view.
|
||||
this._pairedDevicesListView =
|
||||
ListView(this._elements.paired.pairedDevicesList,
|
||||
BtContext.getPairedDevices(),
|
||||
pairedDeviceTemplate);
|
||||
|
||||
// Bind click event for found device item.
|
||||
var foundDeviceTemplate =
|
||||
BtTemplateFactory('remote', this._onFoundDeviceItemClick.bind(this));
|
||||
|
||||
// Create found devices list view.
|
||||
this._foundDevicesListView =
|
||||
ListView(this._elements.found.foundDevicesList,
|
||||
BtContext.getRemoteDevices(),
|
||||
foundDeviceTemplate);
|
||||
|
||||
// Init items state.
|
||||
this._initItemsState();
|
||||
|
||||
// search devices automatically while the panel is inited.
|
||||
BtContext.startDiscovery().then(() => {
|
||||
debug('_init(): startDiscovery successfully');
|
||||
}, (reason) => {
|
||||
debug('_init(): startDiscovery failed, ' +
|
||||
'reason = ' + reason);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* The function init these elements which are displayed in this panel.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf DevicePickerPanel
|
||||
*/
|
||||
_initItemsState: function btdpp__initItemsState() {
|
||||
// paired devices header
|
||||
BtContext.observe('hasPairedDevice',
|
||||
this._updatePairedDevicesHeader.bind(this));
|
||||
this._updatePairedDevicesHeader(BtContext.hasPairedDevice);
|
||||
|
||||
// paired devices list
|
||||
BtContext.observe('state', this._updatePairedDevicesList.bind(this));
|
||||
this._updatePairedDevicesList(BtContext.state);
|
||||
|
||||
// found devices list
|
||||
BtContext.observe('state', this._updateFoundDevicesList.bind(this));
|
||||
this._updateFoundDevicesList(BtContext.state);
|
||||
|
||||
this._pairedDevicesListView.enabled = true;
|
||||
this._foundDevicesListView.enabled = true;
|
||||
|
||||
// search
|
||||
BtContext.observe('state', this._updateSearchItem.bind(this));
|
||||
this._updateSearchItem(BtContext.state);
|
||||
|
||||
BtContext.observe('discovering', this._updateSearchingItem.bind(this));
|
||||
this._updateSearchingItem(BtContext.discovering);
|
||||
},
|
||||
|
||||
/**
|
||||
* Start discovery devices while the search button is clicked.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf DevicePickerPanel
|
||||
*/
|
||||
_onHeaderActionCloseClick: function btdpp__onHeaderActionCloseClick() {
|
||||
debug('_onHeaderActionCloseClick():');
|
||||
// Emit 'cancelSelection' event to outer modules.
|
||||
var event = {
|
||||
type: 'cancelSelection'
|
||||
};
|
||||
this._emitEvent(event);
|
||||
},
|
||||
|
||||
/**
|
||||
* Start discovery devices while the search button is clicked.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf DevicePickerPanel
|
||||
*/
|
||||
_onSearchButtonClick: function btdpp__onSearchButtonClick() {
|
||||
debug('_onSearchButtonClick():');
|
||||
BtContext.startDiscovery().then(() => {
|
||||
debug('_onSearchButtonClick(): startDiscovery successfully');
|
||||
}, (reason) => {
|
||||
debug('_onSearchButtonClick(): startDiscovery failed, ' +
|
||||
'reason = ' + reason);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* While the paired device is clicked, emit event to outer modules with
|
||||
* the device item.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf DevicePickerPanel
|
||||
*/
|
||||
_onPairedDeviceItemClick:
|
||||
function btdpp__onPairedDeviceItemClick(deviceItem) {
|
||||
debug('_onPairedDeviceItemClick(): deviceItem.address = ' +
|
||||
deviceItem.address);
|
||||
debug('_onPairedDeviceItemClick(): deviceItem.paired = ' +
|
||||
deviceItem.paired);
|
||||
// Emit 'devicePicked' event to outer modules with the device item.
|
||||
var event = {
|
||||
type: 'devicePicked',
|
||||
detail: {
|
||||
address: deviceItem.address
|
||||
}
|
||||
};
|
||||
this._emitEvent(event);
|
||||
},
|
||||
|
||||
/**
|
||||
* Pair with the clicked device. While the device is paired successfully,
|
||||
* emit event to outer modules with the device item.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf DevicePickerPanel
|
||||
*/
|
||||
_onFoundDeviceItemClick:
|
||||
function btdpp__onFoundDeviceItemClick(deviceItem) {
|
||||
debug('_onFoundDeviceItemClick(): deviceItem.address = ' +
|
||||
deviceItem.address);
|
||||
// Update device pairing status first.
|
||||
deviceItem.paired = 'pairing';
|
||||
// Pair with the remote device.
|
||||
BtContext.pair(deviceItem.address).then(() => {
|
||||
debug('_onFoundDeviceItemClick(): pair successfully');
|
||||
// Emit 'devicePicked' event to outer modules with the
|
||||
// paired device item.
|
||||
var event = {
|
||||
type: 'devicePicked',
|
||||
detail: {
|
||||
address: deviceItem.address
|
||||
}
|
||||
};
|
||||
this._emitEvent(event);
|
||||
}, (reason) => {
|
||||
debug('_onFoundDeviceItemClick(): pair failed, ' +
|
||||
'reason = ' + reason);
|
||||
// Reset the paired status back to false,
|
||||
// since the 'pairing' status is given in Gaia side.
|
||||
deviceItem.paired = false;
|
||||
// Show alert message while pair device failed.
|
||||
this._alertPairFailedErrorMessage(reason);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Show an alert dialog since the device is failed in pairing process.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf DevicePickerPanel
|
||||
*/
|
||||
_alertPairFailedErrorMessage:
|
||||
function btdpp__alertPairFailedErrorMessage(errorReason) {
|
||||
var _ = navigator.mozL10n.get;
|
||||
var msg = _('error-pair-title'); // This is a default message.
|
||||
if (errorReason === 'Repeated Attempts') {
|
||||
msg = msg + '\n' + _('error-pair-toofast');
|
||||
} else if (errorReason === 'Authentication Failed') {
|
||||
msg = msg + '\n' + _('error-pair-pincode');
|
||||
}
|
||||
window.alert(msg);
|
||||
},
|
||||
|
||||
/**
|
||||
* A function to show/hide paired devices header.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf DevicePickerPanel
|
||||
*/
|
||||
_updatePairedDevicesHeader:
|
||||
function btdpp__updatePairedDevicesHeader(hasPairedDevice) {
|
||||
debug('_updatePairedDevicesHeader(): ' +
|
||||
'callback from observe "hasPairedDevice" = ' + hasPairedDevice);
|
||||
this._elements.paired.pairedDevicesHeader.hidden =
|
||||
!(hasPairedDevice && (BtContext.state === 'enabled'));
|
||||
},
|
||||
|
||||
/**
|
||||
* A function to show/hide paired devices list.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf DevicePickerPanel
|
||||
*/
|
||||
_updatePairedDevicesList: function btdpp__updatePairedDevicesList(state) {
|
||||
debug('_updatePairedDevicesList(): ' +
|
||||
'callback from observe "state" = ' + state);
|
||||
this._elements.paired.pairedDevicesHeader.hidden =
|
||||
!((state === 'enabled') && (BtContext.hasPairedDevice));
|
||||
this._elements.paired.pairedDevicesList.hidden = (state !== 'enabled');
|
||||
},
|
||||
|
||||
/**
|
||||
* A function to show/hide found devices list.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf DevicePickerPanel
|
||||
*/
|
||||
_updateFoundDevicesList: function btdpp__updateFoundDevicesList(state) {
|
||||
debug('_updateFoundDevicesList(): ' +
|
||||
'callback from observe "state" = ' + state);
|
||||
this._elements.found.foundDevicesHeader.hidden = (state !== 'enabled');
|
||||
this._elements.found.foundDevicesList.hidden = (state !== 'enabled');
|
||||
},
|
||||
|
||||
/**
|
||||
* A function to show/hide search item.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf DevicePickerPanel
|
||||
*/
|
||||
_updateSearchItem: function btdpp__updateSearchItem(state) {
|
||||
debug('_updateSearchItem(): ' +
|
||||
'callback from observe "state" = ' + state);
|
||||
this._elements.search.searchItem.hidden = (state !== 'enabled');
|
||||
},
|
||||
|
||||
/**
|
||||
* A function to show/hide searching description item.
|
||||
*
|
||||
* @access private
|
||||
* @memberOf DevicePickerPanel
|
||||
*/
|
||||
_updateSearchingItem: function btdpp__updateSearchingItem(discovering) {
|
||||
debug('_updateSearchingItem(): ' +
|
||||
'callback from observe "discovering" = ' + discovering);
|
||||
this._elements.search.searchingItem.hidden = !discovering;
|
||||
this._elements.search.searchButton.disabled = discovering;
|
||||
},
|
||||
|
||||
/**
|
||||
* A function to emit event to each registered listener by the input type.
|
||||
*
|
||||
* @memberOf DevicePickerPanel
|
||||
* @param {Object} options
|
||||
* @param {String} options.type - type of event name
|
||||
* @param {Object} options.detail - the object pass to the listener
|
||||
*/
|
||||
_emitEvent: function btdpp__emitEvent(options) {
|
||||
this._listeners[options.type].forEach(function(listener) {
|
||||
listener(options);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* The method will provide event listener for outer modules to regist.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf DevicePickerPanel
|
||||
* @param {String} eventName
|
||||
* @param {Function} callback
|
||||
*/
|
||||
addEventListener: function btdpp_addEventListener(eventName, callback) {
|
||||
if (callback && (this._listeners.hasOwnProperty(eventName))) {
|
||||
this._listeners[eventName].push(callback);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The method will provide event listener for outer modules to un-regist.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf DevicePickerPanel
|
||||
* @param {String} eventName
|
||||
* @param {Function} callback
|
||||
*/
|
||||
removeEventListener:
|
||||
function btdpp_removeEventListener(eventName, callback) {
|
||||
if (callback && (this._listeners.hasOwnProperty(eventName))) {
|
||||
var index = this._listeners[eventName].indexOf(callback);
|
||||
if (index >= 0) {
|
||||
this._listeners[eventName].splice(index, 1);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The setter will show/hide the device picker view for outer modules.
|
||||
*
|
||||
* @access public
|
||||
* @memberOf DevicePickerPanel
|
||||
* @param {Boolean} enabled
|
||||
*/
|
||||
set visible(enabled) {
|
||||
// display device picker view
|
||||
this._elements.view.viewDevicePicker.hidden = !enabled;
|
||||
}
|
||||
};
|
||||
|
||||
DevicePickerPanel._init();
|
||||
return DevicePickerPanel;
|
||||
});
|
||||
39
gaia/apps/bluetooth/js/views/pair_expired_dialog.js
Executable file
39
gaia/apps/bluetooth/js/views/pair_expired_dialog.js
Executable file
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* PairExpiredDialog is responsible for:
|
||||
* Notify a user that the earlier pairing request which fired in
|
||||
* notification center is expired. There is a prompt handling via here.
|
||||
*/
|
||||
define(function(require) {
|
||||
'use strict';
|
||||
|
||||
var PairExpiredDialog = {
|
||||
init: function() {
|
||||
this.dialog = document.getElementById('pairing-request-timeout');
|
||||
this.confirmBtn =
|
||||
document.getElementById('incoming-pairing-timeout-confirm');
|
||||
},
|
||||
|
||||
showConfirm: function(callback) {
|
||||
var self = this;
|
||||
this.confirmBtn.onclick = function() {
|
||||
self.close();
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
this.dialog.hidden = false;
|
||||
},
|
||||
|
||||
get isVisible() {
|
||||
return (!this.dialog.hidden);
|
||||
},
|
||||
|
||||
close: function() {
|
||||
this.dialog.hidden = true;
|
||||
}
|
||||
};
|
||||
|
||||
PairExpiredDialog.init();
|
||||
return PairExpiredDialog;
|
||||
});
|
||||
|
||||
219
gaia/apps/bluetooth/js/views/pair_view.js
Executable file
219
gaia/apps/bluetooth/js/views/pair_view.js
Executable file
|
|
@ -0,0 +1,219 @@
|
|||
/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil -*- */
|
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
||||
/* global getTruncated */
|
||||
/* exported Pairview */
|
||||
|
||||
'use strict';
|
||||
|
||||
var Pairview = {
|
||||
/**
|
||||
* flag for debugging.
|
||||
*/
|
||||
_debug: false,
|
||||
|
||||
/**
|
||||
* remote device name to pair with.
|
||||
*/
|
||||
_remoteDeviceName: null,
|
||||
|
||||
/**
|
||||
* device authentication method
|
||||
*/
|
||||
_pairMethod: null,
|
||||
|
||||
/**
|
||||
* event handler of pairing request
|
||||
*/
|
||||
_options: null,
|
||||
|
||||
_passkey: '',
|
||||
|
||||
pairview: document.getElementById('pair-view'),
|
||||
|
||||
nameLabel: document.getElementById('label-name'),
|
||||
pairDescription: document.getElementById('pair-description'),
|
||||
pairButton: document.getElementById('button-pair'),
|
||||
closeButton: document.getElementById('button-close'),
|
||||
|
||||
comfirmationItem: document.getElementById('confirmation-method'),
|
||||
pinInputItem: document.getElementById('pin-input-method'),
|
||||
|
||||
passkey: document.getElementById('passkey'),
|
||||
pinInput: document.getElementById('pin-input'),
|
||||
|
||||
get isFullAttentionMode() {
|
||||
return (window.innerHeight > 200);
|
||||
},
|
||||
|
||||
show: function pv_show() {
|
||||
if (!this.isFullAttentionMode) {
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
|
||||
var _ = navigator.mozL10n.get;
|
||||
this.pairButton.addEventListener('click', this);
|
||||
this.closeButton.addEventListener('click', this);
|
||||
window.addEventListener('resize', this);
|
||||
|
||||
this._remoteDeviceName = this._remoteDeviceName || _('unnamed-device');
|
||||
var truncatedDeviceName = getTruncated(this._remoteDeviceName, {
|
||||
node: this.nameLabel,
|
||||
maxLine: 2,
|
||||
ellipsisIndex: 3
|
||||
});
|
||||
|
||||
this.nameLabel.textContent = truncatedDeviceName;
|
||||
this.pairview.hidden = false;
|
||||
|
||||
// Since pairing process is migrated from Settings app to Bluetooth app,
|
||||
// there is no way to identify the pairing request in active/passive mode.
|
||||
// In order to let the pairing messsage consistency,
|
||||
// given the pairing mode to be passive.
|
||||
var stringName = 'passive-pair-' + this._pairMethod;
|
||||
this.pairDescription.textContent =
|
||||
_(stringName, {device: truncatedDeviceName});
|
||||
|
||||
switch (this._pairMethod) {
|
||||
case 'displaypasskey':
|
||||
case 'confirmation':
|
||||
this.passkey.textContent = this._passkey;
|
||||
this.comfirmationItem.hidden = false;
|
||||
this.pinInputItem.hidden = true;
|
||||
break;
|
||||
case 'enterpincode':
|
||||
this.pinInputItem.hidden = false;
|
||||
this.comfirmationItem.hidden = true;
|
||||
this.pinInput.focus();
|
||||
break;
|
||||
case 'consent':
|
||||
this.comfirmationItem.hidden = false;
|
||||
this.pinInputItem.hidden = true;
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* It is used to init pairing information in this pair view.
|
||||
*
|
||||
* @memberOf Pairview
|
||||
* @access public
|
||||
* @param {String} method - method of this pairing request
|
||||
* @param {Object} options
|
||||
* @param {String} options.deviceName - name of the remote bluetooth device
|
||||
* @param {BluetoothPairingHandle} options.handle - property handle that
|
||||
carries specific method
|
||||
to reply by user.
|
||||
*/
|
||||
init: function pv_init(method, options) {
|
||||
this._pairMethod = method;
|
||||
this._options = options;
|
||||
this._remoteDeviceName = options.deviceName;
|
||||
|
||||
if (options.handle && options.handle.passkey) {
|
||||
var passkey = options.handle.passkey;
|
||||
var len = passkey.toString().length;
|
||||
var zeros = (len < 6) ? (new Array((6 - len) + 1)).join('0') : '';
|
||||
this._passkey = zeros + passkey;
|
||||
}
|
||||
|
||||
// show() only until the page is localized.
|
||||
navigator.mozL10n.once(Pairview.show.bind(Pairview));
|
||||
},
|
||||
|
||||
close: function pv_close() {
|
||||
// Since user clicked close button, we reject pairing request.
|
||||
// Because the reject() interface of pairing requests are same, we do same
|
||||
// operation here.
|
||||
this._options.handle.reject().then(() => {
|
||||
this.debug('Resolved in reject ' + this._pairMethod + ' request.');
|
||||
// Close window by self.
|
||||
window.close();
|
||||
}, (aReason) => {
|
||||
this.debug('Rejected in reject ' + this._pairMethod +
|
||||
' request with reason: ' + aReason);
|
||||
// Close window by self.
|
||||
window.close();
|
||||
});
|
||||
},
|
||||
|
||||
closeInput: function pv_closeInput() {
|
||||
if (!this.pinInputItem.hidden) {
|
||||
this.pinInput.blur();
|
||||
}
|
||||
},
|
||||
|
||||
handleEvent: function pv_handleEvent(evt) {
|
||||
var _ = navigator.mozL10n.get;
|
||||
if (!evt.target) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (evt.type) {
|
||||
case 'click':
|
||||
evt.preventDefault();
|
||||
|
||||
switch (evt.target.id) {
|
||||
case 'button-pair':
|
||||
this.pairDescription.textContent = _('device-status-waiting');
|
||||
this.pairButton.disabled = true;
|
||||
this.closeButton.disabled = true;
|
||||
|
||||
switch (this._pairMethod) {
|
||||
case 'displaypasskey':
|
||||
// Do nothing here since the pairing method display passkey only
|
||||
// Close window by self.
|
||||
window.close();
|
||||
break;
|
||||
case 'enterpincode':
|
||||
var pinCode = this.pinInput.value;
|
||||
this._options.handle.setPinCode(pinCode).then(() => {
|
||||
this.debug('Resolved setPinCode operation');
|
||||
// Close window by self.
|
||||
window.close();
|
||||
}, (aReason) => {
|
||||
this.debug('Rejected setPinCode with reason: ' + aReason);
|
||||
// Close window by self.
|
||||
window.close();
|
||||
});
|
||||
break;
|
||||
case 'confirmation':
|
||||
case 'consent':
|
||||
this._options.handle.accept().then(() => {
|
||||
this.debug('Resolved in ' + this._pairMethod + ' request');
|
||||
// Close window by self.
|
||||
window.close();
|
||||
}, (aReason) => {
|
||||
this.debug('Rejected in ' + this._pairMethod + ' request ' +
|
||||
' with reason: ' + aReason);
|
||||
// Close window by self.
|
||||
window.close();
|
||||
});
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'button-close':
|
||||
this.close();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'resize':
|
||||
// XXX: this is hack that we have to close the attention in this case,
|
||||
// while in most other cases, we would just change it into an active
|
||||
// status bar
|
||||
if (!this.isFullAttentionMode) {
|
||||
this.close();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
debug: function(msg) {
|
||||
if (this._debug) {
|
||||
console.log('Pairview(): ' + msg);
|
||||
}
|
||||
}
|
||||
};
|
||||
51
gaia/apps/bluetooth/js/views/turn_bluetooth_on_dialog.js
Executable file
51
gaia/apps/bluetooth/js/views/turn_bluetooth_on_dialog.js
Executable file
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* TurnBluetoothOnDialog is responsible for:
|
||||
* A confirmation dialog to ask user to turn Bluetooth on or not.
|
||||
* There is a confirmation handling via here.
|
||||
*/
|
||||
define(function() {
|
||||
'use strict';
|
||||
|
||||
var TurnBluetoothOnDialog = {
|
||||
init: function() {
|
||||
this.dialog = document.getElementById('enable-bluetooth-view');
|
||||
this.cancelBtn =
|
||||
document.getElementById('enable-bluetooth-button-cancel');
|
||||
this.confirmBtn =
|
||||
document.getElementById('enable-bluetooth-button-turn-on');
|
||||
},
|
||||
|
||||
showConfirm: function() {
|
||||
return new Promise((resolve) => {
|
||||
this.cancelBtn.onclick = (evt) => {
|
||||
if (evt) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
this.close();
|
||||
resolve('cancel');
|
||||
};
|
||||
|
||||
this.confirmBtn.onclick = (evt) => {
|
||||
if (evt) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
this.close();
|
||||
resolve('confirm');
|
||||
};
|
||||
|
||||
this.dialog.hidden = false;
|
||||
});
|
||||
},
|
||||
|
||||
get isVisible() {
|
||||
return (!this.dialog.hidden);
|
||||
},
|
||||
|
||||
close: function() {
|
||||
this.dialog.hidden = true;
|
||||
}
|
||||
};
|
||||
|
||||
TurnBluetoothOnDialog.init();
|
||||
return TurnBluetoothOnDialog;
|
||||
});
|
||||
Loading…
Reference in a new issue