diff options
Diffstat (limited to 'apps/system/js/bluetooth_transfer.js')
-rw-r--r-- | apps/system/js/bluetooth_transfer.js | 511 |
1 files changed, 511 insertions, 0 deletions
diff --git a/apps/system/js/bluetooth_transfer.js b/apps/system/js/bluetooth_transfer.js new file mode 100644 index 0000000..47483f7 --- /dev/null +++ b/apps/system/js/bluetooth_transfer.js @@ -0,0 +1,511 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ +'use strict'; + +var BluetoothTransfer = { + bannerContainer: null, + pairList: { + index: [] + }, + _deviceStorage: navigator.getDeviceStorage('sdcard'), + _debug: false, + + get transferStatusList() { + delete this.transferStatusList; + return this.transferStatusList = + document.getElementById('bluetooth-transfer-status-list'); + }, + + get banner() { + delete this.banner; + return this.banner = document.getElementById('system-banner'); + }, + + init: function bt_init() { + // Bind message handler for transferring file callback + navigator.mozSetMessageHandler('bluetooth-opp-receiving-file-confirmation', + this.onReceivingFileConfirmation.bind(this) + ); + + // Listen to 'bluetooth-opp-transfer-start' from bluetooth.js + window.addEventListener('bluetooth-opp-transfer-start', + this.onUpdateProgress.bind(this, 'start') + ); + + navigator.mozSetMessageHandler('bluetooth-opp-update-progress', + this.onUpdateProgress.bind(this, 'progress') + ); + + // Listen to 'bluetooth-opp-transfer-complete' from bluetooth.js + window.addEventListener('bluetooth-opp-transfer-complete', + this.onTransferComplete.bind(this) + ); + this.bannerContainer = this.banner.firstElementChild; + }, + + getDeviceName: function bt_getDeviceName(address) { + var _ = navigator.mozL10n.get; + var length = this.pairList.index.length; + for (var i = 0; i < length; i++) { + if (this.pairList.index[i].address == address) + return this.pairList.index[i].name; + } + return _('unknown-device'); + }, + + getPairedDevice: function bt_getPairedDevice(callback) { + var adapter = Bluetooth.getAdapter(); + if (adapter == null) { + var msg = 'Cannot get Bluetooth adapter.'; + this.debug(msg); + return; + } + var self = this; + var req = adapter.getPairedDevices(); + req.onsuccess = function bt_getPairedSuccess() { + self.pairList.index = req.result; + var length = self.pairList.index.length; + if (length == 0) { + var msg = + 'There is no paired device! Please pair your bluetooth device first.'; + self.debug(msg); + return; + } + if (callback) { + callback(); + } + }; + req.onerror = function() { + var msg = 'Can not get paired devices from adapter.'; + self.debug(msg); + }; + }, + + debug: function bt_debug(msg) { + if (!this._debug) + return; + + console.log('[System Bluetooth Transfer]: ' + msg); + }, + + humanizeSize: function bt_humanizeSize(bytes) { + var _ = navigator.mozL10n.get; + var units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + var size, e; + if (bytes) { + e = Math.floor(Math.log(bytes) / Math.log(1024)); + size = (bytes / Math.pow(1024, e)).toFixed(2); + } else { + e = 0; + size = '0.00'; + } + return _('fileSize', { + size: size, + unit: _('byteUnit-' + units[e]) + }); + }, + + onReceivingFileConfirmation: function bt_onReceivingFileConfirmation(evt) { + // Prompt appears when a transfer request from a paired device is received. + var _ = navigator.mozL10n.get; + + var fileSize = evt.fileLength; + var self = this; + var icon = 'style/bluetooth_transfer/images/icon_bluetooth.png'; + + // Check storage is available or not before the prompt. + this.checkStorageSpace(fileSize, + function checkStorageSpaceComplete(isStorageAvailable, errorMessage) { + if (isStorageAvailable) { + NotificationHelper.send(_('notification-fileTransfer-title'), + _('notification-fileTransfer-description'), + icon, + function() { + UtilityTray.hide(); + self.showReceivePrompt(evt); + }); + } else { + self.showStorageUnavaliablePrompt(errorMessage); + } + }); + }, + + showReceivePrompt: function bt_showReceivePrompt(evt) { + var _ = navigator.mozL10n.get; + + var address = evt.address; + var fileName = evt.fileName; + var fileSize = this.humanizeSize(evt.fileLength); + var cancel = { + title: _('deny'), + callback: this.declineReceive.bind(this, address) + }; + + var confirm = { + title: _('transfer'), + callback: this.acceptReceive.bind(this, address) + }; + + var deviceName = ''; + var self = this; + this.getPairedDevice(function getPairedDeviceComplete() { + deviceName = self.getDeviceName(address); + CustomDialog.show(_('acceptFileTransfer'), + _('wantToReceiveFile', + { deviceName: deviceName, + fileName: fileName, + fileSize: fileSize }), + cancel, confirm); + }); + }, + + declineReceive: function bt_declineReceive(address) { + CustomDialog.hide(); + var adapter = Bluetooth.getAdapter(); + if (adapter != null) { + adapter.confirmReceivingFile(address, false); + } else { + var msg = 'Cannot get adapter from system Bluetooth monitor.'; + this.debug(msg); + } + }, + + acceptReceive: function bt_acceptReceive(address, fileSize) { + CustomDialog.hide(); + var adapter = Bluetooth.getAdapter(); + if (adapter != null) { + adapter.confirmReceivingFile(address, true); + } else { + var msg = 'Cannot get adapter from system Bluetooth monitor.'; + this.debug(msg); + } + }, + + showStorageUnavaliablePrompt: function bt_showStorageUnavaliablePrompt(msg) { + var _ = navigator.mozL10n.get; + var confirm = { + title: _('confirm'), + callback: function() { + CustomDialog.hide(); + } + }; + + var body = msg; + CustomDialog.show(_('cannotReceiveFile'), body, confirm); + }, + + checkStorageSpace: function bt_checkStorageSpace(fileSize, callback) { + if (!callback) + return; + + var _ = navigator.mozL10n.get; + var storage = this._deviceStorage; + + var availreq = storage.available(); + availreq.onsuccess = function(e) { + switch (availreq.result) { + case 'available': + // skip down to the code below + break; + case 'unavailable': + callback(false, _('sdcard-not-exist')); + return; + case 'shared': + callback(false, _('sdcard-in-use')); + return; + default: + callback(false, _('unknown-error')); + return; + } + + // If we get here, then the sdcard is available, so we need to find out + // if there is enough free space on it + var freereq = storage.freeSpace(); + freereq.onsuccess = function() { + if (freereq.result >= fileSize) + callback(true, ''); + else + callback(false, _('sdcard-no-space2')); + }; + freereq.onerror = function() { + callback(false, _('cannotGetStorageState')); + }; + }; + + availreq.onerror = function(e) { + callback(false, _('cannotGetStorageState')); + }; + }, + + onUpdateProgress: function bt_onUpdateProgress(mode, evt) { + switch (mode) { + case 'start': + var transferInfo = evt.detail.transferInfo; + this.initProgress(transferInfo); + break; + + case 'progress': + var address = evt.address; + var processedLength = evt.processedLength; + var fileLength = evt.fileLength; + var progress = 0; + if (fileLength == 0) { + //XXX: May need to handle unknow progress + } else if (processedLength > fileLength) { + // According Bluetooth spec., + // the processed length is a referenced value only. + // XXX: If processed length is bigger than file length, + // show an unknown progress + } else { + progress = processedLength / fileLength; + } + this.updateProgress(progress, evt); + break; + } + }, + + initProgress: function bt_initProgress(evt) { + var _ = navigator.mozL10n.get; + // Create progress dynamically in notification center + var address = evt.address; + var transferMode = + (evt.received == true) ? + _('bluetooth-receiving-progress') : _('bluetooth-sending-progress'); + var content = + '<img src="style/bluetooth_transfer/images/transfer.png" />' + + '<div class="bluetooth-transfer-progress">' + transferMode + '</div>' + + // XXX: Bug 804533 - [Bluetooth] + // Need sending/receiving icon for Bluetooth file transfer + '<progress value="0" max="1"></progress>'; + + var transferTask = document.createElement('div'); + transferTask.id = 'bluetooth-transfer-status'; + transferTask.className = 'notification'; + transferTask.setAttribute('data-id', address); + transferTask.innerHTML = content; + transferTask.addEventListener('click', + this.onCancelTransferTask.bind(this)); + this.transferStatusList.appendChild(transferTask); + }, + + updateProgress: function bt_updateProgress(value, evt) { + var address = evt.address; + var id = 'div[data-id="' + address + '"] progress'; + var progressEl = this.transferStatusList.querySelector(id); + progressEl.value = value; + }, + + removeProgress: function bt_removeProgress(evt) { + var address = evt.address; + var id = 'div[data-id="' + address + '"]'; + var finishedTask = this.transferStatusList.querySelector(id); + finishedTask.removeEventListener('click', + this.onCancelTransferTask.bind(this)); + this.transferStatusList.removeChild(finishedTask); + }, + + showBanner: function bt_showBanner(isComplete) { + var _ = navigator.mozL10n.get; + var status = (isComplete) ? 'complete' : 'failed'; + this.banner.addEventListener('animationend', function animationend() { + this.banner.removeEventListener('animationend', animationend); + this.banner.classList.remove('visible'); + }.bind(this)); + this.bannerContainer.textContent = _('bluetooth-file-transfer-result', + { status: status }); + this.banner.classList.add('visible'); + }, + + onCancelTransferTask: function bt_onCancelTransferTask(evt) { + var id = evt.target.dataset.id; + // Show confirm dialog for user to cancel transferring task + UtilityTray.hide(); + this.showCancelTransferPrompt(id); + }, + + showCancelTransferPrompt: function bt_showCancelTransferPrompt(address) { + var _ = navigator.mozL10n.get; + + var cancel = { + title: _('continue'), + callback: this.continueTransfer.bind(this) + }; + + var confirm = { + title: _('cancel'), + callback: this.cancelTransfer.bind(this, address) + }; + + CustomDialog.show(_('cancelFileTransfer'), _('cancelFileTransfer'), + cancel, confirm); + }, + + continueTransfer: function bt_continueTransfer() { + CustomDialog.hide(); + }, + + cancelTransfer: function bt_cancelTransfer(address) { + CustomDialog.hide(); + var adapter = Bluetooth.getAdapter(); + if (adapter != null) { + adapter.stopSendingFile(address); + } else { + var msg = 'Cannot get adapter from system Bluetooth monitor.'; + this.debug(msg); + } + }, + + onTransferComplete: function bt_onTransferComplete(evt) { + var transferInfo = evt.detail.transferInfo; + var _ = navigator.mozL10n.get; + // Remove transferring progress + this.removeProgress(transferInfo); + var fileName = + (transferInfo.fileName) ? transferInfo.fileName : _('unknown-file'); + var icon = 'style/bluetooth_transfer/images/icon_bluetooth.png'; + // Show banner and notification + if (transferInfo.success == true) { + // Show completed message of transferred result on the banner + this.showBanner(true); + if (transferInfo.received) { + // Received file can be opened only + // TODO: Need to modify the icon after visual provide + NotificationHelper.send(_('transferFinished-receivedSuccessful-title'), + fileName, + icon, + this.openReceivedFile.bind(this, transferInfo)); + } else { + NotificationHelper.send(_('transferFinished-sentSuccessful-title'), + fileName, + icon); + } + } else { + // Show failed message of transferred result on the banner + this.showBanner(false); + if (transferInfo.received) { + NotificationHelper.send(_('transferFinished-receivedFailed-title'), + fileName, + icon); + } else { + NotificationHelper.send(_('transferFinished-sentFailed-title'), + fileName, + icon); + } + } + }, + + openReceivedFile: function bt_openReceivedFile(evt) { + // Launch the gallery with an open activity to view this specific photo + // XXX: The prefix file path should be refined when API is ready to provide + var filePath = 'downloads/bluetooth/' + evt.fileName; + var contentType = evt.contentType; + var storageType = 'sdcard'; + var self = this; + var storage = navigator.getDeviceStorage(storageType); + var getreq = storage.get(filePath); + + getreq.onerror = function() { + var msg = 'failed to get file:' + + filePath + getreq.error.name + + a.error.name; + self.debug(msg); + }; + + getreq.onsuccess = function() { + var file = getreq.result; + // When we got the file by storage type of "sdcard" + // use the file.type to replace the empty fileType which is given by API + var fileType = ''; + var fileName = file.name; + if (contentType != '' && contentType != 'image/*') { + fileType = contentType; + } else { + var fileNameExtension = + fileName.substring(fileName.lastIndexOf('.') + 1); + if (file.type != '') { + fileType = file.type; + // Refine the file type to "audio/ogg" when the file format is *.ogg + if (fileType == 'video/ogg' && + (fileNameExtension.indexOf('ogg') != -1)) { + fileType == 'audio/ogg'; + } + } else { + // Parse Filename Extension to find out MIMETYPE + // Following formats are supported by Gallery and Music APPs + var imageFormatList = ['jpg', 'jpeg', 'png']; + var audioFormatList = ['mp3', 'ogg', 'aac', 'mp4', 'm4a']; + var imageFormatIndex = imageFormatList.indexOf(fileNameExtension); + switch (imageFormatIndex) { + case 0: + case 1: + // The file type of format *.jpg, *.jpeg should be "image/jpeg" + fileType = 'image/jpeg'; + break; + case 2: + // The file type of format *.png should be "image/png" + fileType = 'image/png'; + break; + } + + var audioFormatIndex = audioFormatList.indexOf(fileNameExtension); + switch (audioFormatIndex) { + case 0: + // The file type of format *.mp3 should be "audio/mpeg" + fileType = 'audio/mpeg'; + break; + case 1: + // The file type of format *.ogg should be "audio/ogg" + fileType = 'audio/ogg'; + break; + case 2: + case 3: + case 4: + // The file type of format *.acc, *.mp4, *.m4a + // should be "audio/mp4" + fileType = 'audio/mp4'; + break; + } + } + } + + var a = new MozActivity({ + name: 'open', + data: { + type: fileType, + blob: file, + // XXX: https://bugzilla.mozilla.org/show_bug.cgi?id=812098 + // Pass the file name for Music APP since it can not open blob + filename: file.name + } + }); + + a.onerror = function(e) { + var msg = 'open activity error:' + a.error.name; + self.debug(msg); + // Cannot identify MIMETYPE + // So, show cannot open file dialog with unknow media type + UtilityTray.hide(); + self.showUnknownMediaPrompt(fileName); + }; + a.onsuccess = function(e) { + var msg = 'open activity onsuccess'; + self.debug(msg); + }; + }; + }, + + showUnknownMediaPrompt: function bt_showUnknownMediaPrompt(fileName) { + var _ = navigator.mozL10n.get; + var confirm = { + title: _('confirm'), + callback: function() { + CustomDialog.hide(); + } + }; + + var body = _('unknownMediaTypeToOpen') + ' ' + fileName; + CustomDialog.show(_('cannotOpenFile'), body, confirm); + } +}; + +BluetoothTransfer.init(); |