Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
445 lines
12 KiB
JavaScript
Executable file
445 lines
12 KiB
JavaScript
Executable file
'use strict';
|
|
/* global AirplaneMode */
|
|
/* global CustomLogoPath */
|
|
/* global Event */
|
|
/* global LogoLoader */
|
|
/* global OrientationManager */
|
|
/* global SettingsCache */
|
|
/* global focusManager */
|
|
|
|
(function(exports) {
|
|
|
|
/**
|
|
* SleepMenu controls functionality found in the menu after long-pressing on
|
|
* the power button. Controls include: airplane mode, silence incoming calls,
|
|
* restart, and power off. This file also currently contains a
|
|
* developerOptions object which is not currently in use.
|
|
* @class SleepMenu
|
|
* @requires AirplaneMode
|
|
* @requires InitLogoHandler
|
|
* @requires LogoLoader
|
|
* @requires OrientationManager
|
|
* @requires SettingsListener
|
|
*/
|
|
function SleepMenu() {}
|
|
|
|
SleepMenu.prototype = {
|
|
|
|
/**
|
|
* Indicate setting status of airplane mode
|
|
* @memberof SleepMenu.prototype
|
|
* @type {Boolean}
|
|
*/
|
|
isFlightModeEnabled: false,
|
|
|
|
/**
|
|
* Indicate setting status of developer.menu.enabled
|
|
* @memberof SleepMenu.prototype
|
|
* @type {Boolean}
|
|
*/
|
|
isDeveloperMenuEnabled: false,
|
|
|
|
/**
|
|
* Additional options to show in the menu if developer menu is enabled.
|
|
* @memberof SleepMenu.prototype
|
|
* @type {Object}
|
|
*/
|
|
developerOptions: {
|
|
},
|
|
|
|
/**
|
|
* Indicate setting status of volume
|
|
* @memberof SleepMenu.prototype
|
|
* @type {Boolean}
|
|
*/
|
|
isSilentModeEnabled: false,
|
|
|
|
/**
|
|
* References to elements in the menu.
|
|
* @memberof SleepMenu.prototype
|
|
* @type {Object}
|
|
*/
|
|
elements: {},
|
|
|
|
/**
|
|
* Whether or not the sleep menu is visibile.
|
|
* @memberof SleepMenu.prototype
|
|
* @return {Boolean}
|
|
*/
|
|
get visible() {
|
|
return this.elements.overlay.classList.contains('visible');
|
|
},
|
|
|
|
/**
|
|
* Whether or not the sleep menu is focusable.
|
|
*/
|
|
isFocusable() {
|
|
return this.visible;
|
|
},
|
|
|
|
/**
|
|
* returns the sleep menu dom element.
|
|
* @return {HTMLElement}
|
|
*/
|
|
getElement: function sm_getElement() {
|
|
return this.elements.overlay;
|
|
},
|
|
|
|
/**
|
|
* focus back to its buttons.
|
|
*/
|
|
focus: function sm_focus() {
|
|
document.activeElement.blur();
|
|
this.elements.cancel.focus();
|
|
},
|
|
|
|
/**
|
|
* Populates this.elements with references to elements.
|
|
* @memberof SleepMenu.prototype
|
|
*/
|
|
getAllElements: function sm_getAllElements() {
|
|
this.elements.overlay = document.getElementById('sleep-menu');
|
|
this.elements.container =
|
|
document.querySelector('#sleep-menu-container ul');
|
|
this.elements.cancel = document.querySelector('#sleep-menu button');
|
|
},
|
|
|
|
/**
|
|
* Start listening for events and settings changes.
|
|
* @memberof SleepMenu.prototype
|
|
*/
|
|
start: function sm_init() {
|
|
this.getAllElements();
|
|
window.addEventListener('holdsleep', this.show.bind(this));
|
|
window.addEventListener('click', this, true);
|
|
window.addEventListener('screenchange', this, true);
|
|
window.addEventListener('home', this);
|
|
window.addEventListener('batteryshutdown', this);
|
|
window.addEventListener('cardviewbeforeshow', this);
|
|
|
|
window.addEventListener('attentionopened', this);
|
|
this.elements.cancel.addEventListener('click', this);
|
|
|
|
window.addEventListener('airplanemodechanged', this);
|
|
|
|
var self = this;
|
|
SettingsCache.observe('developer.menu.enabled', false,
|
|
function(value) {
|
|
self.isDeveloperMenuEnabled = value;
|
|
});
|
|
|
|
for (var option in this.developerOptions) {
|
|
/* jshint loopfunc: true */
|
|
(function attachListenerToDeveloperOption(opt) {
|
|
SettingsCache.observe(opt.setting, opt.value, function(value) {
|
|
opt.value = value;
|
|
});
|
|
})(this.developerOptions[option]);
|
|
}
|
|
|
|
SettingsCache.observe('audio.volume.notification', 7, function(value) {
|
|
self.isSilentModeEnabled = (value === 0);
|
|
});
|
|
focusManager.addUI(this);
|
|
},
|
|
|
|
/**
|
|
* Generates menu items.
|
|
* @memberof SleepMenu.prototype
|
|
*/
|
|
generateItems: function sm_generateItems() {
|
|
var items = [];
|
|
var options = {
|
|
airplane: {
|
|
label: 'airplane',
|
|
value: 'airplane'
|
|
},
|
|
airplaneOff: {
|
|
label: 'airplaneOff',
|
|
value: 'airplane'
|
|
},
|
|
restart: {
|
|
label: 'restart',
|
|
value: 'restart'
|
|
},
|
|
power: {
|
|
label: 'power',
|
|
value: 'power'
|
|
}
|
|
};
|
|
|
|
if (this.isFlightModeEnabled) {
|
|
items.push(options.airplaneOff);
|
|
} else {
|
|
items.push(options.airplane);
|
|
}
|
|
|
|
items.push(options.restart);
|
|
items.push(options.power);
|
|
|
|
// Add the developer options at the end.
|
|
if (this.isDeveloperMenuEnabled) {
|
|
for (var option in this.developerOptions) {
|
|
if (this.developerOptions[option].value) {
|
|
items.push(options[option]);
|
|
} else {
|
|
items.push(options[option + 'Off']);
|
|
}
|
|
}
|
|
}
|
|
|
|
return items;
|
|
},
|
|
|
|
/**
|
|
* Shows the sleep menu.
|
|
* @memberof SleepMenu.prototype
|
|
*/
|
|
show: function sm_show() {
|
|
this.elements.container.innerHTML = '';
|
|
this.buildMenu(this.generateItems());
|
|
this.elements.overlay.classList.add('visible');
|
|
// Lock to default orientation
|
|
screen.mozLockOrientation(OrientationManager.defaultOrientation);
|
|
// let focus manager to calculate the top most and focus for us.
|
|
focusManager.focus();
|
|
},
|
|
|
|
/**
|
|
* Builds markup for all menu items.
|
|
* @memberof SleepMenu.prototype
|
|
* @param {Array} items A list of items.
|
|
*/
|
|
buildMenu: function sm_buildMenu(items) {
|
|
items.forEach(function traveseItems(item) {
|
|
var item_li = document.createElement('li');
|
|
item_li.dataset.value = item.value;
|
|
item_li.setAttribute('data-l10n-id', item.label);
|
|
item_li.setAttribute('role', 'menuitem');
|
|
this.elements.container.appendChild(item_li);
|
|
}, this);
|
|
},
|
|
|
|
/**
|
|
* Hides the sleep menu.
|
|
* @memberof SleepMenu.prototype
|
|
*/
|
|
hide: function lm_hide() {
|
|
if (!this.elements.overlay.classList.contains('visible')) {
|
|
return;
|
|
}
|
|
this.elements.overlay.classList.remove('visible');
|
|
window.dispatchEvent(new Event('sleepmenuhide'));
|
|
// focus back to the top most window/overlay.
|
|
focusManager.focus();
|
|
},
|
|
|
|
/**
|
|
* General event handler.
|
|
* @memberof SleepMenu.prototype
|
|
* @param {Event} evt The event to handle.
|
|
*/
|
|
handleEvent: function sm_handleEvent(evt) {
|
|
switch (evt.type) {
|
|
case 'cardviewbeforeshow':
|
|
this.hide();
|
|
break;
|
|
|
|
case 'batteryshutdown':
|
|
window.dispatchEvent(
|
|
new CustomEvent('requestshutdown', {detail: this}));
|
|
break;
|
|
|
|
case 'screenchange':
|
|
if (!evt.detail.screenEnabled) {
|
|
this.hide();
|
|
}
|
|
break;
|
|
|
|
case 'click':
|
|
if (!this.visible) {
|
|
return;
|
|
}
|
|
|
|
if (evt.currentTarget === this.elements.cancel) {
|
|
this.hide();
|
|
return;
|
|
}
|
|
|
|
var action = evt.target.dataset.value;
|
|
if (!action) {
|
|
return;
|
|
}
|
|
this.clickHandler(action);
|
|
break;
|
|
|
|
case 'home':
|
|
case 'attentionopened':
|
|
if (this.visible) {
|
|
this.hide();
|
|
}
|
|
break;
|
|
|
|
case 'airplanemodechanged':
|
|
this.isFlightModeEnabled = evt.detail.enabled;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Handles click events on menu items.
|
|
* @memberof SleepMenu.prototype
|
|
* @param {String} action The action of the clicked menu item.
|
|
*/
|
|
clickHandler: function sm_clickHandler(action) {
|
|
switch (action) {
|
|
case 'airplane':
|
|
this.hide();
|
|
// Airplane mode should turn off
|
|
//
|
|
// Wifi
|
|
// Bluetooth
|
|
// Geolocation
|
|
//
|
|
// It should also save the status of the latter 4 items so when
|
|
// leaving the airplane mode we could know which one to turn on.
|
|
AirplaneMode.enabled = !this.isFlightModeEnabled;
|
|
break;
|
|
|
|
case 'restart':
|
|
this.startPowerOff(true);
|
|
|
|
break;
|
|
|
|
case 'power':
|
|
this.startPowerOff(false);
|
|
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Begins the power off of the device. Also reboots the device if
|
|
* requested.
|
|
* @memberof SleepMenu.prototype
|
|
* @param {Boolean} reboot Whether or not to reboot the phone.
|
|
*/
|
|
startPowerOff: function sm_startPowerOff(reboot) {
|
|
var power = navigator.mozPower;
|
|
var self = this;
|
|
if (!power) {
|
|
return;
|
|
}
|
|
|
|
// Early return if we are already shutting down.
|
|
if (document.getElementById('poweroff-splash')) {
|
|
return;
|
|
}
|
|
|
|
|
|
// Show shutdown animation before actually performing shutdown.
|
|
// * step1: fade-in poweroff-splash.
|
|
// * step2: - As default, 3-ring animation is performed on the screen.
|
|
// - Manufacturer can customize the animation using mp4/png
|
|
// file to replace the default.
|
|
var div = document.createElement('div');
|
|
div.dataset.zIndexLevel = 'poweroff-splash';
|
|
div.id = 'poweroff-splash';
|
|
|
|
|
|
var logoLoader = new LogoLoader(CustomLogoPath.poweroff);
|
|
|
|
logoLoader.onload = function customizedAnimation(elem) {
|
|
// Perform customized animation.
|
|
div.appendChild(elem);
|
|
div.className = 'step1';
|
|
|
|
if (elem.tagName.toLowerCase() == 'video' && !elem.ended) {
|
|
elem.onended = function() {
|
|
elem.classList.add('hide');
|
|
// XXX workaround of bug 831747
|
|
// Unload the video. This releases the video decoding hardware
|
|
// so other apps can use it.
|
|
elem.removeAttribute('src');
|
|
elem.load();
|
|
};
|
|
elem.play();
|
|
} else {
|
|
div.addEventListener('animationend', function() {
|
|
elem.classList.add('hide');
|
|
if (elem.tagName.toLowerCase() == 'video') {
|
|
// XXX workaround of bug 831747
|
|
// Unload the video. This releases the video decoding hardware
|
|
// so other apps can use it.
|
|
elem.removeAttribute('src');
|
|
elem.load();
|
|
}
|
|
});
|
|
}
|
|
|
|
elem.addEventListener('transitionend', function() {
|
|
self._actualPowerOff(reboot);
|
|
});
|
|
document.getElementById('screen').appendChild(div);
|
|
};
|
|
|
|
logoLoader.onnotfound = function defaultAnimation() {
|
|
// - Perform OS default animation.
|
|
|
|
// The overall animation ends when the inner span of the bottom ring
|
|
// is animated, so we store it for detecting.
|
|
var inner;
|
|
|
|
for (var i = 1; i <= 3; i++) {
|
|
var outer = document.createElement('span');
|
|
outer.className = 'poweroff-ring';
|
|
outer.id = 'poweroff-ring-' + i;
|
|
div.appendChild(outer);
|
|
|
|
inner = document.createElement('span');
|
|
outer.appendChild(inner);
|
|
}
|
|
|
|
div.className = 'step1';
|
|
var nextAnimation = function nextAnimation(e) {
|
|
// Switch to next class
|
|
if (e.target == div) {
|
|
div.className = 'step2';
|
|
}
|
|
|
|
if (e.target != inner) {
|
|
return;
|
|
}
|
|
|
|
self._actualPowerOff(reboot);
|
|
};
|
|
div.addEventListener('animationend', nextAnimation);
|
|
|
|
document.getElementById('screen').appendChild(div);
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Helper for powering the device off.
|
|
* @memberof SleepMenu.prototype
|
|
* @param {Boolean} isReboot Whether or not to reboot the phone.
|
|
*/
|
|
_actualPowerOff: function sm_actualPowerOff(isReboot) {
|
|
var power = navigator.mozPower;
|
|
|
|
if (isReboot) {
|
|
power.reboot();
|
|
} else {
|
|
power.powerOff();
|
|
}
|
|
}
|
|
};
|
|
|
|
exports.SleepMenu = SleepMenu;
|
|
|
|
}(window));
|