VulpesOS/overrides/system.gaiamobile.org/js/screen_manager.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

690 lines
22 KiB
JavaScript

'use strict';
/* globals SettingsListener, Service,
ScreenBrightnessTransition, ScreenWakeLockManager,
ScreenAutoBrightness, LazyLoader */
(function(exports) {
var ScreenManager = {
name: '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,
/**
* If user is unlocking, postpone the timeout counter.
*/
_unlocking: 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 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,
/*
* This property will host a ScreenBrightnessTransition instance
* and control the brightness transition for us.
* Eventually we want to move all brightness controls
* (including auto-brightness toggle and calculation) out of this module.
*/
_screenBrightnessTransition: null,
/*
* ScreenAutoBrightness instance
* manages the devicelight events and adjusts the screen brightness
* automatically
*/
_screenAutoBrightness: null,
/**
* Timeout to black the screen when locking.
*/
LOCKING_TIMEOUT: 10,
/*
* 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,
/*
* To track the reason caused screen off?
*/
_screenOffBy: null,
_screenOffTimeout: 0,
/*
* Request wakelock during in_call state.
* To ensure turnScreenOff by proximity event is protected by wakelock for
* early suspend only.
*/
_cpuWakeLock: null,
/*
* A screen reader feature to allow screen brightness to remain 0
* regardless of the enabled state of the screen. This allows the user to
* conserve battery and have more privacy while still using the touchscreen.
*/
_screenShade: false,
start: function() {
window.addEventListener('attentionopening', this);
window.addEventListener('attentionopened', this);
window.addEventListener('sleep', this);
window.addEventListener('wake', this);
window.addEventListener('nfc-tech-discovered', this);
window.addEventListener('nfc-tech-lost', this);
window.addEventListener('requestshutdown', this);
// User is unlocking by sliding or other methods.
window.addEventListener('unlocking-start', this);
window.addEventListener('unlocking-stop', this);
// When secure app is on, do not turn the screen off.
// And when it's down, reset the timeout.
window.addEventListener('secure-appopened', this);
window.addEventListener('secure-appterminated', this);
window.addEventListener('logohidden', this);
// User is actively using the screen reader.
window.addEventListener('accessibility-action', this);
this.screen = document.getElementById('screen');
LazyLoader.load([
'js/screen_auto_brightness.js',
'js/screen_brightness_transition.js'
]).then(function() {
this._screenBrightnessTransition = new ScreenBrightnessTransition();
this._screenAutoBrightness = new ScreenAutoBrightness();
this._screenAutoBrightness.onbrightnesschange = function(brightness) {
this.setScreenBrightness(brightness, true);
}.bind(this);
this._screenBrightnessTransition.ontransitionbegin =
this._screenAutoBrightness.pause.bind(this._screenAutoBrightness);
this._screenBrightnessTransition.ontransitionend =
this._screenAutoBrightness.resume.bind(this._screenAutoBrightness);
}.bind(this))['catch'](function(err) {
console.error(err);
});
var self = this;
var power = navigator.mozPower;
// Start the screen wake lock manager so it will monitor screen wake lock
// for us. We will need to re-config the screen timeout
// when the lock state is changed.
//
// Noted that getting a lock while the screen is off will not
// turn on the screen, since no frame is considered visible by Gecko when
// the screen is off. See discussion in bug 818840.
this._wakeLockManager = new ScreenWakeLockManager();
this._wakeLockManager.onwakelockchange =
this._reconfigScreenTimeout.bind(this);
this._wakeLockManager.start();
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) {
// Profiles from the legacy range control may contain numeric strings.
var brightness = Number(value);
if (!Number.isFinite(brightness)) return;
self._userBrightness = Math.max(0, Math.min(1, brightness));
// Follow the slider immediately; lock/wake transitions keep their fades.
self.setScreenBrightness(self._userBrightness, true);
});
var telephony = window.navigator.mozTelephony;
if (telephony) {
telephony.addEventListener('callschanged', this);
}
Service.registerState('screenEnabled', this);
Service.register('turnScreenOn', this);
Service.register('turnScreenOff', this);
Service.register('turnShadeOn', this);
Service.register('turnShadeOff', this);
},
_getNumOfCalls: function scm_getNumOfCalls() {
if (navigator.mozTelephony) {
return navigator.mozTelephony.calls.length +
(navigator.mozTelephony.conferenceGroup.calls.length ? 1 : 0);
} else {
return 0;
}
},
handleEvent: function scm_handleEvent(evt) {
var telephony = window.navigator.mozTelephony;
var call;
switch (evt.type) {
case 'attentionopening':
case 'attentionopened':
if (!this.enabled) {
this.turnScreenOn();
}
break;
case 'devicelight':
if (!this._deviceLightEnabled || !this.screenEnabled ||
this._inTransition) {
return;
}
this._screenAutoBrightness &&
this._screenAutoBrightness.autoAdjust(evt.value);
break;
case 'sleep':
// If calls are present then the power button will be handled by the
// dialer agent.
if (this._getNumOfCalls() === 0) {
this.turnScreenOff(true, 'powerkey');
}
break;
case 'wake':
// If calls are present then the power button will be handled by the
// dialer agent.
if (this._getNumOfCalls() === 0) {
this.turnScreenOn();
}
break;
case 'accessibility-action':
this._reconfigScreenTimeout();
break;
case 'nfc-tech-discovered':
case 'nfc-tech-lost':
if (this._inTransition) {
this.turnScreenOn();
} else {
this._reconfigScreenTimeout();
}
break;
case 'unlocking-start':
this._setUnlocking();
break;
// When secure app is on, do not turn the screen off.
// And when it's down, reset the timeout.
case 'secure-appopened':
case 'secure-appterminated':
this._reconfigScreenTimeout();
break;
case 'unlocking-stop':
this._resetUnlocking();
break;
case 'userproximity':
if (Service.query('Bluetooth.isSCOProfileConnected') ||
telephony.speakerEnabled ||
Service.query('isHeadsetConnected')) {
// XXX: Remove this hack in Bug 868348
// We shouldn't access headset status from statusbar.
if (this._screenOffBy == 'proximity') {
this.turnScreenOn();
}
break;
}
if (evt.near) {
this.turnScreenOff(true, 'proximity');
} else {
this.turnScreenOn();
}
break;
case 'callschanged':
if (this._getNumOfCalls() === 0) {
this._uninstallProximityListener();
break;
}
// If the _cpuWakeLock is already set we are in a multiple
// call setup, the user will be notified by a tone.
if (this._cpuWakeLock) {
break;
}
// Enable the user proximity sensor once the call is connected.
call = telephony.calls[0];
if (call.state === 'dialing') {
this._installProximityListener();
} else {
call.addEventListener('statechange', this);
}
break;
case 'statechange':
call = evt.target;
if (['connected', 'alerting'].indexOf(call.state) === -1) {
break;
}
// The call is connected (MT call) or alerting/dialing (MO call).
// Remove the statechange listener and enable the user proximity
// sensor.
call.removeEventListener('statechange', this);
this._installProximityListener();
break;
// Reconfig screen time out after booting.
case 'logohidden':
window.removeEventListener('logohidden', this);
this._reconfigScreenTimeout();
break;
case 'lockscreen-appclosing' :
case 'lockpanelchange' :
window.removeEventListener('lockscreen-appclosing', this);
window.removeEventListener('lockpanelchange', this);
// Prevent racing: wakeLockManager may broadcast the message
// before this event, so one lockscreen event is actually representing
// different meanings depends on that.
//
// This happens partly because this handler doesn't enter the
// '_reconfigScreenTimeout' as other handlers may do, but in fact
// the centralized design of putting all different timeouts in one
// management function is not a good idea, because that fuzzes the
// real intention of each branch. So maybe a more complete solution
// is to decentralize that function, and make every small timeout
// configuring functions more clear.
//
// Another reason why just to set the timeout is bad because it
// depends on the event order heavily without checking any status
// at the moment the event comes. To perform such check can prevent
// that, too.
if (!this._wakeLockManager.isHeld) {
this._setIdleTimeout(this._idleTimeout, false);
}
break;
case 'requestshutdown':
this.turnScreenOn();
if (evt.detail && evt.detail.startPowerOff) {
evt.detail.startPowerOff(false);
}
break;
}
},
toggleScreen: function scm_toggleScreen() {
if (this.screenEnabled) {
// Currently there is no one used toggleScreen, so just set reason as
// toggle. If it is used by someone in the future, we can rename it.
this.turnScreenOff(true, 'toggle');
} else {
this.turnScreenOn();
}
},
turnShadeOn: function scm_turnShadeOn() {
this._screenShade = true;
// Remember the current screen brightness. We will restore it when
// we turn the shade back on.
this._savedBrightness = navigator.mozPower.screenBrightness;
this.setScreenBrightness(0, true);
},
turnShadeOff: function scm_turnShadeOff() {
// Bug 1185036: If the screen was disable with the shade on, we won't
// the screen won't turn on again until the user puts their finger on the
// screen again.
var brightness = this._savedBrightness || this._userBrightness || 1;
this._screenShade = false;
this.setScreenBrightness(brightness, true);
},
turnScreenOff: function scm_turnScreenOff(instant, reason) {
if (!this.screenEnabled) {
return false;
}
var self = this;
if (reason) {
this._screenOffBy = reason;
}
// Remember the current screen brightness. We will restore it when
// we turn the screen back on.
self._savedBrightness = navigator.mozPower.screenBrightness;
// Remove the cpuWakeLock and listening of proximity event, if screen is
// turned off by power key.
if (this._cpuWakeLock != null && this._screenOffBy == 'powerkey') {
window.removeEventListener('userproximity', this);
this._cpuWakeLock.unlock();
this._cpuWakeLock = null;
}
var screenOff = function scm_screenOff() {
self._setIdleTimeout(0);
if (self._deviceLightEnabled) {
window.removeEventListener('devicelight', self);
}
window.removeEventListener('lockscreen-appclosing', self);
window.removeEventListener('lockpanelchange', self);
self.screenEnabled = false;
self._inTransition = false;
clearTimeout(self._screenOffTimeout);
self._screenOffTimeout = 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;
navigator.mozPower.keyLightEnabled = false;
self._screenAutoBrightness && self._screenAutoBrightness.pause();
}, 20);
self.fireScreenChangeEvent();
};
if (instant) {
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) {
clearTimeout(this._screenOffTimeout);
if (this.screenEnabled) {
if (this._inTransition) {
// Cancel the dim out
this._inTransition = false;
this.setScreenBrightness(this._savedBrightness, true);
this._reconfigScreenTimeout();
}
return false;
}
// Actually turn the screen on.
var power = navigator.mozPower;
if (power) {
power.screenEnabled = true;
power.keyLightEnabled = true;
}
this.screenEnabled = true;
// Set the brightness before the screen is on.
this.setScreenBrightness(this._savedBrightness, instant);
// Resume auto brightness, which is paused when the screen is turned off.
this._screenAutoBrightness && this._screenAutoBrightness.resume();
// If we are in a call or a conference call and there
// is no cpuWakeLock, we would get one here.
var telephony = window.navigator.mozTelephony;
var ongoingConference = telephony && telephony.conferenceGroup &&
telephony.conferenceGroup.calls.length;
if (!this._cpuWakeLock && telephony &&
(telephony.calls.length || ongoingConference)) {
var connected = telephony.calls.some(function(call) {
if (call.state == 'connected') {
return true;
}
return false;
});
if (connected || ongoingConference) {
this._cpuWakeLock = navigator.requestWakeLock('cpu');
window.addEventListener('userproximity', this);
}
}
// 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 or
// if no app has been displayed yet.
if (this._wakeLockManager.isHeld ||
(!Service.query('AppWindowManager.getActiveWindow') &&
!Service.query('locked'))) {
this._setIdleTimeout(0);
// The screen should be turn off with shorter timeout if
// it was never unlocked.
} else if (!this._unlocking) {
if (Service.query('getTopMostWindow') &&
Service.query('getTopMostWindow').CLASS_NAME ===
'LockScreenWindow') {
this._setIdleTimeout(this.LOCKING_TIMEOUT, true);
window.addEventListener('lockscreen-appclosing', this);
window.addEventListener('lockpanelchange', this);
} else {
this._setIdleTimeout(this._idleTimeout, false);
}
}
},
/**
* If user is unlocking, postpone the timeout counter.
*
* @this {ScreenManager}
*/
_setUnlocking: function scm_setUnlocking() {
this._unlocking = true;
// Need to cancel it: the last set timeout would still be triggered.
window.clearIdleTimeout(this._idleTimerId);
},
/**
* Reset the state of user unlocking.
*
* @this {ScreenManager}
*/
_resetUnlocking: function scm_resetUnlocking() {
this._unlocking = false;
this._reconfigScreenTimeout();
},
setScreenBrightness: function scm_setScreenBrightness(brightness, instant) {
// Don't touch the brightness if the screen shade is on.
if (this._screenShade && !!brightness) {
return;
}
this._targetBrightness = brightness;
var power = navigator.mozPower;
if (!power) {
return;
}
// Stop the current transition
if (this._screenBrightnessTransition &&
this._screenBrightnessTransition.isRunning) {
this._screenBrightnessTransition.abort();
}
if (typeof instant !== 'boolean') {
instant = true;
}
if (instant) {
power.screenBrightness = brightness;
return;
}
if (this._screenBrightnessTransition) {
this._screenBrightnessTransition.transitionTo(this._targetBrightness);
} else {
power.screenBrightness = this._targetBrightness;
}
},
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;
this._screenAutoBrightness &&
this._screenAutoBrightness.reset();
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, 'idle_timeout');
};
var activeCallback = function active_proxy() {
self.turnScreenOn(true);
};
var finalTimeout =
instant ? time * 1000 : (time * 1000) - this._dimNotice;
this._idleTimerId = window.setIdleTimeout(idleCallback,
activeCallback, finalTimeout);
},
fireScreenChangeEvent: function scm_fireScreenChangeEvent() {
var detail = { screenEnabled: this.screenEnabled };
// Tell others the cause of screen-off.
detail.screenOffBy = this._screenOffBy;
var evt = new CustomEvent('screenchange',
{ bubbles: true, cancelable: false,
detail: detail });
window.dispatchEvent(evt);
},
_installProximityListener: function() {
if (this._cpuWakeLock) {
return;
}
this._cpuWakeLock = navigator.requestWakeLock('cpu');
window.addEventListener('userproximity', this);
},
_uninstallProximityListener: function() {
window.removeEventListener('userproximity', this);
if (this._cpuWakeLock) {
this._cpuWakeLock.unlock();
this._cpuWakeLock = null;
}
}
};
exports.ScreenManager = ScreenManager;
}(window));