VulpesOS/assets/webapps/bluetooth.gaiamobile.org/js/modules/transfer_manager.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

349 lines
12 KiB
JavaScript

/*
* 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(['require','modules/bluetooth/bluetooth_adapter_manager','modules/bluetooth/bluetooth_context','views/cannot_transfer_dialog','views/device_picker_panel','views/turn_bluetooth_on_dialog'],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;
});