Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
398 lines
No EOL
13 KiB
JavaScript
Executable file
398 lines
No EOL
13 KiB
JavaScript
Executable file
/* -*- 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
|
|
};
|
|
})();
|
|
}); |