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.
This commit is contained in:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
86
gaia/apps/system/lockscreen/js/clock.js
Executable file
86
gaia/apps/system/lockscreen/js/clock.js
Executable file
|
|
@ -0,0 +1,86 @@
|
|||
/**
|
||||
Copyright 2012, Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
(function(exports) {
|
||||
|
||||
/**
|
||||
* Creates an object used for refreshing the clock UI element. Handles all
|
||||
* related timer manipulation (start/stop/cancel).
|
||||
* @class Clock
|
||||
*/
|
||||
function Clock() {
|
||||
/**
|
||||
* One-shot timer used to refresh the clock at a minute's turn
|
||||
* @memberOf Clock
|
||||
*/
|
||||
this.timeoutID = null;
|
||||
|
||||
/**
|
||||
* Timer used to refresh the clock every minute
|
||||
* @memberOf Clock
|
||||
*/
|
||||
this.timerID = null;
|
||||
|
||||
/**
|
||||
* Start the timer used to refresh the clock, will call the specified
|
||||
* callback at every timer tick to refresh the UI. The callback used to
|
||||
* refresh the UI will also be called immediately to ensure the UI is
|
||||
* consistent.
|
||||
*
|
||||
* @param {Function} refresh Function used to refresh the UI at every timer
|
||||
* tick, should accept a date object as its only argument.
|
||||
* @memberOf Clock
|
||||
*/
|
||||
this.start = function cl_start(refresh) {
|
||||
var date = new Date();
|
||||
var self = this;
|
||||
|
||||
refresh(date);
|
||||
|
||||
if (this.timeoutID == null) {
|
||||
this.timeoutID = window.setTimeout(function cl_setClockInterval() {
|
||||
|
||||
if (self.timerID == null) {
|
||||
self.timerID = window.setInterval(function cl_clockInterval() {
|
||||
refresh(new Date());
|
||||
}, 60000);
|
||||
}
|
||||
}, (60 - date.getSeconds()) * 1000);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Stops the timer used to refresh the clock
|
||||
* @memberOf Clock
|
||||
*/
|
||||
this.stop = function cl_stop() {
|
||||
if (this.timeoutID != null) {
|
||||
window.clearTimeout(this.timeoutID);
|
||||
this.timeoutID = null;
|
||||
}
|
||||
|
||||
if (this.timerID != null) {
|
||||
window.clearInterval(this.timerID);
|
||||
this.timerID = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** @exports Clock */
|
||||
exports.Clock = Clock;
|
||||
|
||||
})(window);
|
||||
124
gaia/apps/system/lockscreen/js/component/lockscreen_basic_component.js
Executable file
124
gaia/apps/system/lockscreen/js/component/lockscreen_basic_component.js
Executable file
|
|
@ -0,0 +1,124 @@
|
|||
/* global LockScreenStateLogger */
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Component provides:
|
||||
*
|
||||
* 1. Resource keeper: to let all states share the same resources (cache).
|
||||
* 2. Reference to the current activate state: so that parent component can
|
||||
* command and wait the sub-components to do things without tracking the
|
||||
* actual active state.
|
||||
*
|
||||
* Every states of this component would receive the Component instance as
|
||||
* a way to access these common resources & properties. And every state
|
||||
* transferring would done by the 'transferTo' method in this component,
|
||||
* so that the component can update the active state correctly.
|
||||
*/
|
||||
(function(exports) {
|
||||
|
||||
/**
|
||||
* View is the only thing parent component needs to manage.
|
||||
*/
|
||||
var LockScreenBasicComponent = function(view) {
|
||||
this._subcomponents = null;
|
||||
this._activeState = null;
|
||||
// Concrete components should extend these to let States access them.
|
||||
// The first state component kick off should take responsibility for
|
||||
// initializing these things.
|
||||
//
|
||||
// Resources is for external resources like settings value or DOM elements.
|
||||
this.resources = {
|
||||
elements: {
|
||||
view: view
|
||||
}
|
||||
};
|
||||
this.configs = {
|
||||
logger: {
|
||||
debug: false // turn on it when we're debugging this component
|
||||
}
|
||||
};
|
||||
this.logger = new LockScreenStateLogger();
|
||||
};
|
||||
|
||||
/**
|
||||
* State' phase is the component's phase.
|
||||
*/
|
||||
LockScreenBasicComponent.prototype.phase =
|
||||
function() {
|
||||
return this._activeState.phase();
|
||||
};
|
||||
|
||||
/**
|
||||
* Every state of this component should call the method to do transferring,
|
||||
* so that the component can update the 'activeState' correctly.
|
||||
*
|
||||
* The order of transferring is:
|
||||
*
|
||||
* [current.stop] -> [next.start] -> (call)[previous.destroy]
|
||||
*
|
||||
*/
|
||||
LockScreenBasicComponent.prototype.transferTo = function(clazz) {
|
||||
var nextState = new clazz(this);
|
||||
var currentState = this._activeState;
|
||||
this._previousState = currentState;
|
||||
this._activeState = nextState;
|
||||
this.logger.transfer(currentState.configs.name, nextState.configs.name);
|
||||
return currentState.stop()
|
||||
.next(() => nextState.start());
|
||||
};
|
||||
|
||||
/**
|
||||
* Would receive resources from parent and *extends* the default one.
|
||||
* After that, transfer to the next state, which is usually an initialization
|
||||
* state, that would do lots of sync/async things to update the
|
||||
* resources & properties.
|
||||
*
|
||||
* However, since basic component couldn't know what is the
|
||||
* initialization state, so that the concrete component should
|
||||
* implement the setup function, which would return the state after
|
||||
* receive the component instance.
|
||||
*/
|
||||
LockScreenBasicComponent.prototype.start = function(resources) {
|
||||
this.logger.start(this.configs.logger);
|
||||
if (resources) {
|
||||
for (var key in this.resources) {
|
||||
if ('undefined' !== resources[key]) {
|
||||
this.resources[key] = resources[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
// Get the initialization state and let it fetch & set all.
|
||||
// 'initializeStateMachine', if Java doomed the world.
|
||||
// (and this is ECMAScript, a language (partially) inspired by Scheme!).
|
||||
this._activeState = this.setup();
|
||||
return this._activeState.start();
|
||||
};
|
||||
|
||||
/**
|
||||
* Receive the component instance and return a initialization state
|
||||
* which would fetch resources and set properties for this component.
|
||||
*
|
||||
* The concrete component should implement this.
|
||||
*/
|
||||
LockScreenBasicComponent.prototype.setup = function(component) {};
|
||||
|
||||
LockScreenBasicComponent.prototype.stop = function() {
|
||||
return this._activeState.stop();
|
||||
};
|
||||
|
||||
LockScreenBasicComponent.prototype.destroy = function() {
|
||||
return this._activeState.destroy()
|
||||
.next(() => { this.logger.stop(); });
|
||||
};
|
||||
|
||||
LockScreenBasicComponent.prototype.live = function() {
|
||||
return this._activeState.until('stop');
|
||||
};
|
||||
|
||||
LockScreenBasicComponent.prototype.exist = function() {
|
||||
return this._activeState.until('destroy');
|
||||
};
|
||||
|
||||
exports.LockScreenBasicComponent = LockScreenBasicComponent;
|
||||
})(window);
|
||||
|
||||
1009
gaia/apps/system/lockscreen/js/lockscreen.js
Executable file
1009
gaia/apps/system/lockscreen/js/lockscreen.js
Executable file
File diff suppressed because it is too large
Load diff
40
gaia/apps/system/lockscreen/js/lockscreen_base_state.js
Executable file
40
gaia/apps/system/lockscreen/js/lockscreen_base_state.js
Executable file
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
Copyright 2012, Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* This is the basic prototype of LockScreen states.
|
||||
* Extend this prototype to implement your state.
|
||||
*/
|
||||
(function(exports) {
|
||||
var LockScreenBaseState = function() {};
|
||||
LockScreenBaseState.prototype.start = function() {
|
||||
return this;
|
||||
};
|
||||
LockScreenBaseState.prototype.transferOut = function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
LockScreenBaseState.prototype.transferTo = function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
exports.LockScreenBaseState = LockScreenBaseState;
|
||||
})(window);
|
||||
|
||||
36
gaia/apps/system/lockscreen/js/lockscreen_bootstrap.js
Executable file
36
gaia/apps/system/lockscreen/js/lockscreen_bootstrap.js
Executable file
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
Copyright 2012, Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/* global LockScreen, LockScreenNotifications, LockScreenFrameAgent */
|
||||
'use strict';
|
||||
(function() {
|
||||
window.addEventListener('lockscreen-frame-bootstrap', function startup() {
|
||||
// XXX: Because we don't have real app bootstraping yet.
|
||||
window.lockScreenFrameAgent = new LockScreenFrameAgent(window.parent);
|
||||
window.lockScreenFrameAgent.start();
|
||||
this.lockscreen = new LockScreen();
|
||||
window.lockScreen = this.lockscreen;
|
||||
window.lockScreenStateManager = new window.LockScreenStateManager();
|
||||
window.lockScreenStateManager.start(window.lockScreen);
|
||||
window.lockScreenNotifications = new LockScreenNotifications();
|
||||
// After Bug 1094759, screen locker initialises itself asynchronously,
|
||||
// so we need to make sure everthing is ready for the components depends
|
||||
// on it.
|
||||
this.lockscreen.bootstrapping.then(() => {
|
||||
window.lockScreenNotifications.start(this.lockscreen,
|
||||
this.lockscreen.notificationsContainer);
|
||||
});
|
||||
});
|
||||
})();
|
||||
88
gaia/apps/system/lockscreen/js/lockscreen_charging.js
Executable file
88
gaia/apps/system/lockscreen/js/lockscreen_charging.js
Executable file
|
|
@ -0,0 +1,88 @@
|
|||
'use strict';
|
||||
(function(exports) {
|
||||
|
||||
/**
|
||||
* Creates an object used for refreshing the charging string UI element.
|
||||
* @class LockScreenChargingStatus
|
||||
*/
|
||||
function LockScreenChargingStatus() {
|
||||
this.elements = {
|
||||
charging: document.getElementById('lockscreen-charging')
|
||||
};
|
||||
|
||||
// Create a text node so we can access its data later for updating the status.
|
||||
if (!this.elements.charging.firstChild) {
|
||||
this.elements.charging.appendChild(document.createTextNode(''));
|
||||
}
|
||||
}
|
||||
|
||||
LockScreenChargingStatus.listenEvents = [
|
||||
'chargingchange',
|
||||
'levelchange',
|
||||
'chargingtimechange'
|
||||
];
|
||||
|
||||
LockScreenChargingStatus.prototype.start = function cs_start() {
|
||||
this.refresh();
|
||||
|
||||
LockScreenChargingStatus.listenEvents.forEach(
|
||||
ename => navigator.battery.addEventListener(ename, this));
|
||||
};
|
||||
|
||||
LockScreenChargingStatus.prototype.stop = function cs_stop() {
|
||||
LockScreenChargingStatus.listenEvents.forEach(
|
||||
ename => navigator.battery.removeEventListener(ename, this));
|
||||
};
|
||||
|
||||
LockScreenChargingStatus.prototype.handleEvent = function cs_handleEvent(evt) {
|
||||
this.refresh();
|
||||
};
|
||||
|
||||
LockScreenChargingStatus.prototype.refresh = function cs_refresh() {
|
||||
|
||||
if (!navigator.battery.charging) {
|
||||
if (!this.elements.charging.hasAttribute('hidden')) {
|
||||
this.elements.charging.setAttribute('hidden', '');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var l10nAttrs = {
|
||||
id: null,
|
||||
args: {
|
||||
level: parseInt(navigator.battery.level * 100)
|
||||
}
|
||||
};
|
||||
|
||||
var chargingTime = navigator.battery.chargingTime;
|
||||
|
||||
if (chargingTime === Infinity ||
|
||||
chargingTime < 60) { // less than a minute remaining
|
||||
l10nAttrs.id = 'charging-no-time';
|
||||
} else {
|
||||
var timeLeft = new Date(0, 0, 0, 0, 0, chargingTime);
|
||||
l10nAttrs.args.hours = timeLeft.getHours();
|
||||
l10nAttrs.args.minutes = timeLeft.getMinutes();
|
||||
|
||||
if (l10nAttrs.args.hours > 0) {
|
||||
l10nAttrs.id = 'charging-hours';
|
||||
} else {
|
||||
l10nAttrs.id = 'charging-minutes';
|
||||
}
|
||||
}
|
||||
|
||||
// Update data here since changing textContent will replace the text node and
|
||||
// could cause flickering while transitioning docshell.isActive to true.
|
||||
navigator.mozL10n.formatValue(l10nAttrs.id, l10nAttrs.args).then((value) => {
|
||||
this.elements.charging.firstChild.data = value;
|
||||
});
|
||||
|
||||
if (this.elements.charging.hasAttribute('hidden')) {
|
||||
this.elements.charging.removeAttribute('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
/** @exports LockScreenChargingStatus */
|
||||
exports.LockScreenChargingStatus = LockScreenChargingStatus;
|
||||
|
||||
})(window);
|
||||
64
gaia/apps/system/lockscreen/js/lockscreen_frame_agent.js
Executable file
64
gaia/apps/system/lockscreen/js/lockscreen_frame_agent.js
Executable file
|
|
@ -0,0 +1,64 @@
|
|||
/**
|
||||
Copyright 2012, Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* XXX: The name here is a temporary name. If we're in a LockScreen app,
|
||||
* it should be 'SystemAgent' as a counterpart of 'LockScreenAgent'
|
||||
* in System app. However, to put a 'SystemAgent' in System app
|
||||
* would confuse people.
|
||||
*
|
||||
* Forward all public events inside the LockScreen frame to System.
|
||||
* LockScreen --> System
|
||||
*/
|
||||
'use strict';
|
||||
(function(exports) {
|
||||
var LockScreenFrameAgent = function(targetWindow) {
|
||||
this.configs = {
|
||||
listens: [
|
||||
'unlocking-start',
|
||||
'unlocking-stop',
|
||||
'secure-killapps',
|
||||
'secure-closeapps',
|
||||
'secure-launchapp',
|
||||
'secure-modeoff',
|
||||
'secure-modeon',
|
||||
'lockpanelchange',
|
||||
'lockscreen-request-unlock',
|
||||
'lockscreen-request-mediacommand'
|
||||
]
|
||||
};
|
||||
this.targetWindow = targetWindow;
|
||||
};
|
||||
LockScreenFrameAgent.prototype.start = function() {
|
||||
this.configs.listens.forEach((ename) => {
|
||||
window.addEventListener(ename, this);
|
||||
});
|
||||
};
|
||||
|
||||
LockScreenFrameAgent.prototype.handleEvent = function(event) {
|
||||
// XXX: Before we make LockScreen really as an iframe,
|
||||
// do nothing and let events pass through.
|
||||
// After we put LockScreen in an iframe, re-create the event and
|
||||
// dispatch it to System's window.
|
||||
};
|
||||
|
||||
LockScreenFrameAgent.prototype.stop = function() {
|
||||
this.configs.listens.forEach((ename) => {
|
||||
window.removeEventListener(ename, this);
|
||||
});
|
||||
};
|
||||
exports.LockScreenFrameAgent = LockScreenFrameAgent;
|
||||
})(window);
|
||||
379
gaia/apps/system/lockscreen/js/lockscreen_inputpad.js
Executable file
379
gaia/apps/system/lockscreen/js/lockscreen_inputpad.js
Executable file
|
|
@ -0,0 +1,379 @@
|
|||
/**
|
||||
Copyright 2012, Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* XXX: Before we use real keyboard as input pad, we need this to
|
||||
* control the custom input pad. See Bug 1053680.
|
||||
**/
|
||||
(function(exports) {
|
||||
/**
|
||||
* We still need the interface to notify the LockScreen.
|
||||
*
|
||||
* @param LockScreenFaçade
|
||||
**/
|
||||
var LockScreenInputpad = function(lockScreen) {
|
||||
this.lockScreen = lockScreen;
|
||||
this.configs = {
|
||||
padVibrationDuration: 50
|
||||
};
|
||||
this.states = {
|
||||
// Keep in sync with Dialer and Keyboard vibration
|
||||
padVibrationEnabled: false,
|
||||
passCodeEntered: '',
|
||||
passCodeErrorTimeoutPending: false
|
||||
};
|
||||
};
|
||||
LockScreenInputpad.prototype.start = function() {
|
||||
this.addEventListener('lockscreen-notify-passcode-validationfailed');
|
||||
this.addEventListener('lockscreen-notify-passcode-validationreset');
|
||||
this.addEventListener('lockscreen-notify-passcode-validationsuccess');
|
||||
this.addEventListener('lockscreen-inputappclosed', this);
|
||||
this.addEventListener('lockscreen-inputappopened', this);
|
||||
|
||||
this.passcodeCode = document.getElementById('lockscreen-passcode-code');
|
||||
this.passcodePad = document.getElementById('lockscreen-passcode-pad');
|
||||
this.emergencyCallBtn = this.passcodePad.querySelector('a[data-key=e]');
|
||||
|
||||
/* lastTouchedKey
|
||||
* Keeps track of the key that was under the finger during
|
||||
* the previous 'touchmove' event. UI must be redrawn only if
|
||||
* the finger moved to a different key.
|
||||
*/
|
||||
this.lastTouchedKey = null;
|
||||
|
||||
this.passcodePad.addEventListener('click', this);
|
||||
this.passcodePad.addEventListener('touchstart', this);
|
||||
this.passcodePad.addEventListener('touchmove', this);
|
||||
this.passcodePad.addEventListener('touchend', this);
|
||||
|
||||
window.SettingsListener.observe('keyboard.vibration',
|
||||
false, (function(value) {
|
||||
this.states.padVibrationEnabled = !!value;
|
||||
}).bind(this));
|
||||
|
||||
this.renderUI();
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Rendering the whole UI, including waiting all necessary conditions.
|
||||
* So rendering functions all should be Promised.
|
||||
*/
|
||||
LockScreenInputpad.prototype.renderUI = function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.toggleEmergencyButton();
|
||||
this.updatePassCodeUI();
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
LockScreenInputpad.prototype.toggleEmergencyButton = function() {
|
||||
if ('undefined' === typeof navigator.mozTelephony ||
|
||||
!navigator.mozTelephony) {
|
||||
this.disableEmergencyButton();
|
||||
} else {
|
||||
this.enableEmergencyButton();
|
||||
}
|
||||
};
|
||||
|
||||
LockScreenInputpad.prototype.disableEmergencyButton = function() {
|
||||
this.emergencyCallBtn.classList.add('disabled');
|
||||
};
|
||||
|
||||
LockScreenInputpad.prototype.enableEmergencyButton = function() {
|
||||
this.emergencyCallBtn.classList.remove('disabled');
|
||||
};
|
||||
|
||||
LockScreenInputpad.prototype.handleEvent = function(evt) {
|
||||
/* The event flow from lockscreen.js is:
|
||||
* on pass code fail:
|
||||
* - 'validationfailed' -> (validation timeout) -> 'validationreset'
|
||||
* on pass code success:
|
||||
* - 'validationsuccess'
|
||||
*/
|
||||
switch (evt.type) {
|
||||
case 'lockscreen-notify-passcode-validationfailed':
|
||||
this.states.passCodeErrorTimeoutPending = true;
|
||||
this.updatePassCodeUI();
|
||||
break;
|
||||
case 'lockscreen-notify-passcode-validationreset':
|
||||
case 'lockscreen-notify-passcode-validationsuccess':
|
||||
// Currently both validationreset and validationsuccess
|
||||
// just need to reset Inputpad's internal state.
|
||||
this.states.passCodeEntered = '';
|
||||
this.states.passCodeErrorTimeoutPending = false;
|
||||
this.updatePassCodeUI();
|
||||
break;
|
||||
case 'lockscreen-inputappopened':
|
||||
case 'lockscreen-inputappclosed':
|
||||
this.updatePassCodeUI();
|
||||
break;
|
||||
case 'touchstart':
|
||||
this.onTouchStart(evt);
|
||||
break;
|
||||
case 'touchmove':
|
||||
this.onTouchMove(evt);
|
||||
break;
|
||||
case 'touchend':
|
||||
evt.preventDefault(); // prevent the 'click'
|
||||
this.onTouchEnd(evt);
|
||||
break;
|
||||
case 'click':
|
||||
this.onClick(evt);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/* On touchstart, mark key under finger as active
|
||||
* and vibrate if vibration is enabled.
|
||||
*/
|
||||
LockScreenInputpad.prototype.onTouchStart = function(evt) {
|
||||
var target = this._touchTarget(evt);
|
||||
// Only act if touch landed on a key
|
||||
var key = this._keyForTarget(target);
|
||||
if (key) {
|
||||
this._visualizeKeypress(target);
|
||||
if (this.states.padVibrationEnabled &&
|
||||
!this.states.passCodeErrorTimeoutPending) {
|
||||
navigator.vibrate(this.configs.padVibrationDuration);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* On touchmove, update keypad display if and only if
|
||||
* finger moves between keys.
|
||||
*/
|
||||
LockScreenInputpad.prototype.onTouchMove = function(evt) {
|
||||
// State update is only required if the touch location moved
|
||||
// onto a different key than in the last round.
|
||||
// _visualizeKeypress keeps track of this.
|
||||
var target = this._touchTarget(evt);
|
||||
this._visualizeKeypress(target);
|
||||
};
|
||||
|
||||
/* On touchend, handle input from the key over which the
|
||||
* finger was released.
|
||||
*/
|
||||
LockScreenInputpad.prototype.onTouchEnd = function(evt) {
|
||||
var target = this._touchTarget(evt);
|
||||
this.handlePassCodeInput(target);
|
||||
this._visualizeKeypress(null); // deactivates all keys
|
||||
};
|
||||
|
||||
/* Handler for traditional click events. Only required on
|
||||
* platforms that don't support touch events.
|
||||
*/
|
||||
LockScreenInputpad.prototype.onClick = function(evt) {
|
||||
var target = evt.target;
|
||||
this._visualizeKeypress(target);
|
||||
this.handlePassCodeInput(target);
|
||||
setTimeout(() => {
|
||||
this._visualizeKeypress(null); // deactivates all keys
|
||||
}, 200); // after 200ms
|
||||
};
|
||||
|
||||
/* Visualize the current key as active, and the previous
|
||||
* key as inactive, and keep track of which key was active
|
||||
* for the next round, and only update if there was a key
|
||||
* change.
|
||||
*/
|
||||
LockScreenInputpad.prototype._visualizeKeypress = function(target) {
|
||||
// State update is only required if the touch location moved
|
||||
// onto a different key than in the last round or if target is
|
||||
// null, requesting internal reset.
|
||||
// Comparing DOM elements is tricky. .isEqualNode() and friends
|
||||
// do not always work as expected. Comparing textContent instead.
|
||||
if (!target || !this.lastTouchedKey ||
|
||||
target.textContent !== this.lastTouchedKey.textContent) {
|
||||
// Make old key inactive if there was one
|
||||
if (this.lastTouchedKey) {
|
||||
this._makeKeyInactive(this.lastTouchedKey);
|
||||
}
|
||||
// Make new key active if touch is actually on a key
|
||||
var key = this._keyForTarget(target);
|
||||
if (key) {
|
||||
this._makeKeyActive(target);
|
||||
// Remember new key for the next event
|
||||
this.lastTouchedKey = target;
|
||||
} else {
|
||||
// If there's no new key, touch moved beyond the keypad
|
||||
// or target was null
|
||||
this.lastTouchedKey = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
LockScreenInputpad.prototype._makeKeyActive = function(target) {
|
||||
var anchor = this._anchorForTarget(target);
|
||||
if (anchor) {
|
||||
anchor.classList.add('active-key');
|
||||
} else {
|
||||
throw Error('lsip_makeKeyActive called with non-key node');
|
||||
}
|
||||
};
|
||||
|
||||
LockScreenInputpad.prototype._makeKeyInactive = function(target) {
|
||||
var anchor = this._anchorForTarget(target);
|
||||
if (anchor) {
|
||||
anchor.classList.remove('active-key');
|
||||
} else {
|
||||
throw Error('lsip_makeKeyInactive called with non-key node');
|
||||
}
|
||||
};
|
||||
|
||||
LockScreenInputpad.prototype._keyForTarget = function(target) {
|
||||
var anchor = this._anchorForTarget(target);
|
||||
if (anchor) {
|
||||
return anchor.dataset.key;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
LockScreenInputpad.prototype._anchorForTarget = function(target) {
|
||||
// Find ancestorial anchor that has info on the key
|
||||
// Current structure: a > div > span
|
||||
try {
|
||||
if (target.tagName !== 'A') {
|
||||
target = target.parentNode;
|
||||
}
|
||||
if (target.tagName !== 'A') {
|
||||
target = target.parentNode;
|
||||
}
|
||||
if (target.tagName === 'A') {
|
||||
return target;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
catch (e) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
LockScreenInputpad.prototype._touchTarget = function(event) {
|
||||
// Determine from touch coordinates in the event
|
||||
// which key the finger is on.
|
||||
// Can't use evt.target, because it remains
|
||||
// on where the touch started.
|
||||
// CAVE: .elementFromPoint() is experimental and may change
|
||||
var touch = event.changedTouches[0];
|
||||
return document.elementFromPoint(touch.clientX, touch.clientY);
|
||||
};
|
||||
|
||||
LockScreenInputpad.prototype.dispatchEvent = function(evt) {
|
||||
window.dispatchEvent(evt);
|
||||
};
|
||||
|
||||
LockScreenInputpad.prototype.addEventListener = function(name, cb) {
|
||||
cb = cb || this;
|
||||
window.addEventListener(name, cb);
|
||||
};
|
||||
|
||||
LockScreenInputpad.prototype.removeEventListener = function(name, cb) {
|
||||
cb = cb || this;
|
||||
window.removeEventListener(name, cb);
|
||||
};
|
||||
|
||||
LockScreenInputpad.prototype.updatePassCodeUI =
|
||||
function() {
|
||||
if (this.states.passCodeEntered) {
|
||||
this.passcodePad.classList.add('passcode-entered');
|
||||
} else {
|
||||
this.passcodePad.classList.remove('passcode-entered');
|
||||
}
|
||||
if (this.states.passCodeEntered.length !== 4 &&
|
||||
this.passcodePad.classList.contains('passcode-fulfilled')) {
|
||||
this.passcodePad.classList.remove('passcode-fulfilled');
|
||||
}
|
||||
if (this.states.passCodeErrorTimeoutPending) {
|
||||
this.passcodeCode.classList.add('error');
|
||||
} else {
|
||||
this.passcodeCode.classList.remove('error');
|
||||
}
|
||||
var i = 4;
|
||||
while (i--) {
|
||||
var span = this.passcodeCode.childNodes[i];
|
||||
if (span) {
|
||||
if (this.states.passCodeEntered.length > i) {
|
||||
span.dataset.dot = true;
|
||||
} else {
|
||||
delete span.dataset.dot;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
LockScreenInputpad.prototype.handlePassCodeInput =
|
||||
function(target) {
|
||||
var key = this._keyForTarget(target);
|
||||
switch (key) {
|
||||
case 'e': // 'E'mergency Call
|
||||
// Cancel the notification clicked to activate.
|
||||
if (this.lockScreen._unlockingMessage.notificationId) {
|
||||
delete this.lockScreen._unlockingMessage.notificationId;
|
||||
}
|
||||
this.lockScreen.invokeSecureApp('emergency-call');
|
||||
break;
|
||||
|
||||
case 'c': // 'C'ancel
|
||||
// Cancel the notification clicked to activate.
|
||||
if (this.lockScreen._unlockingMessage.notificationId) {
|
||||
delete this.lockScreen._unlockingMessage.notificationId;
|
||||
}
|
||||
this.dispatchEvent(new window.CustomEvent(
|
||||
'lockscreen-keypad-input', { detail: {
|
||||
key: key
|
||||
}
|
||||
}));
|
||||
break;
|
||||
|
||||
case 'b': // 'B'ackspace for correction
|
||||
if (this.states.passCodeErrorTimeoutPending) {
|
||||
break;
|
||||
}
|
||||
this.states.passCodeEntered =
|
||||
this.states.passCodeEntered.substr(0,
|
||||
this.states.passCodeEntered.length - 1);
|
||||
this.updatePassCodeUI();
|
||||
break;
|
||||
|
||||
case null: // Ignore non-keys
|
||||
break;
|
||||
|
||||
default:
|
||||
if (this.states.passCodeErrorTimeoutPending) {
|
||||
break;
|
||||
}
|
||||
// If it's already 4 digits and this is the > 5th one,
|
||||
// don't do anything.
|
||||
if (this.states.passCodeEntered.length === 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.states.passCodeEntered += key;
|
||||
this.updatePassCodeUI();
|
||||
|
||||
if (this.states.passCodeEntered.length === 4) {
|
||||
this.passcodePad.classList.add('passcode-fulfilled');
|
||||
this.lockScreen.checkPassCode(this.states.passCodeEntered);
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
exports.LockScreenInputpad = LockScreenInputpad;
|
||||
})(window);
|
||||
33
gaia/apps/system/lockscreen/js/lockscreen_inputpad_bootstrap.js
Executable file
33
gaia/apps/system/lockscreen/js/lockscreen_inputpad_bootstrap.js
Executable file
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
Copyright 2012, Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/* global LockScreenInputpad */
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* XXX: Bootstrap LockScreen. Note this is NOT flexible, so we need to
|
||||
* rewrite it with some dependency managing bootstraping component.
|
||||
**/
|
||||
(function(exports) {
|
||||
window.addEventListener('lockscreen-inputpad-frame-bootstrap',
|
||||
function startup() {
|
||||
// XXX: We assume LockScreenWindow is before us and it registered
|
||||
// the 'lockscreen' in 'window' This should be eliminated once
|
||||
// we moved LockScreen as an iframe and killed LockScreen dependencies.
|
||||
window.lockScreenInputpad = (new LockScreenInputpad(window.lockScreen))
|
||||
.start();
|
||||
});
|
||||
})(window);
|
||||
|
||||
229
gaia/apps/system/lockscreen/js/lockscreen_media_playback.js
Executable file
229
gaia/apps/system/lockscreen/js/lockscreen_media_playback.js
Executable file
|
|
@ -0,0 +1,229 @@
|
|||
/* global Service */
|
||||
'use strict';
|
||||
|
||||
function LockScreenMediaPlaybackWidget(container, options) {
|
||||
this.container = container;
|
||||
this.nowPlaying = container.querySelector('.media-playback-nowplaying');
|
||||
this.controls = container.querySelector('.media-playback-controls');
|
||||
|
||||
this.track = container.querySelector('.track');
|
||||
|
||||
this.previousButton = container.querySelector('.previous');
|
||||
this.playPauseButton = container.querySelector('.play-pause');
|
||||
this.nextButton = container.querySelector('.next');
|
||||
|
||||
this.previousButton.addEventListener(
|
||||
'contextmenu', this.handleContextmenu.bind(this)
|
||||
);
|
||||
this.nextButton.addEventListener(
|
||||
'contextmenu', this.handleContextmenu.bind(this)
|
||||
);
|
||||
this.container.addEventListener('touchend', this.handleTouchend.bind(this));
|
||||
|
||||
// When prev/next button releases, we need to know user simply clicking on it
|
||||
// or holding on and perform fastseeking. Keeping this state to know which
|
||||
// command should issue.
|
||||
this.isFastSeeking = false;
|
||||
|
||||
window.addEventListener('iac-mediacomms', this.handleMessage.bind(this));
|
||||
// When SCO status changes, we need to adjust the ui of the playback controls
|
||||
window.addEventListener(
|
||||
'bluetoothprofileconnectionchange', this.handleSCOChange.bind(this)
|
||||
);
|
||||
|
||||
if (options && options.nowPlayingAction === 'openapp') {
|
||||
this.nowPlaying.addEventListener('click', this.openMediaApp.bind(this));
|
||||
}
|
||||
|
||||
// Listen for when the music app is terminated. We know which app to look
|
||||
// for because we got it from the "appinfo" message. Then we hide the Now
|
||||
// Playing container. XXX: This is a gigantic hack, stemming from
|
||||
// <https://bugzilla.mozilla.org/show_bug.cgi?id=915880>. If you're thinking
|
||||
// about doing something similar, step away from your keyboard immediately.
|
||||
window.addEventListener('appterminated', function(event) {
|
||||
if (event.detail.origin === this.origin) {
|
||||
this.hidden = true;
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
// Requesting the current data (if any)
|
||||
this.sendCommandEvent('updateplaystatus');
|
||||
this.sendCommandEvent('updatemetadata');
|
||||
}
|
||||
|
||||
LockScreenMediaPlaybackWidget.prototype = {
|
||||
get hidden() {
|
||||
return this.container.hidden;
|
||||
},
|
||||
|
||||
set hidden(value) {
|
||||
this.container.hidden = value;
|
||||
},
|
||||
|
||||
handleMessage: function mpw_handleMessage(event) {
|
||||
var message = event.detail;
|
||||
switch (message.type) {
|
||||
case 'appinfo':
|
||||
this.updateAppInfo(message.data);
|
||||
break;
|
||||
case 'nowplaying':
|
||||
this.updateNowPlaying(message.data);
|
||||
break;
|
||||
case 'status':
|
||||
this.updatePlaybackStatus(message.data);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
handleSCOChange: function mpw_handleSCOChange(event) {
|
||||
var name = event.detail.name;
|
||||
var connected = event.detail.connected;
|
||||
|
||||
if (name === 'sco') {
|
||||
this.container.classList.toggle('disabled', connected);
|
||||
}
|
||||
},
|
||||
|
||||
updateAppInfo: function mpw_updateAppInfo(info) {
|
||||
if (!info) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.origin = info.origin;
|
||||
},
|
||||
|
||||
updateNowPlaying: function mpw_updateNowPlaying(metadata) {
|
||||
if (!metadata) {
|
||||
return;
|
||||
}
|
||||
|
||||
var title = metadata.title.trim();
|
||||
var artist = metadata.artist.trim();
|
||||
|
||||
if (title || artist) {
|
||||
this.track.removeAttribute('data-l10n-id');
|
||||
this.track.textContent= '';
|
||||
|
||||
if (title) {
|
||||
var titleNode = document.createTextNode(title);
|
||||
this.track.appendChild(titleNode);
|
||||
}
|
||||
|
||||
if (title && artist) {
|
||||
var emDashNode = document.createTextNode(' — '); // Using a —
|
||||
var artistNode = document.createTextNode(artist);
|
||||
var artistBdiNode = document.createElement('bdi');
|
||||
this.track.appendChild(emDashNode);
|
||||
artistBdiNode.appendChild(artistNode);
|
||||
this.track.appendChild(artistBdiNode);
|
||||
}
|
||||
else if (artist) { // but no title
|
||||
this.track.appendChild(document.createTextNode(artist));
|
||||
}
|
||||
} else {
|
||||
this.track.setAttribute('data-l10n-id', 'UnknownTrack');
|
||||
}
|
||||
},
|
||||
|
||||
updatePlaybackStatus: function mp_updatePlaybackStatus(status) {
|
||||
switch (status.playStatus) {
|
||||
case 'PLAYING':
|
||||
// WORKAROUND: Sometimes we can still get the playing event after music
|
||||
// app terminated, so we check the music app existence when playback is
|
||||
// hidden initially before showing. Remove it once Bug 915880 fixed.
|
||||
var origin = this.origin;
|
||||
if (this.hidden && !Service.query('AppWindowManager.getApp', origin)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.hidden = false;
|
||||
this.playPauseButton.dataset.icon = 'pause';
|
||||
this.playPauseButton.setAttribute('data-l10n-id', 'mediaPlaybackPause');
|
||||
break;
|
||||
case 'PAUSED':
|
||||
this.hidden = false;
|
||||
this.playPauseButton.dataset.icon = 'play';
|
||||
this.playPauseButton.setAttribute('data-l10n-id', 'mediaPlaybackPlay');
|
||||
break;
|
||||
case 'STOPPED':
|
||||
this.hidden = true;
|
||||
break;
|
||||
case 'mozinterruptbegin':
|
||||
this.hidden = true;
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
openMediaApp: function mp_openMediaApp(event) {
|
||||
if (this.origin) {
|
||||
var evt = new CustomEvent('displayapp', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
detail: Service.query('AppWindowManager.getApp', this.origin)
|
||||
});
|
||||
window.dispatchEvent(evt);
|
||||
}
|
||||
},
|
||||
|
||||
sendCommandEvent: function mp_sendCommandEvent(command) {
|
||||
// XXX: Before we implement LockScreen as a standalone app,
|
||||
// we make it inside a iframe, which would block this function
|
||||
// to get the IAC port. So these commands need to be send outside
|
||||
// the frame to System app, and System app would forward them via IAC.
|
||||
window.dispatchEvent(new CustomEvent(
|
||||
'lockscreen-request-mediacommand', {
|
||||
detail: command
|
||||
}));
|
||||
},
|
||||
|
||||
handleContextmenu: function mp_handleContextmenu(event) {
|
||||
var command = null;
|
||||
|
||||
switch (event.target) {
|
||||
case this.previousButton:
|
||||
command = 'rewindstart';
|
||||
break;
|
||||
case this.nextButton:
|
||||
command = 'fastforwardstart';
|
||||
break;
|
||||
}
|
||||
|
||||
if (command) {
|
||||
this.isFastSeeking = true;
|
||||
this.sendCommandEvent(command);
|
||||
}
|
||||
},
|
||||
|
||||
handleTouchend: function mp_handleTouchend(event) {
|
||||
var command = null;
|
||||
|
||||
switch (event.target) {
|
||||
case this.previousButton:
|
||||
if (this.isFastSeeking) {
|
||||
this.isFastSeeking = false;
|
||||
command = 'rewindend';
|
||||
} else {
|
||||
command = 'prevtrack';
|
||||
}
|
||||
break;
|
||||
case this.nextButton:
|
||||
if (this.isFastSeeking) {
|
||||
this.isFastSeeking = false;
|
||||
command = 'fastforwardend';
|
||||
} else {
|
||||
command = 'nexttrack';
|
||||
}
|
||||
break;
|
||||
case this.playPauseButton:
|
||||
// The play/pause indicator will get set once the music app replies
|
||||
// with its "mode" message, but this will make us appear speedier.
|
||||
event.target.classList.toggle('is-paused');
|
||||
command = 'playpause';
|
||||
break;
|
||||
}
|
||||
|
||||
if (command) {
|
||||
this.sendCommandEvent(command);
|
||||
}
|
||||
}
|
||||
};
|
||||
155
gaia/apps/system/lockscreen/js/lockscreen_notification_builder.js
Executable file
155
gaia/apps/system/lockscreen/js/lockscreen_notification_builder.js
Executable file
|
|
@ -0,0 +1,155 @@
|
|||
/**
|
||||
Copyright 2012, Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* To build an LockScreen notification and prevent to add such building
|
||||
* code in the manager components.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
(function(exports) {
|
||||
|
||||
var LockScreenNotificationBuilder = function(container) {
|
||||
this.container = container;
|
||||
};
|
||||
|
||||
LockScreenNotificationBuilder.prototype.start =
|
||||
function() {
|
||||
this.selector = {
|
||||
highlighted: '.notification.actionable',
|
||||
notification: '.notification'
|
||||
};
|
||||
|
||||
// UX require to cancel the highlighted state after
|
||||
// idle 3 seconds.
|
||||
this.timeoutHighlighted = 3000;
|
||||
};
|
||||
|
||||
/**
|
||||
* To make new LockScreen notification from an existing one.
|
||||
* This is to keep the compatibility with the existing code.
|
||||
*
|
||||
* XXX: A better way should be create the notifiction of LockScreen
|
||||
* from this component only, but to refactor the old notifications
|
||||
* is the perquisite.
|
||||
*
|
||||
* @param node {DOMElement} - the notification node
|
||||
* @return {DOMElement}
|
||||
*/
|
||||
LockScreenNotificationBuilder.prototype.decorate =
|
||||
function lsnb_decorate(node) {
|
||||
// If this is the previous node before the activated one,
|
||||
// it should hide the bottom border.
|
||||
var next = node.nextElementSibling;
|
||||
if (null !== next && next.classList.contains('actionable')) {
|
||||
node.classList.add('previous-actionable');
|
||||
}
|
||||
|
||||
node.addEventListener('click',
|
||||
this.requestHighlight.bind(this, node));
|
||||
return node;
|
||||
};
|
||||
|
||||
LockScreenNotificationBuilder.prototype.requestHighlight =
|
||||
function lsnb_requestHighlight(node) {
|
||||
window.dispatchEvent(new CustomEvent(
|
||||
'lockscreen-notification-request-highlight', {
|
||||
detail: {
|
||||
node: node,
|
||||
highlighter: this.highlight.bind(this, node)
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* When it get clicked, do the UI change.
|
||||
*/
|
||||
LockScreenNotificationBuilder.prototype.highlight =
|
||||
function lsnb_highlight(node) {
|
||||
// Cancel other's highlight first.
|
||||
var highlighted = this.container.querySelector(this.selector.highlighted);
|
||||
if (highlighted) {
|
||||
this.removeHighlight(highlighted);
|
||||
}
|
||||
var button = this.createActionButton(node.dataset.notificationId);
|
||||
node.appendChild(button);
|
||||
node.classList.add('actionable');
|
||||
window.dispatchEvent(new CustomEvent(
|
||||
'lockscreen-notification-notify-highlighted', {
|
||||
detail: {
|
||||
id: node.dataset.notificationId,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
}));
|
||||
setTimeout(() => {
|
||||
// UX require to clean idle and highlighted notification
|
||||
// after sevaral seconds.
|
||||
window.dispatchEvent(new CustomEvent(
|
||||
'lockscreen-notification-request-clean-highlighted', {
|
||||
detail: {
|
||||
id: node.dataset.notificationId,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
}));
|
||||
}, this.timeoutHighlighted);
|
||||
|
||||
// Find previous one and cancel it's bottom border.
|
||||
var previous = node.previousElementSibling;
|
||||
var oldPrevious = document.querySelector(
|
||||
'#notifications-lockscreen-container '+
|
||||
'.notification.previous-actionable');
|
||||
if (null !== oldPrevious) {
|
||||
oldPrevious.classList.remove('previous-actionable');
|
||||
}
|
||||
if (null !== previous) {
|
||||
previous.classList.add('previous-actionable');
|
||||
}
|
||||
};
|
||||
|
||||
LockScreenNotificationBuilder.prototype.removeHighlight =
|
||||
function lsnb_removeHighlight(node) {
|
||||
node.classList.remove('actionable');
|
||||
node.querySelector('.button-actionable').remove();
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the button and make the notification actionalbe.
|
||||
*
|
||||
* @param notificationId {string} - the ID need to fire the event
|
||||
* @return {DOMElement}
|
||||
*/
|
||||
LockScreenNotificationBuilder.prototype.createActionButton =
|
||||
function lsnb_createActionButton(notificationId) {
|
||||
var button = document.createElement('span');
|
||||
button.classList.add('button-actionable');
|
||||
button.dataset.l10nId = 'open-notification';
|
||||
button.addEventListener('touchstart', (evt) => {
|
||||
button.parentElement.classList.add('activated');
|
||||
});
|
||||
|
||||
button.addEventListener('touchend', (evt) => {
|
||||
button.parentElement.classList.remove('activated');
|
||||
button.classList.add('activated');
|
||||
window.dispatchEvent(new CustomEvent(
|
||||
'lockscreen-notification-request-activate', {
|
||||
detail: { 'notificationId': notificationId }
|
||||
}));
|
||||
});
|
||||
return button;
|
||||
};
|
||||
|
||||
exports.LockScreenNotificationBuilder = LockScreenNotificationBuilder;
|
||||
})(window);
|
||||
593
gaia/apps/system/lockscreen/js/lockscreen_notifications.js
Executable file
593
gaia/apps/system/lockscreen/js/lockscreen_notifications.js
Executable file
|
|
@ -0,0 +1,593 @@
|
|||
'use strict';
|
||||
|
||||
/* global LockScreenNotificationBuilder, LazyLoader, mozIntl */
|
||||
|
||||
(function(exports) {
|
||||
/**
|
||||
* Manage Notifications in Lockscreen:
|
||||
* Handle notification changes from Notifications, and
|
||||
* update Lockscreen visuals accordingly.
|
||||
*
|
||||
* @constructor LockScreenNotifications
|
||||
*/
|
||||
var LockScreenNotifications = function() {};
|
||||
|
||||
LockScreenNotifications.prototype.start =
|
||||
function lsn_start(lockScreen, container){
|
||||
this._lockScreen = lockScreen;
|
||||
this.container = container;
|
||||
LazyLoader.load(['lockscreen/js/lockscreen_notification_builder.js'])
|
||||
.then(() => {
|
||||
this._lockScreenNotificationBuilder =
|
||||
new LockScreenNotificationBuilder(this.container);
|
||||
this._lockScreenNotificationBuilder.start();
|
||||
});
|
||||
this.arrow =
|
||||
document.getElementById('lockscreen-notification-arrow');
|
||||
// The 'scroll' event can't be forwarded via 'window'.
|
||||
this.container.addEventListener('scroll', this);
|
||||
this.configs = {
|
||||
listens: [
|
||||
'lockscreen-notification-request-highlight',
|
||||
'lockscreen-notification-notify-highlighted',
|
||||
'lockscreen-notification-request-clean-highlighted',
|
||||
'lockscreen-notification-request-activate',
|
||||
'lockscreen-notification-request-append',
|
||||
'lockscreen-notification-request-remove',
|
||||
'lockscreen-notification-request-clear',
|
||||
'lockscreen-appopened',
|
||||
'lockscreen-appclosed',
|
||||
'touchstart',
|
||||
'visibilitychange',
|
||||
'scroll'
|
||||
],
|
||||
// UX require to cancel the highlighted state after
|
||||
// idle 3 seconds.
|
||||
timeoutHighlighted: 3000
|
||||
};
|
||||
this.configs.listens.forEach((ename) => {
|
||||
window.addEventListener(ename, this);
|
||||
});
|
||||
|
||||
this.states = {
|
||||
// UX require to clean idle and highlighted notification
|
||||
// after sevaral seconds.
|
||||
highlightedNotifications: {},
|
||||
currentHighlighted: null,
|
||||
currentHighlightedId: null,
|
||||
arrowVisible: false // container is scrollable
|
||||
};
|
||||
};
|
||||
|
||||
LockScreenNotifications.prototype.handleEvent =
|
||||
function lsn_handleEvent(evt) {
|
||||
var detail = evt.detail || {};
|
||||
var { id, timestamp, node } = detail;
|
||||
switch (evt.type) {
|
||||
case 'lockscreen-notification-request-append':
|
||||
this.onNotificationAppend(id, node);
|
||||
break;
|
||||
case 'lockscreen-notification-request-remove':
|
||||
var containerEmpty = false;
|
||||
if (evt.detail && evt.detail.containerEmpty) {
|
||||
containerEmpty = true;
|
||||
}
|
||||
this.onNotificationRemoved(containerEmpty);
|
||||
break;
|
||||
case 'lockscreen-notification-request-clear':
|
||||
this.onNotificationsClear();
|
||||
break;
|
||||
case 'lockscreen-notification-request-highlight':
|
||||
if (!this.notificationOutOfViewport(evt.detail.node)) {
|
||||
evt.detail.highlighter();
|
||||
}
|
||||
break;
|
||||
case 'lockscreen-notification-notify-highlighted':
|
||||
this.onNotificationHighlighted(id, timestamp);
|
||||
break;
|
||||
case 'lockscreen-notification-request-clean-highlighted':
|
||||
this.onCleanHighlighted(id, timestamp);
|
||||
break;
|
||||
case 'lockscreen-notification-request-activate':
|
||||
this.onNotificationActivate(evt.detail);
|
||||
break;
|
||||
case 'touchstart':
|
||||
if (!evt.target.classList.contains('notification') &&
|
||||
!evt.target.classList.contains('button-actionable')) {
|
||||
this.onNotificationsBlur();
|
||||
}
|
||||
break;
|
||||
case 'lockscreen-appopened':
|
||||
// When it's visible because of locking, bind the listener.
|
||||
window.addEventListener('touchstart', this);
|
||||
break;
|
||||
case 'lockscreen-appclosed':
|
||||
// When it's invisible because of unlocking, unbind the listener.
|
||||
window.removeEventListener('touchstart', this);
|
||||
break;
|
||||
case 'scroll':
|
||||
this.onContainerScrolling();
|
||||
break;
|
||||
case 'visibilitychange':
|
||||
if (!document.hidden) {
|
||||
// When it's visible, bind the listener.
|
||||
window.addEventListener('touchstart', this);
|
||||
this.updateTimestamps();
|
||||
} else {
|
||||
// When it's invisible, unbind the listener.
|
||||
window.removeEventListener('touchstart', this);
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
LockScreenNotifications.prototype.onContainerScrolling =
|
||||
function lsn_onContainerScrolling() {
|
||||
if (this.notificationOutOfViewport(this.states.currentHighlighted)) {
|
||||
this.onNotificationsBlur();
|
||||
}
|
||||
};
|
||||
|
||||
LockScreenNotifications.prototype.notificationOutOfViewport =
|
||||
function lsn_notificationOutOfViewport(node) {
|
||||
var top, bottom;
|
||||
var topBoundary, bottomBoundary;
|
||||
if (node) {
|
||||
// If we're in HVGA resolution, the too small notification container
|
||||
// would lead second and following up notifications overlap the boundary
|
||||
// by several pixels.
|
||||
var HVGATolerance = 0;
|
||||
if (this._getWindowInnerDimension().height <= 480) {
|
||||
HVGATolerance = 1;
|
||||
}
|
||||
topBoundary = this.container.getBoundingClientRect().top;
|
||||
bottomBoundary = this.container.getBoundingClientRect().bottom;
|
||||
top = node.getBoundingClientRect().top;
|
||||
bottom = node.getBoundingClientRect().bottom;
|
||||
if (top < topBoundary ||
|
||||
bottom > bottomBoundary + HVGATolerance) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the notification at the bottom boundary.
|
||||
*/
|
||||
LockScreenNotifications.prototype.notificationAtBottomBoundary =
|
||||
function lsn_notificationAtBottomBoundary() {
|
||||
var boundaryElement = this.arrow;
|
||||
var detectingPoint = {
|
||||
x: window.innerWidth >> 1,
|
||||
y: boundaryElement.getBoundingClientRect().top +
|
||||
this.configs.boundaryThresholdBottom
|
||||
};
|
||||
var element = document.elementFromPoint(detectingPoint.x, detectingPoint.y);
|
||||
if (element.classList.contains('notification')) {
|
||||
return element;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the notification at the top boundary.
|
||||
*/
|
||||
LockScreenNotifications.prototype.notificationAtTopBoundary =
|
||||
function lsn_notificationAtBottomBoundary() {
|
||||
var boundaryElement = this.container;
|
||||
var detectingPoint = {
|
||||
x: window.innerWidth >> 1,
|
||||
y: boundaryElement.getBoundingClientRect().top +
|
||||
this.configs.boundaryThresholdTop
|
||||
};
|
||||
var element = document.elementFromPoint(detectingPoint.x, detectingPoint.y);
|
||||
if (element.classList.contains('notification')) {
|
||||
return element;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
LockScreenNotifications.prototype.onNotificationsBlur =
|
||||
function lsn_onNotificationsBlur() {
|
||||
var id = this.states.currentHighlightedId;
|
||||
var notificationNode = this.states.currentHighlighted;
|
||||
if (notificationNode &&
|
||||
notificationNode.classList.contains('actionable')) {
|
||||
this.removeNotificationHighlight(notificationNode);
|
||||
delete this.states.highlightedNotifications[id];
|
||||
this.states.currentHighlighted = null;
|
||||
this.states.currentHighlightedId = null;
|
||||
|
||||
this._tryAddTopMaskByNotification(notificationNode);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Record the timestamp when a notification is highlighted.
|
||||
*/
|
||||
LockScreenNotifications.prototype.onNotificationHighlighted =
|
||||
function lsn_onNotificationHighlighted(id, timestamp) {
|
||||
// Overwrite existing because re-clicking on it is legal.
|
||||
this.states.highlightedNotifications[id] = timestamp;
|
||||
this.states.currentHighlightedId = id;
|
||||
this.states.currentHighlighted = this.container.querySelector(
|
||||
'[data-notification-id="' + id + '"]');
|
||||
|
||||
// because background of an active actionable notification can be clipped
|
||||
// by the top mask implemented in lockscreen visual refresh 2.0 (bug1023500)
|
||||
// we need to cancel the mask when the 'top' (in the visible viewport, not
|
||||
// the whole container) notification is active, by 'top-actionable' class.
|
||||
// (note: the 'top mask' displays only when the container scrolls to bottom)
|
||||
|
||||
// such 'top'-ness is decided by the viewport size of the container:
|
||||
// if the container can show N visible notifications,
|
||||
// then the last-Nth notification is that 'top' notification.
|
||||
|
||||
// Illustration:
|
||||
//
|
||||
// --top------------------ container -------------bottom--
|
||||
// ======= viewport ========
|
||||
// ------------------------------=========================
|
||||
// | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|
||||
// | | | | | | 0 | 1 | 2 | 3 |
|
||||
// ------------------------------=========================
|
||||
// In this illustration, the ID of the visually top notification in the
|
||||
// viewport is #5; the number of notifications in container viewport is 4.
|
||||
// We can select the #5 notification by :nth-last-of-type(4).
|
||||
|
||||
var numNotificationInContainerViewport =
|
||||
this._getNumNotificationInContainerViewport();
|
||||
|
||||
if (this.container.querySelector('div.notification:nth-last-of-type(' +
|
||||
numNotificationInContainerViewport + ')') ===
|
||||
this.states.currentHighlighted){
|
||||
this.container.classList.add('top-actionable');
|
||||
}else{
|
||||
this.container.classList.remove('top-actionable');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clean the highlighted notification.
|
||||
*/
|
||||
LockScreenNotifications.prototype.onCleanHighlighted =
|
||||
function lsn_onNotificationHighlighted(id, timestamp) {
|
||||
var prevTimestamp = this.states.highlightedNotifications[id];
|
||||
if (prevTimestamp &&
|
||||
timestamp - prevTimestamp > this.configs.timeoutHighlighted) {
|
||||
var notificationNode = this.container.querySelector(
|
||||
'[data-notification-id="' + id + '"]');
|
||||
if (notificationNode &&
|
||||
notificationNode.classList.contains('actionable')) {
|
||||
this.removeNotificationHighlight(notificationNode);
|
||||
delete this.states.highlightedNotifications[id];
|
||||
this.states.currentHighlighted = null;
|
||||
this.states.currentHighlightedId = null;
|
||||
|
||||
this._tryAddTopMaskByNotification(notificationNode);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
LockScreenNotifications.prototype.removeNotificationHighlight =
|
||||
function lsn_removeNotificationHighlight(node) {
|
||||
node.classList.remove('actionable');
|
||||
node.querySelector('.button-actionable').remove();
|
||||
var oldPrevious = this.container.querySelector(
|
||||
'.notification.previous-actionable');
|
||||
if (null !== oldPrevious) {
|
||||
oldPrevious.classList.remove('previous-actionable');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* When user clicked on the notification while it's locked.
|
||||
*/
|
||||
LockScreenNotifications.prototype.onNotificationActivate =
|
||||
function lsn_onNotificationActivate(info) {
|
||||
this._lockScreen._unlockingMessage = {
|
||||
notificationId: info.notificationId
|
||||
};
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('lockscreen-notification-request-activate-unlock'));
|
||||
};
|
||||
|
||||
/**
|
||||
* When new notification appended, do the visual change.
|
||||
* Would receive a ordinary Notificaition node.
|
||||
*/
|
||||
LockScreenNotifications.prototype.onNotificationAppend =
|
||||
function lsn_onNotificationsAppend(id, node) {
|
||||
var notifSelector = '[data-notification-id="' + id + '"]';
|
||||
// First we try and find an existing notification with the same id.
|
||||
// If we have one, we'll replace it. If not, we'll create a new node.
|
||||
var oldLockScreenNode =
|
||||
this.container.querySelector(notifSelector);
|
||||
if (oldLockScreenNode) {
|
||||
this.container.replaceChild(
|
||||
node,
|
||||
oldLockScreenNode
|
||||
);
|
||||
}
|
||||
else {
|
||||
this.container.insertBefore(
|
||||
node,
|
||||
this.container.firstElementChild
|
||||
);
|
||||
}
|
||||
|
||||
this._lockScreenNotificationBuilder.decorate(node);
|
||||
this.showColoredMaskBG();
|
||||
|
||||
// UX specifies that the container should scroll to top
|
||||
/* note two things:
|
||||
* 1. we need to call adjustContainerVisualHints even
|
||||
* though we're setting scrollTop, since setting sT doesn't
|
||||
* necessarily invoke onscroll (if the old container is already
|
||||
* scrolled to top, we might still need to decide to show
|
||||
* the arrow)
|
||||
* 2. set scrollTop before calling adjustContainerVisualHints
|
||||
* since sT = 0 will hide the mask if it's showing,
|
||||
* and if we call aCVH before setting sT,
|
||||
* under some circumstances aCVH would decide to show mask,
|
||||
* only to be negated by st = 0 (waste of energy!).
|
||||
*/
|
||||
this.scrollToTop();
|
||||
|
||||
// check if lockscreen notifications visual
|
||||
// hints (masks & arrow) need to show
|
||||
this.adjustContainerVisualHints();
|
||||
};
|
||||
|
||||
LockScreenNotifications.prototype.onNotificationRemoved =
|
||||
function lsn_onNotificationRemoved(containerEmpty) {
|
||||
// if we don't have any notifications,
|
||||
// use the no-notifications masked background for lockscreen
|
||||
if (containerEmpty) {
|
||||
this.hideColoredMaskBG();
|
||||
}
|
||||
// check if lockscreen notifications visual
|
||||
// hints (masks & arrow) need to show
|
||||
this.adjustContainerVisualHints();
|
||||
};
|
||||
|
||||
LockScreenNotifications.prototype.onNotificationsClear =
|
||||
function lsn_onNotificationsClear() {
|
||||
// remove the "have notifications" masked background from lockscreen
|
||||
this.hideColoredMaskBG();
|
||||
// check if lockscreen notifications visual
|
||||
// hints (masks & arrow) need to show
|
||||
this.adjustContainerVisualHints();
|
||||
};
|
||||
|
||||
/**
|
||||
* Bind lockscreen onto this module
|
||||
*
|
||||
* @this {LockScreenNotifications}
|
||||
* @memberof LockScreenNotifications
|
||||
* @param bindLockScreen the lockScreen instance
|
||||
*/
|
||||
LockScreenNotifications.prototype.bindLockScreen =
|
||||
function lsn_bindLockScreen(lockScreen) {
|
||||
this._lockScreen = lockScreen;
|
||||
};
|
||||
|
||||
/**
|
||||
* When we have notifications, show bgcolor from wallpaper
|
||||
* Remove the simple gradient at the same time
|
||||
*
|
||||
* @memberof LockScreenNotifications
|
||||
* @this {LockScreenNotifications}
|
||||
*/
|
||||
LockScreenNotifications.prototype.showColoredMaskBG =
|
||||
function lsn_showColoredMaskBG() {
|
||||
this._lockScreen.maskedBackground.style.backgroundColor =
|
||||
this._lockScreen.maskedBackground.dataset.wallpaperColor;
|
||||
|
||||
this._lockScreen.maskedBackground.classList.remove('blank');
|
||||
};
|
||||
|
||||
/**
|
||||
* When we don't have notifications, use the
|
||||
* simple gradient as lockscreen's masked background
|
||||
*
|
||||
* @memberof LockScreenNotifications
|
||||
* @this {LockScreenNotifications}
|
||||
*/
|
||||
LockScreenNotifications.prototype.hideColoredMaskBG =
|
||||
function lsn_hideColoredMaskBG() {
|
||||
this._lockScreen.maskedBackground.classList.add('blank');
|
||||
};
|
||||
|
||||
/**
|
||||
* We use a smaller notifications container when we have a media player
|
||||
* widget on the lockscreen. This function collapses the container.
|
||||
*
|
||||
* @memberof LockScreenNotifications
|
||||
* @this {LockScreenNotifications}
|
||||
*/
|
||||
LockScreenNotifications.prototype.collapseNotifications =
|
||||
function lsn_collapseNotifications() {
|
||||
this._lockScreen.notificationsContainer.classList.add('collapsed');
|
||||
this._lockScreen.notificationArrow.classList.add('collapsed');
|
||||
};
|
||||
|
||||
/**
|
||||
* ...and this function expands the container.
|
||||
*
|
||||
* @memberof LockScreenNotifications
|
||||
* @this {LockScreenNotifications}
|
||||
*/
|
||||
LockScreenNotifications.prototype.expandNotifications =
|
||||
function lsn_expandNotifications() {
|
||||
this._lockScreen.notificationsContainer.classList.remove('collapsed');
|
||||
this._lockScreen.notificationArrow.classList.remove('collapsed');
|
||||
};
|
||||
|
||||
/**
|
||||
* adjust the container's visual hints: masks and "more notification" arrow
|
||||
* @memberof LockScreenNotifications
|
||||
* @this {LockScreenNotifications}
|
||||
*/
|
||||
LockScreenNotifications.prototype.adjustContainerVisualHints =
|
||||
function lsn_adjustContainerVisualHints() {
|
||||
var notificationsContainer = this._lockScreen.notificationsContainer;
|
||||
|
||||
/* mask:
|
||||
* the gradient masks only show when:
|
||||
* - "top" mask: when the user scrolls to bottom (this is a solid mask)
|
||||
* - "both" mask: when the user scrolls in between (this is a gradient mask)
|
||||
* (but we need to rule out the situation that
|
||||
* the container isn't actually scrollable)
|
||||
*/
|
||||
if(notificationsContainer.clientHeight ===
|
||||
notificationsContainer.scrollHeight){
|
||||
// no mask if the container can't be scrolled
|
||||
this._setMaskVisibility(false, false);
|
||||
}else{
|
||||
if(notificationsContainer.scrollTop +
|
||||
notificationsContainer.clientHeight ===
|
||||
notificationsContainer.scrollHeight){
|
||||
// user scrolls to bottom -> top mask
|
||||
this._setMaskVisibility(true, false);
|
||||
}else if(0 === notificationsContainer.scrollTop){
|
||||
// user scrolls to top -> no mask
|
||||
this._setMaskVisibility(false, false);
|
||||
}else{
|
||||
// anything in between -> "both" mask
|
||||
this._setMaskVisibility(false, true);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* arrow:
|
||||
* The "more notifications" arrow only shows
|
||||
* when the user is on the top of the container
|
||||
* and the container is scrollable
|
||||
*/
|
||||
this._setArrowVisibility(
|
||||
0 === notificationsContainer.scrollTop &&
|
||||
notificationsContainer.clientHeight <
|
||||
notificationsContainer.scrollHeight
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the masks' visibility.
|
||||
*
|
||||
* @private
|
||||
* @memberof LockScreenNotifications
|
||||
* @this {LockScreenNotifications}
|
||||
* @param top Boolen shows top solid mask or not
|
||||
* @param both Boolen shows top-and-bottom gradient masks or not
|
||||
*/
|
||||
LockScreenNotifications.prototype._setMaskVisibility =
|
||||
function lsn__setMaskVisibility(top, both) {
|
||||
var notificationsContainer = this._lockScreen.notificationsContainer;
|
||||
|
||||
if(top){
|
||||
notificationsContainer.classList.add('masked-top');
|
||||
}else{
|
||||
notificationsContainer.classList.remove('masked-top');
|
||||
}
|
||||
|
||||
if(both){
|
||||
notificationsContainer.classList.add('masked-both');
|
||||
}else{
|
||||
notificationsContainer.classList.remove('masked-both');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the arrow's visibility.
|
||||
*
|
||||
* @private
|
||||
* @memberof LockScreenNotifications
|
||||
* @this {LockScreenNotifications}
|
||||
* @param top Boolen shows top solid mask or not
|
||||
* @param both Boolen shows top-and-bottom gradient masks or not
|
||||
*/
|
||||
LockScreenNotifications.prototype._setArrowVisibility =
|
||||
function lsn__setArrowVisibility(visible) {
|
||||
var notificationArrow = this._lockScreen.notificationArrow;
|
||||
|
||||
if(visible){
|
||||
notificationArrow.classList.add('visible');
|
||||
this.states.arrowVisible = true;
|
||||
}else{
|
||||
notificationArrow.classList.remove('visible');
|
||||
this.states.arrowVisible = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Scrolls the notifications to the top
|
||||
*/
|
||||
LockScreenNotifications.prototype.scrollToTop =
|
||||
function lsn_scrollToTop() {
|
||||
this._lockScreen.notificationsContainer.scrollTop = 0;
|
||||
};
|
||||
|
||||
LockScreenNotifications.prototype._getWindowInnerDimension =
|
||||
function lsn_getWindowInnerDimension() {
|
||||
return {
|
||||
height: window.innerHeight,
|
||||
width: window.innerWidth
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the number of notifications visible in the container.
|
||||
* we have 2 viewable notifications for HVGA lockscreen with music player
|
||||
* widget, and for larger screen, and the absence of the widget,
|
||||
* we can allow one, or two, more viewable notifications.
|
||||
*/
|
||||
LockScreenNotifications.prototype._getNumNotificationInContainerViewport =
|
||||
function lsn_getNumNotificationInContainerViewport() {
|
||||
var numNotificationInContainerViewport = 2;
|
||||
|
||||
// monitor > HVGA => allow one more notification
|
||||
if (this._getWindowInnerDimension().height > 480) {
|
||||
numNotificationInContainerViewport++;
|
||||
}
|
||||
|
||||
// no music player widget => also allow one more notification
|
||||
if (!this.container.classList.contains('collapsed')) {
|
||||
numNotificationInContainerViewport++;
|
||||
}
|
||||
|
||||
return numNotificationInContainerViewport;
|
||||
};
|
||||
|
||||
LockScreenNotifications.prototype._tryAddTopMaskByNotification =
|
||||
function lsn_tryAddTopMaskByNotification(notificationNode) {
|
||||
// if the unhighlighted node was the visually top notification of the
|
||||
// viewport, add the top mask back by removing the top-actionable class.
|
||||
// (see also: onNotificationHighlighted)
|
||||
var numNotificationInContainerViewport =
|
||||
this._getNumNotificationInContainerViewport();
|
||||
if (this.container.querySelector('div.notification:nth-last-of-type(' +
|
||||
numNotificationInContainerViewport + ')') === notificationNode){
|
||||
this.container.classList.remove('top-actionable');
|
||||
}
|
||||
};
|
||||
|
||||
LockScreenNotifications.prototype.updateTimestamps =
|
||||
function lsn_updateTimestamps() {
|
||||
var timestamps = [...document.querySelectorAll('.notification .timestamp')];
|
||||
var formatter = mozIntl._gaia.RelativeDate(navigator.languages, {
|
||||
style: 'short'
|
||||
});
|
||||
|
||||
timestamps.forEach((element) => {
|
||||
var date = new Date(element.dataset.timestamp);
|
||||
formatter.formatElement(element, date);
|
||||
});
|
||||
};
|
||||
|
||||
/** @exports LockScreenWindowManager */
|
||||
exports.LockScreenNotifications = LockScreenNotifications;
|
||||
})(window);
|
||||
56
gaia/apps/system/lockscreen/js/lockscreen_state_keypadhiding.js
Executable file
56
gaia/apps/system/lockscreen/js/lockscreen_state_keypadhiding.js
Executable file
|
|
@ -0,0 +1,56 @@
|
|||
/**
|
||||
Copyright 2012, Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/* global Promise, LockScreenBaseState */
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* This state would guarantee the LockScreen plays the animation.
|
||||
*/
|
||||
(function(exports) {
|
||||
|
||||
var LockScreenStateKeypadHiding = function() {
|
||||
LockScreenBaseState.apply(this, arguments);
|
||||
};
|
||||
LockScreenStateKeypadHiding.prototype =
|
||||
Object.create(LockScreenBaseState.prototype);
|
||||
|
||||
LockScreenStateKeypadHiding.prototype.start = function(lockScreen) {
|
||||
this.type = 'keypadHiding';
|
||||
this.lockScreen = lockScreen;
|
||||
return this;
|
||||
};
|
||||
|
||||
LockScreenStateKeypadHiding.prototype.transferTo =
|
||||
function lsskh_transferTo() {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Copy from the original switching method.
|
||||
this.lockScreen.overlay.classList.remove('no-transition');
|
||||
// Trigger the transition.
|
||||
this.lockScreen.overlay.dataset.panel = 'main';
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('lockscreen-request-inputpad-close'));
|
||||
// XXX: We need a overall refactoring about panel and
|
||||
// panel styling in the future.
|
||||
this.lockScreen.overlay.classList.remove('passcode-unlocking');
|
||||
// XXX: We assume this is sync in order. But if we use real
|
||||
// input window this would be broken.
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
exports.LockScreenStateKeypadHiding = LockScreenStateKeypadHiding;
|
||||
})(window);
|
||||
56
gaia/apps/system/lockscreen/js/lockscreen_state_keypadrising.js
Executable file
56
gaia/apps/system/lockscreen/js/lockscreen_state_keypadrising.js
Executable file
|
|
@ -0,0 +1,56 @@
|
|||
/**
|
||||
Copyright 2012, Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/* global Promise, LockScreenBaseState */
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* This state would guarantee the LockScreen plays the animation.
|
||||
*/
|
||||
(function(exports) {
|
||||
|
||||
var LockScreenStateKeypadRising = function() {
|
||||
LockScreenBaseState.apply(this, arguments);
|
||||
};
|
||||
LockScreenStateKeypadRising.prototype =
|
||||
Object.create(LockScreenBaseState.prototype);
|
||||
|
||||
LockScreenStateKeypadRising.prototype.start = function(lockScreen) {
|
||||
this.type = 'keypadRising';
|
||||
this.lockScreen = lockScreen;
|
||||
return this;
|
||||
};
|
||||
|
||||
LockScreenStateKeypadRising.prototype.transferTo =
|
||||
function lsskr_transferTo(inputs) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// XXX: Because when it's 'success', the keyboard would be hidden.
|
||||
// This should be fixed: we should manage the show/hide with other
|
||||
// CSS flags.
|
||||
this.lockScreen.overlay.dataset.passcodeStatus = '';
|
||||
this.lockScreen.overlay.dataset.panel = 'passcode';
|
||||
// XXX: Before it become a real input window.
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('lockscreen-request-inputpad-open'));
|
||||
// XXX: We need a overall refactoring about panel and
|
||||
// panel styling in the future.
|
||||
this.lockScreen.overlay.classList.add('passcode-unlocking');
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
exports.LockScreenStateKeypadRising = LockScreenStateKeypadRising;
|
||||
})(window);
|
||||
54
gaia/apps/system/lockscreen/js/lockscreen_state_keypadshow.js
Executable file
54
gaia/apps/system/lockscreen/js/lockscreen_state_keypadshow.js
Executable file
|
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
Copyright 2012, Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/* global Promise, LockScreenBaseState */
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* This state would guarantee the LockScreen is showing with the keypad.
|
||||
*/
|
||||
(function(exports) {
|
||||
|
||||
var LockScreenStateKeypadShow = function() {
|
||||
LockScreenBaseState.apply(this, arguments);
|
||||
};
|
||||
LockScreenStateKeypadShow.prototype =
|
||||
Object.create(LockScreenBaseState.prototype);
|
||||
|
||||
LockScreenStateKeypadShow.prototype.start = function(lockScreen) {
|
||||
this.type = 'keypadShow';
|
||||
this.lockScreen = lockScreen;
|
||||
return this;
|
||||
};
|
||||
|
||||
LockScreenStateKeypadShow.prototype.transferTo =
|
||||
function lssks_transferTo() {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Copy from the original switching method.
|
||||
this.lockScreen.overlay.classList.add('no-transition');
|
||||
this.lockScreen.overlay.dataset.panel = 'passcode';
|
||||
// Resetting slider when it get hidden by the passcode keypad
|
||||
this.lockScreen._unlocker.reset();
|
||||
// XXX: Because when it's 'success', the keyboard would be hidden.
|
||||
// This should be fixed: we should manage the show/hide with other
|
||||
// CSS flags.
|
||||
this.lockScreen.overlay.dataset.passcodeStatus = '';
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
exports.LockScreenStateKeypadShow = LockScreenStateKeypadShow;
|
||||
})(window);
|
||||
147
gaia/apps/system/lockscreen/js/lockscreen_state_logger.js
Executable file
147
gaia/apps/system/lockscreen/js/lockscreen_state_logger.js
Executable file
|
|
@ -0,0 +1,147 @@
|
|||
/**
|
||||
Copyright 2012, Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* To log state transferring in a proper way, rather dump raw console
|
||||
* messages and then overwhelm it.
|
||||
*/
|
||||
|
||||
(function(exports) {
|
||||
var LockScreenStateLogger = function() {};
|
||||
|
||||
LockScreenStateLogger.prototype.start =
|
||||
function lss_start(configs) {
|
||||
this.stateStack = [];
|
||||
this.configs = {
|
||||
verbose: false || configs.verbose,
|
||||
warning: false || configs.warning,
|
||||
error: true && configs.error,
|
||||
graph: false || configs.graph,
|
||||
debug: true && configs.debug,
|
||||
reporter: configs.reporter || this.consoleReporter.bind(this)
|
||||
};
|
||||
return this;
|
||||
};
|
||||
|
||||
LockScreenStateLogger.prototype.debug =
|
||||
function lss_debug() {
|
||||
if (this.configs.debug) {
|
||||
this.log.apply(this, ['[I] '].concat(Array.from(arguments)));
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
LockScreenStateLogger.prototype.verbose =
|
||||
function lss_verbose() {
|
||||
if (this.configs.verbose) {
|
||||
this.log.apply(this, ['[V] '].concat(Array.from(arguments)));
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
LockScreenStateLogger.prototype.warning = function lss_warning() {
|
||||
if (this.configs.warning || this.configs.verbose) {
|
||||
this.log.apply(this, ['[!] '].concat(Array.from(arguments)));
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
LockScreenStateLogger.prototype.error =
|
||||
function lss_error() {
|
||||
if (this.configs.error || this.configs.warning ||
|
||||
this.configs.verbose) {
|
||||
this.log.apply(this, ['[E] '].concat(Array.from(arguments)));
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Print the stack. For example:
|
||||
*
|
||||
* logger.debug('the thing at stack: ').stack()
|
||||
*
|
||||
* would print out the message and the stack.
|
||||
*/
|
||||
LockScreenStateLogger.prototype.stack = function lss_stack() {
|
||||
this.log((new Error()).stack);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Log the transferring manipulation.
|
||||
*
|
||||
* To log the conditions, this function would stringify the conditions
|
||||
* and then parse it to do the deep copy. So please turn off the
|
||||
* `config.debug` in production mode.
|
||||
*
|
||||
* @param from {string} - from state type
|
||||
* @param to {string} - to state type
|
||||
* @param conditions {object} - under what conditions we do the transferring
|
||||
*/
|
||||
LockScreenStateLogger.prototype.transfer =
|
||||
function lss_transfer(from, to, conditions = {}) {
|
||||
if (!this.configs.debug) {
|
||||
return;
|
||||
}
|
||||
var transferDetails = {
|
||||
from: from,
|
||||
to: to,
|
||||
conditions: JSON.parse(JSON.stringify(conditions))
|
||||
};
|
||||
if (this.configs.graph) {
|
||||
this.stateStack.push(transferDetails);
|
||||
}
|
||||
this.debug(`State transfer: from ${from} to ${to} because of:`,
|
||||
transferDetails.conditions);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* To get the graph. The array it return would be:
|
||||
*
|
||||
* [ 'foo', {conditions}, 'bar', {conditions}, 'gamma'...]
|
||||
*
|
||||
* which can be rendered as a real graph.
|
||||
*/
|
||||
LockScreenStateLogger.prototype.graph =
|
||||
function lss_graph() {
|
||||
return this.stateStack.reduce((prev, info) => {
|
||||
return prev.concat([info.from, info.conditions, info.to]);
|
||||
}, []);
|
||||
};
|
||||
|
||||
LockScreenStateLogger.prototype.log =
|
||||
function lss_log() {
|
||||
var reporter = this.configs.reporter;
|
||||
reporter.apply(this, arguments);
|
||||
return this;
|
||||
};
|
||||
|
||||
LockScreenStateLogger.prototype.stop =
|
||||
function lss_stop() {
|
||||
this.stateStack.length = 0;
|
||||
return this;
|
||||
};
|
||||
|
||||
LockScreenStateLogger.prototype.consoleReporter =
|
||||
function lss_consoleReporter() {
|
||||
console.log.apply(console, arguments);
|
||||
return this;
|
||||
};
|
||||
exports.LockScreenStateLogger = LockScreenStateLogger;
|
||||
})(window);
|
||||
1002
gaia/apps/system/lockscreen/js/lockscreen_state_manager.js
Executable file
1002
gaia/apps/system/lockscreen/js/lockscreen_state_manager.js
Executable file
File diff suppressed because it is too large
Load diff
54
gaia/apps/system/lockscreen/js/lockscreen_state_panelhide.js
Executable file
54
gaia/apps/system/lockscreen/js/lockscreen_state_panelhide.js
Executable file
|
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
Copyright 2012, Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/* global Promise, LockScreenBaseState */
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* This state would guarantee the LockScreen plays the animation.
|
||||
*/
|
||||
(function(exports) {
|
||||
|
||||
var LockScreenStatePanelHide = function() {
|
||||
LockScreenBaseState.apply(this, arguments);
|
||||
};
|
||||
LockScreenStatePanelHide.prototype =
|
||||
Object.create(LockScreenBaseState.prototype);
|
||||
|
||||
LockScreenStatePanelHide.prototype.start = function(lockScreen) {
|
||||
this.type = 'panelHide';
|
||||
this.lockScreen = lockScreen;
|
||||
return this;
|
||||
};
|
||||
|
||||
LockScreenStatePanelHide.prototype.transferTo =
|
||||
function lssph_transferTo() {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Copy from the original switching method.
|
||||
this.lockScreen.overlay.classList.add('no-transition');
|
||||
// XXX: clear the passcode we entered.
|
||||
// To have 'passcodeStatus' would make UI don't update.
|
||||
delete this.lockScreen.overlay.dataset.passcodeStatus;
|
||||
this.lockScreen.passCodeEntered = '';
|
||||
// XXX: even though we set the no-transition, it still do transition.
|
||||
this.lockScreen.overlay.dataset.panel = 'passcode';
|
||||
this.lockScreen.overlay.dataset.passcodeStatus = 'success';
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
exports.LockScreenStatePanelHide = LockScreenStatePanelHide;
|
||||
})(window);
|
||||
46
gaia/apps/system/lockscreen/js/lockscreen_state_secure_app_launching.js
Executable file
46
gaia/apps/system/lockscreen/js/lockscreen_state_secure_app_launching.js
Executable file
|
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
Copyright 2012, Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/* global Promise, LockScreenBaseState */
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* A state represents the mode Secure app launching and launched.
|
||||
*/
|
||||
(function(exports) {
|
||||
|
||||
var LockScreenStateSecureAppLaunching = function() {
|
||||
LockScreenBaseState.apply(this, arguments);
|
||||
};
|
||||
LockScreenStateSecureAppLaunching.prototype =
|
||||
Object.create(LockScreenBaseState.prototype);
|
||||
|
||||
LockScreenStateSecureAppLaunching.prototype.start = function(lockScreen) {
|
||||
this.type = 'secureAppLaunching';
|
||||
this.lockScreen = lockScreen;
|
||||
return this;
|
||||
};
|
||||
|
||||
LockScreenStateSecureAppLaunching.prototype.transferTo =
|
||||
function lsskh_transferTo(inputs) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.lockScreen.invokeSecureApp(inputs.appName);
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
exports.LockScreenStateSecureAppLaunching = LockScreenStateSecureAppLaunching;
|
||||
})(window);
|
||||
47
gaia/apps/system/lockscreen/js/lockscreen_state_sliderestore.js
Executable file
47
gaia/apps/system/lockscreen/js/lockscreen_state_sliderestore.js
Executable file
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
Copyright 2012, Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/* global Promise, LockScreenBaseState */
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* This state would guarantee the LockScreen shows the slide.
|
||||
*/
|
||||
(function(exports) {
|
||||
|
||||
var LockScreenStateSlideRestore = function() {
|
||||
LockScreenBaseState.apply(this, arguments);
|
||||
};
|
||||
LockScreenStateSlideRestore.prototype =
|
||||
Object.create(LockScreenBaseState.prototype);
|
||||
|
||||
LockScreenStateSlideRestore.prototype.start = function(lockScreen) {
|
||||
this.type = 'slideRestore';
|
||||
this.lockScreen = lockScreen;
|
||||
return this;
|
||||
};
|
||||
|
||||
LockScreenStateSlideRestore.prototype.transferTo =
|
||||
function lssss_transferTo(inputs) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.lockScreen._unlocker.reset();
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
exports.LockScreenStateSlideRestore = LockScreenStateSlideRestore;
|
||||
})(window);
|
||||
|
||||
80
gaia/apps/system/lockscreen/js/lockscreen_state_slideshow.js
Executable file
80
gaia/apps/system/lockscreen/js/lockscreen_state_slideshow.js
Executable file
|
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
Copyright 2012, Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/* global LockScreenBaseState */
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* This state would guarantee the LockScreen shows the slide.
|
||||
*/
|
||||
(function(exports) {
|
||||
|
||||
var LockScreenStateSlideShow = function() {
|
||||
LockScreenBaseState.apply(this, arguments);
|
||||
};
|
||||
LockScreenStateSlideShow.prototype =
|
||||
Object.create(LockScreenBaseState.prototype);
|
||||
|
||||
LockScreenStateSlideShow.prototype.start = function(lockScreen) {
|
||||
this.type = 'slideShow';
|
||||
this.lockScreen = lockScreen;
|
||||
this.lockScreen.nextStep(() => {
|
||||
this.stopWhenActivated(this.lockScreen._unlocker);
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Slider should be stopped until the manager do
|
||||
* the decision to transfer to which state. And we
|
||||
* stop it because the slideshow is the only state
|
||||
* that shows the slider, so to stop it before doing
|
||||
* any decision.
|
||||
**/
|
||||
LockScreenStateSlideShow.prototype.stopWhenActivated =
|
||||
function lssss_stopWhenActivated(unlocker) {
|
||||
var stopIt = function stopIt() {
|
||||
unlocker._stop();
|
||||
window.removeEventListener('lockscreenslide-activate-left', stopIt);
|
||||
window.removeEventListener('lockscreenslide-activate-right', stopIt);
|
||||
};
|
||||
window.addEventListener('lockscreenslide-activate-left', stopIt);
|
||||
window.addEventListener('lockscreenslide-activate-right', stopIt);
|
||||
};
|
||||
|
||||
LockScreenStateSlideShow.prototype.transferTo =
|
||||
function lssss_transferTo(inputs) {
|
||||
// Only when the lockscreen is ready, we do the things.
|
||||
return this.lockScreen.nextStep(() => {
|
||||
var unlocker = this.lockScreen._unlocker;
|
||||
// Clear passcode while the keypad is hiding.
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('lockscreen-request-inputpad-close'));
|
||||
// Resetting slider before we want to show it again
|
||||
unlocker.reset();
|
||||
// Start it because the slideshow is the only state
|
||||
// that shows the slider, so start it here.
|
||||
unlocker._start();
|
||||
|
||||
// Copy from the original switching method.
|
||||
this.lockScreen.overlay.classList.add('no-transition');
|
||||
this.lockScreen.overlay.dataset.panel = 'main';
|
||||
});
|
||||
};
|
||||
|
||||
exports.LockScreenStateSlideShow = LockScreenStateSlideShow;
|
||||
})(window);
|
||||
57
gaia/apps/system/lockscreen/js/lockscreen_state_unlock.js
Executable file
57
gaia/apps/system/lockscreen/js/lockscreen_state_unlock.js
Executable file
|
|
@ -0,0 +1,57 @@
|
|||
/**
|
||||
Copyright 2012, Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/* global Promise, LockScreenBaseState */
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* If we follow the ordinary unlocking logic, that is, no need to care about
|
||||
* instantly unlock and activity, we need to integrate the unlocking function
|
||||
* call into state to coordinate the relationships between panel switching and
|
||||
* unlocking animation.
|
||||
*/
|
||||
(function(exports) {
|
||||
|
||||
var LockScreenStateUnlock = function() {
|
||||
LockScreenBaseState.apply(this, arguments);
|
||||
};
|
||||
LockScreenStateUnlock.prototype =
|
||||
Object.create(LockScreenBaseState.prototype);
|
||||
|
||||
LockScreenStateUnlock.prototype.start = function(lockScreen, innerStates) {
|
||||
this.type = 'unlock';
|
||||
this.lockScreen = lockScreen;
|
||||
this.innerStates = innerStates;
|
||||
return this;
|
||||
};
|
||||
|
||||
LockScreenStateUnlock.prototype.transferTo =
|
||||
function lsskh_transferTo(inputs) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var unlockingDetails = {
|
||||
'unlockSoundEnabled': this.innerStates.unlockSoundEnabled
|
||||
};
|
||||
if (inputs.unlockingAppActivated) {
|
||||
this.lockScreen._activateUnlockingCamera(unlockingDetails);
|
||||
} else {
|
||||
this.lockScreen.unlock(false, unlockingDetails);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
exports.LockScreenStateUnlock = LockScreenStateUnlock;
|
||||
})(window);
|
||||
64
gaia/apps/system/lockscreen/js/settings/settings_cache.js
Executable file
64
gaia/apps/system/lockscreen/js/settings/settings_cache.js
Executable file
|
|
@ -0,0 +1,64 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* A settings getter/setter cache.
|
||||
* Provide as few as possible APIs like the native APIs do.
|
||||
**/
|
||||
(function(exports) {
|
||||
var LockScreenSettingsCache = function() {
|
||||
this.cache = {};
|
||||
this.handleSettings = this.handleSettings.bind(this);
|
||||
};
|
||||
LockScreenSettingsCache.prototype.get = function(entry) {
|
||||
if (this.cache[entry]) {
|
||||
return Promise.resolve(this.cache[entry]);
|
||||
}
|
||||
|
||||
var resolve, reject;
|
||||
var promise = new Promise((rev, rej) => {
|
||||
resolve = rev;
|
||||
reject = rej;
|
||||
});
|
||||
var lock = navigator.mozSettings.createLock();
|
||||
var req = lock.get(entry);
|
||||
req.then(() => {
|
||||
this.cache[entry] = req.result;
|
||||
// Once it getted, monitor it to update cache.
|
||||
navigator.mozSettings
|
||||
.addObserver(entry, this.handleSettings);
|
||||
resolve(req.result);
|
||||
}).catch(() => {
|
||||
reject(req.error);
|
||||
});
|
||||
return promise;
|
||||
};
|
||||
LockScreenSettingsCache.prototype.set = function(entry, value) {
|
||||
var resolve, reject;
|
||||
var promise = new Promise((rev, rej) => {
|
||||
resolve = rev;
|
||||
reject = rej;
|
||||
});
|
||||
var lock = navigator.mozSettings.createLock();
|
||||
var reqcontent = {};
|
||||
reqcontent[entry] = value;
|
||||
var req = lock.set(reqcontent);
|
||||
req.then(() => {
|
||||
this.cache[entry] = value;
|
||||
resolve();
|
||||
}).catch(() => {
|
||||
reject();
|
||||
});
|
||||
return promise;
|
||||
};
|
||||
LockScreenSettingsCache.prototype.handleSettings = function(evt) {
|
||||
var { settingsName, settingsValue } = evt;
|
||||
this.cache[settingsName] = settingsValue;
|
||||
};
|
||||
LockScreenSettingsCache.prototype.stop = function() {
|
||||
Object.keys(this.cache).forEach((entry) => {
|
||||
navigator.mozSettings.removeObserver(entry, this.handleSettings);
|
||||
});
|
||||
};
|
||||
exports.LockScreenSettingsCache = LockScreenSettingsCache;
|
||||
})(window);
|
||||
|
||||
52
gaia/apps/system/lockscreen/js/source/dom_event_source.js
Executable file
52
gaia/apps/system/lockscreen/js/source/dom_event_source.js
Executable file
|
|
@ -0,0 +1,52 @@
|
|||
/* global SourceEvent */
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* DOM event source for Stream. One Stream can collect events from multiple
|
||||
* sources, which pass different native events (not only DOM events)
|
||||
* to Stream.
|
||||
**/
|
||||
(function(exports) {
|
||||
var DOMEventSource = function(configs) {
|
||||
this.configs = {
|
||||
events: configs.events || [],
|
||||
};
|
||||
this._collector = window.addEventListener.bind(window);
|
||||
this._decollector = window.removeEventListener.bind(window);
|
||||
this._forwardTo; // The forwarding target.
|
||||
|
||||
// Some API you just can't bind it with the object,
|
||||
// but a function.
|
||||
this.onchange = this.onchange.bind(this);
|
||||
};
|
||||
|
||||
DOMEventSource.prototype.start = function(forwardTo) {
|
||||
this.configs.events.forEach((ename) => {
|
||||
this._collector(ename, this.onchange);
|
||||
});
|
||||
this._forwardTo = forwardTo;
|
||||
return this;
|
||||
};
|
||||
|
||||
DOMEventSource.prototype.stop = function() {
|
||||
this._forwardTo = null;
|
||||
this.configs.events.forEach((ename) => {
|
||||
this._decollector(ename, this.onchange);
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* For forwarding to the target.
|
||||
*/
|
||||
DOMEventSource.prototype.onchange = function(domevt) {
|
||||
if (this._forwardTo) {
|
||||
var sourceEvent = new SourceEvent(
|
||||
domevt.type, domevt.detail, domevt);
|
||||
this._forwardTo(sourceEvent);
|
||||
}
|
||||
};
|
||||
|
||||
exports.DOMEventSource = DOMEventSource;
|
||||
})(window);
|
||||
|
||||
71
gaia/apps/system/lockscreen/js/source/minute_clock_source.js
Executable file
71
gaia/apps/system/lockscreen/js/source/minute_clock_source.js
Executable file
|
|
@ -0,0 +1,71 @@
|
|||
/* global SourceEvent */
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* A source fire events every clock minutes.
|
||||
**/
|
||||
(function(exports) {
|
||||
var MinuteClockSource = function(configs) {
|
||||
this.configs = {
|
||||
type: configs.type,
|
||||
interval: 60000 // one minute.
|
||||
};
|
||||
this._tickId = null;
|
||||
this._forwardTo = null;
|
||||
// Some API you just can't bind it with the object,
|
||||
// but a function.
|
||||
this.onchange = this.onchange.bind(this);
|
||||
};
|
||||
|
||||
MinuteClockSource.prototype.start = function(forwardTo) {
|
||||
this._forwardTo = forwardTo;
|
||||
var seconds = (new Date()).getSeconds();
|
||||
// If it's the #0 second of that minute,
|
||||
// immediately tick or we would lost this minute.
|
||||
if (0 === seconds) {
|
||||
this.onchange();
|
||||
}
|
||||
// For the first tick we must set timeout for it.
|
||||
this._tickId = window.setTimeout(() => {
|
||||
this.tick();
|
||||
}, this.calcLeftMilliseconds());
|
||||
return this;
|
||||
};
|
||||
|
||||
MinuteClockSource.prototype.tick = function() {
|
||||
this.onchange();
|
||||
// For the first tick we must set timeout for it.
|
||||
this._tickId = window.setTimeout(() => {
|
||||
this.tick();
|
||||
}, this.calcLeftMilliseconds());
|
||||
};
|
||||
|
||||
MinuteClockSource.prototype.stop = function() {
|
||||
this._forwardTo = null;
|
||||
if (this._tickId) {
|
||||
window.clearTimeout(this._tickId);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* For forwarding to the target.
|
||||
* When the time is up, fire an event by generator.
|
||||
* So that the onchange method would forward it to the target.
|
||||
*/
|
||||
MinuteClockSource.prototype.onchange = function() {
|
||||
if (this._forwardTo) {
|
||||
this._forwardTo(new SourceEvent(this.configs.type));
|
||||
}
|
||||
};
|
||||
|
||||
MinuteClockSource.prototype.calcLeftMilliseconds = function() {
|
||||
var seconds = (new Date()).getSeconds();
|
||||
// If it's at the second 0th of the minute, immediate start to tick.
|
||||
var leftMilliseconds = (60 - seconds) * 1000;
|
||||
return leftMilliseconds;
|
||||
};
|
||||
|
||||
exports.MinuteClockSource = MinuteClockSource;
|
||||
})(window);
|
||||
|
||||
52
gaia/apps/system/lockscreen/js/source/setting_source.js
Executable file
52
gaia/apps/system/lockscreen/js/source/setting_source.js
Executable file
|
|
@ -0,0 +1,52 @@
|
|||
/* global SourceEvent */
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Event source for Stream. One Stream can collect events from multiple
|
||||
* sources, which pass different native events (not only DOM events)
|
||||
* to Stream.
|
||||
**/
|
||||
(function(exports) {
|
||||
var SettingSource = function(configs) {
|
||||
this.configs = {
|
||||
settings: configs.settings || []
|
||||
};
|
||||
this._collector = navigator.mozSettings.addObserver;
|
||||
this._decollector = navigator.mozSettings.removeObserver;
|
||||
this._forwardTo = null;
|
||||
// Some API you just can't bind it with the object,
|
||||
// but a function.
|
||||
this.onchange = this.onchange.bind(this);
|
||||
};
|
||||
|
||||
SettingSource.prototype.start = function(forwardTo) {
|
||||
this.configs.settings.forEach((key) => {
|
||||
this._collector(key, this.onchange);
|
||||
});
|
||||
this._forwardTo = forwardTo;
|
||||
return this;
|
||||
};
|
||||
|
||||
SettingSource.prototype.stop = function() {
|
||||
this._forwardTo = null;
|
||||
this.configs.settings.forEach((key) => {
|
||||
this._decollector(key, this.onchange);
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* For forwarding to the target.
|
||||
* Would transform the original 'settingName' and 'settingValue' pair as
|
||||
* 'type' and 'detail', as the event formant.
|
||||
*/
|
||||
SettingSource.prototype.onchange = function(change) {
|
||||
if (this._forwardTo) {
|
||||
this._forwardTo(
|
||||
new SourceEvent(change.settingName, change.settingValue));
|
||||
}
|
||||
};
|
||||
|
||||
exports.SettingSource = SettingSource;
|
||||
})(window);
|
||||
|
||||
14
gaia/apps/system/lockscreen/js/source/source_event.js
Executable file
14
gaia/apps/system/lockscreen/js/source/source_event.js
Executable file
|
|
@ -0,0 +1,14 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* A datum that every source would fire.
|
||||
**/
|
||||
(function(exports) {
|
||||
var SourceEvent = function(type, detail, original) {
|
||||
this.type = type;
|
||||
this.detail = detail;
|
||||
this.original = original; // original event, if any.
|
||||
};
|
||||
exports.SourceEvent = SourceEvent;
|
||||
})(window);
|
||||
|
||||
71
gaia/apps/system/lockscreen/js/state/lockscreen_basic_state.js
Executable file
71
gaia/apps/system/lockscreen/js/state/lockscreen_basic_state.js
Executable file
|
|
@ -0,0 +1,71 @@
|
|||
/* global Stream */
|
||||
'use strict';
|
||||
|
||||
(function(exports) {
|
||||
var LockScreenBasicState = function(component) {
|
||||
// Replace with the name of concrete state.
|
||||
this.configs = {
|
||||
name: 'LockScreenBasicState',
|
||||
// Note the event means events forwarded from sources, not DOM events.
|
||||
stream: {
|
||||
events: [],
|
||||
interrupts: [],
|
||||
sources: []
|
||||
}
|
||||
};
|
||||
// Component reference proivdes every resource & property
|
||||
// need by all states.
|
||||
this.component = component;
|
||||
};
|
||||
|
||||
/**
|
||||
* Stream' phase is the state's phase.
|
||||
*/
|
||||
LockScreenBasicState.prototype.phase =
|
||||
function() {
|
||||
return this.stream.phase();
|
||||
};
|
||||
|
||||
/**
|
||||
* Derived states should extend these basic methods.
|
||||
*/
|
||||
LockScreenBasicState.prototype.start =
|
||||
function() {
|
||||
this.stream = new Stream(this.configs.stream);
|
||||
return this.stream.start(this.handleSourceEvent.bind(this))
|
||||
.next(this.stream.ready.bind(this.stream));
|
||||
};
|
||||
|
||||
LockScreenBasicState.prototype.stop = function() {
|
||||
return this.stream.stop();
|
||||
};
|
||||
|
||||
LockScreenBasicState.prototype.destroy = function() {
|
||||
return this.stream.destroy();
|
||||
};
|
||||
|
||||
LockScreenBasicState.prototype.live = function() {
|
||||
return this.stream.until('stop');
|
||||
};
|
||||
|
||||
LockScreenBasicState.prototype.exist = function() {
|
||||
return this.stream.until('destroy');
|
||||
};
|
||||
|
||||
/**
|
||||
* Must transfer to next state via component's method.
|
||||
* Or the component cannot track the last active state.
|
||||
*/
|
||||
LockScreenBasicState.prototype.transferTo = function() {
|
||||
return this.component.transferTo.apply(this.component, arguments);
|
||||
};
|
||||
|
||||
/**
|
||||
* If this handler return a Promise, or Process, the underlying Stream
|
||||
* can make sure the steps are queued even with asynchronous steps.
|
||||
*/
|
||||
LockScreenBasicState.prototype.handleSourceEvent = function() {};
|
||||
|
||||
exports.LockScreenBasicState = LockScreenBasicState;
|
||||
})(window);
|
||||
|
||||
386
gaia/apps/system/lockscreen/js/stream/process.js
Executable file
386
gaia/apps/system/lockscreen/js/stream/process.js
Executable file
|
|
@ -0,0 +1,386 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* The core component to sequentialize asynchronous steps.
|
||||
* Basically it's an 'interruptable promise', but more than be interrupted,
|
||||
* it could 'shift' from one to another phase, with the non-preemptive
|
||||
* interrupting model.
|
||||
*
|
||||
* Example:
|
||||
* var process = new Process();
|
||||
* process.start() // the default phase is 'start'
|
||||
* .next(stepA)
|
||||
* .next(stepB)
|
||||
* ...
|
||||
* // later, some urgent events come
|
||||
* process.stop() // one of the default three phases
|
||||
* .next(stopStepA)
|
||||
* .next(stopStepB)
|
||||
* ....
|
||||
* // later, some other interrupts come
|
||||
* process.shift('stop', 'dizzy')
|
||||
* .next(dizzyStepA)
|
||||
* .next(dizzyStepB)
|
||||
*
|
||||
* The phases listed above would immediately interrupt the steps scheduled
|
||||
* at the previous phase. However, this is a *non-preemptive* process by
|
||||
* default. So, if there is a long-waiting Promise step in the 'start' phase:
|
||||
*
|
||||
* process.start()
|
||||
* .next( longlonglongWaitingPromise ) // <--- now it's waiting this
|
||||
* .next( thisStepIsStarving )
|
||||
* .next( andThisOneToo )
|
||||
* .next( poorSteps )
|
||||
* ....
|
||||
* // some urgent event occurs when it goes to the long waiting promise
|
||||
* process.stop()
|
||||
* .next( doThisAsQuickAsPossible )
|
||||
*
|
||||
* The first step of the 'stop' phase, namely the 'doThisAsQuickAsPossible',
|
||||
* would *not* get executed immediately, since the promise is still waiting the
|
||||
* last step earlier interruption. So, even the following steps of the 'start'
|
||||
* phase would all get dropped, the new phase still need to wait the last one
|
||||
* asynchronous step get resolved to get kicked off.
|
||||
*
|
||||
* ---
|
||||
* ## About the non-preemptive model
|
||||
*
|
||||
* The reason why we can't have a preemptive process is because we couldn't
|
||||
* interrupt each single step in the process, so the most basic unit could be
|
||||
* interrupted is the step. So, the caveat here is make the step as small as
|
||||
* possible, and treat it as some atomic operation that guaranteed to not been
|
||||
* interrupted by Process. For example, if we alias 'next' as 'atomic':
|
||||
*
|
||||
* process.start()
|
||||
* .atomic(stepA) // <--- now it's waiting this
|
||||
* .atomic(stepB)
|
||||
*
|
||||
* // some urgent event occurs
|
||||
* process.stop()
|
||||
* .atomic( doThisAsQuickAsPossible )
|
||||
*
|
||||
* It would be better than:
|
||||
*
|
||||
* process.start()
|
||||
* .atomic(() => stepA.then(stepB))
|
||||
*
|
||||
* // some urgent event occurs
|
||||
* process.stop()
|
||||
* .atomic( doThisAsQuickAsPossible )
|
||||
*
|
||||
* Since in the second example the first step of the 'stop' phase must wait
|
||||
* both the stepA & stepB, while in the first one it only needs to wait stepA.
|
||||
* However, this depends on which atomic operations is needed.
|
||||
*
|
||||
* Nevertheless, user is able to make the steps 'interruptible' via some special
|
||||
* methods of the process. That is, to monitor the phase changes to nullify the
|
||||
* step:
|
||||
*
|
||||
* var process = new Process();
|
||||
* process.start()
|
||||
* .next(() => {
|
||||
* var phaseShifted = false;
|
||||
* process.until('stop')
|
||||
* .next(() => {phaseShifted = true;});
|
||||
* return new Promise((r, rj) => {
|
||||
* setTimeout(() => {
|
||||
* if (phaseShifted) { console.log('do nothing'); }
|
||||
* else { console.log('do something'); }
|
||||
* }, 1000)
|
||||
* });
|
||||
* })
|
||||
*
|
||||
* // some urgent event occurs
|
||||
* process.stop()
|
||||
* .next( doThisAsQuickAsPossible )
|
||||
*
|
||||
* So that the first step of the 'stop' phase would execute immediately after
|
||||
* the phase shifted, since the last step of the previous phase aborted itself.
|
||||
* In future the trick to nullify the last step may be included in as a method
|
||||
* of Process, but currently the manual detecting is still necessary.
|
||||
*/
|
||||
|
||||
(function(exports) {
|
||||
var Process = function() {
|
||||
this.states = {
|
||||
phase: null,
|
||||
currentPromise: null,
|
||||
until: {
|
||||
resolver: null,
|
||||
phase: null
|
||||
},
|
||||
// @see: #next
|
||||
stepResults: [],
|
||||
// @see: #next
|
||||
currentPhaseSteps: 0
|
||||
};
|
||||
};
|
||||
|
||||
Process.prototype.start = function() {
|
||||
this.states.phase = 'start';
|
||||
this.states.currentPromise = Promise.resolve();
|
||||
return this;
|
||||
};
|
||||
|
||||
Process.prototype.stop = function() {
|
||||
return this.shift('start', 'stop');
|
||||
};
|
||||
|
||||
Process.prototype.destroy = function() {
|
||||
return this.shift('stop', 'destroy');
|
||||
};
|
||||
|
||||
Process.prototype.shift = function(prev, current) {
|
||||
if (prev !== this.states.phase) {
|
||||
var error = new Error(`Must be ${prev} before shift to ${current},
|
||||
but now it's ${this.states.phase}`);
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
this.states.phase = current;
|
||||
if (this.until.phase === this.states.phase) {
|
||||
this.until.resolver();
|
||||
}
|
||||
// Concat new step to switch to the 'next promise'.
|
||||
this.states.currentPromise =
|
||||
this.states.currentPromise.catch((err) => {
|
||||
if (!(err instanceof Process.InterruptError)) {
|
||||
// We need to re-throw it again and bypass the whole
|
||||
// phase, until the next phase (final phase) to
|
||||
// handle it. Since in Promise, steps after catch would
|
||||
// not be affected by the catched error and keep executing.
|
||||
throw err;
|
||||
}
|
||||
// And if it's an interrupt error we do nothing, so that it would
|
||||
// make the chain omit this error and execute the following steps.
|
||||
});
|
||||
// At the moment of shifting, there are no steps belong to the new phase.
|
||||
this.states.currentPhaseSteps = 0;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a Promise that only be resolved when we get shifed to the
|
||||
* target phase.
|
||||
*/
|
||||
Process.prototype.until = function(phase) {
|
||||
var promise = new Promise((res) => {
|
||||
this.states.until.resolver = res;
|
||||
});
|
||||
return promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* The 'step' can only be a function return Promise/Process/plain value.
|
||||
* No matter a Promise or Process it would return,
|
||||
* the chain would concat it as the Promise rule.
|
||||
* If it's plain value then this process would ignore it, as
|
||||
* what a Promise does.
|
||||
*
|
||||
* About the resolving values:
|
||||
*
|
||||
* .next( fnResolveA, fnResolveB ) --> #save [a, b] in this process
|
||||
* .next( fnResolveC ) --> #receive [a, b] as first argument
|
||||
* .next( fnResolveD ) --> #receive c as first argument
|
||||
* .next( fnResolveE, fnResolveF) --> #each of them receive d as argument
|
||||
*/
|
||||
Process.prototype.next = function(...tasks) {
|
||||
if (!this.states.currentPromise) {
|
||||
throw new Error('Process should initialize with the `start` method');
|
||||
}
|
||||
// At definition stage, set it's phase.
|
||||
// And check if it's a function.
|
||||
tasks.forEach((task) => {
|
||||
if ('function' !== typeof task) {
|
||||
throw new Error(`The task is not a function: ${task}`);
|
||||
}
|
||||
task.phase = this.states.phase;
|
||||
});
|
||||
|
||||
// First, concat a 'then' to check interrupt.
|
||||
this.states.currentPromise =
|
||||
this.states.currentPromise.then(() => {
|
||||
// Would check: if the phase it belongs to is not what we're in,
|
||||
// the process need to be interrputed.
|
||||
for (var task of tasks) {
|
||||
if (this.checkInterrupt(task)) {
|
||||
throw new Process.InterruptError();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Read it as:
|
||||
// 1. execute all tasks to generate resolvable-promises
|
||||
// 2. Promise.all(...) to wait these resolvable-promises
|
||||
// 3. append a general error handler after the Promise.all
|
||||
// so that if any error occurs it would print them out
|
||||
// So, including the code above, we would have:
|
||||
//
|
||||
// currentPromise {
|
||||
// [checkInterrupt(tasks)]
|
||||
// [Promise.all([taskA1, taskA2...])]
|
||||
// [error handler] +}
|
||||
//
|
||||
// The 'checkInterrupt' and 'error handler' wrap the actual steps
|
||||
// to do the necessary checks.
|
||||
this.states.currentPromise =
|
||||
this.states.currentPromise.then(() => this.generateStep(tasks));
|
||||
this.states.currentPromise =
|
||||
this.states.currentPromise.catch(this.generateErrorLogger({
|
||||
'nth-step': this.currentPhaseSteps
|
||||
}));
|
||||
|
||||
// A way to know if these tasks is the first steps in the current phase,
|
||||
// and it's also convenient for debugging.
|
||||
this.states.currentPhaseSteps += 1;
|
||||
return this;
|
||||
|
||||
};
|
||||
|
||||
Process.prototype.rescue = function(handler) {
|
||||
this.states.currentPromise =
|
||||
this.states.currentPromise.catch((err) => {
|
||||
if (err instanceof Process.InterruptError) {
|
||||
// Only built-in phase transferring catch can handle interrupts.
|
||||
// Re-throw it until we reach the final catch we set.
|
||||
throw err;
|
||||
} else {
|
||||
handler(err);
|
||||
}
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* An interface to explicitly put multiple tasks execute at one time.
|
||||
**/
|
||||
Process.prototype.wait = function() {
|
||||
this.next.apply(this, arguments);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* At the preemptive mode a suppressor would be installed as the final step
|
||||
* of the previous interrupted Promise, to igore any errors since it has been
|
||||
* interrupted.
|
||||
*/
|
||||
Process.prototype.generateSuppressor = function() {
|
||||
return (err) => {};
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute task and get Promises or plain values them return,
|
||||
* and then return the wrapped Promise as the next step of this
|
||||
* process. The name 'step' indicates the generated Promise,
|
||||
* which is one step of the main Promise of the current phase.
|
||||
*/
|
||||
Process.prototype.generateStep = function(tasks) {
|
||||
// So we unwrap the task first, and then put it in the array.
|
||||
// Since we need to give the 'currentPromise' a function as what the
|
||||
// tasks passed here.
|
||||
var chains = tasks.map((task) => {
|
||||
// Reset the registered results.
|
||||
var stepResults = this.states.stepResults;
|
||||
this.states.stepResults = [];
|
||||
var chain;
|
||||
// If it has multiple results, means it's a task group
|
||||
// generated results.
|
||||
if (this.states.stepResults.length > 1) {
|
||||
chain = task(stepResults);
|
||||
} else {
|
||||
chain = task(stepResults[0]);
|
||||
}
|
||||
|
||||
// Ordinary function returns 'undefine' or other things.
|
||||
if (!chain) {
|
||||
// It's a plain value.
|
||||
// Store it as one of results.
|
||||
this.states.stepResults.push(chain);
|
||||
return Promise.resolve(chain);
|
||||
}
|
||||
|
||||
if (chain instanceof Process) {
|
||||
// Premise: it's a started process.
|
||||
return chain.states.currentPromise.then((resolvedValue) => {
|
||||
this.states.stepResults.push(resolvedValue);
|
||||
});
|
||||
} else if (chain.then) {
|
||||
// Ordinary promise can be concated immediately.
|
||||
return chain.then((resolvedValue) => {
|
||||
this.states.stepResults.push(resolvedValue);
|
||||
});
|
||||
} else {
|
||||
// It's a plain value.
|
||||
// Store it as one of results.
|
||||
this.states.stepResults.push(chain);
|
||||
return Promise.resolve(chain);
|
||||
}
|
||||
});
|
||||
return Promise.all(chains);
|
||||
};
|
||||
|
||||
/** We need this to prevent the step() throw errors.
|
||||
* In this catch, we distinguish the interrupt and other errors,
|
||||
* and then bypass the former and print the later out.
|
||||
*
|
||||
* The final fate of the real errors is it would be re-throw again
|
||||
* after we print the instance out. We need to do that since after an
|
||||
* error the promise shouldn't keep executing. If we don't throw it
|
||||
* again, since the error has been catched, the rest steps in the
|
||||
* promise would still be executed, and the user-set rescues would
|
||||
* not catch this error.
|
||||
*
|
||||
* As a conclusion, no matter whether the error is an interrupt,
|
||||
* we all need to throw it again. The only difference is if it's
|
||||
* and interrupt we don't print it out.
|
||||
*/
|
||||
Process.prototype.generateErrorLogger = function(debuginfo) {
|
||||
return (err) => {
|
||||
if (!(err instanceof Process.InterruptError)) {
|
||||
console.error(`ERROR during #${debuginfo['nth-steps']}
|
||||
step executes: ${err.message}`, err);
|
||||
}
|
||||
throw err;
|
||||
};
|
||||
};
|
||||
|
||||
Process.prototype.checkInterrupt = function(step) {
|
||||
if (step.phase !== this.states.phase) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Because DRY.
|
||||
*/
|
||||
Process.defer = function() {
|
||||
var resolve, reject;
|
||||
var promise = new Promise((res, rej) => {
|
||||
resolve = res;
|
||||
reject = rej;
|
||||
});
|
||||
var result = {
|
||||
'resolve': resolve,
|
||||
'reject': reject,
|
||||
'promise': promise
|
||||
};
|
||||
return result;
|
||||
};
|
||||
|
||||
/* Static version for mimicking Promise.all */
|
||||
Process.wait = function(steps) {
|
||||
var process = new Process();
|
||||
return process.wait(steps);
|
||||
};
|
||||
|
||||
Process.InterruptError = function(message) {
|
||||
this.name = 'InterruptError';
|
||||
this.message = message || '';
|
||||
};
|
||||
|
||||
Process.InterruptError.prototype = new Error();
|
||||
|
||||
exports.Process = Process;
|
||||
})(window);
|
||||
|
||||
123
gaia/apps/system/lockscreen/js/stream/stream.js
Executable file
123
gaia/apps/system/lockscreen/js/stream/stream.js
Executable file
|
|
@ -0,0 +1,123 @@
|
|||
/* global Process */
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Combine the abilities of the event handling and asynchronous operations
|
||||
* sequentializing together. So that every Stream could:
|
||||
*
|
||||
* 1. For the ordinary events, append steps to the main Process to queue
|
||||
* the event handlers.
|
||||
* 2. For other urgent events (interrupts), immediately execute the event
|
||||
* handler without queuing it.
|
||||
* 3. Only receive events when it's 'ready'. Before that, no source events
|
||||
* would be forwarded and handled.
|
||||
* 4. Once phase becomes 'stop', no events would be received again.
|
||||
**/
|
||||
(function(exports) {
|
||||
var Stream = function(configs) {
|
||||
this.configs = {
|
||||
events: configs.events || [],
|
||||
interrupts: configs.interrupts || []
|
||||
};
|
||||
if (configs.sources && 0 !== configs.sources.length) {
|
||||
this.configs.sources = configs.sources;
|
||||
} else {
|
||||
this.configs.sources = [];
|
||||
}
|
||||
this._forwardTo = null;
|
||||
// Need to delegate to Source.
|
||||
this.onchange = this.onchange.bind(this);
|
||||
};
|
||||
|
||||
Stream.prototype.phase = function() {
|
||||
return this.process.states.phase;
|
||||
};
|
||||
|
||||
Stream.prototype.start = function(forwardTo) {
|
||||
this._forwardTo = forwardTo;
|
||||
this.process = new Process();
|
||||
this.process.start();
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Kick off Source and start do things.
|
||||
*/
|
||||
Stream.prototype.ready = function() {
|
||||
this.configs.sources.forEach((source) => {
|
||||
source.start(this.onchange);
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
Stream.prototype.stop = function() {
|
||||
this.process.stop();
|
||||
this.configs.sources.forEach((source) => {
|
||||
source.stop();
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
Stream.prototype.destroy = function() {
|
||||
this.process.destroy();
|
||||
return this;
|
||||
};
|
||||
|
||||
Stream.prototype.next = function(step) {
|
||||
this.process.next(step);
|
||||
return this;
|
||||
};
|
||||
|
||||
Stream.prototype.rescue = function(rescuer) {
|
||||
this.process.rescue(rescuer);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a Promise get resolved when the stream turn to
|
||||
* the specific phase. For example:
|
||||
*
|
||||
* stream.until('stop')
|
||||
* .then(() => { console.log('stream stopped') });
|
||||
* stream.start();
|
||||
*/
|
||||
Stream.prototype.until = function(phase) {
|
||||
return this.process.until(phase);
|
||||
};
|
||||
|
||||
/**
|
||||
* Only when all tasks passed in get resolved,
|
||||
* the process would go to the next.
|
||||
*/
|
||||
Stream.prototype.wait = function(tasks) {
|
||||
this.process.wait(tasks);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* It would receive events from Source, and than queue or not queue
|
||||
* it, depends on whether the event is an interrupt.
|
||||
*/
|
||||
Stream.prototype.onchange = function(evt) {
|
||||
if ('start' !== this.process.states.phase) {
|
||||
return this;
|
||||
}
|
||||
if (-1 !== this.configs.interrupts.indexOf(evt.type)) {
|
||||
// Interrupt would be handled immediately.
|
||||
this._forwardTo(evt);
|
||||
return this;
|
||||
} else {
|
||||
// Event would be handled after queuing.
|
||||
// This is, if the event handle return a Promise or Process,
|
||||
// that can be fulfilled later.
|
||||
this.process.next(() => {
|
||||
return this._forwardTo(evt);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
exports.Stream = Stream;
|
||||
})(window);
|
||||
|
||||
354
gaia/apps/system/lockscreen/js/wallpaper_manager.js
Executable file
354
gaia/apps/system/lockscreen/js/wallpaper_manager.js
Executable file
|
|
@ -0,0 +1,354 @@
|
|||
/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
|
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
||||
|
||||
/* global ImageUtils, LazyLoader */
|
||||
|
||||
'use strict';
|
||||
|
||||
(function(exports) {
|
||||
|
||||
const WALLPAPER_KEY = 'wallpaper.image';
|
||||
const WALLPAPER_VALID_KEY = 'wallpaper.image.valid';
|
||||
const DEFAULT_WALLPAPER_URL = '/resources/images/backgrounds/default.png';
|
||||
|
||||
/**
|
||||
* This system module reads the system wallpaper setting on startup
|
||||
* and monitors changes to that setting, broadcasting a
|
||||
* 'wallpaperchange' event with a blob: URL to tell the system and
|
||||
* lockscreen about the new wallpaper.
|
||||
*
|
||||
* If the wallpaper value read from the settings DB is a URL this
|
||||
* module converts it to a blob. If the wallpaper image does not
|
||||
* exactly match the size of the screen, this module resizes it
|
||||
* (lazy-loading shared/js/image_utils.js when needed). If the
|
||||
* wallpaper value is converted to a blob or resized, the modified
|
||||
* value is saved back to the settings DB so that it will not need
|
||||
* to be modified the next time it is read.
|
||||
*
|
||||
* start(), stop(), and getBlobURL() are the only public methods,
|
||||
* and stop() is only exposed for the benefit of unit
|
||||
* tests. _setWallpaper() is called on startup and whenever the
|
||||
* wallpaper.image setting changes. Each call to _setWallpaper()
|
||||
* eventually causes a call to _publish() which broadcasts the new
|
||||
* wallpaper event to the lockscreen and the rest of the system
|
||||
* app. The call to _publish() does not always happen directly,
|
||||
* however: _setWallpaper() may call _checkSize(), which calls
|
||||
* _publish(), or it may call _toBlob() which calls _checkSize().
|
||||
* Unless the build is mis-configured and the wallpaper in the
|
||||
* settings db and the fallback default wallpaper is broken, every
|
||||
* call to _setWallpaper() ends up broadcasting a 'wallpaperchange'
|
||||
* event with a valid blob: url for a wallpaper image that has the
|
||||
* same size as the screen.
|
||||
*
|
||||
* @class WallpaperManager
|
||||
*/
|
||||
function WallpaperManager() {
|
||||
this._started = false;
|
||||
this._blobURL = null;
|
||||
}
|
||||
|
||||
WallpaperManager.prototype = {
|
||||
/**
|
||||
* Bootstrap the module. Read the current wallpaper from the
|
||||
* settings db and pass it to _setWallpaper(). Also listen for
|
||||
* changes to the wallpaper and invoke _setWallpaper() for each
|
||||
* one.
|
||||
*/
|
||||
start: function() {
|
||||
if (this._started) {
|
||||
throw 'Instance should not be start()\'ed twice.';
|
||||
}
|
||||
this._started = true;
|
||||
debug('started');
|
||||
|
||||
// Query the wallpaper
|
||||
var lock = navigator.mozSettings.createLock();
|
||||
var query = lock.get(WALLPAPER_KEY);
|
||||
query.onsuccess = function() {
|
||||
var wallpaper = query.result[WALLPAPER_KEY];
|
||||
if (!wallpaper) {
|
||||
debug('no wallpaper found at startup; using default');
|
||||
this._setWallpaper(DEFAULT_WALLPAPER_URL);
|
||||
}
|
||||
else if (wallpaper instanceof Blob) {
|
||||
// If the wallpaper is a blob, first go see if we have already
|
||||
// validated it size. Because if we have, we don't have to check
|
||||
// the size again or even load the code to check its size.
|
||||
var query2 = lock.get(WALLPAPER_VALID_KEY);
|
||||
query2.onsuccess = function() {
|
||||
var valid = query2.result[WALLPAPER_VALID_KEY];
|
||||
this._setWallpaper(wallpaper, valid);
|
||||
}.bind(this);
|
||||
}
|
||||
else {
|
||||
// If the wallpaper is not a blob, just pass it to _setWallpaper
|
||||
// and try to convert it to a blob there.
|
||||
this._setWallpaper(wallpaper);
|
||||
}
|
||||
}.bind(this);
|
||||
|
||||
// And register a listener so we'll be notified of future changes
|
||||
// to the wallpaper
|
||||
this.observer = function(e) {
|
||||
this._setWallpaper(e.settingValue);
|
||||
}.bind(this);
|
||||
navigator.mozSettings.addObserver(WALLPAPER_KEY, this.observer);
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop the module an stop listening for changes to the wallpaper setting.
|
||||
* This method is only used by unit tests.
|
||||
*/
|
||||
stop: function() {
|
||||
if (!this._started) { return; }
|
||||
navigator.mozSettings.removeObserver(WALLPAPER_KEY, this.observer);
|
||||
this._started = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the blob URL saved from earlier wallpaper change event
|
||||
* The lockscreen may miss the event and needs to look the URL up here.
|
||||
* @returns {String} the blob URL
|
||||
*/
|
||||
getBlobURL: function() {
|
||||
if (!this._started) { return; }
|
||||
return this._blobURL;
|
||||
},
|
||||
|
||||
//
|
||||
// This method is called on startup and when the wallpaper
|
||||
// changes. It always causes _publish() to be invoked and a
|
||||
// "wallpaperchange" event to be broadcast to interested
|
||||
// listeners. If the new value is a blob that is already
|
||||
// validated, then _publish() is called directly. Otherwise, it is
|
||||
// called indirectly by _toBlob() or _checkSize().
|
||||
//
|
||||
_setWallpaper: function(value, valid) {
|
||||
if (!this._started) { return; }
|
||||
|
||||
// If we are called because we just saved a resized blob back
|
||||
// to the settings db, then ignore the call.
|
||||
if (value instanceof Blob && value.size === this.savedBlobSize) {
|
||||
this.savedBlobSize = false;
|
||||
return;
|
||||
}
|
||||
|
||||
debug('new wallpaper', valid ? 'size already validated' : '');
|
||||
|
||||
if (typeof value === 'string') {
|
||||
this._toBlob(value);
|
||||
}
|
||||
else if (value instanceof Blob) {
|
||||
// If this blob has already been validated, we can just display it.
|
||||
// Otherwise we need to check its size first
|
||||
if (valid) {
|
||||
this._publish(value);
|
||||
}
|
||||
else {
|
||||
this._checkSize(value);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// The value in the settings database is invalid, so
|
||||
// use the default image. Note that this will update the
|
||||
// settings db with a valid value.
|
||||
debug('Invalid wallpaper value in settings;',
|
||||
'reverting to default wallpaper.');
|
||||
this._toBlob(DEFAULT_WALLPAPER_URL);
|
||||
}
|
||||
},
|
||||
|
||||
//
|
||||
// This method expects a wallpaper URL (possibly a data: URL) and
|
||||
// uses XHR to convert it to a blob. If it succeeds, it passes the
|
||||
// blob to _checkSize() which resizes it if needed and calls
|
||||
// _publish() to broadcast the new wallpaper.
|
||||
//
|
||||
_toBlob: function(url) {
|
||||
if (!this._started) { return; }
|
||||
debug('converting wallpaper url to blob');
|
||||
|
||||
// If we trying to convert the default wallpaper url to a blob
|
||||
// note that because there is some error recovery code that behaves
|
||||
// differently in that last resort case.
|
||||
this.tryingDefaultWallpaper = (url === DEFAULT_WALLPAPER_URL);
|
||||
|
||||
// If the settings db had a string in it we assume it is a
|
||||
// relative url or data: url and try to read it with XHR.
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', url);
|
||||
xhr.responseType = 'blob';
|
||||
xhr.send();
|
||||
xhr.onload = function() {
|
||||
// Once we've loaded the wallpaper as a blob, verify its size.
|
||||
// We pass true as the second argument to force it to be saved
|
||||
// back to the db (as a blob) even if the size is okay.
|
||||
this._checkSize(xhr.response, true);
|
||||
}.bind(this);
|
||||
xhr.onerror = function() {
|
||||
// If we couldn't load the url and if it was something other
|
||||
// than the default wallpaper url, then try again with the default.
|
||||
if (!this.tryingDefaultWallpaper) {
|
||||
debug('corrupt wallpaper url in settings;',
|
||||
'reverting to default wallpaper');
|
||||
this._toBlob(DEFAULT_WALLPAPER_URL);
|
||||
}
|
||||
else {
|
||||
// This was our last resort, and it failed, so no wallpaper
|
||||
// image is available.
|
||||
console.error('Cannot load wallpaper from', url);
|
||||
}
|
||||
}.bind(this);
|
||||
},
|
||||
|
||||
//
|
||||
// This method checks the dimensions of the image blob and crops
|
||||
// and resizes the image if necessary so that it is exactly the
|
||||
// same size as the screen. If the image was resized, or if it was
|
||||
// read from a URL, then this method saves the new blob back to
|
||||
// the settings db and marks it as valid. If the image was not
|
||||
// resized, then the image is marked as valid so that the check
|
||||
// does not need to be performed when the phone is rebooted. In
|
||||
// either case, after the image is saved and/or validated, this
|
||||
// method calls _publish() to broadcast the new wallpaper.
|
||||
//
|
||||
// If the blob does not hold a valid image, that will be
|
||||
// discovered while attempting to check its size and in that case,
|
||||
// this method falls back on the default wallpaper by calling
|
||||
// _toBlob() with the default wallpaper URL.
|
||||
//
|
||||
// This method lazy-loads ImageUtils from shared/js/image_utils.js.
|
||||
// Once a wallpaper has had its size checked once, it is marked as
|
||||
// valid in the settings db, so these image utilities will not
|
||||
// need to be loaded into the system app on subsequent reboots.
|
||||
//
|
||||
_checkSize: function(blob, needsToBeSaved) {
|
||||
if (!this._started) { return; }
|
||||
debug('resizing wallpaper if needed');
|
||||
|
||||
// How big (in device pixels) is the screen?
|
||||
var screenWidth = Math.ceil(screen.width * window.devicePixelRatio);
|
||||
var screenHeight = Math.ceil(screen.height * window.devicePixelRatio);
|
||||
|
||||
// For performance we need to guarantee that the size of the wallpaper
|
||||
// is exactly the same as the size of the screen. LazyLoad the
|
||||
// ImageUtils module, and call its resizeAndCropToCover() method to
|
||||
// resize and crop the image as needed so that it is the right size.
|
||||
// Note that this utility funtion can determine the size of an image
|
||||
// without decoding it and if the image is already the right size
|
||||
// it will not modify it.
|
||||
LazyLoader.load('shared/js/image_utils.js', function() {
|
||||
ImageUtils
|
||||
.resizeAndCropToCover(blob, screenWidth, screenHeight, ImageUtils.PNG)
|
||||
.then(
|
||||
function resolve(resizedBlob) {
|
||||
// If the blob changed or if the second argument was true
|
||||
// then we need to save the blob back to the settings db
|
||||
if (resizedBlob !== blob || needsToBeSaved) {
|
||||
this._save(resizedBlob);
|
||||
}
|
||||
else {
|
||||
// If the blob didn't change we don't have to save it,
|
||||
// but we do need to mark it as valid
|
||||
this._validate();
|
||||
}
|
||||
|
||||
// Display the wallpaper
|
||||
this._publish(resizedBlob);
|
||||
}.bind(this),
|
||||
function reject(error) {
|
||||
// This will only happen if the settings db contains a blob that
|
||||
// is not actually an image. If that happens for some reason,
|
||||
// fall back on the default wallpaper.
|
||||
if (!this.tryingDefaultWallpaper) {
|
||||
debug('Corrupt wallpaper image in settings;',
|
||||
'reverting to default wallpaper.');
|
||||
this._toBlob(DEFAULT_WALLPAPER_URL);
|
||||
}
|
||||
else {
|
||||
// We were already trying the default wallpaper and it failed.
|
||||
// So we just give up in this case.
|
||||
console.error('Default wallpaper image is invalid');
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
//
|
||||
// This method sets a property in the settings db to indicate that
|
||||
// the current wallpaper is the same size as the screen. Setting
|
||||
// this property is an optimization that allows us to skip the
|
||||
// call to _checkSize() on subsequent startups. This method
|
||||
// returns synchronously and does not wait for the settings db
|
||||
// operation to complete.
|
||||
//
|
||||
_validate: function() {
|
||||
if (!this._started) { return; }
|
||||
debug('marking wallpaper as valid');
|
||||
var settings = {};
|
||||
settings[WALLPAPER_VALID_KEY] = true; // We've checked its size
|
||||
navigator.mozSettings.createLock().set(settings);
|
||||
},
|
||||
|
||||
//
|
||||
// This method saves the wallpaper blob to the settings db and
|
||||
// also marks it as valid so that we know on subsequent startups
|
||||
// that its size has already been checked. This method returns
|
||||
// synchronously and does not wait for the settings db operation
|
||||
// to complete.
|
||||
//
|
||||
_save: function(blob) {
|
||||
if (!this._started) { return; }
|
||||
debug('saving converted or resized wallpaper to settings');
|
||||
|
||||
// Set a flag so that we don't repeat this whole process when
|
||||
// we're notified about this save. The flag contains the size of
|
||||
// the blob we're saving so it is very unlikely that we'll have
|
||||
// a race condition.
|
||||
this.savedBlobSize = blob.size;
|
||||
|
||||
// Now save the blob to the settings db, and also save a flag
|
||||
// that indicates that we've already checked the size of the image.
|
||||
// This allows us to skip the check at boot time.
|
||||
var settings = {};
|
||||
settings[WALLPAPER_KEY] = blob;
|
||||
settings[WALLPAPER_VALID_KEY] = true; // We've checked its size
|
||||
navigator.mozSettings.createLock().set(settings);
|
||||
},
|
||||
|
||||
//
|
||||
// This method creates a blob: URL for the specfied blob and publishes
|
||||
// the URL via a 'wallpaperchange' event. If there was a previous
|
||||
// wallpaper, its blob: URL is revoked. This method is synchronous.
|
||||
//
|
||||
_publish: function(blob) {
|
||||
if (!this._started) { return; }
|
||||
debug('publishing wallpaperchange event');
|
||||
|
||||
// If we have a blob:// url for previous wallpaper, release it now
|
||||
if (this._blobURL) {
|
||||
URL.revokeObjectURL(this._blobURL);
|
||||
}
|
||||
|
||||
// Create a new blob:// url for this blob
|
||||
this._blobURL = URL.createObjectURL(blob);
|
||||
|
||||
// And tell the system about it.
|
||||
window.dispatchEvent(new CustomEvent('wallpaperchange',
|
||||
{ detail: { url: this._blobURL } }));
|
||||
}
|
||||
};
|
||||
|
||||
// Log debug messages
|
||||
function debug(...args) {
|
||||
if (WallpaperManager.DEBUG) {
|
||||
args.unshift('[WallpaperManager]');
|
||||
console.log.apply(console, args);
|
||||
}
|
||||
}
|
||||
WallpaperManager.DEBUG = false; // Set to false to silence debug output
|
||||
|
||||
/** @exports WallpaperManager */
|
||||
exports.WallpaperManager = WallpaperManager;
|
||||
}(window));
|
||||
111
gaia/apps/system/lockscreen/js/widgets/clock/lockscreen_clock_widget.js
Executable file
111
gaia/apps/system/lockscreen/js/widgets/clock/lockscreen_clock_widget.js
Executable file
|
|
@ -0,0 +1,111 @@
|
|||
/* global LockScreenBasicComponent, LockScreenClockWidgetSetup, mozIntl */
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* The Clock widget on LockScreen.
|
||||
* Clock widget states:
|
||||
* ClockSetup, ClockTick, ClockStop
|
||||
**/
|
||||
(function(exports) {
|
||||
var LockScreenClockWidget = function() {
|
||||
LockScreenBasicComponent.apply(this);
|
||||
this.resources.elements.time = 'lockscreen-clock-time';
|
||||
this.resources.elements.date = 'lockscreen-date';
|
||||
this.resources.elements.alarm = 'lockscreen-alarm';
|
||||
this.resources.elements.alarmtime = 'lockscreen-alarm-time';
|
||||
this.timeFormatter = null;
|
||||
this.dateFormatter = null;
|
||||
this.configs.logger.debug = false; // turn on this when we're debugging
|
||||
};
|
||||
LockScreenClockWidget.prototype =
|
||||
Object.create(LockScreenBasicComponent.prototype);
|
||||
|
||||
LockScreenClockWidget.prototype.setup = function() {
|
||||
return (new LockScreenClockWidgetSetup(this));
|
||||
};
|
||||
|
||||
LockScreenClockWidget.prototype.updateFormatters = function() {
|
||||
this.dateFormatter = Intl.DateTimeFormat(navigator.languages, {
|
||||
weekday: 'long',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
});
|
||||
|
||||
// we're using mozIntl.DateTimeFormat to remove dayperiod
|
||||
this.timeFormatter = mozIntl.DateTimeFormat(navigator.languages, {
|
||||
hour12: navigator.mozHour12,
|
||||
dayperiod: false,
|
||||
hour: 'numeric',
|
||||
minute: 'numeric'
|
||||
});
|
||||
};
|
||||
|
||||
LockScreenClockWidget.prototype.updateClock =
|
||||
function() {
|
||||
var now = new Date();
|
||||
|
||||
var timeText = this.timeFormatter.format(now);
|
||||
var dateText = this.dateFormatter.format(now);
|
||||
|
||||
this.resources.elements.time.firstChild.data = timeText;
|
||||
this.resources.elements.date.firstChild.data = dateText;
|
||||
this.logger.debug('Clock updated', now);
|
||||
};
|
||||
|
||||
LockScreenClockWidget.prototype.updateAlarm =
|
||||
function() {
|
||||
this.fetchAlarmData()
|
||||
.then((alarmData) => {
|
||||
// if there is no alarm
|
||||
if(!alarmData) {
|
||||
this.resources.elements.alarm.classList.add('no-alarms');
|
||||
this.resources.elements.alarm.classList.remove('has-alarm');
|
||||
return;
|
||||
}
|
||||
|
||||
var type = 'AM',
|
||||
hour = parseInt(alarmData.hour, 10);
|
||||
|
||||
// Decide wheter to use AM or PM
|
||||
if(hour > 12) {
|
||||
type = 'PM';
|
||||
hour = hour - 12;
|
||||
}
|
||||
|
||||
var fullAlarmTime = hour + ':' + alarmData.minute + type;
|
||||
this.resources.elements.alarmtime.textContent = fullAlarmTime;
|
||||
this.resources.elements.alarm.classList.remove('no-alarms');
|
||||
this.resources.elements.alarm.classList.add('has-alarm');
|
||||
this.logger.debug('Alarm updated to :', fullAlarmTime);
|
||||
}).catch(function(e){
|
||||
console.error('Error occured while alarm info on lockscreen : ', e);
|
||||
throw(e);
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* Gets the Alarm Data from the DataStore.
|
||||
*/
|
||||
LockScreenClockWidget.prototype.fetchAlarmData = function() {
|
||||
return navigator.getDataStores('alarms')
|
||||
.then((stores) => {
|
||||
return [stores[0].getLength(), stores[0]];
|
||||
}).then(([len, store]) => {
|
||||
if(0 === len) {
|
||||
return;
|
||||
}
|
||||
return store.get(1);
|
||||
}).then((fetchedData = {}) => {
|
||||
if(0 === Object.keys(fetchedData).length) {
|
||||
return null;
|
||||
}
|
||||
return fetchedData;
|
||||
}).catch(function(e){
|
||||
console.error('Error occured while getting alarm data : ', e);
|
||||
throw(e);
|
||||
});
|
||||
};
|
||||
|
||||
exports.LockScreenClockWidget = LockScreenClockWidget;
|
||||
})(window);
|
||||
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
/* global DOMEventSource */
|
||||
/* global LockScreenBasicState */
|
||||
/* global LockScreenClockWidgetTick */
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Setup state would initialize all resources and properties the
|
||||
* component has.
|
||||
*
|
||||
* This is only a initialization state, and would immediately
|
||||
* transfer to the starting state.
|
||||
*
|
||||
* Initialization state should be the only one state which isn't
|
||||
* reentrant. Other states by default should be reentrant.
|
||||
**/
|
||||
(function(exports) {
|
||||
var LockScreenClockWidgetSetup = function(component) {
|
||||
LockScreenBasicState.apply(this, arguments);
|
||||
this.configs.name = 'LockScreenClockWidgetSetup';
|
||||
// Just to prevent stream without stream would throw error.
|
||||
this.configs.stream.sources = [ new DOMEventSource({}) ];
|
||||
};
|
||||
LockScreenClockWidgetSetup.prototype =
|
||||
Object.create(LockScreenBasicState.prototype);
|
||||
|
||||
LockScreenClockWidgetSetup.prototype.start = function() {
|
||||
return LockScreenBasicState.prototype.start.call(this)
|
||||
.next(this.queryElements.bind(this))
|
||||
.next(this.component.updateFormatters.bind(this.component))
|
||||
.next(this.component.updateClock.bind(this.component))
|
||||
.next(this.component.updateAlarm.bind(this.component))
|
||||
.next(this.transferToTick.bind(this));
|
||||
};
|
||||
|
||||
LockScreenClockWidgetSetup.prototype.transferToTick = function() {
|
||||
return this.transferTo(LockScreenClockWidgetTick);
|
||||
};
|
||||
|
||||
LockScreenClockWidgetSetup.prototype.queryElements = function() {
|
||||
var elements = this.component.resources.elements;
|
||||
for (var key in elements) {
|
||||
if ('string' === typeof elements[key]) {
|
||||
var query = elements[key];
|
||||
var result = document.getElementById(query);
|
||||
if (!result) {
|
||||
throw new Error(`Can't query element ${key} with query: ${query}`);
|
||||
}
|
||||
|
||||
// Create a text node so we can update its data later, otherwise if we
|
||||
// change textContent it will remove and insert a new text node, which
|
||||
// could cause flickering if the docshell still has isActive false.
|
||||
if (!result.firstChild) {
|
||||
result.appendChild(document.createTextNode(''));
|
||||
}
|
||||
elements[key] = result;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.LockScreenClockWidgetSetup = LockScreenClockWidgetSetup;
|
||||
})(window);
|
||||
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
/* global Stream, DOMEventSource */
|
||||
/* global LockScreenClockWidgetTick, LockScreenBasicState */
|
||||
'use strict';
|
||||
|
||||
/***/
|
||||
(function(exports) {
|
||||
var LockScreenClockWidgetSuspend = function(component) {
|
||||
LockScreenBasicState.apply(this, arguments);
|
||||
this.configs.name = 'LockScreenClockWidgetSuspend';
|
||||
this.configs.stream.interrupts = [
|
||||
'screenchange',
|
||||
];
|
||||
this.configs.stream.sources =
|
||||
[new DOMEventSource({events: ['screenchange']})];
|
||||
this.handleSourceEvent = this.handleSourceEvent.bind(this);
|
||||
};
|
||||
LockScreenClockWidgetSuspend.prototype =
|
||||
Object.create(LockScreenBasicState.prototype);
|
||||
|
||||
LockScreenClockWidgetSuspend.prototype.start = function() {
|
||||
this.stream = new Stream(this.configs.stream);
|
||||
return this.stream.start(this.handleSourceEvent)
|
||||
.next(this.stream.ready.bind(this.stream));
|
||||
};
|
||||
|
||||
LockScreenClockWidgetSuspend.prototype.stop =
|
||||
function() {
|
||||
return LockScreenBasicState.prototype.stop.call(this);
|
||||
};
|
||||
|
||||
LockScreenClockWidgetSuspend.prototype.handleSourceEvent =
|
||||
function(evt) {
|
||||
switch (evt.type) {
|
||||
case 'screenchange':
|
||||
if (evt.detail.screenEnabled) {
|
||||
// Bug 1234731 mentioned that if we update clock in tick,
|
||||
// asynchronous step means it will be queued after other incidents
|
||||
// at the moment screen get light up (ex: ril events in Gecko),
|
||||
// so there is a visual lag that we need to deal with.
|
||||
//
|
||||
// The solution is we update the clock before we transfer to the
|
||||
// Tick state, so that the first painting will be earlier, and using
|
||||
// another entry in tick to transfer to, so that we can avoid
|
||||
// calling the function again in the Tick state.
|
||||
this.component.updateClock();
|
||||
return this.transferToTick();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
LockScreenClockWidgetSuspend.prototype.transferToTick =
|
||||
function() {
|
||||
this.component.transferTo(LockScreenClockWidgetTick);
|
||||
};
|
||||
|
||||
exports.LockScreenClockWidgetSuspend = LockScreenClockWidgetSuspend;
|
||||
})(window);
|
||||
|
||||
101
gaia/apps/system/lockscreen/js/widgets/clock/lockscreen_clock_widget_tick.js
Executable file
101
gaia/apps/system/lockscreen/js/widgets/clock/lockscreen_clock_widget_tick.js
Executable file
|
|
@ -0,0 +1,101 @@
|
|||
/* global Stream, DOMEventSource, MinuteClockSource */
|
||||
/* global LockScreenClockWidgetSuspend, LockScreenBasicState */
|
||||
'use strict';
|
||||
|
||||
/***/
|
||||
(function(exports) {
|
||||
var LockScreenClockWidgetTick = function(component) {
|
||||
LockScreenBasicState.apply(this, arguments);
|
||||
this.configs.name = 'LockScreenClockWidgetTick';
|
||||
this.configs.stream.events = [
|
||||
'ftudone',
|
||||
'moztimechange',
|
||||
'lockscreen-notification-clock-tick'
|
||||
];
|
||||
this.configs.stream.interrupts = [
|
||||
'screenchange'
|
||||
];
|
||||
this.configs.stream.sources =
|
||||
[new DOMEventSource({events: [
|
||||
'screenchange',
|
||||
'ftudone',
|
||||
'moztimechange',
|
||||
'screenchange',
|
||||
'lockscreen-notification-clock-tick'
|
||||
]}),
|
||||
new MinuteClockSource({
|
||||
'type': 'lockscreen-notification-clock-tick'
|
||||
})
|
||||
];
|
||||
this.handleSourceEvent = this.handleSourceEvent.bind(this);
|
||||
};
|
||||
LockScreenClockWidgetTick.prototype =
|
||||
Object.create(LockScreenBasicState.prototype);
|
||||
|
||||
/**
|
||||
* Note:
|
||||
* Due to performance issue we actually forked the start method to two,
|
||||
* the first one, with previous state is Setup, means it's from the widget
|
||||
* initialization, and need a full warming up before we call the clock
|
||||
* updating function; the second one, because of Bug 1234731, will update
|
||||
* the clock in the Suspend state in prior to this state, so we shouldn't
|
||||
* call it here again.
|
||||
*
|
||||
* In fact, a state diagram conscious solution is to split one common Tick
|
||||
* state into two "TickAfterSetup" and "TickFromSuspend" states.
|
||||
* However, this is too rigid and verbosed, so that although we concern about
|
||||
* the growth of code may soon mix the complicated statements up, we may
|
||||
* treat the argument and branch as the threshold. If we trick the start
|
||||
* method more than this case, we should immediately reconsider to split the
|
||||
* state.
|
||||
*/
|
||||
LockScreenClockWidgetTick.prototype.start = function() {
|
||||
if ('LockScreenClockWidgetSetup' !==
|
||||
this.component._previousState.configs.name) {
|
||||
return this.restart();
|
||||
}
|
||||
|
||||
this.stream = new Stream(this.configs.stream);
|
||||
return this.stream.start(this.handleSourceEvent)
|
||||
.next(this.component.updateFormatters.bind(this.component))
|
||||
.next(this.component.updateClock.bind(this.component))
|
||||
.next(this.stream.ready.bind(this.stream));
|
||||
};
|
||||
|
||||
LockScreenClockWidgetTick.prototype.restart = function() {
|
||||
this.stream = new Stream(this.configs.stream);
|
||||
return this.stream.start(this.handleSourceEvent)
|
||||
.next(this.stream.ready.bind(this.stream));
|
||||
};
|
||||
|
||||
LockScreenClockWidgetTick.prototype.stop =
|
||||
function() {
|
||||
return LockScreenBasicState.prototype.stop.call(this);
|
||||
};
|
||||
|
||||
LockScreenClockWidgetTick.prototype.handleSourceEvent =
|
||||
function(evt) {
|
||||
switch (evt.type) {
|
||||
case 'moztimechange':
|
||||
this.component.updateFormatters();
|
||||
this.component.updateClock();
|
||||
break;
|
||||
case 'ftudone':
|
||||
case 'lockscreen-notification-clock-tick':
|
||||
this.component.updateClock();
|
||||
break;
|
||||
case 'screenchange':
|
||||
if (!evt.detail.screenEnabled) {
|
||||
return this.transferToSuspend();
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
LockScreenClockWidgetTick.prototype.transferToSuspend = function() {
|
||||
this.component.transferTo(LockScreenClockWidgetSuspend);
|
||||
};
|
||||
|
||||
exports.LockScreenClockWidgetTick = LockScreenClockWidgetTick;
|
||||
})(window);
|
||||
|
||||
Loading…
Reference in a new issue