'use strict'; /* global Service, LockScreenWindow, LockScreenInputWindow */ /* global LazyLoader */ (function(exports) { /** * Manage LockScreenWindow apps. This is a subset of the AppWindow manager, * and will not handle most cases the later one would handle. Only those * meaningful cases, like secure app open, close, requesting kill all apps * or turn the secure mode on/off, would be handled. However, if we need * to handle cases in the future, we would extend this manager. * * So far the LockScreenWindowManager would only manager 1 secure app at once, * but there're already some designations for multiple apps. * * @constructor LockScreenWindowManager */ var LockScreenWindowManager = function() {}; LockScreenWindowManager.prototype.setup = function lswm_setup() { /** * @memberof LockScreenWindowManager# * @prop {DOMElement} windows - the `#windows` element, which is the same * element that the would AppWindowManager use. * @prop {DOMElement} screen - the `#screen` element. */ this.elements = { windows: null, screen: null }; /** * @memberof LockScreenWindowManager# */ this.states = { ready: false, FTUOccurs: false, enabled: true, instance: null, windowCreating: false }; /** * @memberof LockScreenWindowManager# */ this.configs = { inputWindow: { // Before we put things inside an iframe, do not resize LW. resizeMode: false, get height() { return 300; } }, listens: ['lockscreen-request-unlock', 'lockscreen-appcreated', 'lockscreen-appterminated', 'lockscreen-appclose', 'screenchange', 'system-resize', 'ftuopen', 'ftudone', 'overlaystart', 'showlockscreenwindow', 'secure-appclosed', 'lockscreen-request-inputpad-open', 'lockscreen-request-inputpad-close' ] }; }; LockScreenWindowManager.prototype.name = 'LockScreenWindowManager'; LockScreenWindowManager.prototype.EVENT_PREFIX = 'lockscreenwindowmanager'; /** * This function will be invoked by hierarchyManager. */ LockScreenWindowManager.prototype.getActiveWindow = function() { return this.isActive() ? this.states.instance : null; }; LockScreenWindowManager.prototype.locked = function() { return this.isActive(); }; /** * To initialize the class instance (register events, observe settings, etc.) */ LockScreenWindowManager.prototype.start = function lwm_start() { this.setup(); this.startEventListeners(); this.startObserveSettings(); this.initElements(); Service.register('unlock', this); Service.register('lock', this); Service.request('registerHierarchy', this); Service.registerState('locked', this); }; LockScreenWindowManager.prototype._handle_home = function() { if (this.isActive()) { // XXX: I don't want to change the order of event registration // at this early-refactoring stage, so do this to minimize the // risk and complete the work. window.dispatchEvent( new CustomEvent('lockscreen-notify-homepressed')); return false; } return true; }; LockScreenWindowManager.prototype._handle_holdhome = function() { if (this.isActive()) { return false; } return true; }; LockScreenWindowManager.prototype.respondToHierarchyEvent = function(evt) { if (this['_handle_' + evt.type]) { return this['_handle_' + evt.type](evt); } else { return true; } }; /** * @listens lockscreen-appcreated - when a lockscreen app got created, it * would fire this event. * @listens lockscreen-appterminated - when a lockscreen app got really * closed, it would fire this event. * @listens lockscreen-apprequestclose - when a lockscreen app has been * called to close itself, the event * would be fired * @listens screenchange - means to initialize the lockscreen and its window * @this {LockScreenWindowManager} * @memberof LockScreenWindowManager */ LockScreenWindowManager.prototype.handleEvent = function lwm_handleEvent(evt) { var app = null; switch (evt.type) { case 'overlaystart': if (this.isActive()) { this.states.instance.setVisible(false); } break; case 'showlockscreenwindow': if (this.isActive()) { this.states.instance.setVisible(true); } break; case 'ftuopen': this.states.FTUOccurs = true; // Need immediatly unlocking (hide window). this.closeApp(true); break; case 'ftudone': this.states.FTUOccurs = false; break; case 'lockscreen-request-unlock': this.responseUnlock(evt.detail); break; case 'lockscreen-appcreated': app = evt.detail; this.registerApp(app); break; case 'lockscreen-appterminated': app = evt.detail; this.unregisterApp(app); break; case 'secure-appclosed': this.states.instance.lockOrientation(); break; case 'system-resize': if (this.states.instance && this.states.instance.isActive()) { var p = this.states.instance.resize(); if (typeof evt.detail.waitUntil === 'function') { evt.detail.waitUntil(p); } } break; case 'screenchange': // The screenchange may be invoked by proximity sensor, // or the power button. If it's caused by the proximity sensor, // we should not open the LockScreen, because the user may stay // in another app, not the LockScreen. if ('proximity' !== evt.detail.screenOffBy && !this.states.FTUOccurs && this.states.ready) { // The app would be inactive while screen off. this.states.enabled && this.openApp(); if (evt.detail.screenEnabled && this.states.instance && this.isActive() && !Service.query('SecureWindowManager.isActive')) { // In theory listen to 'visibilitychange' event can solve this // issue, since it would be fired at the correct moment that // we can lock the orientation successfully, but this event // would not be received when user press the button twice // quickly, so we need to keep this workaround. this.states.instance.lockOrientation(); } } break; case 'lockscreen-request-inputpad-open': this.onInputpadOpen(); break; case 'lockscreen-request-inputpad-close': this.onInputpadClose(); break; } }; /** * @private * @this {LockScreenWindowManager} * @memberof LockScreenWindowManager */ LockScreenWindowManager.prototype.initElements = function lwm_initElements() { var selectors = { windows: 'windows', screen: 'screen'}; for (var name in selectors) { var id = selectors[name]; this.elements[name] = document.getElementById(id); } }; /** * Hook observers of settings to allow or ban window opening. * * @private * @this {LockScreenWindowManager} * @memberof LockScreenWindowManager */ LockScreenWindowManager.prototype.startObserveSettings = function lwm_startObserveSettings() { var enabledListener = this.handleEnable.bind(this); // FIXME(ggp) this is currently used by Find My Device to force locking. // Should be replaced by a proper IAC API in bug 992277. var lockListener = (event) => { if (true === event.settingValue) { this.openApp(); } }; window.SettingsListener.observe('lockscreen.enabled', true, enabledListener); // We are only interested in changes to the setting, rather // than its value, so just observe it instead of using SettingsListener navigator.mozSettings.addObserver('lockscreen.lock-immediately', lockListener); }; /** * Hook listeners of events this manager interested in. * * @private * @this {LockScreenWindowManager} * @memberof LockScreenWindowManager */ LockScreenWindowManager.prototype.startEventListeners = function lwm_startEventListeners() { this.configs.listens.forEach((function _initEvent(type) { self.addEventListener(type, this); }).bind(this)); }; /** * Remove listeners of events this manager interested in. * * @private * @this {LockScreenWindowManager} * @memberof LockScreenWindowManager */ LockScreenWindowManager.prototype.stopEventListeners = function lwm_stopEventListeners() { this.configs.listens.forEach((function _unbind(ename) { self.removeEventListener(ename, this); }).bind(this)); }; /** * Close the lockscreen app. * If it's not enabled, would do nothing. * * @param {boolean} instant - true if instantly close. * @private * @this {LockScreenWindowManager} * @memberof LockScreenWindowManager */ LockScreenWindowManager.prototype.closeApp = function lwm_closeApp(instant) { if (!this.states.enabled || !this.isActive()) { return; } this.states.instance.close(instant ? 'immediate': undefined); this.elements.screen.classList.remove('locked'); this.toggleLockedSetting(false); this.publish(this.EVENT_PREFIX + '-deactivated', this); }; /** * Open the lockscreen app. * If it's necessary, would create a new window. * If it's not enabled, would do nothing. * * @private * @this {LockScreenWindowManager} * @memberof LockScreenWindowManager */ LockScreenWindowManager.prototype.openApp = function lwm_openApp() { if (!this.states.instance) { var app = this.createWindow(); app && app.open(); } else { this.states.instance.open(); } this.elements.screen.classList.add('locked'); this.toggleLockedSetting(true); this.publish(this.EVENT_PREFIX + '-activated', this); }; /** * Message passing method. Would publish to the whole System app. * * @private * @this {LockScreenWindowManager} * @memberof LockScreenWindowManager */ LockScreenWindowManager.prototype.publish = function lwm_publish(ne, detail) { var event = new CustomEvent(ne, { detail: detail }); window.dispatchEvent(event); }; /** * @private * @this {LockScreenWindowManager} * @memberof LockScreenWindowManager */ LockScreenWindowManager.prototype.registerApp = function lwm_registerApp(app) { this.states.instance = app; }; /** * @private * @this {LockScreenWindowManager} * @memberof LockScreenWindowManager */ LockScreenWindowManager.prototype.unregisterApp = function lwm_unregisterApp(app) { this.states.instance = null; }; /** * When screenchange hanneped, create LockScreen and LockScreenWindow * if it is needed. * * @private * @return {LockScreenWindow} * @this {LockScreenWindowManager} * @memberof LockScreenWindowManager */ LockScreenWindowManager.prototype.createWindow = function lwm_createWindow() { if (this.states.windowCreating) { return false; } this.states.windowCreating = true; var app = new LockScreenWindow(); // XXX: Before we can use real InputWindow and InputWindowManager, // we need this to LazyLoader.load(['js/lockscreen_input_window.js']).then(() => { app.inputWindow = new LockScreenInputWindow(); }).catch((err) => { console.error(err); }); this.states.windowCreating = false; return app; }; /** * First time we launch, we must check the init value of enabled, * to see if we need to open the window. * * @private * @this {LockScreenWindowManager} * @memberof LockScreenWindowManager */ LockScreenWindowManager.prototype.handleEnable = function(enabled) { this.states.ready = true; if (typeof(enabled) === String) { enabled = (enabled === 'true'); } this.states.enabled = enabled; }; LockScreenWindowManager.prototype.unlock = function lwm_unlock(detail) { // XXX: // There is a self-routing here: // Service.request('unlock') -> // LockscreenWindowManager#unlock -> // ['lockscreen-request-unlock'] -> // LockscreenWindowManager#responseUnlock | // VisibilityManager#firing showwindow // // We should just call responseUnlock here, // but VisibilityManager needs this event to notify // AppWindowManager to showwindow correctly; // The reason not using lockscreen-appclosing/lockscreen-appclosed // is the race of mozActivity launch coming from lockscreen // and homescreen will race to be opened and cause performance issue. // // We should adjust LockScreenWindow to use lockscreen-appclosing/closed // to have the activitiy/notification info hence we could change // VisibilityManager later to avoid this workaround. this.publish('lockscreen-request-unlock', detail); }; LockScreenWindowManager.prototype.responseUnlock = function lwm_responseUnlock(detail) { var forcibly = (detail && detail.forcibly) ? true : false; this.closeApp(forcibly); }; LockScreenWindowManager.prototype.lock = function lwm_lock(detail) { this.openApp(); }; LockScreenWindowManager.prototype.getInstance = function lwm_getInstance() { return this.states.instance; }; LockScreenWindowManager.prototype.isActive = function lwm_isActive() { if (null === this.states.instance) { return false; } else { return this.states.instance.isActive(); } }; LockScreenWindowManager.prototype.onInputpadOpen = function lwm_onInputpadOpen() { this.states.instance.inputWindow.open(); this.states.instance.inputWindow.setVisible(true); this.states.instance.resize(); }; LockScreenWindowManager.prototype.onInputpadClose = function lwm_onInputpadClose() { this.states.instance.inputWindow.close(); this.states.instance.resize(); }; LockScreenWindowManager.prototype.toggleLockedSetting = function lswm_toggleLockedSetting(value) { if (!window.navigator.mozSettings) { return; } window.SettingsListener.getSettingsLock().set({ 'lockscreen.locked': value }); }; /** @exports LockScreenWindowManager */ exports.LockScreenWindowManager = LockScreenWindowManager; })(window);