Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
274 lines
8.9 KiB
JavaScript
Executable file
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;
|
|
|
|
});
|