VulpesOS/gaia/apps/bluetooth/js/deviceList.js
OverSu b38dea0597 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.
2026-09-30 23:13:03 +02:00

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
};
})();
});