diff options
Diffstat (limited to 'apps/system/js/screen_manager.js')
-rw-r--r-- | apps/system/js/screen_manager.js | 503 |
1 files changed, 503 insertions, 0 deletions
diff --git a/apps/system/js/screen_manager.js b/apps/system/js/screen_manager.js new file mode 100644 index 0000000..9a69e8c --- /dev/null +++ b/apps/system/js/screen_manager.js @@ -0,0 +1,503 @@ +/* -*- 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 ScreenManager = { + /* + * return the current screen status + * Must not mutate directly - use toggleScreen/turnScreenOff/turnScreenOn. + * Listen to 'screenchange' event to properly handle status changes + * This value can be "out of sync" with real mozPower value, + * we do this to give screen some time to flash before actual turn off. + */ + screenEnabled: false, + + /* + * before idle-screen-off, invoke a nice dimming to the brightness + * to notify the user that the screen is about to be turn off. + * The user can cancel the idle-screen-off by touching the screen + * and by pressing a button (trigger onactive callback on Idle API) + * + */ + _inTransition: false, + + /* + * Whether the wake lock is enabled or not + */ + _screenWakeLocked: false, + + /* + * Whether the device light is enabled or not + * sync with setting 'screen.automatic-brightness' + */ + _deviceLightEnabled: true, + + /* + * Preferred brightness without considering device light nor dimming + * sync with setting 'screen.brightness' + */ + _userBrightness: 1, + _savedBrightness: 1, + + /* + * The auto-brightness algorithm will never set the screen brightness + * to a value smaller than this. 0.1 seems like a good screen brightness + * in a completely dark room on a Unagi. + */ + AUTO_BRIGHTNESS_MINIMUM: 0.1, + + /* + * This constant is used in the auto brightness algorithm. We take + * the base 10 logarithm of the incoming lux value from the light + * sensor and multiplied it by this constant. That value is used to + * compute a weighted average with the current brightness and + * finally that average brightess is and then clamped to the range + * [AUTO_BRIGHTNESS_MINIMUM, 1.0]. + * + * Making this value larger will increase the brightness for a given + * ambient light level. At a value of about .25, the screen will be + * at full brightness in sunlight or in a well-lighted work area. + * At a value of about .3, the screen will typically be at maximum + * brightness in outdoor daylight conditions, even when overcast. + */ + AUTO_BRIGHTNESS_CONSTANT: .27, + + /* + * When we change brightness we animate it smoothly. + * This constant is the number of milliseconds between adjustments + */ + BRIGHTNESS_ADJUST_INTERVAL: 20, + + /* + * When brightening or dimming the screen, this is how much we adjust + * the brightness value at a time. + */ + BRIGHTNESS_ADJUST_STEP: 0.04, + + /* + * Wait for _dimNotice milliseconds during idle-screen-off + */ + _dimNotice: 10 * 1000, + + /* + * We track the value of the idle timeout pref in this variable. + */ + _idleTimeout: 0, + _idleTimerId: 0, + + /* + * If the screen off is triggered by promixity during phon call then + * we need wake it up while phone is ended. + */ + _screenOffByProximity: false, + + /* + * Request wakelock during in_call state. + * To ensure turnScreenOff by proximity event is protected by wakelock for + * early suspend only. + */ + _cpuWakeLock: null, + + init: function scm_init() { + window.addEventListener('sleep', this); + window.addEventListener('wake', this); + + this.screen = document.getElementById('screen'); + + var self = this; + var power = navigator.mozPower; + + if (power) { + power.addWakeLockListener(function scm_handleWakeLock(topic, state) { + switch (topic) { + case 'screen': + self._screenWakeLocked = (state == 'locked-foreground'); + + if (self._screenWakeLocked) + // Turn screen on if wake lock is acquire + self.turnScreenOn(); + self._reconfigScreenTimeout(); + break; + + case 'cpu': + power.cpuSleepAllowed = (state != 'locked-foreground' && + state != 'locked-background'); + break; + } + }); + } + + this._firstOn = false; + SettingsListener.observe('screen.timeout', 60, + function screenTimeoutChanged(value) { + if (typeof value !== 'number') + value = parseInt(value); + self._idleTimeout = value; + self._setIdleTimeout(self._idleTimeout); + + if (!self._firstOn) { + self._firstOn = true; + + // During boot up, the brightness was set by bootloader as 0.5, + // Let's set the API value to that so setScreenBrightness() can + // dim nicely to value set by user. + power.screenBrightness = 0.5; + + // Turn screen on with dim. + self.turnScreenOn(false); + } + }); + + SettingsListener.observe('screen.automatic-brightness', true, + function deviceLightSettingChanged(value) { + self.setDeviceLightEnabled(value); + }); + + SettingsListener.observe('screen.brightness', 1, + function brightnessSettingChanged(value) { + self._userBrightness = value; + self.setScreenBrightness(value, false); + }); + + var telephony = window.navigator.mozTelephony; + if (telephony) { + telephony.addEventListener('callschanged', this); + } + }, + + // + // Automatically adjust the screen brightness based on the ambient + // light (in lux) measured by the device light sensor + // + autoAdjustBrightness: function scm_adjustBrightness(lux) { + var currentBrightness = this._targetBrightness; + + if (lux < 1) // Can't take the log of 0 or negative numbers + lux = 1; + + var computedBrightness = + Math.log(lux) / Math.LN10 * this.AUTO_BRIGHTNESS_CONSTANT; + + var clampedBrightness = Math.max(this.AUTO_BRIGHTNESS_MINIMUM, + Math.min(1.0, computedBrightness)); + + // If nothing changed, we're done. + if (clampedBrightness === currentBrightness) + return; + + this.setScreenBrightness(clampedBrightness, false); + }, + + handleEvent: function scm_handleEvent(evt) { + switch (evt.type) { + case 'devicelight': + if (!this._deviceLightEnabled || !this.screenEnabled || + this._inTransition) + return; + this.autoAdjustBrightness(evt.value); + break; + + case 'sleep': + this.turnScreenOff(true); + break; + + case 'wake': + this.turnScreenOn(); + break; + + case 'userproximity': + this._screenOffByProximity = evt.near; + if (evt.near) { + this.turnScreenOff(true); + } else { + this.turnScreenOn(); + } + break; + + case 'callschanged': + var telephony = window.navigator.mozTelephony; + if (!telephony.calls.length) { + if (this._screenOffByProximity) { + this.turnScreenOn(); + } + + window.removeEventListener('userproximity', this); + this._screenOffByProximity = false; + + if (this._cpuWakeLock) { + this._cpuWakeLock.unlock(); + this._cpuWakeLock = null; + } + break; + } + + // If the _cpuWakeLock is already set we are in a multiple + // call setup, turning the screen on to let user see the + // notification. + if (this._cpuWakeLock) { + this.turnScreenOn(); + + break; + } + + // Enable the user proximity sensor once the call is connected. + var call = telephony.calls[0]; + call.addEventListener('statechange', this); + + break; + + case 'statechange': + var call = evt.target; + if (call.state !== 'connected') { + break; + } + + // The call is connected. Remove the statechange listener + // and enable the user proximity sensor. + call.removeEventListener('statechange', this); + + this._cpuWakeLock = navigator.requestWakeLock('cpu'); + window.addEventListener('userproximity', this); + break; + } + }, + + toggleScreen: function scm_toggleScreen() { + if (this.screenEnabled) { + this.turnScreenOff(); + } else { + this.turnScreenOn(); + } + }, + + turnScreenOff: function scm_turnScreenOff(instant) { + if (!this.screenEnabled) + return false; + + var self = this; + + // Remember the current screen brightness. We will restore it when + // we turn the screen back on. + self._savedBrightness = navigator.mozPower.screenBrightness; + + // Remove the cpuWakeLock if screen is not turned off by + // userproximity event. + if (!this._screenOffByProximity && this._cpuWakeLock) { + window.removeEventListener('userproximity', this); + this._cpuWakeLock.unlock(); + this._cpuWakeLock = null; + } + + var screenOff = function scm_screenOff() { + self._setIdleTimeout(0); + + window.removeEventListener('devicelight', self); + + self.screenEnabled = false; + self._inTransition = false; + self.screen.classList.add('screenoff'); + setTimeout(function realScreenOff() { + self.setScreenBrightness(0, true); + // Sometimes the ScreenManager.screenEnabled and mozPower.screenEnabled + // values are out of sync. Since the rest of the world relies only on + // the value of ScreenManager.screenEnabled it can be some situations + // where the screen is off but ScreenManager think it is on... (see + // bug 822463). Ideally a callback should have been used, like + // ScreenManager.getScreenState(function(value) { ...} ); but there + // are too many places to change that for now. + self.screenEnabled = false; + navigator.mozPower.screenEnabled = false; + }, 20); + + self.fireScreenChangeEvent(); + }; + + if (instant) { + if (!WindowManager.isFtuRunning()) { + screenOff(); + } + return true; + } + + this.setScreenBrightness(0.1, false); + this._inTransition = true; + setTimeout(function noticeTimeout() { + if (!self._inTransition) + return; + + screenOff(); + }, self._dimNotice); + + return true; + }, + + turnScreenOn: function scm_turnScreenOn(instant) { + if (this.screenEnabled) { + if (this._inTransition) { + // Cancel the dim out + this._inTransition = false; + this.setScreenBrightness(this._savedBrightness, true); + this._reconfigScreenTimeout(); + } + return false; + } + + // Set the brightness before the screen is on. + this.setScreenBrightness(this._savedBrightness, instant); + + // If we are in a call and there is no cpuWakeLock, + // we would have to get one here. + var telephony = window.navigator.mozTelephony; + if (!this._cpuWakeLock && telephony && telephony.calls.length) { + telephony.calls.some(function checkCallConnection(call) { + if (call.state == 'connected') { + this._cpuWakeLock = navigator.requestWakeLock('cpu'); + window.addEventListener('userproximity', this); + return true; + } + return false; + }, this); + } + + // Actually turn the screen on. + var power = navigator.mozPower; + if (power) + power.screenEnabled = true; + this.screenEnabled = true; + this.screen.classList.remove('screenoff'); + + // Attaching the event listener effectively turn on the hardware + // device light sensor, which _must be_ done after power.screenEnabled. + if (this._deviceLightEnabled) + window.addEventListener('devicelight', this); + + this._reconfigScreenTimeout(); + this.fireScreenChangeEvent(); + + return true; + }, + + _reconfigScreenTimeout: function scm_reconfigScreenTimeout() { + // Remove idle timer if screen wake lock is acquired. + if (this._screenWakeLocked) { + this._setIdleTimeout(0); + // The screen should be turn off with shorter timeout if + // it was never unlocked. + } else if (LockScreen.locked) { + this._setIdleTimeout(10, true); + var self = this; + var stopShortIdleTimeout = function scm_stopShortIdleTimeout() { + window.removeEventListener('unlock', stopShortIdleTimeout); + window.removeEventListener('lockpanelchange', stopShortIdleTimeout); + self._setIdleTimeout(self._idleTimeout, false); + }; + + window.addEventListener('unlock', stopShortIdleTimeout); + window.addEventListener('lockpanelchange', stopShortIdleTimeout); + } else { + this._setIdleTimeout(this._idleTimeout, false); + } + }, + + setScreenBrightness: function scm_setScreenBrightness(brightness, instant) { + this._targetBrightness = brightness; + var power = navigator.mozPower; + if (!power) + return; + + // Make sure we don't have another brightness change scheduled + if (this._transitionBrightnessTimer) { + clearTimeout(this._transitionBrightnessTimer); + this._transitionBrightnessTimer = null; + } + + if (typeof instant !== 'boolean') + instant = true; + + if (instant) { + power.screenBrightness = brightness; + return; + } + + // transitionBrightness() is a looping function that will + // gracefully tune the brightness to _targetBrightness for us. + this.transitionBrightness(); + }, + + transitionBrightness: function scm_transitionBrightness() { + var self = this; + var power = navigator.mozPower; + var screenBrightness = power.screenBrightness; + var delta = this.BRIGHTNESS_ADJUST_STEP; + + // Is this the last time adjustment we need to make? + if (Math.abs(this._targetBrightness - screenBrightness) <= delta) { + power.screenBrightness = this._targetBrightness; + this._transitionBrightnessTimer = null; + return; + } + + if (screenBrightness > this._targetBrightness) + delta *= -1; + + screenBrightness += delta; + power.screenBrightness = screenBrightness; + + this._transitionBrightnessTimer = + setTimeout(function transitionBrightnessTimeout() { + self.transitionBrightness(); + }, this.BRIGHTNESS_ADJUST_INTERVAL); + }, + + setDeviceLightEnabled: function scm_setDeviceLightEnabled(enabled) { + if (!enabled && this._deviceLightEnabled) { + // Disabled -- set the brightness back to preferred brightness + this.setScreenBrightness(this._userBrightness, false); + } + this._deviceLightEnabled = enabled; + + if (!this.screenEnabled) + return; + + // Disable/enable device light sensor accordingly. + // This will also toggle the actual hardware, which + // must be done while the screen is on. + if (enabled) { + window.addEventListener('devicelight', this); + } else { + window.removeEventListener('devicelight', this); + } + }, + + _setIdleTimeout: function scm_setIdleTimeout(time, instant) { + window.clearIdleTimeout(this._idleTimerId); + + // Reset the idled state back to false. + this._idled = false; + + // 0 is the value used to disable idle timer by user and by us. + if (time === 0) + return; + + var self = this; + var idleCallback = function idle_proxy() { + self.turnScreenOff(instant); + }; + var activeCallback = function active_proxy() { + self.turnScreenOn(true); + }; + + this._idleTimerId = window.setIdleTimeout(idleCallback, + activeCallback, time * 1000); + }, + + fireScreenChangeEvent: function scm_fireScreenChangeEvent() { + var evt = new CustomEvent('screenchange', + { bubbles: true, cancelable: false, + detail: { screenEnabled: this.screenEnabled } }); + window.dispatchEvent(evt); + } +}; + +ScreenManager.init(); |