/* global MozActivity, Notification, Service */ 'use strict'; (function(exports) { /** * This system module takes a screenshot of the currently running app * or homescreen and stores it with DeviceStorage when the user * presses the home and sleep buttons at the same time. It communicates * with gecko code running in b2g/chrome/content/shell.js using a private * event-based API. It is the gecko code that creates the screenshot. * * This script must be used with the defer attribute. * * @class Screenshot * */ function Screenshot() { this._started = false; } Screenshot.prototype = { /** @lends Screenshot */ /** * Assumption for making sure we have enough space to save the image. * * Assume that the maximum screenshot size is 4 bytes per device pixel * plus a bit extra. In practice, with compression, our PNG files will be * much smaller than this. * * @type {Number} * @memberof Screenshot.prototype */ MAX_SCREENSHOT_SIZE: window.innerWidth * window.devicePixelRatio * window.innerHeight * window.devicePixelRatio * 4 + 4096, /** * Start to handle screenshot events. * @memberof Screenshot.prototype */ start: function() { if (this._started) { throw 'Instance should not be start()\'ed twice.'; } this._started = true; Service.request('handleSystemMessageNotification', 'screenshot', this); window.addEventListener('volumedown+sleep', this); window.addEventListener('mozChromeEvent', this); }, /** * Stop handling screenshot events. * @memberof Screenshot.prototype */ stop: function() { if (!this._started) { throw 'Instance was never start()\'ed but stop() is called.'; } this._started = false; Service.request('unhandleSystemMessageNotification', 'screenshot', this); window.removeEventListener('volumedown+sleep', this); window.removeEventListener('mozChromeEvent', this); }, /** * Handle screenshot events. * @param {DOMEvent} evt DOM Event to handle. * @memberof Screenshot.prototype */ handleEvent: function(evt) { switch (evt.type) { case 'volumedown+sleep': this.takeScreenshot(); break; case 'mozChromeEvent': if (evt.detail.type === 'take-screenshot-success') { this.handleTakeScreenshotSuccess(evt.detail.file); } else if (evt.detail.type === 'take-screenshot-error') { this._notify('screenshotFailed', evt.detail.error); } break; default: console.debug('Unhandled event: ' + evt.type); break; } }, /** * Gets called when a system message notification for a screenshots is being * hit. **/ handleSystemMessageNotification: function(message) { this.openImage(message.body); this.closeSystemMessageNotification(message); }, closeSystemMessageNotification: function(msg) { Notification.get({ tag: msg.tag }).then(notifs => { notifs.forEach(notif => { if (notif.tag) { // Close notification with the matching tag if (notif.tag === msg.tag) { notif.close && notif.close(); } } else { // If we have notification without a tag, check on the body if (notif.body === msg.body) { notif.close && notif.close(); } } }); }); }, /** * Actually take a screenshot (by do some check and send a mozContentEvent.) * @memberof Screenshot.prototype */ takeScreenshot: function() { // Give feedback that the screenshot request was received navigator.vibrate(100); // We don't need device storage here, but check to see that // it is available before sending the screenshot request to chrome. // If device storage is available, the callback will be called. // Otherwise, an error message notification will be displayed. this._getDeviceStorage(function() { // Let chrome know we'd like a screenshot. // This is a completely non-standard undocumented API // for communicating with our chrome code. var screenshotProps = { detail: { type: 'take-screenshot' } }; window.dispatchEvent( new CustomEvent('mozContentEvent', screenshotProps)); }); }, openImage: function(filename) { this._getDeviceStorage(function(storage) { var request = storage.get(filename); request.onsuccess = function() { var imgblob = this.result; /*jshint nonew: false */ new MozActivity({ name: 'open', data: { type: imgblob.type, filename: filename, blob: imgblob, exitWhenHidden: true } }); }; }); }, /** * Handle the take-screenshot-success mozChromeEvent. * @param {Blob} file Blob object received from the event. * @memberof Screenshot.prototype */ handleTakeScreenshotSuccess: function(file) { try { this._getDeviceStorage(function(storage) { var d = new Date(); d = new Date(d.getTime() - d.getTimezoneOffset() * 60000); var filename = 'screenshots/' + d.toISOString().slice(0, -5).replace(/[:T]/g, '-') + '.png'; var saveRequest = storage.addNamed(file, filename); saveRequest.onsuccess = (function ss_onsuccess() { // Vibrate again when the screenshot is saved navigator.vibrate(100); // Display filename in a notification this._notify('screenshotSaved', filename, null, this.openImage.bind(this, filename)); }).bind(this); saveRequest.onerror = (function ss_onerror() { this._notify('screenshotFailed', saveRequest.error.name); }).bind(this); }); } catch (e) { console.log('exception in screenshot handler', e); this._notify('screenshotFailed', e.toString()); } }, /** * Get a DeviceStorage object and pass it to the callback. * Or, if device storage is not available, display a notification. * @param {Function} callback Callback to run. * @memberof Screenshot.prototype */ _getDeviceStorage: function(callback) { var storage = navigator.getDeviceStorage('pictures'); var availreq = storage.available(); availreq.onsuccess = (function() { var state = availreq.result; if (state === 'unavailable') { this._notify('screenshotFailed', null, 'screenshotNoSDCard'); } else if (state === 'shared') { this._notify('screenshotFailed', null, 'screenshotSDCardInUse'); } else if (state === 'available') { var freereq = storage.freeSpace(); freereq.onsuccess = (function() { if (freereq.result < this.MAX_SCREENSHOT_SIZE) { this._notify('screenshotFailed', null, 'screenshotSDCardLow'); } else { callback.call(this, storage); } }).bind(this); freereq.onerror = (function() { this._notify( 'screenshotFailed', freereq.error && freereq.error.name); }).bind(this); } }).bind(this); availreq.onerror = (function() { this._notify( 'screenshotFailed', availreq.error && availreq.error.name); }).bind(this); }, /** * Display a screenshot success or failure notification. * Localize the first argument, and localize the third if the second is null * @param {String} titleid l10n ID of the string to show. * @param {String} body Label to show as body, or null. * @param {String} bodyid l10n ID of the label to show as body. * @param {String} onClick Optional handler if the notification is clicked * @memberof Screenshot.prototype */ _notify: function notify(titleid, body, bodyid, onClick) { var title = navigator.mozL10n.get(titleid) || titleid; body = body || navigator.mozL10n.get(bodyid); var notification = new window.Notification(title, { body: body, icon: '/style/icons/Gallery.png', tag: 'screenshot:' + (new Date().getTime()), data: { systemMessageTarget: 'screenshot' } }); notification.onclick = function() { notification.close(); if (onClick) { onClick(); } }; } }; exports.Screenshot = Screenshot; }(window));