'use strict'; angular.module('copayApp.services').service('scannerService', function($log, $timeout, platformInfo, $rootScope, $window) { var isDesktop = !platformInfo.isCordova; var QRScanner = $window.QRScanner; var lightEnabled = false; var backCamera = true; // the plugin defaults to the back camera // Initalize known capabilities var isAvailable = isDesktop? false: true; // assume camera exists on mobile var hasPermission = isDesktop? true: false; // assume desktop has permission var isDenied = false; var isRestricted = false; var canEnableLight = false; var canChangeCamera = false; var canOpenSettings = false; function _checkCapabilities(status){ $log.debug('scannerService is reviewing platform capabilities...'); // Permission can be assumed on the desktop builds hasPermission = (isDesktop || status.authorized)? true: false; isDenied = status.denied? true : false; isRestricted = status.restricted? true : false; canEnableLight = status.canEnableLight? true : false; canChangeCamera = status.canChangeCamera? true : false; canOpenSettings = status.canOpenSettings? true : false; _logCapabilities(); } function _logCapabilities(){ function _orIsNot(bool){ return bool? '' : 'not '; } $log.debug('A camera is ' + _orIsNot(isAvailable) + 'available to this app.'); var access = 'not authorized'; if(hasPermission) access = 'authorized'; if(isDenied) access = 'denied'; if(isRestricted) access = 'restricted'; $log.debug('Camera access is ' + access + '.'); $log.debug('Support for opening device settings is ' + _orIsNot(canOpenSettings) + 'available on this platform.'); $log.debug('A light is ' + _orIsNot(canEnableLight) + 'available on this platform.'); $log.debug('A second camera is ' + _orIsNot(canChangeCamera) + 'available on this platform.'); } /** * Immediately return known capabilities of the current platform. */ this.getCapabilities = function(){ return { isAvailable: isAvailable, hasPermission: hasPermission, isDenied: isDenied, isRestricted: isRestricted, canEnableLight: canEnableLight, canChangeCamera: canChangeCamera, canOpenSettings: canOpenSettings }; }; var initializeStarted = false; /** * If camera access has been granted, pre-initialize the QRScanner. This method * can be safely called before the scanner is visible to improve perceived * scanner loading times. * * The `status` of QRScanner is returned to the callback. */ this.gentleInitialize = function(callback) { if(initializeStarted){ QRScanner.getStatus(function(status){ _completeInitialization(status, callback); }); return; } initializeStarted = true; $log.debug('Trying to pre-initialize QRScanner.'); if(!isDesktop){ QRScanner.getStatus(function(status){ _checkCapabilities(status); if(status.authorized){ $log.debug('Camera permission already granted.'); initialize(callback); } else { $log.debug('QRScanner not authorized, waiting to initalize.'); _completeInitialization(status, callback); } }); } else { $log.debug('Camera permission assumed on desktop.'); initialize(callback); } }; function initialize(callback){ $log.debug('Initializing scanner...'); QRScanner.prepare(function(err, status){ if(err){ $log.error(err); // does not return `status` if there is an error QRScanner.getStatus(function(status){ _completeInitialization(status, callback); }); } else { isAvailable = true; _completeInitialization(status, callback); } }); } this.initialize = initialize; // This could be much cleaner with a Promise API // (needs a polyfill for some platforms) var initializeCompleted = false; function _completeInitialization(status, callback){ _checkCapabilities(status); initializeCompleted = true; $rootScope.$emit('scannerServiceInitialized'); if(typeof callback === "function"){ callback(status); } } this.isInitialized = function(){ return initializeCompleted; }; this.initializeStarted = function(){ return initializeStarted; }; var nextHide = null; var nextDestroy = null; var hideAfterSeconds = 10; var destroyAfterSeconds = 5 * 60; /** * (Re)activate the QRScanner, and cancel the timeouts if present. * * The `status` of QRScanner is passed to the callback when activation * is complete. */ this.activate = function(callback) { $log.debug('Activating scanner...'); QRScanner.show(function(status){ _checkCapabilities(status); if(typeof callback === "function"){ callback(status); } }); if(nextHide !== null){ $timeout.cancel(nextHide); nextHide = null; } if(nextDestroy !== null){ $timeout.cancel(nextDestroy); nextDestroy = null; } }; /** * Start a new scan. * * The callback receives: (err, contents) */ this.scan = function(callback) { $log.debug('Scanning...'); QRScanner.scan(callback); }; this.pausePreview = function() { QRScanner.pausePreview(); }; this.resumePreview = function() { QRScanner.resumePreview(); }; /** * Deactivate the QRScanner. To balance user-perceived performance and power * consumption, this kicks off a countdown which will "sleep" the scanner * after a certain amount of time. * * The `status` of QRScanner is passed to the callback when deactivation * is complete. */ this.deactivate = function(callback) { $log.debug('Deactivating scanner...'); QRScanner.cancelScan(); nextHide = $timeout(_hide, hideAfterSeconds * 1000); nextDestroy = $timeout(_destroy, destroyAfterSeconds * 1000); }; // Natively hide the QRScanner's preview // On mobile platforms, this can reduce GPU/power usage // On desktop, this fully turns off the camera (and any associated privacy lights) function _hide(){ $log.debug('Scanner not in use for ' + hideAfterSeconds + ' seconds, hiding...'); QRScanner.hide(); } // Reduce QRScanner power/processing consumption by the maximum amount function _destroy(){ $log.debug('Scanner not in use for ' + destroyAfterSeconds + ' seconds, destroying...'); QRScanner.destroy(); } this.reinitialize = function(callback){ initializeCompleted = false; QRScanner.destroy(); initialize(callback); } /** * Toggle the device light (if available). * * The callback receives a boolean which is `true` if the light is enabled. */ this.toggleLight = function(callback) { $log.debug('Toggling light...'); if(lightEnabled){ QRScanner.disableLight(_handleResponse); } else { QRScanner.enableLight(_handleResponse); } function _handleResponse(err, status){ if(err){ $log.error(err); } else { lightEnabled = status.lightEnabled; var state = lightEnabled? 'enabled' : 'disabled'; $log.debug('Light ' + state + '.'); } callback(lightEnabled); } }; /** * Switch cameras (if a second camera is available). * * The `status` of QRScanner is passed to the callback when activation * is complete. */ this.toggleCamera = function(callback) { var nextCamera = backCamera? 1 : 0; function cameraToString(index){ return index === 1? 'front' : 'back'; // front = 1, back = 0 }; $log.debug('Toggling to the ' + cameraToString(nextCamera) + ' camera...'); QRScanner.useCamera(nextCamera, function(err, status){ if(err){ $log.error(err); } backCamera = status.currentCamera === 1? false : true; $log.debug('Camera toggled. Now using the ' + cameraToString(backCamera) + ' camera.'); callback(status); }); }; this.openSettings = function() { $log.debug('Attempting to open device settings...'); QRScanner.openSettings(); }; });