VulpesOS/gaia/apps/clock/js/ring_view.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

274 lines
8.9 KiB
JavaScript
Executable file

define(function(require) {
'use strict';
var Utils = require('utils');
var AudioManager = require('audio_manager');
var PostMessageProxy = require('./panels/alarm/post_message_proxy');
var ChildWindowManager = require('./panels/alarm/child_window_manager');
/**
* RingView displays the alert screen when a timer or alarm fires.
* This screen may receive multiple different alarm/timer events;
* ideally it would display all events that fire, but currently it
* only shows the most recent event.
*/
function whenDocumentReady() {
return new Promise(function(resolve, reject) {
if (document.readyState === 'complete') {
resolve();
} else {
const listener = function() {
if (document.readyState === 'complete') {
resolve();
document.removeEventListener('readystatechange', listener);
}
};
document.addEventListener('readystatechange', listener);
}
});
}
function RingView() {
this.alerts = [];
this.ringtonePlayer = AudioManager.createAudioPlayer();
this.snoozeButton.addEventListener('click', this.onClickSnooze.bind(this));
this.closeButton.addEventListener('click', this.onClickClose.bind(this));
window.addEventListener('beforeunload', this.onBeforeUnload.bind(this));
window.addEventListener('timeformatchange', this.refreshDisplay.bind(this));
this.activeAlarm = PostMessageProxy.create(window.opener, 'activeAlarm');
PostMessageProxy.receive('ringView', this);
if (window.opener) {
// We have to wait for the document.readyState to be complete before
// we fireReady. Otherwise, when the screen is off, it doesn't get
// turned back on at all
whenDocumentReady().then(() => {
ChildWindowManager.fireReady();
});
}
}
// The time, in milliseconds, to keep the screen awake while showing
// an alarm. After this time, the screen shuts off and the alarm
// silences itself.
const WAKE_DURATION = 10 * 60 * 1000;
RingView.prototype = {
/**
* Fire the notification for an alarm or timer.
*
* Presently, we only display one notification at a time, and the
* _most recent_ one at that. Each notification gets its own wake
* lock, ensuring that the screen will remain on for WAKE_DURATION.
*
* @param {string} alert.type 'alarm' or 'timer'
* @param {string} alert.label Label to display (optional).
* @param {string} alert.sound Filename of a sound to play (optional).
* @param {boolean} alert.vibrate True if the alert should vibrate.
*/
addAlert: function(alert) {
// If we previously had an alert visible, this one is
// going to override it, as though the previous alert was
// dismissed.
if (this.alerts.length) {
var oldAlert = this.alerts.shift();
oldAlert.releaseScreenWakeLock();
}
alert.releaseScreenWakeLock = function() { };
if (this._silenceTimeout) {
clearTimeout(this._silenceTimeout);
this._silenceTimeout = null;
}
this._silenceTimeout = setTimeout(() => {
this.silence();
alert.releaseScreenWakeLock();
}, WAKE_DURATION);
// Insert this alert at the front of the stack, so that it
// overrides any previous alert that was being displayed.
this.alerts.unshift(alert);
this.refreshDisplay();
// Acquire a CPU wake lock so that we don't fall asleep waiting
// for the document to become visible. We'll only try to hold a
// lock for a few seconds as we wait for the document to become
// visible, out of an abundance of caution.
Utils.safeWakeLock({ type: 'cpu', timeoutMs: 5000 }, (releaseCpu) => {
// When the document is visible, acquire a screen wake lock so
// that we can safely display the alert.
this.whenVisible(() => {
Utils.safeWakeLock({ type: 'screen', timeoutMs: WAKE_DURATION },
(releaseScreenWakeLock) => {
// Once we have acquired the screen wake lock, we can
// release the CPU lock.
releaseCpu();
// Save off the screen wake lock for when we dismiss the
// alert; all alarms each have their own screen wake lock.
alert.releaseScreenWakeLock = releaseScreenWakeLock;
});
});
});
},
/**
* Update the display to show the currently active alert. If there
* are a stack of alerts pending, only the most recent alert is
* shown, as added to this.alerts by this.addAlert().
*/
refreshDisplay: function() {
// First, silence any existing sound or vibration. If a previous
// alarm was going off, this alarm may have different settings.
// The new alarm will replace any prior settings.
this.silence();
var alert = this.alerts[0];
// Set the label (blank or falsey becomes a default string).
if (alert.label) {
this.ringLabel.removeAttribute('data-l10n-id');
this.ringLabel.textContent = alert.label;
} else {
this.ringLabel.setAttribute('data-l10n-id',
alert.type === 'alarm' ? 'alarm' : 'timer');
}
// Display the proper screen widgets.
this.ringDisplay.dataset.ringType = alert.type;
var displayedTime;
if (alert.type === 'alarm') {
// Display the time as specified with "hour" and "minute". The timestamp
// stored by mozAlarms does not move when the timezone changes, so we
// cannot just use the value stored in '.time'. See
// https://bugzilla.mozilla.org/show_bug.cgi?id=1178208#c4
displayedTime = new Date();
displayedTime.setHours(alert.hour);
displayedTime.setMinutes(alert.minute);
} else {
// XXX: Timers may be affected by timezone changes just like alarms, but
// the fix here is not as straightforward, since we cannot take the
// same shortcut as above. Since timers are rarely used across timezone
// changes (unlike alarms), I'm punting on this for now.
displayedTime = alert.time;
}
// Set the time to display.
this.time.innerHTML = Utils.getLocalizedTimeText(displayedTime);
if (alert.sound) {
this.ringtonePlayer.playRingtone(alert.sound);
}
// Vibrate if we want to shakey shakey.
if (alert.vibrate && ('vibrate' in navigator)) {
clearInterval(this.vibrateInterval);
var vibrateOnce = function() {
navigator.vibrate([1000]);
};
this.vibrateInterval = setInterval(vibrateOnce, 2000);
vibrateOnce();
}
document.documentElement.classList.add('ready');
// If the window has been hidden, show the window.
if (document.hidden) {
window.focus();
}
},
/**
* Stop all sounds and vibration immediately.
*/
silence: function() {
// Stop the alert sound, if one was playin'.
this.ringtonePlayer.pause();
// Stop vibrating, if we were shakin'.
clearInterval(this.vibrateInterval);
this.vibrateInterval = null;
},
/**
* Clean up any state when we close this alert window. This includes
* silencing the alarm and releasing any locks we have acquired.
*/
onBeforeUnload: function(evt) {
// Clean up any wake locks we still have.
while (this.alerts.length) {
var alert = this.alerts.shift();
alert.releaseScreenWakeLock();
}
this.silence();
},
/**
* Snooze the current alarm. (The snooze button is only visible for
* alarms, not timers. Alarms have an ID; timers do not.)
*/
onClickSnooze: function(evt) {
var alert = this.alerts[0];
this.activeAlarm.snoozeAlarm(alert.id).then(() => window.close()).catch(console.error);
},
/**
* Close this window, notifying ActiveAlarm, which will pop the user
* back to the appropriate location if they are still using the
* Clock app.
*/
onClickClose: function(evt) {
var alert = this.alerts[0];
this.activeAlarm.close(alert.type, alert.id).then(() => window.close()).catch(console.error);
},
/**
* Call the callback when `document.hidden` is false. Due to a bug
* in the B2G Browser API <https://bugzil.la/810431>, the window may
* not be immediately visible, particularly if the screen is off.
* The recommended workaround for that bug was to use setTimeout. If
* the page is still hidden after that, we listen for
* `visibilitychange`. When that bug has some action, we should
* revisit how much of this method is needed.
*/
whenVisible: function(cb) {
if (!document.hidden) {
cb();
} else {
setTimeout(() => {
if (!document.hidden) {
cb();
} else {
var listener = function(e) {
if (!document.hidden) {
document.removeEventListener('visibilitychange', listener);
cb();
}
};
document.addEventListener('visibilitychange', listener);
}
});
}
}
};
Utils.extendWithDomGetters(RingView.prototype, {
time: '#ring-clock-time',
ringLabel: '#ring-label',
snoozeButton: '#ring-button-snooze',
closeButton: '#ring-button-stop',
ringDisplay: '.ring-display'
});
return RingView;
});