Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
1009 lines
32 KiB
JavaScript
1009 lines
32 KiB
JavaScript
/* global LockScreenClockWidget, Service, LockScreenSlide, LazyLoader,
|
|
LockScreenConnInfoManager, PasscodeHelper */
|
|
'use strict';
|
|
|
|
/**
|
|
* LockScreen now use strategy pattern to adapt the unlocker, which would
|
|
* report intentions like unlocking and launching camera to finish the job.
|
|
*
|
|
* @see intentionRouter in the component.
|
|
*/
|
|
(function(exports) {
|
|
|
|
var LockScreen = function() {
|
|
};
|
|
LockScreen.prototype = {
|
|
name: 'LockScreen',
|
|
|
|
configs: {
|
|
mode: 'default'
|
|
},
|
|
// 'notificationId' for opening app after unlocking.
|
|
_unlockingMessage: {},
|
|
// The unlocking strategy.
|
|
_unlocker: null,
|
|
_unlockerInitialized: false,
|
|
|
|
/*
|
|
* Lockscreen connection information manager
|
|
*/
|
|
_lockscreenConnInfoManager: null,
|
|
|
|
/*
|
|
* Boolean return true when initialized.
|
|
*/
|
|
ready: false,
|
|
|
|
/*
|
|
* Boolean return the status of the lock screen.
|
|
* Must not multate directly - use unlock()/lockIfEnabled()
|
|
* Listen to 'lockscreen-appclosed/opening/opened' events to properly
|
|
* handle status changes
|
|
*/
|
|
locked: true,
|
|
|
|
/*
|
|
* Boolean return whether if the lock screen is enabled or not.
|
|
* Must not multate directly - use setEnabled(val)
|
|
* Only Settings Listener should change this value to sync with data
|
|
* in Settings API.
|
|
*/
|
|
enabled: true,
|
|
|
|
/*
|
|
* Boolean returns whether the screen is enabled, as mutated by screenchange
|
|
* event.
|
|
* Note: 'undefined' should be regarded as 'true' as screenchange event
|
|
* doesn't trigger on device boot, and we want to make fail-safe procedures
|
|
* under such circamstances -- as we are never sure if screen is on or off.
|
|
*/
|
|
_screenEnabled: undefined,
|
|
|
|
/*
|
|
* Boolean should regenerate overlay color for notifications background
|
|
* When this is true, and when we're locking the device, we should
|
|
* regenerate the overlay color as specified in bug 950884
|
|
* Instead of doing the color generation in updateBackground,
|
|
* by doing this we can reduce critical path of updateBackground,
|
|
* and perceived performance of selecting wallpaper.
|
|
*/
|
|
_shouldRegenerateMaskedBackgroundColor: false,
|
|
|
|
/*
|
|
* String url of the background image to regenerate overlay color from
|
|
*/
|
|
_regenerateMaskedBackgroundColorFrom: undefined,
|
|
|
|
/**
|
|
* How long the unlocked session is.
|
|
*/
|
|
_lastUnlockedInterval: 0,
|
|
_lastUnlockedTimeStamp: 0,
|
|
/**
|
|
* How long the locked session is.
|
|
*/
|
|
_lastLockedInterval: 0,
|
|
_lastLockedTimeStamp: 0,
|
|
|
|
/*
|
|
* Check the timeout of passcode lock
|
|
*/
|
|
_passCodeTimeoutCheck: false,
|
|
|
|
/*
|
|
* If user is sliding.
|
|
*/
|
|
_sliding: false,
|
|
|
|
/*
|
|
* If user had released the finger and the handle already
|
|
* reached one of the ends.
|
|
*/
|
|
_slideReachEnd: false,
|
|
|
|
/**
|
|
* Are we currently switching panels ?
|
|
*/
|
|
_switchingPanel: false,
|
|
|
|
/*
|
|
* Timeout after incorrect attempt
|
|
*/
|
|
kPassCodeErrorTimeout: 500,
|
|
|
|
/*
|
|
* Counter after incorrect attempt
|
|
*/
|
|
kPassCodeErrorCounter: 0,
|
|
|
|
/*
|
|
* Timeout ID for backing from triggered state to normal state
|
|
*/
|
|
triggeredTimeoutId: 0,
|
|
|
|
/*
|
|
* Max value for handle swiper up
|
|
*/
|
|
HANDLE_MAX: 70,
|
|
chargingStatus: new window.LockScreenChargingStatus()
|
|
}; // -- LockScreen.prototype --
|
|
|
|
LockScreen.prototype.handleEvent =
|
|
function ls_handleEvent(evt) {
|
|
switch (evt.type) {
|
|
case 'lockscreen-appopened':
|
|
this.lock();
|
|
break;
|
|
case 'screenchange':
|
|
// Don't lock if screen is turned off by promixity sensor.
|
|
if (evt.detail.screenOffBy == 'proximity') {
|
|
break;
|
|
}
|
|
|
|
this._screenEnabled = evt.detail.screenEnabled;
|
|
|
|
// XXX: If the screen is not turned off by ScreenManager
|
|
// we would need to lock the screen again
|
|
// when it's being turned back on
|
|
if (!evt.detail.screenEnabled) {
|
|
// Remove camera once screen turns off
|
|
if (this.camera && this.camera.firstElementChild) {
|
|
this.camera.removeChild(this.camera.firstElementChild);
|
|
}
|
|
this.chargingStatus.stop();
|
|
} else {
|
|
if (!this.lockScreenClockWidget) {
|
|
this.createClockWidget();
|
|
}
|
|
this.chargingStatus.start();
|
|
}
|
|
// No matter turn on or off from screen timeout or poweroff,
|
|
// all secure apps would be hidden.
|
|
this.dispatchEvent('secure-killapps');
|
|
this.overlayLocked(true);
|
|
break;
|
|
|
|
case 'touchstart':
|
|
// Edge case: when the passcode is valid, passpad should fade out.
|
|
// So the touchevent should do nothing.
|
|
var passcodeValid =
|
|
('success' === this.overlay.dataset.passcodeStatus);
|
|
if (passcodeValid) {
|
|
return;
|
|
}
|
|
|
|
window.addEventListener('touchend', this);
|
|
this.overlay.classList.add('touched');
|
|
break;
|
|
|
|
case 'touchend':
|
|
window.removeEventListener('touchmove', this);
|
|
window.removeEventListener('touchend', this);
|
|
this.overlay.classList.remove('touched');
|
|
break;
|
|
case 'transitionend':
|
|
if (evt.target !== this.overlay) {
|
|
return;
|
|
}
|
|
|
|
if (this.overlay.dataset.panel !== 'camera' &&
|
|
this.camera.firstElementChild) {
|
|
this.camera.removeChild(this.camera.firstElementChild);
|
|
}
|
|
|
|
if (!this.locked) {
|
|
this.overlay.classList.add('unlocked');
|
|
this.unlockDetail = undefined;
|
|
}
|
|
break;
|
|
|
|
case 'home':
|
|
if (this.locked) {
|
|
this.dispatchEvent('secure-closeapps');
|
|
evt.stopImmediatePropagation();
|
|
}
|
|
break;
|
|
|
|
case 'holdhome':
|
|
if (!this.locked) {
|
|
return;
|
|
}
|
|
|
|
evt.stopImmediatePropagation();
|
|
evt.stopPropagation();
|
|
break;
|
|
|
|
case 'lockscreenslide-unlocker-initializer':
|
|
this._unlockerInitialized = true;
|
|
break;
|
|
case 'lockscreenslide-unlocking-start':
|
|
this._notifyUnlockingStart();
|
|
break;
|
|
case 'lockscreenslide-unlocking-stop':
|
|
this._notifyUnlockingStop();
|
|
break;
|
|
/**
|
|
* we need to know whether the media player widget is shown or not,
|
|
* in order to decide notification container's height
|
|
* we listen to the same events (iac-mediacomms & appterminated) as
|
|
* in media player widget's codes (/apps/system/js/media_playback.js)
|
|
*/
|
|
case 'iac-mediacomms':
|
|
if (evt.detail.type === 'status') {
|
|
switch (evt.detail.data.playStatus) {
|
|
case 'PLAYING':
|
|
case 'PAUSED':
|
|
window.lockScreenNotifications.collapseNotifications();
|
|
window.lockScreenNotifications.adjustContainerVisualHints();
|
|
break;
|
|
case 'STOPPED':
|
|
case 'mozinterruptbegin':
|
|
window.lockScreenNotifications.expandNotifications();
|
|
window.lockScreenNotifications.adjustContainerVisualHints();
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case 'appterminated':
|
|
if (evt.detail.origin === this.mediaPlaybackWidget.origin) {
|
|
window.lockScreenNotifications.expandNotifications();
|
|
window.lockScreenNotifications.adjustContainerVisualHints();
|
|
}
|
|
break;
|
|
case 'scroll':
|
|
if (this.notificationsContainer === evt.target) {
|
|
window.lockScreenNotifications.adjustContainerVisualHints();
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}; // -- LockScreen#handleEvent --
|
|
|
|
/**
|
|
* This function would exist until we refactor the lockscreen.js with
|
|
* new patterns. @see https://bugzil.la/960381
|
|
*
|
|
* @memberof LockScreen
|
|
* @this {LockScreen}
|
|
*/
|
|
LockScreen.prototype.init =
|
|
function ls_init() {
|
|
this.ready = true;
|
|
/**
|
|
* "new style" slider: as described in https://bugzil.la/950884
|
|
* setting this parameter to true causes the LockScreenSlide to render
|
|
* the slider specified in that bugzilla issue
|
|
*/
|
|
this.bootstrapping =
|
|
LazyLoader.load(['shared/js/lockscreen_slide.js',
|
|
'shared/js/passcode_helper.js']).then(() => {
|
|
this._unlocker = new LockScreenSlide({useNewStyle: true});
|
|
this.getAllElements();
|
|
this.notificationsContainer =
|
|
document.getElementById('notifications-lockscreen-container');
|
|
|
|
this.lock(true);
|
|
this.initUnlockerEvents();
|
|
|
|
// This component won't know when the it get locked unless
|
|
// it listens to this event.
|
|
window.addEventListener('lockscreen-appopened', this);
|
|
|
|
/* Status changes */
|
|
window.addEventListener(
|
|
'lockscreen-notification-request-activate-unlock', this);
|
|
window.addEventListener('screenchange', this);
|
|
|
|
/* Incoming and normal mode would be different */
|
|
window.addEventListener('lockscreen-mode-switch', this);
|
|
|
|
/* Gesture */
|
|
this.area.addEventListener('touchstart', this);
|
|
this.altCameraButton.addEventListener('click', this);
|
|
this.iconContainer.addEventListener('touchstart', this);
|
|
|
|
/* Unlock & camera panel clean up */
|
|
this.overlay.addEventListener('transitionend', this);
|
|
|
|
/* switching panels */
|
|
window.addEventListener('home', this);
|
|
|
|
/* blocking holdhome and prevent Cards View from show up */
|
|
window.addEventListener('holdhome', this, true);
|
|
window.addEventListener('ftudone', this);
|
|
window.addEventListener('moztimechange', this);
|
|
window.addEventListener('timeformatchange', this);
|
|
|
|
/* media playback widget */
|
|
this.mediaPlaybackWidget =
|
|
new window.LockScreenMediaPlaybackWidget(this.mediaContainer);
|
|
|
|
// listen to media playback events to adjust notification container height
|
|
window.addEventListener('iac-mediacomms', this);
|
|
window.addEventListener('appterminated', this);
|
|
|
|
// it is possible that lockscreen is initialized after wallpapermanager
|
|
// (e.g. user turns on lockscreen in settings after system is booted);
|
|
// if this is the case, then the wallpaperchange event might not be
|
|
// captured and the lockscreen would initialize into empty wallpaper
|
|
// so we need to see if there is already a wallpaper blob available
|
|
if (Service.query('getWallpaper')) {
|
|
var wallpaperURL = Service.query('getWallpaper');
|
|
if (wallpaperURL) {
|
|
this.updateBackground(wallpaperURL);
|
|
this.overlay.classList.remove('uninit');
|
|
}
|
|
}
|
|
window.addEventListener('wallpaperchange', (function(evt) {
|
|
this.updateBackground(evt.detail.url);
|
|
this.overlay.classList.remove('uninit');
|
|
}).bind(this));
|
|
|
|
this.notificationsContainer.addEventListener('scroll', this);
|
|
|
|
window.SettingsListener.observe('lockscreen.lock-message',
|
|
'', (function(value) {
|
|
this.setLockMessage(value);
|
|
}).bind(this));
|
|
|
|
// FIXME(ggp) this is currently used by Find My Device
|
|
// to force locking. Should be replaced by a proper IAC API in
|
|
// bug 992277. We don't need to use SettingsListener because
|
|
// we're only interested in changes to the setting, and don't
|
|
// keep track of its value.
|
|
window.SettingsListener.observe('lockscreen.lock-immediately',
|
|
false, (function ls_observeImmediately(value) {
|
|
if (value === true) {
|
|
// Enforce immediate pass code unlock
|
|
this.resetTimeoutForcibly();
|
|
window.dispatchEvent(new CustomEvent('lockscreen-request-lock'));
|
|
}
|
|
}).bind(this));
|
|
|
|
navigator.mozL10n.ready(this.l10nInit.bind(this));
|
|
|
|
// when lockscreen is just initialized,
|
|
// it will lock itself (if enabled) before calling updatebackground,
|
|
// so we need to generate overlay if needed here
|
|
if(this._checkGenerateMaskedBackgroundColor()){
|
|
this._generateMaskedBackgroundColor();
|
|
}
|
|
this.chargingStatus.start();
|
|
Service.register('setPassCodeEnabled', this);
|
|
Service.register('setPassCodeLockTimeout', this);
|
|
}).catch(function(err) {console.error(err);});
|
|
return this.bootstrapping;
|
|
};
|
|
|
|
LockScreen.prototype.initUnlockerEvents =
|
|
function ls_initUnlockerEvents() {
|
|
window.addEventListener('lockscreenslide-unlocker-initializer', this);
|
|
window.addEventListener('lockscreenslide-near-left', this);
|
|
window.addEventListener('lockscreenslide-near-right', this);
|
|
window.addEventListener('lockscreenslide-unlocking-start', this);
|
|
window.addEventListener('lockscreenslide-activate-left', this);
|
|
window.addEventListener('lockscreenslide-activate-right', this);
|
|
window.addEventListener('lockscreenslide-unlocking-stop', this);
|
|
};
|
|
|
|
LockScreen.prototype.suspendUnlockerEvents =
|
|
function ls_initUnlockerEvents() {
|
|
window.removeEventListener('lockscreenslide-unlocker-initializer', this);
|
|
window.removeEventListener('lockscreenslide-near-left', this);
|
|
window.removeEventListener('lockscreenslide-near-right', this);
|
|
window.removeEventListener('lockscreenslide-unlocking-start', this);
|
|
window.removeEventListener('lockscreenslide-activate-left', this);
|
|
window.removeEventListener('lockscreenslide-activate-right', this);
|
|
window.removeEventListener('lockscreenslide-unlocking-stop', this);
|
|
};
|
|
|
|
/**
|
|
* We need to do some refreshing thing after l10n is ready.
|
|
*
|
|
* @memberof LockScreen
|
|
* @this {LockScreen}
|
|
*/
|
|
LockScreen.prototype.l10nInit =
|
|
function ls_l10nInit() {
|
|
this.l10nready = true;
|
|
this.createClockWidget();
|
|
|
|
// mobile connection state on lock screen.
|
|
// It needs L10n too. But it's not a re-entrable function,
|
|
// so we need to check if it's already initialized.
|
|
if (this._lockscreenConnInfoManager ||
|
|
!window.navigator.mozMobileConnections) {
|
|
return;
|
|
}
|
|
// XXX: improve the dependency.
|
|
if (window.SIMSlotManager) {
|
|
this.startConnectionInfoManager();
|
|
} else {
|
|
window.addEventListener('simslotmanagerstarted', function s() {
|
|
window.removeEventListener('simslotmanagerstarted', s);
|
|
this.startConnectionInfoManager();
|
|
}.bind(this));
|
|
}
|
|
};
|
|
|
|
LockScreen.prototype.startConnectionInfoManager = function() {
|
|
LazyLoader.load(
|
|
['shared/js/lockscreen_connection_info_manager.js']).then(() => {
|
|
this._lockscreenConnInfoManager =
|
|
new LockScreenConnInfoManager(this.connStates);
|
|
}).catch(function(err) {console.error(err);});
|
|
};
|
|
|
|
/**
|
|
* Concat step and handle `capture`.
|
|
*/
|
|
LockScreen.prototype.nextStep =
|
|
function ls_nextStep(step) {
|
|
this.bootstrapping =
|
|
this.bootstrapping.then(step)
|
|
.catch((err) => {
|
|
console.error('LockScreen Error: ', err);
|
|
});
|
|
return this.bootstrapping;
|
|
};
|
|
|
|
LockScreen.prototype.setLockMessage =
|
|
function ls_setLockMessage(val) {
|
|
this.message.textContent = val;
|
|
this.message.hidden = (val === '');
|
|
};
|
|
|
|
LockScreen.prototype._notifyUnlockingStart =
|
|
function ls_notifyUnlockingStart() {
|
|
window.dispatchEvent(new CustomEvent('unlocking-start'));
|
|
};
|
|
|
|
LockScreen.prototype._notifyUnlockingStop =
|
|
function ls_notifyUnlockingStop() {
|
|
window.dispatchEvent(new CustomEvent('unlocking-stop'));
|
|
};
|
|
|
|
/**
|
|
* Activate the camera.
|
|
*
|
|
* @this {LockScreen}
|
|
*/
|
|
LockScreen.prototype._activateUnlockingCamera =
|
|
function ls_activateUnlockingCamera(unlockingDetails) {
|
|
var activityDetail = {
|
|
name: 'record',
|
|
data: {
|
|
type: 'photos'
|
|
}
|
|
};
|
|
this._unlockingMessage = { 'activity': activityDetail };
|
|
this.unlock(false, unlockingDetails);
|
|
};
|
|
|
|
LockScreen.prototype._activateUnlock =
|
|
function ls_activateUnlock() {
|
|
this.unlock(false);
|
|
};
|
|
|
|
LockScreen.prototype.invokeSecureApp =
|
|
function ls_invokeSecureApp(name) {
|
|
var url =
|
|
window.parent.location.href.replace('system', name),
|
|
manifestUrl =
|
|
url.replace(/(\/)*(index.html#?)*$/, '/manifest.webapp');
|
|
|
|
url += '#secure';
|
|
window.dispatchEvent(new window.CustomEvent('secure-launchapp',
|
|
{
|
|
'detail': {
|
|
'appURL': url,
|
|
'appManifestURL': manifestUrl
|
|
}
|
|
}
|
|
));
|
|
};
|
|
|
|
LockScreen.prototype.unlock =
|
|
function ls_unlock(instant, details) {
|
|
details = details || {};
|
|
var wasAlreadyUnlocked = !this.locked;
|
|
this.locked = false;
|
|
|
|
this.chargingStatus.stop();
|
|
|
|
if (wasAlreadyUnlocked) {
|
|
return;
|
|
}
|
|
// It ends the locked session.
|
|
var now = Date.now();
|
|
this._lastLockedInterval = now - this._lastLockedTimeStamp;
|
|
this._lastUnlockedTimeStamp = now;
|
|
|
|
this.lockScreenClockWidget.stop().destroy();
|
|
delete this.lockScreenClockWidget;
|
|
|
|
if (details.unlockSoundEnabled) {
|
|
var unlockAudio = new Audio('/resources/sounds/unlock.opus');
|
|
unlockAudio.mozAudioChannelType = 'system';
|
|
unlockAudio.play();
|
|
}
|
|
|
|
this.overlay.classList.toggle('no-transition', instant);
|
|
this.dispatchEvent('lockscreen-request-unlock', this._unlockingMessage);
|
|
this.dispatchEvent('secure-modeoff');
|
|
if (instant) {
|
|
this.overlay.classList.add('unlocked');
|
|
// Otherwise, postpone it to transitionend.
|
|
}
|
|
|
|
// Clear the state after we send the request.
|
|
this._unlockingMessage = {};
|
|
};
|
|
|
|
LockScreen.prototype.overlayLocked = function(instant) {
|
|
this.overlay.focus();
|
|
this.overlay.classList.toggle('no-transition', instant);
|
|
this.overlay.classList.remove('unlocked');
|
|
};
|
|
|
|
LockScreen.prototype.lock =
|
|
function ls_lock(instant) {
|
|
var wasAlreadyLocked = this.locked;
|
|
this.locked = true;
|
|
|
|
if (!wasAlreadyLocked) {
|
|
// It ends the unlocked session.
|
|
var now = Date.now();
|
|
this._lastUnlockedInterval = now - this._lastUnlockedTimeStamp;
|
|
this._lastLockedTimeStamp = now;
|
|
|
|
this.overlayLocked();
|
|
// Because 'document.hidden' changes slower than this,
|
|
// so if we depend on that it would create the widget
|
|
// while the screen is off.
|
|
if (Service.query('screenEnabled')) {
|
|
this.createClockWidget();
|
|
}
|
|
if (document.mozFullScreen) {
|
|
document.mozCancelFullScreen();
|
|
}
|
|
// Any changes made to this,
|
|
// also need to be reflected in apps/system/js/storage.js
|
|
this.dispatchEvent('secure-modeon');
|
|
|
|
if(this._checkGenerateMaskedBackgroundColor()){
|
|
this._generateMaskedBackgroundColor();
|
|
}
|
|
// Remove Masked Background if there are no
|
|
// notifications
|
|
if (this.maskedBackground.classList.contains('blank')) {
|
|
this.maskedBackground.style.backgroundColor = 'transparent';
|
|
}
|
|
}
|
|
};
|
|
|
|
LockScreen.prototype.loadPanel =
|
|
function ls_loadPanel(panel, callback) {
|
|
this._loadingPanel = true;
|
|
|
|
switch (panel) {
|
|
case 'passcode':
|
|
case 'main':
|
|
this.overlay.classList.add('no-transition');
|
|
if (callback) {
|
|
setTimeout(callback);
|
|
}
|
|
break;
|
|
}
|
|
};
|
|
|
|
LockScreen.prototype.unloadPanel =
|
|
function ls_unloadPanel(panel, toPanel, callback) {
|
|
switch (panel) {
|
|
case 'passcode':
|
|
// Reset passcode panel only if the status is not error
|
|
if (this.overlay.dataset.passcodeStatus == 'error') {
|
|
break;
|
|
}
|
|
|
|
delete this.overlay.dataset.passcodeStatus;
|
|
break;
|
|
|
|
case 'main':
|
|
/* falls through */
|
|
default:
|
|
var unload = (function() {
|
|
this.overlay.classList.remove('triggered');
|
|
clearTimeout(this.triggeredTimeoutId);
|
|
}).bind(this);
|
|
|
|
if (toPanel !== 'camera') {
|
|
unload();
|
|
break;
|
|
}
|
|
|
|
this.overlay.addEventListener('transitionend',
|
|
(function ls_unloadDefaultPanel(evt) {
|
|
if (evt.target !== this) {
|
|
return;
|
|
}
|
|
this.overlay.removeEventListener('transitionend',
|
|
ls_unloadDefaultPanel);
|
|
unload();
|
|
}).bind(this)
|
|
);
|
|
break;
|
|
}
|
|
|
|
if (callback) {
|
|
setTimeout(callback);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Switch the panel to the target type.
|
|
* Will actually call the load and unload panel function.
|
|
*
|
|
* @param {PanelType} panel Could be 'camera', 'passcode', 'emergency-call' or
|
|
* undefined. Undefined means the main panel.
|
|
*/
|
|
LockScreen.prototype.switchPanel =
|
|
function ls_switchPanel(panel) {
|
|
if (this._switchingPanel) {
|
|
return;
|
|
}
|
|
|
|
panel = panel || 'main';
|
|
var overlay = this.overlay;
|
|
var currentPanel = overlay.dataset.panel;
|
|
|
|
if (currentPanel && currentPanel === panel) {
|
|
return;
|
|
}
|
|
|
|
this._switchingPanel = true;
|
|
this.loadPanel(panel, (function() {
|
|
this.unloadPanel(overlay.dataset.panel, panel,
|
|
(function() {
|
|
this.dispatchEvent('lockpanelchange', { 'panel': panel });
|
|
overlay.dataset.panel = panel;
|
|
this._switchingPanel = false;
|
|
}).bind(this));
|
|
}).bind(this));
|
|
};
|
|
|
|
/**
|
|
* This function would fire an event to validate the passcode.
|
|
* The validator is a component in System app, and LockScreen should
|
|
* not validate it.
|
|
*/
|
|
LockScreen.prototype.checkPassCode =
|
|
function lockscreen_checkPassCode(passcode) {
|
|
PasscodeHelper.check(passcode).then((result) => {
|
|
if (result) {
|
|
this.onPasscodeValidationSuccess();
|
|
} else {
|
|
this.onPasscodeValidationFailed();
|
|
}
|
|
}) .catch((error) => {
|
|
this.onPasscodeValidationFailed(error);
|
|
});
|
|
};
|
|
|
|
LockScreen.prototype.updateBackground =
|
|
function ls_updateBackground(value) {
|
|
var background = document.getElementById('lockscreen-background'),
|
|
url = 'url(' + value + ')';
|
|
background.style.backgroundImage = url;
|
|
// if screen is locked and display is on, regenerate the color immediately
|
|
// as it's possible that notifications come in without we ever having a
|
|
// chance to generate the color (triggered in lockscreen.locked)
|
|
this._regenerateMaskedBackgroundColorFrom = value;
|
|
// see this._screenEnabled's definition above on
|
|
// why 'undefined' is seen as 'true'
|
|
if ((this._screenEnabled === undefined || this._screenEnabled) &&
|
|
this.locked) {
|
|
this._generateMaskedBackgroundColor();
|
|
}else{
|
|
this._shouldRegenerateMaskedBackgroundColor = true;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Check if we should regenerate masked background color
|
|
*/
|
|
LockScreen.prototype._checkGenerateMaskedBackgroundColor =
|
|
function ls_checkGenerateMaskedBackgroundColor() {
|
|
// XXX: request animation frame?
|
|
return (this._shouldRegenerateMaskedBackgroundColor &&
|
|
!!this._regenerateMaskedBackgroundColorFrom);
|
|
};
|
|
|
|
/**
|
|
* Generate a single color from wallpaper
|
|
* to be used as the background color of Masked Background
|
|
*/
|
|
LockScreen.prototype._generateMaskedBackgroundColor =
|
|
function ls_generateMaskedBackgroundColor() {
|
|
// downsample the image to avoid calculation taking too much time
|
|
var SAMPLE_IMAGE_SIZE_BASE = 100;
|
|
|
|
var img = new Image();
|
|
img.onload = (function(){
|
|
var sampleImageWidth;
|
|
var sampleImageHeight;
|
|
|
|
if(img.height > img.width){
|
|
sampleImageWidth =
|
|
Math.floor(SAMPLE_IMAGE_SIZE_BASE * window.devicePixelRatio);
|
|
sampleImageHeight =
|
|
Math.floor(sampleImageWidth * (img.height / img.width));
|
|
}else{
|
|
sampleImageHeight =
|
|
Math.floor(SAMPLE_IMAGE_SIZE_BASE * window.devicePixelRatio);
|
|
sampleImageWidth =
|
|
Math.floor(sampleImageHeight * (img.width / img.height));
|
|
}
|
|
|
|
var canvas = document.createElement('canvas');
|
|
canvas.width = sampleImageWidth;
|
|
canvas.height = sampleImageHeight;
|
|
|
|
var context = canvas.getContext('2d');
|
|
context.drawImage(img, 0, 0, sampleImageWidth, sampleImageHeight);
|
|
|
|
var data =
|
|
context.getImageData(0, 0, sampleImageWidth, sampleImageHeight).data;
|
|
var r = 0, g = 0, b = 0;
|
|
|
|
for (var row = 0; row < sampleImageHeight; row++) {
|
|
for (var col = 0; col < sampleImageWidth; col++) {
|
|
r += data[((sampleImageWidth * row) + col) * 4];
|
|
g += data[((sampleImageWidth * row) + col) * 4 + 1];
|
|
b += data[((sampleImageWidth * row) + col) * 4 + 2];
|
|
}
|
|
}
|
|
|
|
r = r / (sampleImageWidth * sampleImageHeight) / 255;
|
|
g = g / (sampleImageWidth * sampleImageHeight) / 255;
|
|
b = b / (sampleImageWidth * sampleImageHeight) / 255;
|
|
|
|
// http://en.wikipedia.org/wiki/HSL_and_HSV#Formal_derivation
|
|
var M = Math.max(r, g, b);
|
|
var m = Math.min(r, g, b);
|
|
var C = M - m;
|
|
var h, s, l;
|
|
|
|
l = 0.5 * (M + m);
|
|
if (C === 0) {
|
|
h = s = 0; // no satuaration (monochromatic)
|
|
} else {
|
|
switch (M) {
|
|
case r:
|
|
h = ((g - b) / C) % 6;
|
|
break;
|
|
case g:
|
|
h = ((b - r) / C) + 2;
|
|
break;
|
|
case b:
|
|
h = ((r - g) / C) + 4;
|
|
break;
|
|
}
|
|
h *= 60;
|
|
h = (h + 360) % 360;
|
|
s = C / (1 - Math.abs(2 * l - 1));
|
|
}
|
|
|
|
l *= 0.9;
|
|
|
|
h = parseInt(h);
|
|
s = parseInt(s * 100) + '%';
|
|
l = parseInt(l * 100) + '%';
|
|
|
|
var value = 'hsla(' + h + ', ' + s + ', ' + l + ', 0.7)';
|
|
this.maskedBackground.dataset.wallpaperColor = value;
|
|
if (!this.maskedBackground.classList.contains('blank')) {
|
|
this.maskedBackground.style.backgroundColor = value;
|
|
}
|
|
}).bind(this);
|
|
|
|
img.src = this._regenerateMaskedBackgroundColorFrom;
|
|
this._shouldRegenerateMaskedBackgroundColor = false;
|
|
this._regenerateMaskedBackgroundColorFrom = undefined;
|
|
};
|
|
|
|
/**
|
|
* To get all elements this component will use.
|
|
* Note we do a name mapping here: DOM variables named like 'passcodePad'
|
|
* are actually corresponding to the lowercases with hyphen one as
|
|
* 'passcode-pad', then be prefixed with 'lookscreen'.
|
|
*/
|
|
LockScreen.prototype.getAllElements =
|
|
function ls_getAllElements() {
|
|
// ID of elements to create references
|
|
var elements = ['conn-states', 'clock-time', 'date', 'area',
|
|
'area-unlock', 'area-camera', 'icon-container',
|
|
'area-slide', 'media-container', 'passcode-code',
|
|
'alt-camera', 'alt-camera-button',
|
|
'passcode-pad',
|
|
'panel-emergency-call', 'canvas', 'message',
|
|
'notification-arrow', 'masked-background'];
|
|
|
|
var toCamelCase = function toCamelCase(str) {
|
|
return str.replace(/\-(.)/g, function replacer(str, p1) {
|
|
return p1.toUpperCase();
|
|
});
|
|
};
|
|
|
|
elements.forEach((function createElementRef(name) {
|
|
var element = document.getElementById('lockscreen-' + name);
|
|
if (!element) {
|
|
console.error('No such element: lockscreen-'+ name);
|
|
}
|
|
name = toCamelCase(name);
|
|
this[name] = element;
|
|
}).bind(this));
|
|
|
|
this.overlay = document.getElementById('lockscreen');
|
|
this.mainScreen = document.getElementById('screen');
|
|
};
|
|
|
|
LockScreen.prototype.dispatchEvent =
|
|
function ls_dispatchEvent(name, detail) {
|
|
var evt = new CustomEvent(name, {
|
|
'bubbles': true,
|
|
'cancelable': true,
|
|
// Set event detail if needed for the specific event 'name' (relevant for
|
|
// passing which button triggered the event)
|
|
'detail': detail
|
|
});
|
|
window.dispatchEvent(evt);
|
|
};
|
|
|
|
/**
|
|
* @param {boolean} switcher - true if mode is on, false if off.
|
|
*/
|
|
LockScreen.prototype.modeSwitch =
|
|
function ls_modeSwitch(mode, switcher) {
|
|
if (switcher) {
|
|
if (mode !== this.configs.mode) {
|
|
this.suspend();
|
|
}
|
|
} else {
|
|
if (mode !== this.configs.mode) {
|
|
this.resume();
|
|
}
|
|
}
|
|
};
|
|
|
|
LockScreen.prototype.suspend =
|
|
function ls_suspend() {
|
|
this.suspendUnlockerEvents();
|
|
};
|
|
|
|
LockScreen.prototype.resume =
|
|
function ls_resume() {
|
|
this.initUnlockerEvents();
|
|
};
|
|
|
|
/**
|
|
* Check if the timeout has been expired and we need to check the passcode.
|
|
*/
|
|
LockScreen.prototype.checkPassCodeTimeout =
|
|
function ls_checkPassCodeTimeout(timeoutSetting) {
|
|
var timeout = timeoutSetting * 1000;
|
|
var lockedInterval = this.fetchLockedInterval();
|
|
var unlockedInterval = this.fetchUnlockedInterval();
|
|
|
|
// If user set timeout, then
|
|
// - if timeout expired, do check
|
|
// - if timeout is valid, do not check
|
|
if (0 !== timeoutSetting) {
|
|
if (lockedInterval > timeout ||
|
|
unlockedInterval > timeout ) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
} else {
|
|
return true;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Reset lock/unlock time stamps such that checkPassCodeTimeout
|
|
* will return that pass code must be checked.
|
|
*/
|
|
LockScreen.prototype.resetTimeoutForcibly =
|
|
function ls_resetTimeoutForcibly() {
|
|
// Pass code unlock requirement is checked against timeout of
|
|
// either timestamp. Resetting these to epoch 0 ensures that both
|
|
// will timeout even if one timestamp is set to Date.now()
|
|
// due to lockscreen activation or deactivation in a different
|
|
// code path, avoiding a potential race condition. The respective
|
|
// pre-calculated intervals are adjusted accordingly.
|
|
// Fixes bug 1186100
|
|
var now = Date.now();
|
|
this._lastLockedTimeStamp = 0;
|
|
this._lastLockedInterval = now;
|
|
this._lastUnlockedTimeStamp = 0;
|
|
this._lastUnlockedInterval = now;
|
|
};
|
|
|
|
/**
|
|
* When validation failed, do UI change.
|
|
*/
|
|
LockScreen.prototype.onPasscodeValidationFailed =
|
|
function ls_onPasscodeValidationFailed() {
|
|
this.overlay.dataset.passcodeStatus = 'error';
|
|
this.kPassCodeErrorCounter++;
|
|
//double delay if >5 failed attempts
|
|
if (this.kPassCodeErrorCounter > 5) {
|
|
this.kPassCodeErrorTimeout = 2 * this.kPassCodeErrorTimeout;
|
|
}
|
|
// have inputpad display the error
|
|
window.dispatchEvent(new CustomEvent(
|
|
'lockscreen-notify-passcode-validationfailed'));
|
|
if ('vibrate' in navigator) {
|
|
navigator.vibrate([50, 50, 50]);
|
|
}
|
|
setTimeout(() => {
|
|
delete this.overlay.dataset.passcodeStatus;
|
|
// tell inputpad that error delay has passed
|
|
window.dispatchEvent(new CustomEvent(
|
|
'lockscreen-notify-passcode-validationreset'));
|
|
}, this.kPassCodeErrorTimeout);
|
|
};
|
|
|
|
/**
|
|
* When validation success, do unlock.
|
|
*/
|
|
LockScreen.prototype.onPasscodeValidationSuccess =
|
|
function ls_onPasscodeValidationSuccess() {
|
|
this.kPassCodeErrorTimeout = 500;
|
|
this.kPassCodeErrorCounter = 0;
|
|
// delegate the unlocking function call to panel state.
|
|
window.dispatchEvent(new CustomEvent(
|
|
'lockscreen-notify-passcode-validationsuccess'));
|
|
};
|
|
|
|
LockScreen.prototype.createClockWidget = function() {
|
|
// Adapt a state-widget in the curret architecture.
|
|
this.lockScreenClockWidget = new LockScreenClockWidget(
|
|
document.getElementById('lockscreen-clock-widget'));
|
|
this.lockScreenClockWidget.start();
|
|
};
|
|
|
|
LockScreen.prototype.fetchLockedInterval = function() {
|
|
// If: the session is still pending, so need to calculate it.
|
|
// Else: the session was already over, so need to get it.
|
|
if (this.locked) {
|
|
this._lastLockedInterval = Date.now() - this._lastLockedTimeStamp;
|
|
return this._lastLockedInterval;
|
|
} else {
|
|
return this._lastLockedInterval;
|
|
}
|
|
};
|
|
|
|
LockScreen.prototype.fetchUnlockedInterval = function() {
|
|
// If: the session is still pending, so need to calculate it.
|
|
// Else: the session was already over, so need to get it.
|
|
if (!this.locked) {
|
|
this._lastUnlockedInterval = Date.now() - this._lastUnlockedTimeStamp;
|
|
return this._lastUnlockedInterval;
|
|
} else {
|
|
return this._lastUnlockedInterval;
|
|
}
|
|
};
|
|
|
|
/** @exports LockScreen */
|
|
exports.LockScreen = LockScreen;
|
|
|
|
// XXX: Before we stop components directly reading this value,
|
|
// we need this to satisfy those components which would be loaded
|
|
// before this file.
|
|
if (!window.lockScreen) {
|
|
/** @global*/
|
|
window.lockScreen = { locked: false };
|
|
}
|
|
})(window);
|