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
125
gaia/apps/ftu/js/app.js
Executable file
125
gaia/apps/ftu/js/app.js
Executable file
|
|
@ -0,0 +1,125 @@
|
|||
/* global DataMobile, Navigation, SimManager, TimeManager,
|
||||
UIManager, WifiManager, ImportIntegration, Tutorial,
|
||||
VersionHelper, LanguageManager, eventSafety */
|
||||
/* exported AppManager */
|
||||
'use strict';
|
||||
|
||||
var AppManager = {
|
||||
EVENT_PREFIX: 'ftu-',
|
||||
|
||||
init: function init(versionInfo) {
|
||||
window.performance.mark('initialize');
|
||||
this.isInitialized = true;
|
||||
|
||||
LanguageManager.init();
|
||||
UIManager.init();
|
||||
Navigation.init();
|
||||
|
||||
window.performance.mark('navigationLoaded');
|
||||
this.publish('setup');
|
||||
|
||||
var splashTimeout = 1025;
|
||||
var splashScreenHidden = this.whenEvent(UIManager.splashScreen,
|
||||
'transitionend',
|
||||
splashTimeout).then(() => {
|
||||
UIManager.container.removeAttribute('aria-hidden');
|
||||
window.performance.mark('visuallyLoaded');
|
||||
});
|
||||
|
||||
var languageListReady = this.whenEvent(window, 'languagelistready');
|
||||
|
||||
Promise.all([splashScreenHidden, languageListReady]).then(() => {
|
||||
window.performance.mark('fullyLoaded');
|
||||
});
|
||||
|
||||
// if it's an upgrade we can jump to tutorial directly
|
||||
if (versionInfo && versionInfo.isUpgrade()) {
|
||||
var stepsKey = versionInfo.delta();
|
||||
// Play the FTU Tuto steps directly on update
|
||||
UIManager.changeStatusBarColor(UIManager.DARK_THEME);
|
||||
UIManager.splashScreen.classList.remove('show');
|
||||
UIManager.activationScreen.classList.remove('show');
|
||||
UIManager.updateScreen.classList.add('show');
|
||||
|
||||
// Load and play the what's new tutorial
|
||||
Tutorial.init(stepsKey, function() {
|
||||
Tutorial.start();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
SimManager.init();
|
||||
WifiManager.init();
|
||||
ImportIntegration.init();
|
||||
TimeManager.init();
|
||||
DataMobile.init();
|
||||
Tutorial.init();
|
||||
|
||||
var kSplashTimeout = 700;
|
||||
// Retrieve mobile connection if available
|
||||
// this is used to keep all tests passing while introducing multi-sim APIs
|
||||
var conn = window.navigator.mozMobileConnections &&
|
||||
window.navigator.mozMobileConnections[0];
|
||||
|
||||
if (!conn) {
|
||||
setTimeout(function() {
|
||||
// For desktop
|
||||
window.location.hash = '#';
|
||||
UIManager.activationScreen.classList.add('show');
|
||||
window.location.hash = '#languages';
|
||||
|
||||
UIManager.splashScreen.classList.remove('show');
|
||||
window.performance.mark('navigationInteractive');
|
||||
window.performance.mark('contentInteractive');
|
||||
}, kSplashTimeout);
|
||||
return;
|
||||
}
|
||||
|
||||
// Do we need pin code after splash screen?
|
||||
setTimeout(function() {
|
||||
// TODO Include VIVO SIM Card management
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=801269#c6
|
||||
Navigation.manageStep();
|
||||
UIManager.activationScreen.classList.add('show');
|
||||
// Remove the splash
|
||||
UIManager.splashScreen.classList.remove('show');
|
||||
window.performance.mark('navigationInteractive');
|
||||
window.performance.mark('contentInteractive');
|
||||
}, kSplashTimeout);
|
||||
},
|
||||
whenEvent: function (target, name, timeoutMs) {
|
||||
return new Promise((resolve, reject) => {
|
||||
eventSafety(target, name, resolve, timeoutMs || 1000);
|
||||
});
|
||||
},
|
||||
publish: function(name, detail) {
|
||||
var evt = new CustomEvent(this.EVENT_PREFIX + name,
|
||||
{
|
||||
bubbles: true,
|
||||
detail: detail || this
|
||||
});
|
||||
window.dispatchEvent(evt);
|
||||
}
|
||||
};
|
||||
|
||||
function init() {
|
||||
VersionHelper.getVersionInfo().then(function(versionInfo) {
|
||||
if (!AppManager.isInitialized) {
|
||||
AppManager.init(versionInfo);
|
||||
} else {
|
||||
UIManager.initTZ();
|
||||
if (!UIManager.mainTitle.hasAttribute('data-l10n-id')) {
|
||||
UIManager.mainTitle.setAttribute('data-l10n-id', 'language');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
init();
|
||||
document.addEventListener('DOMRetranslated', init);
|
||||
|
||||
document.l10n.ready.then(() => {
|
||||
window.performance.mark('l10nready');
|
||||
});
|
||||
|
||||
115
gaia/apps/ftu/js/data_mobile.js
Executable file
115
gaia/apps/ftu/js/data_mobile.js
Executable file
|
|
@ -0,0 +1,115 @@
|
|||
/* exported DataMobile */
|
||||
/* global Navigation */
|
||||
/* global UIManager */
|
||||
|
||||
'use strict';
|
||||
|
||||
var DataMobile = {
|
||||
key: 'ril.data.enabled',
|
||||
keySV: 'ftu.ril.data.enabled',
|
||||
STEP_DATA_3G: 2,
|
||||
apnRetrieved: false,
|
||||
|
||||
init: function dm_init() {
|
||||
var settings = navigator.mozSettings;
|
||||
if (!settings) {
|
||||
console.log('Settings is not available');
|
||||
return;
|
||||
}
|
||||
this.settings = settings;
|
||||
},
|
||||
|
||||
removeSVStatusObserver: function dm_removeSVStatusObserver() {
|
||||
this.settings.removeObserver(this.keySV, this.getStatus);
|
||||
},
|
||||
|
||||
getStatus: function dm_getStatus(aCallback) {
|
||||
var self = this;
|
||||
var reqSV = this.settings.createLock().get(this.keySV);
|
||||
reqSV.onsuccess = function gst_svsuccess() {
|
||||
// We need to be sure that we are on Cellular Data screen
|
||||
// if the user has passed the screen very fast this configuration will
|
||||
// have no effect
|
||||
var svStatus = reqSV.result[self.keySV];
|
||||
if (svStatus !== undefined && !UIManager.dataConnectionChangedByUsr) {
|
||||
self.settings.removeObserver(self.keySV, self.getStatus);
|
||||
if (Navigation.currentStep === self.STEP_DATA_3G) {
|
||||
self.toggle(svStatus);
|
||||
if (typeof aCallback === 'function') {
|
||||
aCallback(svStatus);
|
||||
}
|
||||
} else {
|
||||
self.getRealStatus(aCallback);
|
||||
}
|
||||
} else {
|
||||
self.getRealStatus(aCallback);
|
||||
}
|
||||
};
|
||||
|
||||
reqSV.onerror = function gst_error() {
|
||||
console.log('Error retrieving ' + self.keySV);
|
||||
};
|
||||
|
||||
this.settings.addObserver(this.keySV, this.getStatus.bind(this, aCallback));
|
||||
},
|
||||
|
||||
getRealStatus: function dm_getRealStatus(callback) {
|
||||
var request = this.settings.createLock().get(this.key);
|
||||
var self = this;
|
||||
request.onsuccess = function gst_success() {
|
||||
var currentStatus = request.result[self.key];
|
||||
self.isDataAvailable = currentStatus;
|
||||
callback(currentStatus);
|
||||
};
|
||||
request.onerror = function gst_error() {
|
||||
console.log('Error retrieving ' + self.key);
|
||||
};
|
||||
},
|
||||
|
||||
toggle: function dm_toggle(status, callback) {
|
||||
var options = {};
|
||||
options[this.key] = status;
|
||||
if (!this.apnRetrieved) {
|
||||
// I need to retrieve APN
|
||||
var self = this;
|
||||
this.getAPN(function apn_recovered() {
|
||||
self.settings.createLock().set(options);
|
||||
self.apnRetrieved = true;
|
||||
self.isDataAvailable = status;
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.settings.createLock().set(options);
|
||||
this.apnRetrieved = true;
|
||||
this.isDataAvailable = status;
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
getAPN: function dm_getapn(callback) {
|
||||
// By the time the APN settings are needed in the FTU before enabling data
|
||||
// calls the system app (through the operator variant logic) might store the
|
||||
// APN settings into the settings database. If not wait for that before
|
||||
// enabling data calls.
|
||||
var _self = this;
|
||||
function ensureApnSettings() {
|
||||
var req = _self.settings.createLock().get('ril.data.apnSettings');
|
||||
req.onsuccess = function loadApn() {
|
||||
var apnSettings = req.result['ril.data.apnSettings'];
|
||||
if (apnSettings) {
|
||||
if (callback) {
|
||||
callback(req.result);
|
||||
}
|
||||
_self.settings.removeObserver('ril.data.apnSettings',
|
||||
ensureApnSettings);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ensureApnSettings();
|
||||
this.settings.addObserver('ril.data.apnSettings', ensureApnSettings);
|
||||
}
|
||||
};
|
||||
70
gaia/apps/ftu/js/finish_screen.js
Executable file
70
gaia/apps/ftu/js/finish_screen.js
Executable file
|
|
@ -0,0 +1,70 @@
|
|||
/* exported FinishScreen */
|
||||
/* global ScreenLayout, Utils */
|
||||
(function(exports) {
|
||||
'use strict';
|
||||
// Defaut layout is 'tiny', the one for mobile device
|
||||
var currentLayout = 'tiny';
|
||||
|
||||
var elementIDs = [
|
||||
'tutorialFinished',
|
||||
'enjoyYourPhone',
|
||||
'enjoyYourPhoneUpdated'
|
||||
];
|
||||
|
||||
var dom = {};
|
||||
|
||||
var initialized = false;
|
||||
|
||||
var FinishScreen = {
|
||||
init: function (isUpgrade) {
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We consider only 'tablet' & 'mobile' (tiny)
|
||||
if (ScreenLayout.getCurrentLayout() !== 'tiny') {
|
||||
currentLayout = 'large';
|
||||
}
|
||||
// Get panel selector based on layout
|
||||
var panelSelector = 'tutorial-finish-' + currentLayout;
|
||||
|
||||
// Show finish panel
|
||||
var finishPanel = document.getElementById(panelSelector);
|
||||
finishPanel.classList.add('show');
|
||||
|
||||
// Cache non-layout-related DOM elements
|
||||
elementIDs.forEach(function (name) {
|
||||
dom[Utils.camelCase(name)] = document.getElementById(name);
|
||||
}, this);
|
||||
|
||||
if (isUpgrade) {
|
||||
dom.enjoyYourPhone.hidden = true;
|
||||
dom.enjoyYourPhoneUpdated.hidden = false;
|
||||
}
|
||||
|
||||
// Add regular listener if needed
|
||||
if (currentLayout === 'tiny') {
|
||||
dom.tutorialFinished.addEventListener('click', function ftuEnd() {
|
||||
window.close();
|
||||
});
|
||||
} else {
|
||||
// In 'tablet', we have to use IAC to tell system ftu is done
|
||||
navigator.mozApps.getSelf().onsuccess = function(evt) {
|
||||
var app = evt.target.result;
|
||||
app.connect('ftucomms').then(function onConnAccepted(ports) {
|
||||
ports.forEach(function(port) {
|
||||
port.postMessage('done');
|
||||
});
|
||||
}, function onConnRejected(reason) {
|
||||
console.warn('FTU is rejected due to ' + reason);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
};
|
||||
|
||||
exports.FinishScreen = FinishScreen;
|
||||
|
||||
}(this));
|
||||
48
gaia/apps/ftu/js/import_services.js
Executable file
48
gaia/apps/ftu/js/import_services.js
Executable file
|
|
@ -0,0 +1,48 @@
|
|||
/* global ServicesLauncher */
|
||||
/* exported ImportIntegration */
|
||||
'use strict';
|
||||
|
||||
var ImportIntegration = {
|
||||
|
||||
get liveImportButton() {
|
||||
delete this.liveImportButton;
|
||||
return (this.liveImportButton =
|
||||
document.getElementById('live-import-button'));
|
||||
},
|
||||
|
||||
get gmailImportButton() {
|
||||
delete this.gmailImportButton;
|
||||
return (this.gmailImportButton =
|
||||
document.getElementById('gmail-import-button'));
|
||||
},
|
||||
|
||||
init: function () {
|
||||
this.liveImportButton.addEventListener('click', this);
|
||||
this.gmailImportButton.addEventListener('click', this);
|
||||
},
|
||||
|
||||
handleEvent: function (event) {
|
||||
switch (event.type) {
|
||||
case 'click':
|
||||
if (event.target === this.liveImportButton) {
|
||||
ServicesLauncher.start('live');
|
||||
}
|
||||
else if (event.target === this.gmailImportButton) {
|
||||
ServicesLauncher.start('gmail');
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
checkImport: function (nextState) {
|
||||
|
||||
if (nextState === 'disabled') {
|
||||
this.gmailImportButton.setAttribute('disabled', 'disabled');
|
||||
this.liveImportButton.setAttribute('disabled', 'disabled');
|
||||
}
|
||||
else if (nextState === 'enabled') {
|
||||
this.gmailImportButton.removeAttribute('disabled');
|
||||
this.liveImportButton.removeAttribute('disabled');
|
||||
}
|
||||
}
|
||||
};
|
||||
85
gaia/apps/ftu/js/language.js
Executable file
85
gaia/apps/ftu/js/language.js
Executable file
|
|
@ -0,0 +1,85 @@
|
|||
/* global KeyboardHelper, LanguageList */
|
||||
/* exported LanguageManager */
|
||||
'use strict';
|
||||
|
||||
var LanguageManager = {
|
||||
settings: window.navigator.mozSettings,
|
||||
|
||||
init: function init() {
|
||||
this.buildLanguageList();
|
||||
document.getElementById('languages').addEventListener('change', this);
|
||||
|
||||
var onSettingChange = this._boundHandleSettingChange =
|
||||
this.handleSettingChange.bind(this);
|
||||
this.settings.addObserver('language.current', onSettingChange);
|
||||
this.settings.addObserver('accessibility.screenreader', onSettingChange);
|
||||
},
|
||||
|
||||
uninit: function uninit() {
|
||||
if (this._boundHandleSettingChange) {
|
||||
this.settings.removeObserver('language.current',
|
||||
this._boundHandleSettingChange);
|
||||
this.settings.removeObserver('accessibility.screenreader',
|
||||
this._boundHandleSettingChange);
|
||||
}
|
||||
},
|
||||
|
||||
handleSettingChange: function(evt) {
|
||||
switch (evt.settingName) {
|
||||
case 'accessibility.screenreader':
|
||||
this.buildLanguageList();
|
||||
break;
|
||||
case 'language.current':
|
||||
// the 2nd parameter is to reset the current enabled layouts
|
||||
KeyboardHelper.changeDefaultLayouts(evt.settingValue, true);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
handleEvent: function handleEvent(evt) {
|
||||
if (!this.settings || evt.target.name != 'language.current') {
|
||||
return true;
|
||||
}
|
||||
this.setLanguage(evt.target.value);
|
||||
return false;
|
||||
},
|
||||
|
||||
// update current launguage settings
|
||||
setLanguage: function settings_setLanguage(language) {
|
||||
this.settings.createLock().set({
|
||||
'language.current': language
|
||||
});
|
||||
},
|
||||
|
||||
buildLanguageList: function settings_buildLanguageList() {
|
||||
var panel = document.querySelector('#languages');
|
||||
var container = panel.querySelector('ul');
|
||||
container.innerHTML = '';
|
||||
LanguageList.get(function fillLanguageList(allLanguages, currentLanguage) {
|
||||
for (var lang in allLanguages) {
|
||||
var label = document.createElement('label');
|
||||
// wrap the name of the language in Unicode control codes which force
|
||||
// the proper direction of the text regardless of the direction of
|
||||
// the whole app
|
||||
label.textContent = LanguageList.wrapBidi(lang, allLanguages[lang]);
|
||||
|
||||
var radio = document.createElement('gaia-radio');
|
||||
radio.className = 'checkone';
|
||||
radio.setAttribute('name', 'language.current');
|
||||
radio.setAttribute('value', lang);
|
||||
radio.checked = (lang === currentLanguage);
|
||||
radio.appendChild(label);
|
||||
|
||||
// Needed to use a custom class with dynamic insertion.
|
||||
radio.configureClass();
|
||||
|
||||
var li = document.createElement('li');
|
||||
li.dataset.value = lang;
|
||||
li.appendChild(radio);
|
||||
container.appendChild(li);
|
||||
}
|
||||
panel.dataset.languagesReady = true;
|
||||
window.dispatchEvent(new CustomEvent('languagelistready'));
|
||||
});
|
||||
}
|
||||
};
|
||||
644
gaia/apps/ftu/js/late_customization.js
Executable file
644
gaia/apps/ftu/js/late_customization.js
Executable file
|
|
@ -0,0 +1,644 @@
|
|||
/* global URLSearchParams, Navigation, UIManager,
|
||||
IconsHelper, MobileOperator, Sanitizer, LazyLoader */
|
||||
(function(exports) {
|
||||
'use strict';
|
||||
|
||||
const MANIFEST_SETTING_NAME = 'latecustomization.url';
|
||||
const OPERATOR_INFO_SETTING_NAME = 'latecustomization.operatorInfo';
|
||||
const COMMS_CHANNEL = 'ftucomms';
|
||||
const ICON_SIZE = 32;
|
||||
const IMG_PATH = 'style/images/late_customization';
|
||||
const ICON_INSTALLING_URL = `${IMG_PATH}/app_installing.svg`;
|
||||
const ICON_FAILED_URL = `${IMG_PATH}/app_install_unrecoverable.svg`;
|
||||
const ICON_DEFAULT_URL = `${IMG_PATH}/default_icon.svg`;
|
||||
|
||||
function LateCustomizationPanel() {
|
||||
this.debug('LateCustomizationPanel ctor');
|
||||
}
|
||||
|
||||
LateCustomizationPanel.prototype = {
|
||||
DEBUG: false,
|
||||
name: 'LateCustomizationPanel',
|
||||
constructor: LateCustomizationPanel,
|
||||
_initialized: false,
|
||||
_downloadPossible: false,
|
||||
|
||||
/* The manifest URL for the customization (comes from mozSettings) */
|
||||
// e.g. https://marketplace-dev.allizom.org/\
|
||||
// api/v2/late-customization/?carrier=telenor®ion=ca
|
||||
_customizationManifestUrl: '',
|
||||
|
||||
get enabled() {
|
||||
return Boolean(this._customizationManifestUrl &&
|
||||
(this._operatorInfo || this._predefinedOperatorInfo));
|
||||
},
|
||||
|
||||
get operatorInfo() {
|
||||
var info = {};
|
||||
Object.assign(info,
|
||||
this._operatorInfo || this.getInfoFromMobileConnection(),
|
||||
this._predefinedOperatorInfo);
|
||||
return info;
|
||||
},
|
||||
|
||||
get carrierName() {
|
||||
var info = this.operatorInfo;
|
||||
return info.carrier || info.operator;
|
||||
},
|
||||
|
||||
get region() {
|
||||
return this.operatorInfo.region || '';
|
||||
},
|
||||
|
||||
start: function() {
|
||||
window.addEventListener('hashchange', this);
|
||||
window.addEventListener('ftu-done', this);
|
||||
window.addEventListener('online', this);
|
||||
window.addEventListener('offline', this);
|
||||
|
||||
this._operatorInfo = this.getInfoFromMobileConnection();
|
||||
this.waitForSettingValues().then((results) => {
|
||||
var [url, predefinedInfo] = results;
|
||||
if (url) {
|
||||
this._customizationManifestUrl = url;
|
||||
}
|
||||
if (predefinedInfo) {
|
||||
// store operator/params separate from
|
||||
// bona-fide mobileconnection details
|
||||
this._predefinedOperatorInfo = predefinedInfo;
|
||||
}
|
||||
this.debug(
|
||||
'start, resolved manifest url: ' + this._customizationManifestUrl,
|
||||
' is enabled? ' + this.enabled,
|
||||
' operatorInfo: ', this._operatorInfo);
|
||||
|
||||
if (this.enabled) {
|
||||
// bail completely if not enabled: don't register step, don't render
|
||||
this.init();
|
||||
} else {
|
||||
this.debug('Not enabled; not initializing');
|
||||
if (!this._operatorInfo) {
|
||||
// keep watching in case we can init later
|
||||
this.waitForOperatorInfo().then((info) => {
|
||||
if (!this._initialized && this.enabled) {
|
||||
this.init();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}).catch(e => {
|
||||
this.debug('start: didnt init, error from waitForSettingValues: ', e);
|
||||
});
|
||||
},
|
||||
|
||||
init: function() {
|
||||
// conditions for displaying Late Customization pane
|
||||
// are embodied in this.enabled. If not enabled, init is skipped
|
||||
// and the step is not registered at all.
|
||||
if (this._initialized || !this.enabled) {
|
||||
return;
|
||||
}
|
||||
this._initialized = true;
|
||||
this.appManifestUrls = [];
|
||||
this.render();
|
||||
|
||||
Navigation.registerStep({
|
||||
hash: '#late_customization',
|
||||
afterStep: 'firefox_accounts'
|
||||
});
|
||||
|
||||
this.attemptToPopulateAppList();
|
||||
},
|
||||
|
||||
postConnectMessage: function(message) {
|
||||
navigator.mozApps.getSelf().onsuccess = function(evt) {
|
||||
var app = evt.target.result;
|
||||
if (app.connect) {
|
||||
app.connect(COMMS_CHANNEL).then(function onConnAccepted(ports) {
|
||||
ports.forEach(function(port) {
|
||||
port.postMessage(message);
|
||||
});
|
||||
}, function onConnRejected(reason) {
|
||||
console.error('Cannot connect to comms channel: ', reason);
|
||||
});
|
||||
} else {
|
||||
console.error ('mozApps does not have a connect method. ' +
|
||||
'Cannot connect to the comms channel.');
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
attemptToPopulateAppList: function() {
|
||||
this.debug('attemptToPopulateAppList, waiting to go online');
|
||||
// start waiting to go online.
|
||||
// We bail if ftu completes first
|
||||
var connected = this.whenUsableConnection();
|
||||
|
||||
connected.then(() => {
|
||||
this.debug('attemptToPopulateAppList, came online');
|
||||
return this.fetchAppsToInstall().then((appsMap) => {
|
||||
if (!(this._appsToInstall && this._appsToInstall.size)) {
|
||||
// no apps, bail early
|
||||
this.debug('No apps in response from ' +
|
||||
this._customizationManifestUrl + ', bailing');
|
||||
return;
|
||||
}
|
||||
var urls = Array.from(this._appsToInstall.keys());
|
||||
var postMessageDetail = {
|
||||
type: 'late-customization-apps',
|
||||
urls: urls
|
||||
};
|
||||
this.postConnectMessage(postMessageDetail);
|
||||
this.updateAppList(this._appsToInstall);
|
||||
}).catch(err => {
|
||||
console.warn('LateCustomizationPanel, error fetching customization ' +
|
||||
'manifest from: ' + this._customizationManifestUrl,
|
||||
err.message);
|
||||
});
|
||||
}).catch((e) => {
|
||||
this.debug('No connection available in time to ' +
|
||||
'fetch late customization manifest:', e);
|
||||
});
|
||||
return connected;
|
||||
},
|
||||
fetchAppsToInstall: function() {
|
||||
if (this._appsToInstall) {
|
||||
return Promise.resolve(this._appsToInstall);
|
||||
}
|
||||
return this.fetchManifest().then((manifestResp) => {
|
||||
var appsMap = this.getAppsFromManifest(manifestResp);
|
||||
if (!appsMap) {
|
||||
return Promise.reject('getAppsFromManifest didnt return appsMap');
|
||||
}
|
||||
return (this._appsToInstall = appsMap);
|
||||
}, (err) => {
|
||||
console.warn('LateCustomization: ', err.message || err);
|
||||
});
|
||||
},
|
||||
|
||||
waitForSettingValues: function() {
|
||||
var haveUrl, haveInfo;
|
||||
var lock = navigator.mozSettings.createLock();
|
||||
|
||||
function getSetting(name) {
|
||||
return new Promise((resolve, reject) => {
|
||||
lock = (lock && !lock.closed) ?
|
||||
lock : navigator.mozSettings.createLock();
|
||||
var get = lock.get(name);
|
||||
get.addEventListener('success', (function() {
|
||||
resolve(get.result[name]);
|
||||
}));
|
||||
get.addEventListener('error', (function() {
|
||||
reject();
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
if (this._customizationManifestUrl) {
|
||||
haveUrl = Promise.resolve(this._customizationManifestUrl);
|
||||
} else {
|
||||
haveUrl = getSetting(MANIFEST_SETTING_NAME);
|
||||
}
|
||||
haveInfo = getSetting(OPERATOR_INFO_SETTING_NAME);
|
||||
|
||||
return Promise.all([haveUrl, haveInfo]);
|
||||
},
|
||||
|
||||
waitForOperatorInfo: function() {
|
||||
var info = this.getInfoFromMobileConnection();
|
||||
if (info) {
|
||||
this._operatorInfo = info;
|
||||
return Promise.resolve(info);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this._operatorInfoRequest = {
|
||||
resolve: resolve,
|
||||
reject: reject
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
getInfoFromMobileConnection: function() {
|
||||
var connections = navigator.mozMobileConnections;
|
||||
if (connections) {
|
||||
for (var i=0, conn; i<connections.length; i++) {
|
||||
conn = connections[i];
|
||||
if(conn && conn.voice && conn.voice.connected) {
|
||||
var operatorInfos = MobileOperator.userFacingInfo(conn);
|
||||
if (operatorInfos.operator) {
|
||||
operatorInfos.mcc = conn.voice.network.mcc;
|
||||
operatorInfos.mnc = conn.voice.network.mnc;
|
||||
return operatorInfos;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
getAppsFromManifest: function(data) {
|
||||
var apps = new Map();
|
||||
var langs = navigator.languages;
|
||||
var list = data && (data.apps || data.objects);
|
||||
if (!(list && list.length)) {
|
||||
return apps;
|
||||
}
|
||||
list.filter((app) => {
|
||||
return app.is_disabled !== true &&
|
||||
(new Set(app.device_types)).has('firefoxos');
|
||||
}).forEach((appData) => {
|
||||
appData.manifestURL = appData.manifest_url;
|
||||
// get locale-appropriate name for user display
|
||||
var appName = '';
|
||||
langs.some(function tryLanguage(lang) {
|
||||
if (this[lang]) {
|
||||
appName = this[lang];
|
||||
// aborts [].some
|
||||
return true;
|
||||
}
|
||||
}, appData.name);
|
||||
|
||||
appData.displayName = appName;
|
||||
this.debug('app data: ', appData.displayName, appData);
|
||||
apps.set(appData.manifestURL, appData);
|
||||
});
|
||||
return apps;
|
||||
},
|
||||
|
||||
attemptInstalls: function() {
|
||||
this._installQueue = [];
|
||||
if (this._appsToInstall && this._appsToInstall.size) {
|
||||
var attempts = [];
|
||||
this._appsToInstall.forEach((appData, manifestURL) => {
|
||||
if (appData.installState == 'installed' ||
|
||||
appData.installState == 'pending') {
|
||||
this.debug('attemptInstalls: app install already underway: ',
|
||||
manifestURL, appData.installState);
|
||||
} else {
|
||||
attempts.push(this.enqueAppInstall(appData));
|
||||
}
|
||||
});
|
||||
return Promise.all(attempts);
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
},
|
||||
installApp: function(appData) {
|
||||
this.debug('installApp: ', appData.manifestURL);
|
||||
return this.whenUsableConnection().then(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
var req = window.navigator.mozApps.install(appData.manifestURL);
|
||||
var onOutcome = (arg) => {
|
||||
var selector = `li[data-manifest-url="${appData.manifestURL}"]`;
|
||||
var itemNode = this.appListNode.querySelector(selector);
|
||||
var manifestURL = req.result && req.result.manifestURL;
|
||||
this.debug('appinstall DOMRequest outcome:', arg, req.result);
|
||||
|
||||
if (req.error) {
|
||||
delete appData.installState;
|
||||
itemNode && itemNode.classList.add('failed');
|
||||
console.warn('Install error: ', req.error);
|
||||
} else if (req.result) {
|
||||
appData.installState = req.result.installState;
|
||||
this.debug('Install success, app: ',
|
||||
manifestURL, appData.installState);
|
||||
}
|
||||
itemNode && itemNode.classList.remove('pending');
|
||||
resolve(req);
|
||||
};
|
||||
req.onsuccess = req.onerror = onOutcome;
|
||||
});
|
||||
}).catch(() => {
|
||||
this.debug('Lost connection while attempting install of ' +
|
||||
appData.manifestURL);
|
||||
});
|
||||
},
|
||||
|
||||
enqueAppInstall: function(app) {
|
||||
var installQueue = this._installQueue;
|
||||
installQueue.push(app);
|
||||
this.debug('enqueAppInstall, queue: ', app.manifestURL);
|
||||
|
||||
var currentInstallAttempt = this._installAttempt || Promise.resolve();
|
||||
return currentInstallAttempt.then(req => {
|
||||
if (req && req.error) {
|
||||
console.warn('install error:', req.error);
|
||||
}
|
||||
if (this._installQueue.length) {
|
||||
this._installAttempt =
|
||||
this.installApp(this._installQueue.shift());
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
debug: function() {
|
||||
if (!this.DEBUG) {
|
||||
return;
|
||||
}
|
||||
var args = Array.from(arguments);
|
||||
args.unshift('LateCustomizationPanel');
|
||||
console.log.apply(console, args);
|
||||
},
|
||||
|
||||
buildManifestUrl: function(url, infos={}) {
|
||||
var apiURL = new URL(url);
|
||||
var params = new URLSearchParams(apiURL.search.substr(1));
|
||||
for(var key in infos) {
|
||||
if (typeof infos[key] !== 'undefined') {
|
||||
params.set(key, infos[key]);
|
||||
}
|
||||
}
|
||||
apiURL.search = params.toString();
|
||||
return apiURL.href;
|
||||
},
|
||||
|
||||
fetchManifest: function() {
|
||||
var url = this.buildManifestUrl(this._customizationManifestUrl,
|
||||
this.operatorInfo);
|
||||
if (!url) {
|
||||
return Promise.reject(new Error('No manifest URL available'));
|
||||
}
|
||||
this.debug('fetchManifest at: ' + url);
|
||||
return new Promise((resolve, reject) => {
|
||||
LazyLoader.getJSON(url, true).then((resp) => {
|
||||
if (resp.objects) {
|
||||
resolve(resp);
|
||||
} else {
|
||||
reject('No apps in reponse to: ' + url);
|
||||
}
|
||||
}).catch(ex => {
|
||||
reject(ex);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
whenUsableConnection: function() {
|
||||
if (this._pendingNextOnline) {
|
||||
return this._pendingNextOnline;
|
||||
}
|
||||
var canDownload = navigator.onLine;
|
||||
if (canDownload) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
this._nextOnlineRequest = {
|
||||
resolve: resolve,
|
||||
reject: reject
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
handleEvent: function(evt) {
|
||||
switch (evt.type) {
|
||||
case 'hashchange':
|
||||
if (this.enabled &&
|
||||
evt.target.location.hash === '#late_customization') {
|
||||
if (this._appsToInstall && this._appsToInstall.size) {
|
||||
this.onPanelShown();
|
||||
} else {
|
||||
Navigation.skipStep();
|
||||
}
|
||||
}
|
||||
// TODO: is some event I can listen to to know the sim is ready
|
||||
// to be asked these questions?
|
||||
if (this._operatorInfoRequest) {
|
||||
var info = this.getInfoFromMobileConnection();
|
||||
if (info) {
|
||||
this._operatorInfo = info;
|
||||
this._operatorInfoRequest.resolve(info);
|
||||
this._operatorInfoRequest = null;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'online':
|
||||
case 'offline':
|
||||
// install is underway, try to resume
|
||||
if (navigator.onLine &&
|
||||
this._installQueue && this._installQueue.length) {
|
||||
return this.attemptInstalls();
|
||||
}
|
||||
if (navigator.onLine && this._nextOnlineRequest) {
|
||||
this._nextOnlineRequest.resolve(true);
|
||||
} else if (this._nextOnlineRequest) {
|
||||
this._nextOnlineRequest.reject(new Error('offline'));
|
||||
// re-start the wait/fetch/install process
|
||||
if (!this._appsToInstall) {
|
||||
this.attemptToPopulateAppList();
|
||||
}
|
||||
}
|
||||
this._nextOnlineRequest = null;
|
||||
break;
|
||||
case 'ftu-done':
|
||||
// abort anything pending
|
||||
if (this._operatorInfoRequest) {
|
||||
this._operatorInfoRequest.reject(
|
||||
new Error('FTU complete with no viable operator'));
|
||||
this._operatorInfoRequest = null;
|
||||
}
|
||||
if (this._nextOnlineRequest) {
|
||||
this._nextOnlineRequest.reject(
|
||||
new Error('done before connection became available'));
|
||||
this._nextOnlineRequest = null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
onPanelShown: function() {
|
||||
UIManager.mainTitle.setAttribute('data-l10n-id',
|
||||
'late-customization');
|
||||
this.finalizeRender();
|
||||
this.attemptInstalls();
|
||||
},
|
||||
|
||||
_createElement: function(name, options) {
|
||||
var elm = document.createElement(name);
|
||||
if (options.id) {
|
||||
elm.id = options.id;
|
||||
}
|
||||
if (options.className) {
|
||||
elm.className = options.className;
|
||||
}
|
||||
if (options.dataset) {
|
||||
for(var p in options.dataset) {
|
||||
elm.dataset[p] = options.dataset[p];
|
||||
}
|
||||
}
|
||||
if (options.attributes) {
|
||||
for(var a in options.attributes) {
|
||||
elm.setAttribute(a, options.attributes[a]);
|
||||
}
|
||||
}
|
||||
if ('textContent' in options) {
|
||||
elm.textContent = options.textContent;
|
||||
}
|
||||
return elm;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var section = this.element =
|
||||
document.getElementById('late_customization');
|
||||
if (!section) {
|
||||
section = this._createElement('section', {
|
||||
attributes: { role: 'region' },
|
||||
id: 'late_customization'
|
||||
});
|
||||
this.element = section;
|
||||
}
|
||||
|
||||
section.innerHTML = Sanitizer.escapeHTML `
|
||||
<section id="late-customization-cando" class="content hidden">
|
||||
<p id="late_customization-downloading-message"></p>
|
||||
</section>
|
||||
<section id="late-customization-nocando" class="content">
|
||||
<p id="late_customization-download-later-message"></p>
|
||||
<p id="late_customization-download-later-marketplace"></p>
|
||||
</section>
|
||||
<article data-type="list">
|
||||
<ul id="late_customization-applist" class="hidden"></ul>
|
||||
</article>`;
|
||||
|
||||
function setTranslationById(domId, l10Id, attrs) {
|
||||
var elem = section.querySelector('#' + domId);
|
||||
document.l10n.setAttributes(elem, l10Id, attrs);
|
||||
}
|
||||
|
||||
var carrierName = this.carrierName;
|
||||
if (carrierName) {
|
||||
setTranslationById(
|
||||
'late_customization-downloading-message',
|
||||
'late-customization-downloading-message',
|
||||
{ carrierName: this.carrierName });
|
||||
setTranslationById(
|
||||
'late_customization-download-later-message',
|
||||
'late-customization-download-later',
|
||||
{ carrierName: this.carrierName });
|
||||
} else {
|
||||
// Use an alternate string if we have no carrier name to display
|
||||
setTranslationById(
|
||||
'late_customization-downloading-message',
|
||||
'late-customization-downloading-message-default');
|
||||
setTranslationById(
|
||||
'late_customization-download-later-message',
|
||||
'late-customization-download-later-default');
|
||||
}
|
||||
setTranslationById(
|
||||
'late_customization-download-later-marketplace',
|
||||
'late-customization-download-later-marketplace',
|
||||
{ carrierName: this.carrierName });
|
||||
|
||||
if (!section.parentNode) {
|
||||
document.querySelector('#activation-screen > article')
|
||||
.appendChild(section);
|
||||
}
|
||||
this.appListNode = document.querySelector('#late_customization-applist');
|
||||
},
|
||||
|
||||
renderAppItem: function(appData) {
|
||||
var itemNode = this._createElement('li', {
|
||||
dataset: { manifestUrl: appData.manifestURL },
|
||||
className: 'pending'
|
||||
});
|
||||
var listNode = this.element.querySelector('#late_customization-applist');
|
||||
if (!listNode) {
|
||||
this.debug('No element found to render app details into');
|
||||
return;
|
||||
}
|
||||
|
||||
itemNode.innerHTML = Sanitizer.escapeHTML `
|
||||
<span class="icon"
|
||||
style="background-image: url(${ICON_INSTALLING_URL})"></span>
|
||||
<p dir="auto">${appData.displayName}</p>
|
||||
`;
|
||||
listNode.appendChild(itemNode);
|
||||
var iconElem = itemNode.querySelector('.icon');
|
||||
|
||||
this.getAppIconUrl(appData).then((iconUrl) => {
|
||||
this.debug('getAppIconUrl returned:', iconUrl);
|
||||
var img = new Image();
|
||||
img.onload = () => {
|
||||
iconElem.style.backgroundImage = 'url('+img.src+')';
|
||||
};
|
||||
img.onerror = () => {
|
||||
iconElem.style.backgroundImage = 'url('+ICON_DEFAULT_URL+')';
|
||||
};
|
||||
img.src = iconUrl.href;
|
||||
});
|
||||
return itemNode;
|
||||
},
|
||||
|
||||
getAppIconUrl: function(appData, iconSize = ICON_SIZE) {
|
||||
var siteObj = {};
|
||||
var manifest = appData;
|
||||
var manifestURL = appData.manifest_url;
|
||||
var origin = manifest.origin = new URL(manifestURL).origin;
|
||||
var hasIcons = (manifest.icons && Object.keys(manifest.icons).length);
|
||||
if (!hasIcons) {
|
||||
return Promise.resolve(new URL(ICON_DEFAULT_URL));
|
||||
}
|
||||
//Getting the icons from the FirefoxOS-style manifest
|
||||
siteObj.manifestURL = manifestURL;
|
||||
siteObj.manifest = manifest;
|
||||
var gotIcon = new Promise((resolve, reject) => {
|
||||
IconsHelper.getIcon(origin, iconSize, null, siteObj)
|
||||
.then((iconUrl) => {
|
||||
this.debug('getIcon returned: ', iconUrl);
|
||||
resolve(iconUrl);
|
||||
}).catch(err => {
|
||||
this.debug('Failed to load icon from: ' +
|
||||
JSON.stringify(appData.icons));
|
||||
console.warn('getIcon errback, err: ',err);
|
||||
resolve(new URL(ICON_FAILED_URL));
|
||||
});
|
||||
});
|
||||
return gotIcon;
|
||||
},
|
||||
|
||||
finalizeRender: function() {
|
||||
var container = this.element;
|
||||
var appsMap = this._appsToInstall;
|
||||
var hasApps = (appsMap && appsMap.size);
|
||||
|
||||
var canDoElm = container.querySelector('#late-customization-cando');
|
||||
var noCanDoElm = container.querySelector('#late-customization-nocando');
|
||||
var appListElm = container.querySelector('#late_customization-applist');
|
||||
|
||||
canDoElm.classList.toggle('hidden', !hasApps);
|
||||
appListElm.classList.toggle('hidden', !hasApps);
|
||||
noCanDoElm.classList.toggle('hidden', !!hasApps);
|
||||
},
|
||||
|
||||
updateAppList: function(appsMap) {
|
||||
var list = this.element.querySelector('#late_customization-applist');
|
||||
|
||||
this.debug('updateAppList: ', appsMap);
|
||||
|
||||
while (list.firstChild) {
|
||||
list.firstChild.remove();
|
||||
}
|
||||
|
||||
if (appsMap && appsMap.size) {
|
||||
for (var appData of appsMap.values()) {
|
||||
this.debug('calling renderAppItem with app: ', appData.manifestURL);
|
||||
this.renderAppItem(appData);
|
||||
}
|
||||
}
|
||||
this.finalizeRender();
|
||||
}
|
||||
};
|
||||
|
||||
exports.LateCustomizationPanel = LateCustomizationPanel;
|
||||
var panel;
|
||||
|
||||
document.l10n.ready.then(() => {
|
||||
panel = exports.lateCustomizationPanel = new LateCustomizationPanel();
|
||||
});
|
||||
|
||||
window.addEventListener('ftu-setup', function onSetup(evt) {
|
||||
window.removeEventListener('ftu-setup', onSetup);
|
||||
panel && (panel.manager = evt.detail);
|
||||
panel && panel.start();
|
||||
});
|
||||
|
||||
})(window);
|
||||
164
gaia/apps/ftu/js/memcard_manager.js
Executable file
164
gaia/apps/ftu/js/memcard_manager.js
Executable file
|
|
@ -0,0 +1,164 @@
|
|||
/* global ConfirmDialog,
|
||||
UIManager,
|
||||
utils,
|
||||
VCFReader */
|
||||
|
||||
/* exported SdManager */
|
||||
|
||||
'use strict';
|
||||
|
||||
var SdManager = {
|
||||
available: function() {
|
||||
return utils.sdcard.checkStorageCard();
|
||||
},
|
||||
|
||||
checkSDButton: function sm_checkSDButton() {
|
||||
var sdOption = UIManager.sdImportButton;
|
||||
// If there is an unlocked SD we activate import from SD
|
||||
if (!SdManager.alreadyImported && SdManager.available()) {
|
||||
sdOption.removeAttribute('disabled');
|
||||
UIManager.noMemorycard.classList.add('hidden');
|
||||
} else {
|
||||
sdOption.setAttribute('disabled', 'disabled');
|
||||
if (!SdManager.alreadyImported) {
|
||||
UIManager.noMemorycard.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
importContacts: function sm_importContacts() {
|
||||
var importedContacts = 0;
|
||||
|
||||
UIManager.navBar.setAttribute('aria-disabled', 'true');
|
||||
|
||||
var cancelled = false;
|
||||
var importer = null;
|
||||
var importButton;
|
||||
|
||||
var progress = utils.overlay.show(
|
||||
'memoryCardContacts-importing',
|
||||
'activityBar',
|
||||
'infiniteProgress'
|
||||
);
|
||||
utils.overlay.showMenu();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
utils.overlay.oncancel = () => {
|
||||
cancelled = true;
|
||||
if (importer) {
|
||||
importer.finish();
|
||||
} else {
|
||||
UIManager.navBar.removeAttribute('aria-disabled');
|
||||
utils.overlay.hide();
|
||||
}
|
||||
resolve();
|
||||
};
|
||||
|
||||
importButton = UIManager.sdImportButton;
|
||||
|
||||
utils.sdcard.retrieveFiles([
|
||||
'text/vcard',
|
||||
'text/directory;profile=vCard',
|
||||
'text/directory'
|
||||
], ['vcf', 'vcard'], (error, fileArray) => {
|
||||
if (!error && !fileArray.length) {
|
||||
error = 'No contacts were found';
|
||||
}
|
||||
|
||||
if (error) {
|
||||
reject(error);
|
||||
return import_error(error);
|
||||
}
|
||||
|
||||
if (cancelled) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
var promises = [];
|
||||
fileArray.forEach(file => {
|
||||
promises.push(utils.sdcard.getTextFromFile(file, onContacts));
|
||||
});
|
||||
|
||||
Promise.all(promises).then(() => {
|
||||
utils.misc.setTimestamp('sd');
|
||||
UIManager.navBar.removeAttribute('aria-disabled');
|
||||
utils.overlay.hide();
|
||||
if (cancelled) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
SdManager.alreadyImported = true;
|
||||
importButton.setAttribute('disabled', 'disabled');
|
||||
utils.status.show({
|
||||
id: 'memoryCardContacts-imported3',
|
||||
args: { n: importedContacts }
|
||||
});
|
||||
resolve();
|
||||
}).catch(reject);
|
||||
});
|
||||
});
|
||||
|
||||
function onContacts(text) {
|
||||
if (cancelled) {
|
||||
return Promise.reject();
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
importer = new VCFReader(text);
|
||||
|
||||
if (!text || !importer) {
|
||||
var error = 'No contacts were found';
|
||||
import_error(error);
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
importer.onimported = imported_contact;
|
||||
importer.onerror = error => {
|
||||
import_error(error);
|
||||
reject(error);
|
||||
};
|
||||
|
||||
importer.process(() => {
|
||||
if (cancelled) {
|
||||
reject('Cancelled');
|
||||
utils.overlay.hide();
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function imported_contact() {
|
||||
importedContacts++;
|
||||
progress.update();
|
||||
}
|
||||
|
||||
function import_error(e) {
|
||||
UIManager.navBar.removeAttribute('aria-disabled');
|
||||
utils.overlay.hide();
|
||||
// Just in case the user decides to do so later
|
||||
importButton.removeAttribute('disabled');
|
||||
|
||||
// Showing error message allowing user to retry
|
||||
var cancel = {
|
||||
title: 'cancel',
|
||||
callback: function() {
|
||||
ConfirmDialog.hide();
|
||||
}
|
||||
};
|
||||
|
||||
var retry = {
|
||||
title: 'retry',
|
||||
isRecommend: true,
|
||||
callback: function() {
|
||||
ConfirmDialog.hide();
|
||||
// And now the action is reproduced one more time
|
||||
importButton.click();
|
||||
}
|
||||
};
|
||||
ConfirmDialog.show(null, 'memoryCardContacts-error', cancel, retry);
|
||||
}
|
||||
}
|
||||
};
|
||||
532
gaia/apps/ftu/js/navigation.js
Executable file
532
gaia/apps/ftu/js/navigation.js
Executable file
|
|
@ -0,0 +1,532 @@
|
|||
/* global DataMobile, SimManager, IccHelper,
|
||||
SdManager, UIManager, WifiManager, WifiUI,
|
||||
ImportIntegration,
|
||||
OperatorVariant */
|
||||
/* exported Navigation */
|
||||
(function(exports) {
|
||||
'use strict';
|
||||
|
||||
const DOGFOODSETTING = 'debug.performance_data.dogfooding';
|
||||
const METRICSSETTING = 'metrics.selectedMetrics.level';
|
||||
|
||||
/*
|
||||
Steps of the First Time Usage App
|
||||
*/
|
||||
var steps = [
|
||||
{
|
||||
onlyForward: true,
|
||||
hash: '#languages',
|
||||
requireSIM: false
|
||||
},
|
||||
{
|
||||
onlyForward: false,
|
||||
hash: '#data_3g',
|
||||
requireSIM: true
|
||||
},
|
||||
{
|
||||
onlyForward: false,
|
||||
hash: '#wifi',
|
||||
requireSIM: false
|
||||
},
|
||||
{
|
||||
onlyForward: false,
|
||||
hash: '#date_and_time',
|
||||
requireSIM: false
|
||||
},
|
||||
{
|
||||
onlyForward: false,
|
||||
hash: '#geolocation',
|
||||
requireSIM: false
|
||||
},
|
||||
{
|
||||
onlyForward: false,
|
||||
hash: '#import_contacts',
|
||||
requireSIM: false
|
||||
},
|
||||
{
|
||||
onlyForward: false,
|
||||
hash: '#firefox_accounts',
|
||||
requireSIM: false
|
||||
},
|
||||
{
|
||||
onlyForward: false,
|
||||
hash: '#welcome_browser',
|
||||
requireSIM: false
|
||||
},
|
||||
{
|
||||
onlyForward: false,
|
||||
hash: '#browser_privacy',
|
||||
requireSIM: false
|
||||
}
|
||||
];
|
||||
|
||||
exports.Navigation = {
|
||||
get stepCount() {
|
||||
return this._stepOrder ? this._stepOrder.length : 0;
|
||||
},
|
||||
currentStepIndex: 0,
|
||||
previousStepIndex: 0,
|
||||
simMandatory: false,
|
||||
skipMobileDataScreen: false,
|
||||
skipDateTimeScreen: false,
|
||||
tzInitialized: false,
|
||||
|
||||
_stepsById: null,
|
||||
_stepOrder: null,
|
||||
|
||||
init: function n_init() {
|
||||
this._stepOrder = [];
|
||||
this._stepsById = {};
|
||||
|
||||
var settings = navigator.mozSettings;
|
||||
var forward = document.getElementById('forward');
|
||||
var back = document.getElementById('back');
|
||||
this._onForwardClick = this.forward.bind(this);
|
||||
this._onBackClick = this.back.bind(this);
|
||||
forward.addEventListener('click', this._onForwardClick);
|
||||
back.addEventListener('click', this._onBackClick);
|
||||
window.addEventListener('hashchange', this);
|
||||
this._onActivationScreenClick = this.handleExternalLinksClick.bind(this);
|
||||
UIManager.activationScreen.addEventListener('click',
|
||||
this._onActivationScreenClick);
|
||||
|
||||
// register pre-defined steps in the oder they appear
|
||||
steps.forEach(step => {
|
||||
this.registerStep(step);
|
||||
});
|
||||
|
||||
var self = this;
|
||||
|
||||
var reqSIM =
|
||||
settings && settings.createLock().get('ftu.sim.mandatory') || {};
|
||||
reqSIM.onsuccess = function onSuccess() {
|
||||
self.simMandatory = reqSIM.result['ftu.sim.mandatory'] || false;
|
||||
};
|
||||
|
||||
navigator.mozApps.getSelf().onsuccess = function(evt) {
|
||||
var app = evt.target.result;
|
||||
app.connect('ftucomms').then(function onConnAccepted(ports) {
|
||||
self.ports = ports;
|
||||
ports.forEach(port => port.postMessage('started'));
|
||||
}, function onConnRejected(reason) {
|
||||
console.warn('FTU navigation cannot use IAC: ' + reason);
|
||||
});
|
||||
};
|
||||
},
|
||||
|
||||
uninit: function() {
|
||||
this._stepOrder = null;
|
||||
this._stepsById = null;
|
||||
this.currentStepIndex = 0;
|
||||
this.previousStepIndex = 0;
|
||||
this.ports = null;
|
||||
|
||||
var forward = document.getElementById('forward');
|
||||
var back = document.getElementById('back');
|
||||
var activationScreen = UIManager && UIManager.activationScreen;
|
||||
|
||||
if (forward && this._onForwardClick) {
|
||||
forward.removeEventListener('click', this._onForwardClick);
|
||||
}
|
||||
if (back && this._onBackClick) {
|
||||
back.removeEventListener('click', this._onBackClick);
|
||||
}
|
||||
if (activationScreen && this._onActivationScreenClick) {
|
||||
activationScreen.removeEventListener('click',
|
||||
this._onActivationScreenClick);
|
||||
}
|
||||
window.removeEventListener('hashchange', this);
|
||||
},
|
||||
|
||||
registerStep: function(step) {
|
||||
if (!step.id) {
|
||||
step.id = step.hash.substring(1); // strip off '#' to make an id;
|
||||
}
|
||||
var id = step.id;
|
||||
this._stepsById[id] = step;
|
||||
var beforeStepIdx = step.hasOwnProperty('beforeStep') ?
|
||||
this._stepOrder.indexOf(step.beforeStep) : -1;
|
||||
var afterStepIdx = step.hasOwnProperty('afterStep') ?
|
||||
this._stepOrder.indexOf(step.afterStep) : -1;
|
||||
|
||||
if (beforeStepIdx > -1) {
|
||||
this._stepOrder.splice(beforeStepIdx, 0, id);
|
||||
} else if (afterStepIdx > -1) {
|
||||
this._stepOrder.splice(afterStepIdx+1, 0, id);
|
||||
} else {
|
||||
this._stepOrder.push(id);
|
||||
}
|
||||
},
|
||||
|
||||
stepAt: function(idx) {
|
||||
var stepId = this._stepOrder[idx];
|
||||
if (stepId) {
|
||||
return this._stepsById[stepId];
|
||||
}
|
||||
},
|
||||
indexOfStep: function(step) {
|
||||
var id = (typeof id === 'string') ? step.id : step;
|
||||
return this._stepOrder.indexOf(id);
|
||||
},
|
||||
|
||||
back: function n_back(event) {
|
||||
var currentStep = this.stepAt(this.currentStepIndex);
|
||||
var actualHash = window.location.hash;
|
||||
if (actualHash != currentStep.hash) {
|
||||
window.history.back();
|
||||
} else {
|
||||
var self = this;
|
||||
var goToStep = function() {
|
||||
self.previousStepIndex = self.currentStepIndex;
|
||||
self.currentStepIndex--;
|
||||
if (self.currentStepIndex >= 0) {
|
||||
self.manageStep();
|
||||
}
|
||||
};
|
||||
goToStep();
|
||||
}
|
||||
},
|
||||
|
||||
forward: function n_forward(event) {
|
||||
var self = this;
|
||||
var goToStepForward = function() {
|
||||
self.previousStepIndex = self.currentStepIndex;
|
||||
self.currentStepIndex++;
|
||||
if (self.currentStepIndex >= self.stepCount) {
|
||||
// Try to send Newsletter here
|
||||
UIManager.sendNewsletter(function newsletterSent(result) {
|
||||
if (result) { // sending process ok, we advance
|
||||
UIManager.activationScreen.classList.remove('show');
|
||||
UIManager.changeStatusBarColor(UIManager.DARK_THEME);
|
||||
UIManager.finishScreen.classList.add('show');
|
||||
UIManager.hideActivationScreenFromScreenReader();
|
||||
} else { // error on sending, we stay where we are
|
||||
self.currentStepIndex--;
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
self.manageStep();
|
||||
};
|
||||
goToStepForward();
|
||||
},
|
||||
|
||||
handleExternalLinksClick: function n_handleExternalLinksClick(e) {
|
||||
var link = e.target;
|
||||
if (! link.mozMatchesSelector('a.external')) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
var href = link.href,
|
||||
title = link.getAttribute('title') || link.textContent;
|
||||
|
||||
if (navigator.onLine) {
|
||||
this.displayExternalLink(href, title);
|
||||
} else {
|
||||
UIManager.displayOfflineDialog(href, title);
|
||||
}
|
||||
},
|
||||
|
||||
displayExternalLink: function n_displayExternalLink(href, title) {
|
||||
window.open(href, '', 'dialog');
|
||||
},
|
||||
|
||||
ensureTZInitialized: function () {
|
||||
if (!this.tzInitialized) {
|
||||
return UIManager.initTZ().then(() => {
|
||||
this.skipDateTimeScreen = !UIManager.timeZoneNeedsConfirmation;
|
||||
this.tzInitialized = true;
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
},
|
||||
|
||||
handleEvent: function n_handleEvent(event) {
|
||||
var actualHash = window.location.hash;
|
||||
switch (actualHash) {
|
||||
case '#languages':
|
||||
UIManager.mainTitle.setAttribute('data-l10n-id', 'language');
|
||||
break;
|
||||
case '#data_3g':
|
||||
UIManager.mainTitle.setAttribute('data-l10n-id', '3g');
|
||||
DataMobile.
|
||||
getStatus(UIManager.updateDataConnectionStatus.bind(UIManager));
|
||||
break;
|
||||
case '#wifi':
|
||||
DataMobile.removeSVStatusObserver();
|
||||
UIManager.mainTitle.setAttribute('data-l10n-id', 'selectNetwork');
|
||||
UIManager.activationScreen.classList.remove('no-options');
|
||||
if (UIManager.navBar.classList.contains('secondary-menu')) {
|
||||
UIManager.navBar.classList.remove('secondary-menu');
|
||||
return;
|
||||
}
|
||||
|
||||
// This might seem like an odd place to call UIManager.initTZ, but
|
||||
// there's a reason for it. initTZ tries to determine the timezone
|
||||
// using information about the current mobile network connection.
|
||||
// We want to call initTZ as late as possible to give the mobile
|
||||
// network time to connect before the function is called.
|
||||
// But we have to call initTZ *before* the Date & Time page
|
||||
// appears so that it doesn't delay the appearance of the page.
|
||||
// This is the last good opportunity to call it.
|
||||
|
||||
WifiManager.getNetworks(networks => {
|
||||
this.ensureTZInitialized().then(() => {
|
||||
WifiUI.renderNetworks(networks);
|
||||
});
|
||||
});
|
||||
|
||||
break;
|
||||
case '#date_and_time':
|
||||
UIManager.mainTitle.setAttribute('data-l10n-id', 'dateAndTime');
|
||||
break;
|
||||
case '#geolocation':
|
||||
UIManager.mainTitle.setAttribute('data-l10n-id', 'geolocation');
|
||||
break;
|
||||
case '#import_contacts':
|
||||
UIManager.mainTitle.setAttribute('data-l10n-id', 'importContacts3');
|
||||
// Enabling or disabling SIM import depending on card status
|
||||
SimManager.checkSIMButton();
|
||||
|
||||
// Enabling or disabling SD import depending on card status
|
||||
SdManager.checkSDButton();
|
||||
|
||||
// If we have 3G or Wifi activate online services import
|
||||
var onlineState;
|
||||
if (!WifiManager.api) {
|
||||
// Desktop
|
||||
ImportIntegration.checkImport('enabled');
|
||||
break;
|
||||
}
|
||||
|
||||
onlineState = window.navigator.onLine ? 'enabled' : 'disabled';
|
||||
ImportIntegration.checkImport(onlineState);
|
||||
break;
|
||||
case '#firefox_accounts':
|
||||
UIManager.mainTitle.setAttribute('data-l10n-id', 'firefox-accounts');
|
||||
break;
|
||||
case '#welcome_browser':
|
||||
UIManager.mainTitle.setAttribute('data-l10n-id', 'aboutBrowser');
|
||||
|
||||
// Initialize the radio button to preselect the basic metrics.
|
||||
var settings = navigator.mozSettings;
|
||||
var basicMetrics = document.getElementById('metrics-basic');
|
||||
var enhancedMetrics = document.getElementById('metrics-enhanced');
|
||||
var noneMetrics = document.getElementById('metrics-none');
|
||||
|
||||
var defaultLevel = settings &&
|
||||
settings.createLock().get(METRICSSETTING);
|
||||
if (defaultLevel) {
|
||||
defaultLevel.onsuccess = function() {
|
||||
var result = defaultLevel.result[METRICSSETTING];
|
||||
if (result === 'Basic') {
|
||||
basicMetrics.checked = true;
|
||||
} else if (result === 'Enhanced') {
|
||||
enhancedMetrics.checked = true;
|
||||
} else if (result === 'None') {
|
||||
noneMetrics.checked = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// If it's a dogfooder, we don't want them to disable the metrics.
|
||||
var dogfood = settings && settings.createLock().get(DOGFOODSETTING);
|
||||
if (dogfood) {
|
||||
dogfood.onsuccess = function() {
|
||||
if (dogfood.result[DOGFOODSETTING]) {
|
||||
basicMetrics.setAttribute('disabled', 'true');
|
||||
enhancedMetrics.setAttribute('disabled', 'true');
|
||||
noneMetrics.setAttribute('disabled', 'true');
|
||||
} else {
|
||||
basicMetrics.removeAttribute('disabled');
|
||||
enhancedMetrics.removeAttribute('disabled');
|
||||
noneMetrics.removeAttribute('disabled');
|
||||
}
|
||||
};
|
||||
}
|
||||
break;
|
||||
case '#browser_privacy':
|
||||
UIManager.mainTitle.setAttribute('data-l10n-id', 'aboutBrowser');
|
||||
break;
|
||||
case '#SIM_mandatory':
|
||||
UIManager.mainTitle.setAttribute('data-l10n-id', 'SIM_mandatory');
|
||||
break;
|
||||
case '#about-your-rights':
|
||||
case '#about-your-privacy':
|
||||
UIManager.mainTitle.setAttribute('data-l10n-id', 'aboutBrowser');
|
||||
UIManager.navBar.classList.add('back-only');
|
||||
break;
|
||||
case '#sharing-performance-data':
|
||||
UIManager.mainTitle.setAttribute('data-l10n-id', 'aboutBrowser');
|
||||
UIManager.navBar.classList.add('back-only');
|
||||
break;
|
||||
}
|
||||
|
||||
// If SIM card is mandatory, we hide the button skip
|
||||
if (this.simMandatory) {
|
||||
UIManager.skipPinButton.classList.add('hidden');
|
||||
UIManager.backSimButton.classList.remove('hidden');
|
||||
} else {
|
||||
UIManager.skipPinButton.classList.remove('hidden');
|
||||
UIManager.backSimButton.classList.add('hidden');
|
||||
}
|
||||
|
||||
// Managing options button
|
||||
var lastIndex = this.stepCount - 1;
|
||||
if (this.currentStepIndex <= lastIndex &&
|
||||
(this.stepAt(this.currentStepIndex).hash !== '#wifi')) {
|
||||
UIManager.activationScreen.classList.add('no-options');
|
||||
}
|
||||
|
||||
// Managing nav buttons when coming back from out-of-steps (privacy)
|
||||
if (this.currentStepIndex <= lastIndex &&
|
||||
this.stepAt(this.currentStepIndex).hash === actualHash) {
|
||||
UIManager.navBar.classList.remove('back-only');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Posts IAC message about FTU steps passed.
|
||||
*/
|
||||
postStepMessage: function n_postStepMessage(stepIndex) {
|
||||
if (!this.ports) {
|
||||
return;
|
||||
}
|
||||
var step = this.stepAt(stepIndex);
|
||||
this.ports.forEach(function(port) {
|
||||
port.postMessage({
|
||||
type: 'step',
|
||||
hash: step.hash
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
skipStep: function n_skipStep() {
|
||||
var stepIndex = this.currentStepIndex;
|
||||
stepIndex += stepIndex > this.previousStepIndex ? 1 : -1;
|
||||
if (stepIndex <= 0) {
|
||||
this.previousStepIndex = stepIndex = 0;
|
||||
}
|
||||
if (stepIndex > this.stepCount) {
|
||||
this.previousStepIndex = stepIndex = this.stepCount;
|
||||
}
|
||||
this.currentStepIndex = stepIndex;
|
||||
this.manageStep();
|
||||
},
|
||||
|
||||
manageStep: function n_manageStep() {
|
||||
var self = this;
|
||||
|
||||
// If we moved forward in FTU, post iac message about progress.
|
||||
if (self.currentStepIndex > self.previousStepIndex) {
|
||||
self.postStepMessage(self.previousStepIndex);
|
||||
}
|
||||
|
||||
//SV - We need remember if phone startup with SIM
|
||||
if (self.currentStepIndex >= self.stepCount) {
|
||||
OperatorVariant.setSIMOnFirstBootState();
|
||||
}
|
||||
|
||||
// Reset skip screen flags at beginning of navigation
|
||||
if (self.currentStepIndex === 0) {
|
||||
self.skipMobileDataScreen = false;
|
||||
}
|
||||
|
||||
// Retrieve future location
|
||||
var futureLocation = this.stepAt(self.currentStepIndex);
|
||||
|
||||
// If SIMcard is mandatory and no SIM, go to message window
|
||||
if (this.simMandatory &&
|
||||
!IccHelper.cardState &&
|
||||
futureLocation.requireSIM) {
|
||||
//Send to SIM Mandatory message
|
||||
futureLocation.hash = '#SIM_mandatory';
|
||||
futureLocation.requireSIM = false;
|
||||
futureLocation.onlyBackward = true;
|
||||
}
|
||||
|
||||
// Navigation bar management
|
||||
if (this.stepAt(this.currentStepIndex).onlyForward) {
|
||||
UIManager.navBar.classList.add('forward-only');
|
||||
} else {
|
||||
UIManager.navBar.classList.remove('forward-only');
|
||||
}
|
||||
var nextButton = document.getElementById('forward');
|
||||
if (this.stepAt(this.currentStepIndex).onlyBackward) {
|
||||
nextButton.setAttribute('disabled', 'disabled');
|
||||
} else {
|
||||
nextButton.removeAttribute('disabled');
|
||||
}
|
||||
|
||||
// Substitute button content on last step
|
||||
if (this.currentStepIndex === this.stepCount) {
|
||||
nextButton.setAttribute('data-l10n-id', 'done');
|
||||
} else {
|
||||
nextButton.setAttribute('data-l10n-id', 'navbar-next');
|
||||
}
|
||||
|
||||
if (futureLocation.hash === '#firefox_accounts') {
|
||||
nextButton.setAttribute('data-l10n-id', 'skip');
|
||||
}
|
||||
|
||||
// Change hash to the right location
|
||||
window.location.hash = futureLocation.hash;
|
||||
|
||||
// SIM card management
|
||||
if (futureLocation.requireSIM) {
|
||||
var check_cardState = function(response) {
|
||||
if (!response || (!SimManager.available() &&
|
||||
// Don't skip it if next step is data 3g
|
||||
futureLocation.hash !== '#data_3g')) {
|
||||
self.skipStep();
|
||||
if (self.currentStepIndex > self.previousStepIndex) {
|
||||
self.skipMobileDataScreen = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// if we are navigating backwards, we do not want to
|
||||
// show the SIM unlock screens for the data_3g step
|
||||
var skipUnlockScreens = this.currentStepIndex < this.previousStepIndex;
|
||||
SimManager.handleCardState(check_cardState, skipUnlockScreens);
|
||||
}
|
||||
|
||||
// Only show the Date & Time screen if we couldn't determine the
|
||||
// timezone from the network. (We assume that if we can
|
||||
// determine the timezone, we can determine the time too.)
|
||||
if (this.stepAt(this.currentStepIndex).hash === '#date_and_time') {
|
||||
if (!self.tzInitialized) {
|
||||
self.skipDateTimeScreen = !UIManager.timeZoneNeedsConfirmation;
|
||||
}
|
||||
|
||||
if (self.skipDateTimeScreen) {
|
||||
self.postStepMessage(self.currentStepIndex);
|
||||
if(navigator.onLine) {
|
||||
//if you are online you can get a more accurate guess for the time
|
||||
//time you just need to trigger it
|
||||
UIManager.updateSetting(
|
||||
'time.timezone.automatic-update.enabled',
|
||||
true
|
||||
);
|
||||
UIManager.updateSetting(
|
||||
'time.clock.automatic-update.enabled',
|
||||
true
|
||||
);
|
||||
}
|
||||
self.skipStep();
|
||||
}
|
||||
}
|
||||
|
||||
// if we are not connected we should not try fxa
|
||||
if ((futureLocation.hash === '#firefox_accounts' &&
|
||||
!navigator.onLine) ||
|
||||
(futureLocation.hash === '#firefox_accounts' &&
|
||||
UIManager.skipFxA)) {
|
||||
self.postStepMessage(self.currentStepIndex);
|
||||
self.skipStep();
|
||||
}
|
||||
}
|
||||
};
|
||||
})(window);
|
||||
85
gaia/apps/ftu/js/operatorVariant.js
Executable file
85
gaia/apps/ftu/js/operatorVariant.js
Executable file
|
|
@ -0,0 +1,85 @@
|
|||
/* global IccHelper */
|
||||
/* exported OperatorVariant */
|
||||
'use strict';
|
||||
|
||||
var OperatorVariant = (function() {
|
||||
|
||||
var NO_SIM = '000-000';
|
||||
|
||||
/**
|
||||
* If ftu.simPresentOnFirstBoot setting has value do nothing otherwise
|
||||
* set ftu.simPresentOnFirstBoot = true if
|
||||
* IccHelper.cardState value is:
|
||||
* 'ready'.
|
||||
* otherwise set to false.
|
||||
* 'pinRequired',
|
||||
* 'pukRequired',
|
||||
* 'networkLocked',
|
||||
* 'corporateLocked',
|
||||
* 'serviceProviderLocked',
|
||||
* 'network1Locked',
|
||||
* 'network2Locked',
|
||||
* 'hrpdNetworkLocked',
|
||||
* 'ruimCorporateLocked',
|
||||
* 'ruimServiceProviderLocked'
|
||||
*/
|
||||
function setSIMOnFirstBootState() {
|
||||
var cardValue;
|
||||
|
||||
function normalizeCode(aCode) {
|
||||
var ncode = '' + aCode;
|
||||
while (ncode.length < 3) {
|
||||
ncode = '0' + ncode;
|
||||
}
|
||||
return ncode;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!IccHelper || !IccHelper.cardState) {
|
||||
cardValue = NO_SIM;
|
||||
} else {
|
||||
var mcc = IccHelper.iccInfo.mcc;
|
||||
var mnc = IccHelper.iccInfo.mnc;
|
||||
if ((mcc !== undefined) && (mcc !== null) &&
|
||||
(mnc !== undefined) && (mnc !== null)) {
|
||||
cardValue = normalizeCode(mcc) + '-' + normalizeCode(mnc);
|
||||
} else {
|
||||
cardValue = NO_SIM;
|
||||
}
|
||||
}
|
||||
|
||||
var settings = navigator.mozSettings;
|
||||
if (!settings) {
|
||||
console.log('Settings is not available');
|
||||
return;
|
||||
}
|
||||
|
||||
var req = settings.createLock().get('ftu.simPresentOnFirstBoot');
|
||||
|
||||
req.onsuccess = function ov_onsuccess() {
|
||||
var currentStatus = req.result['ftu.simPresentOnFirstBoot'];
|
||||
if (currentStatus === undefined || currentStatus === null) {
|
||||
var result = navigator.mozSettings.createLock().set(
|
||||
{ 'ftu.simPresentOnFirstBoot' : cardValue });
|
||||
|
||||
result.onerror = function ov_error() {
|
||||
console.error('An error occurre setting ' +
|
||||
'ftu.simPresentOnFirstBoot: ' + cardValue);
|
||||
};
|
||||
}
|
||||
cardValue = null;
|
||||
};
|
||||
|
||||
req.onerror = function ov_error() {
|
||||
console.error('Error retrieving ftu.simPresentOnFirstBoot');
|
||||
};
|
||||
|
||||
} catch (e) {
|
||||
console.error('Error setSIMOnFirstBootState. ' + e);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
setSIMOnFirstBootState: setSIMOnFirstBootState
|
||||
};
|
||||
})();
|
||||
95
gaia/apps/ftu/js/services_launcher.js
Executable file
95
gaia/apps/ftu/js/services_launcher.js
Executable file
|
|
@ -0,0 +1,95 @@
|
|||
/* global utils */
|
||||
/* exported ServicesLauncher */
|
||||
'use strict';
|
||||
|
||||
var FTU_APP_ORIGIN = document.location.protocol + '//' +
|
||||
document.location.host;
|
||||
|
||||
var ServicesLauncher = (function(document) {
|
||||
var extensionFrame = document.querySelector('#iframe_extensions');
|
||||
var oauthFrame = document.querySelector('#iframe_oauth');
|
||||
|
||||
var BASE_IMPORT = '/shared/pages/import/import.html?ftu=1';
|
||||
function getServiceURI(service) {
|
||||
return BASE_IMPORT + '&service=' + service;
|
||||
}
|
||||
|
||||
var access_token, currentURI, currentService;
|
||||
|
||||
|
||||
function open() {
|
||||
extensionFrame.className = 'opening';
|
||||
}
|
||||
|
||||
function load(targetService) {
|
||||
currentService = targetService;
|
||||
currentURI = getServiceURI(targetService);
|
||||
|
||||
window.addEventListener('message', messageHandler);
|
||||
oauthFrame.contentWindow.postMessage({
|
||||
type: 'start',
|
||||
data: {
|
||||
from: 'friends',
|
||||
service: targetService
|
||||
}
|
||||
}, FTU_APP_ORIGIN);
|
||||
}
|
||||
|
||||
function unload() {
|
||||
window.removeEventListener('message', messageHandler);
|
||||
extensionFrame.src = null;
|
||||
}
|
||||
|
||||
function close(messageId) {
|
||||
extensionFrame.addEventListener('transitionend', function tclose() {
|
||||
extensionFrame.removeEventListener('transitionend', tclose);
|
||||
extensionFrame.src = null;
|
||||
if (messageId) {
|
||||
utils.status.show(messageId);
|
||||
}
|
||||
});
|
||||
extensionFrame.className = 'closing';
|
||||
window.removeEventListener('message', messageHandler);
|
||||
}
|
||||
|
||||
|
||||
// This function can also be executed when other messages arrive
|
||||
// That's why we cannot call notifySettings outside the switch block
|
||||
function messageHandler(e) {
|
||||
if (e.origin !== FTU_APP_ORIGIN) {
|
||||
return;
|
||||
}
|
||||
var data = e.data;
|
||||
|
||||
switch (data.type) {
|
||||
case 'ready':
|
||||
open();
|
||||
break;
|
||||
|
||||
case 'abort':
|
||||
unload();
|
||||
break;
|
||||
|
||||
case 'window_close':
|
||||
close(data.messageId);
|
||||
break;
|
||||
|
||||
case 'authenticated':
|
||||
extensionFrame.src = currentURI;
|
||||
access_token = data.data;
|
||||
break;
|
||||
|
||||
case 'messaging_ready':
|
||||
extensionFrame.contentWindow.postMessage({
|
||||
type: 'token',
|
||||
data: access_token
|
||||
}, FTU_APP_ORIGIN);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
start: load
|
||||
};
|
||||
|
||||
})(document);
|
||||
840
gaia/apps/ftu/js/sim_manager.js
Executable file
840
gaia/apps/ftu/js/sim_manager.js
Executable file
|
|
@ -0,0 +1,840 @@
|
|||
/* global ConfirmDialog,
|
||||
MobileOperator,
|
||||
Navigation,
|
||||
SimContactsImporter,
|
||||
UIManager,
|
||||
utils */
|
||||
|
||||
'use strict';
|
||||
|
||||
var SimManager = (function() {
|
||||
|
||||
/*
|
||||
* Icc helper object to track FTE related SIM state
|
||||
*/
|
||||
function Icc(mozIcc) {
|
||||
this.mozIcc = mozIcc;
|
||||
}
|
||||
Icc.prototype = {
|
||||
skipped: false,
|
||||
unlocked: false,
|
||||
alreadyImported: false,
|
||||
mozIcc: null,
|
||||
isLocked: function() {
|
||||
return !this.unlocked && lockStates.indexOf(this.mozIcc.cardState) !== -1;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Update ui element with a retry count message for the given sim
|
||||
*/
|
||||
function showRetryCount(icc, lockType, uiElement) {
|
||||
var request = icc.mozIcc.getCardLockRetryCount(lockType);
|
||||
request.onsuccess = function() {
|
||||
var retryCount = request.result.retryCount;
|
||||
if (retryCount) {
|
||||
var l10nArgs = {n: retryCount};
|
||||
document.l10n.setAttributes(uiElement,'inputCodeRetriesLeft',
|
||||
l10nArgs);
|
||||
uiElement.classList.remove('hidden');
|
||||
}
|
||||
};
|
||||
request.onerror = function() {
|
||||
console.error('Could not fetch CardLockRetryCount', request.error.name);
|
||||
};
|
||||
}
|
||||
|
||||
// mozIcc.cardState values for a locked SIM
|
||||
var lockStates = ['pinRequired', 'pukRequired', 'networkLocked',
|
||||
'corporateLocked', 'serviceProviderLocked', 'network1Locked',
|
||||
'network2Locked', 'hrpdNetworkLocked', 'ruimCorporateLocked',
|
||||
'ruimServiceProviderLocked'];
|
||||
|
||||
return {
|
||||
icc0: null,
|
||||
icc1: null,
|
||||
simSlots: window.navigator.mozMobileConnections ?
|
||||
window.navigator.mozMobileConnections.length : 0,
|
||||
|
||||
// track the SIM that is currently being unlocked
|
||||
_unlockingIcc: null,
|
||||
|
||||
init: function sm_init() {
|
||||
this.mobConn = window.navigator.mozMobileConnections;
|
||||
if (!this.mobConn) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.iccManager = window.navigator.mozIccManager;
|
||||
if (!this.iccManager) {
|
||||
return;
|
||||
}
|
||||
|
||||
// keep track of sim card info in slot 0 and 1
|
||||
this.icc0 = null;
|
||||
this.icc1 = null;
|
||||
|
||||
if (this.iccManager.iccIds[0]) {
|
||||
this.updateIccState(this.iccManager.iccIds[0]);
|
||||
}
|
||||
if (this.iccManager.iccIds[1]) {
|
||||
this.updateIccState(this.iccManager.iccIds[1]);
|
||||
}
|
||||
|
||||
this.iccManager.addEventListener('iccdetected',
|
||||
this.handleIccState.bind(this));
|
||||
|
||||
this.alreadyImported = false;
|
||||
},
|
||||
|
||||
handleUnlockError: function sm_handleUnlockError(lockType, retryCount) {
|
||||
var l10nArgs = {n: retryCount};
|
||||
switch (lockType) {
|
||||
case 'pin':
|
||||
if (retryCount === 0) {
|
||||
this.showPukScreen(this._unlockingIcc);
|
||||
break;
|
||||
}
|
||||
UIManager.pinInput.value = '';
|
||||
UIManager.pinInput.classList.add('onerror');
|
||||
UIManager.pinError.classList.remove('hidden');
|
||||
document.l10n.setAttributes(UIManager.pinRetriesLeft,
|
||||
'inputCodeRetriesLeft', l10nArgs);
|
||||
UIManager.pinRetriesLeft.classList.remove('hidden');
|
||||
if (retryCount === 1) {
|
||||
UIManager.pinError.
|
||||
querySelector('.lastchance').classList.remove('hidden');
|
||||
} else {
|
||||
document.l10n.setAttributes(
|
||||
UIManager.pinError.querySelector('.main'),
|
||||
'pinAttemptMsg2',
|
||||
l10nArgs
|
||||
);
|
||||
UIManager.pinError.querySelector('.main').classList.remove('hidden');
|
||||
}
|
||||
break;
|
||||
case 'puk':
|
||||
UIManager.pukInput.value = '';
|
||||
UIManager.pukInput.classList.add('onerror');
|
||||
UIManager.pukError.classList.remove('hidden');
|
||||
UIManager.pukInfo.classList.add('hidden');
|
||||
document.l10n.setAttributes(UIManager.pukRetriesLeft,
|
||||
'inputCodeRetriesLeft', l10nArgs);
|
||||
UIManager.pukRetriesLeft.classList.remove('hidden');
|
||||
if (retryCount === 1) {
|
||||
UIManager.pukError.
|
||||
querySelector('.lastchance').classList.remove('hidden');
|
||||
} else {
|
||||
document.l10n.setAttributes(
|
||||
UIManager.pukError.querySelector('.main'),
|
||||
'pukAttemptMsg2',
|
||||
l10nArgs
|
||||
);
|
||||
UIManager.pukError.querySelector('.main').classList.remove('hidden');
|
||||
}
|
||||
// TODO what if counter gets to 0 ??
|
||||
break;
|
||||
case 'nck':
|
||||
case 'cck':
|
||||
case 'spck':
|
||||
UIManager.xckInput.value = '';
|
||||
UIManager.xckInput.classList.add('onerror');
|
||||
UIManager.xckError.classList.remove('hidden');
|
||||
document.l10n.setAttributes(UIManager.xckRetriesLeft,
|
||||
'inputCodeRetriesLeft', l10nArgs);
|
||||
UIManager.xckRetriesLeft.classList.remove('hidden');
|
||||
if (retryCount == 1) {
|
||||
UIManager.xckError.
|
||||
querySelector('.lastchance').classList.remove('hidden');
|
||||
} else {
|
||||
document.l10n.setAttributes(
|
||||
UIManager.xckError.querySelector('.main'),
|
||||
'nckAttemptMsg2',
|
||||
l10nArgs
|
||||
);
|
||||
UIManager.xckError.querySelector('.main').classList.remove('hidden');
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
available: function sm_available() {
|
||||
var icc = this.guessIcc();
|
||||
return (icc && icc.cardState === 'ready');
|
||||
},
|
||||
|
||||
handleIccState: function sm_handleIccState(event) {
|
||||
this.updateIccState(event.iccId);
|
||||
},
|
||||
|
||||
updateIccState: function(iccId) {
|
||||
var iccInfo = this.iccManager.getIccById(iccId);
|
||||
if (!iccInfo) {
|
||||
throw new Error('Unrecognized iccID: ' + iccId);
|
||||
}
|
||||
|
||||
// determine SIM slot number
|
||||
if (this.mobConn[0] && iccId === this.mobConn[0].iccId) {
|
||||
this.icc0 = new Icc(iccInfo);
|
||||
} else if (this.mobConn[1] && iccId === this.mobConn[1].iccId) {
|
||||
this.icc1 = new Icc(iccInfo);
|
||||
} else {
|
||||
console.warn('ICC detected in unsupported slot', iccId);
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Possible values:
|
||||
* null,
|
||||
* 'unknown',
|
||||
* 'pinRequired',
|
||||
* 'pukRequired',
|
||||
* 'networkLocked',
|
||||
* 'corporateLocked',
|
||||
* 'serviceProviderLocked',
|
||||
* 'network1Locked',
|
||||
* 'network2Locked',
|
||||
* 'hrpdNetworkLocked',
|
||||
* 'ruimCorporateLocked',
|
||||
* 'ruimServiceProviderLocked'
|
||||
* 'ready'.
|
||||
*/
|
||||
handleCardState: function sm_handleCardState(callback, skipUnlockScreen) {
|
||||
// used to track which SIM's PIN unlock
|
||||
// screen we are currently displaying
|
||||
this._unlockingIcc = null;
|
||||
|
||||
SimManager.checkSIMButton();
|
||||
if (typeof callback === 'function') {
|
||||
this.finishCallback = callback;
|
||||
}
|
||||
|
||||
if (this.shouldShowUnlockScreen(this.icc0) && !skipUnlockScreen) {
|
||||
this.showUnlockScreen(this.icc0);
|
||||
} else if (this.shouldShowUnlockScreen(this.icc1) && !skipUnlockScreen) {
|
||||
this.showUnlockScreen(this.icc1);
|
||||
} else if (this.shouldShowSIMInfoScreen()) {
|
||||
// reset skipped states so if we navigate back
|
||||
// we will redisplay the SIM unlock screens
|
||||
this.resetSkipped();
|
||||
this.showSIMInfoScreen();
|
||||
} else {
|
||||
this.resetSkipped();
|
||||
this.finish();
|
||||
}
|
||||
},
|
||||
|
||||
finish: function() {
|
||||
this.hideScreen();
|
||||
this.hideSIMInfoScreen();
|
||||
// card state has been handled, so return
|
||||
// to normal navigation, and only show cell
|
||||
// data step if we have an unlocked sim
|
||||
var showCellData = (this.icc0 && !this.icc0.isLocked()) ||
|
||||
(this.icc1 && !this.icc1.isLocked());
|
||||
this.finishCallback && this.finishCallback(showCellData);
|
||||
},
|
||||
|
||||
shouldShowUnlockScreen: function sm_shouldShowLockScreen(icc) {
|
||||
return icc && !icc.skipped && icc.isLocked();
|
||||
},
|
||||
|
||||
// only show sim info screen if we have two SIMs inserted
|
||||
shouldShowSIMInfoScreen: function sm_shouldShowSIMInfoScreen() {
|
||||
return (this.icc0 && this.icc1);
|
||||
},
|
||||
|
||||
showUnlockScreen: function sm_showUnlockScreen(icc) {
|
||||
if (icc.unlocked) {
|
||||
return;
|
||||
}
|
||||
this._unlockingIcc = icc;
|
||||
|
||||
switch (icc.mozIcc.cardState) {
|
||||
case 'pinRequired':
|
||||
this.showPinScreen(icc);
|
||||
break;
|
||||
case 'pukRequired':
|
||||
this.showPukScreen(icc);
|
||||
break;
|
||||
case 'networkLocked':
|
||||
case 'corporateLocked':
|
||||
case 'serviceProviderLocked':
|
||||
case 'network1Locked':
|
||||
case 'network2Locked':
|
||||
case 'hrpdNetworkLocked':
|
||||
case 'ruimCorporateLocked':
|
||||
case 'ruimServiceProviderLocked':
|
||||
this.showXckScreen(icc);
|
||||
break;
|
||||
default:
|
||||
throw new Error('Cannot show SIM unlock screen, unknown cardState ' +
|
||||
icc.mozIcc.cardState);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* A container of event listeners to avoid listening several times to the same
|
||||
* event/object pair.
|
||||
*/
|
||||
voiceChangeListeners: [],
|
||||
|
||||
updateSIMInfoText: function sm_updateSIMInfoText(icc) {
|
||||
var iccNumber = (icc === this.icc0) ? 1 : 2;
|
||||
if (icc && icc.isLocked()) {
|
||||
UIManager['simInfo' + iccNumber].classList.add('locked');
|
||||
UIManager['simCarrier' + iccNumber].setAttribute('data-l10n-id',
|
||||
'simPinLocked');
|
||||
UIManager['simNumber' + iccNumber].removeAttribute('data-l10n-id');
|
||||
UIManager['simNumber' + iccNumber].textContent = '';
|
||||
} else {
|
||||
UIManager['simInfo' + iccNumber].classList.remove('locked');
|
||||
|
||||
var mobConn = this.mobConn[iccNumber - 1];
|
||||
var operator = MobileOperator
|
||||
.userFacingInfo(mobConn)
|
||||
.operator;
|
||||
|
||||
if (!operator && !this.voiceChangeListeners[iccNumber - 1]) {
|
||||
// The operator is not yet populated, let's listen to the `voicechange`
|
||||
// event once and update the UI later.
|
||||
this.voiceChangeListeners[iccNumber - 1] = function(evt) {
|
||||
var operator = MobileOperator
|
||||
.userFacingInfo(mobConn)
|
||||
.operator;
|
||||
|
||||
if (operator) {
|
||||
// If we have an operator, we update the UI and stop listening.
|
||||
UIManager['simCarrier' + iccNumber].removeAttribute('data-l10n-id');
|
||||
UIManager['simCarrier' + iccNumber].textContent = operator;
|
||||
mobConn.removeEventListener(
|
||||
'voicechange', this.voiceChangeListeners[iccNumber - 1]);
|
||||
this.voiceChangeListeners[iccNumber - 1] = null;
|
||||
}
|
||||
}.bind(this);
|
||||
|
||||
mobConn.addEventListener(
|
||||
'voicechange', this.voiceChangeListeners[iccNumber - 1]);
|
||||
}
|
||||
|
||||
if (operator) {
|
||||
UIManager['simCarrier' + iccNumber].removeAttribute('data-l10n-id');
|
||||
UIManager['simCarrier' + iccNumber].textContent = operator;
|
||||
} else {
|
||||
UIManager['simCarrier' + iccNumber].setAttribute(
|
||||
'data-l10n-id', 'searchingOperator'
|
||||
);
|
||||
}
|
||||
var number = icc.mozIcc.iccInfo.msisdn ||
|
||||
icc.mozIcc.iccInfo.mdn || '';
|
||||
if (number) {
|
||||
UIManager['simNumber' + iccNumber].textContent = number;
|
||||
UIManager['simInfo' + iccNumber].classList.remove('no-number');
|
||||
} else {
|
||||
UIManager['simInfo' + iccNumber].classList.add('no-number');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
showSIMInfoScreen: function sm_showSIMInfoScreen() {
|
||||
this.updateSIMInfoText(this.icc0);
|
||||
this.updateSIMInfoText(this.icc1);
|
||||
UIManager.activationScreen.classList.remove('show');
|
||||
UIManager.simInfoScreen.classList.add('show');
|
||||
},
|
||||
|
||||
hideSIMInfoScreen: function sm_hidescreen() {
|
||||
UIManager.simInfoScreen.classList.remove('show');
|
||||
UIManager.activationScreen.classList.add('show');
|
||||
},
|
||||
|
||||
checkSIMButton: function sm_checkSIMButton() {
|
||||
if (!this.mobConn) {
|
||||
UIManager.simImport.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
var simOption = UIManager.simImportButton;
|
||||
// If there is an unlocked SIM we activate import from SIM
|
||||
if (!SimManager.alreadyImported && SimManager.available()) {
|
||||
simOption.removeAttribute('disabled');
|
||||
UIManager.noSim.classList.add('hidden');
|
||||
} else {
|
||||
simOption.setAttribute('disabled', 'disabled');
|
||||
if (!SimManager.alreadyImported) {
|
||||
UIManager.noSim.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
showPinScreen: function sm_showPinScreen(icc) {
|
||||
showRetryCount(icc, 'pin', UIManager.pinRetriesLeft);
|
||||
// Button management
|
||||
UIManager.unlockSimButton.disabled = true;
|
||||
UIManager.pinInput.addEventListener('input', function sm_checkInput(event) {
|
||||
UIManager.unlockSimButton.disabled = (event.target.value.length < 4);
|
||||
});
|
||||
// Screen management
|
||||
UIManager.activationScreen.classList.remove('show');
|
||||
UIManager.unlockSimScreen.classList.add('show');
|
||||
UIManager.pincodeScreen.classList.add('show');
|
||||
UIManager.xckcodeScreen.classList.remove('show');
|
||||
|
||||
UIManager.unlockSimHeader.setAttribute('data-l10n-id', 'pincode2');
|
||||
if (this.simSlots > 1) {
|
||||
var simNumber = icc === this.icc0 ? 1 : 2;
|
||||
document.l10n.setAttributes(UIManager.pinLabel,
|
||||
'pincodeLabel', {n: simNumber});
|
||||
} else {
|
||||
UIManager.pinLabel.setAttribute('data-l10n-id', 'type_pin');
|
||||
}
|
||||
UIManager.pinInput.focus();
|
||||
},
|
||||
|
||||
showPukScreen: function sm_showPukScreen(icc) {
|
||||
showRetryCount(icc, 'puk', UIManager.pukRetriesLeft);
|
||||
|
||||
UIManager.unlockSimScreen.classList.add('show');
|
||||
UIManager.activationScreen.classList.remove('show');
|
||||
UIManager.pincodeScreen.classList.remove('show');
|
||||
UIManager.pukcodeScreen.classList.add('show');
|
||||
UIManager.xckcodeScreen.classList.remove('show');
|
||||
|
||||
UIManager.unlockSimHeader.setAttribute('data-l10n-id', 'pukcode');
|
||||
if (this.simSlots > 1) {
|
||||
var simNumber = icc === this.icc0 ? 1 : 2;
|
||||
document.l10n.setAttributes(UIManager.pukLabel,
|
||||
'pukcodeLabel', {n: simNumber});
|
||||
} else {
|
||||
UIManager.pukLabel.setAttribute('data-l10n-id', 'type_puk');
|
||||
}
|
||||
UIManager.pukInput.focus();
|
||||
},
|
||||
|
||||
showXckScreen: function sm_showXckScreen(icc) {
|
||||
var lockType;
|
||||
|
||||
switch (icc.mozIcc.cardState) {
|
||||
case 'networkLocked':
|
||||
lockType = 'nck';
|
||||
break;
|
||||
case 'corporateLocked':
|
||||
lockType = 'cck';
|
||||
break;
|
||||
case 'serviceProviderLocked':
|
||||
lockType = 'spck';
|
||||
break;
|
||||
case 'network1Locked':
|
||||
lockType = 'nck1';
|
||||
break;
|
||||
case 'network2Locked':
|
||||
lockType = 'nck2';
|
||||
break;
|
||||
case 'hrpdNetworkLocked':
|
||||
lockType = 'hnck';
|
||||
break;
|
||||
case 'ruimCorporateLocked':
|
||||
lockType = 'rcck';
|
||||
break;
|
||||
case 'ruimServiceProviderLocked':
|
||||
lockType = 'rspck';
|
||||
break;
|
||||
default:
|
||||
return; // We shouldn't be here.
|
||||
}
|
||||
|
||||
showRetryCount(icc, lockType, UIManager.xckRetriesLeft);
|
||||
|
||||
UIManager.unlockSimScreen.classList.add('show');
|
||||
UIManager.activationScreen.classList.remove('show');
|
||||
UIManager.pincodeScreen.classList.remove('show');
|
||||
UIManager.pukcodeScreen.classList.remove('show');
|
||||
UIManager.xckcodeScreen.classList.add('show');
|
||||
|
||||
var simNumber = icc === this.icc0 ? 1 : 2;
|
||||
switch (icc.mozIcc.cardState) {
|
||||
case 'networkLocked':
|
||||
document.l10n.setAttributes(UIManager.unlockSimHeader,
|
||||
'nckcodeTitle', {n: simNumber});
|
||||
document.l10n.setAttributes(UIManager.xckLabel, 'nckcodeLabel',
|
||||
{n: simNumber});
|
||||
break;
|
||||
case 'corporateLocked':
|
||||
document.l10n.setAttributes(UIManager.unlockSimHeader,
|
||||
'cckcodeTitle', {n: simNumber});
|
||||
document.l10n.setAttributes(UIManager.xckLabel, 'cckcodeLabel',
|
||||
{n: simNumber});
|
||||
break;
|
||||
case 'serviceProviderLocked':
|
||||
document.l10n.setAttributes(UIManager.unlockSimHeader,
|
||||
'spckcodeTitle', {n: simNumber});
|
||||
document.l10n.setAttributes(UIManager.xckLabel, 'spckcodeLabel',
|
||||
{n: simNumber});
|
||||
break;
|
||||
case 'network1Locked':
|
||||
document.l10n.setAttributes(UIManager.unlockSimHeader,
|
||||
'nck1codeTitle', {n: simNumber});
|
||||
document.l10n.setAttributes(UIManager.xckLabel, 'nck1codeLabel',
|
||||
{n: simNumber});
|
||||
break;
|
||||
case 'network2Locked':
|
||||
document.l10n.setAttributes(UIManager.unlockSimHeader,
|
||||
'nck2codeTitle', {n: simNumber});
|
||||
document.l10n.setAttributes(UIManager.xckLabel, 'nck2codeLabel',
|
||||
{n: simNumber});
|
||||
break;
|
||||
case 'hrpdNetworkLocked':
|
||||
document.l10n.setAttributes(UIManager.unlockSimHeader,
|
||||
'hnckcodeTitle', {n: simNumber});
|
||||
document.l10n.setAttributes(UIManager.xckLabel, 'hnckcodeLabel',
|
||||
{n: simNumber});
|
||||
break;
|
||||
case 'ruimCorporateLocked':
|
||||
document.l10n.setAttributes(UIManager.unlockSimHeader,
|
||||
'rcckcodeTitle', {n: simNumber});
|
||||
document.l10n.setAttributes(UIManager.xckLabel, 'rcckcodeLabel',
|
||||
{n: simNumber});
|
||||
break;
|
||||
case 'ruimServiceProviderLocked':
|
||||
document.l10n.setAttributes(UIManager.unlockSimHeader,
|
||||
'rspckcodeTitle', {n: simNumber});
|
||||
document.l10n.setAttributes(UIManager.xckLabel, 'rspckcodeLabel',
|
||||
{n: simNumber});
|
||||
break;
|
||||
}
|
||||
UIManager.xckInput.focus();
|
||||
},
|
||||
|
||||
resetForm: function sm_clearInputs() {
|
||||
this.clearFields();
|
||||
UIManager.newpinInput.value = '';
|
||||
UIManager.confirmNewpinInput.value = '';
|
||||
UIManager.pinInput.value = '';
|
||||
UIManager.pukInput.value = '';
|
||||
UIManager.pukInfo.classList.remove('hidden');
|
||||
UIManager.xckInput.value = '';
|
||||
UIManager.unlockSimButton.disabled = false;
|
||||
},
|
||||
|
||||
hideScreen: function sm_hideScreen() {
|
||||
UIManager.unlockSimScreen.classList.remove('show');
|
||||
UIManager.pincodeScreen.classList.remove('show');
|
||||
UIManager.pukcodeScreen.classList.remove('show');
|
||||
UIManager.xckcodeScreen.classList.remove('show');
|
||||
UIManager.activationScreen.classList.add('show');
|
||||
},
|
||||
|
||||
skip: function sm_skip() {
|
||||
if (this._unlockingIcc) {
|
||||
this._unlockingIcc.skipped = true;
|
||||
}
|
||||
this.resetForm();
|
||||
this.hideScreen();
|
||||
this.handleCardState();
|
||||
},
|
||||
|
||||
resetSkipped: function sm_resetSkipped() {
|
||||
if (this.icc0) {
|
||||
this.icc0.skipped = false;
|
||||
}
|
||||
if (this.icc1) {
|
||||
this.icc1.skipped = false;
|
||||
}
|
||||
},
|
||||
|
||||
back: function sm_back() {
|
||||
this.resetForm();
|
||||
this.resetSkipped();
|
||||
this.hideScreen();
|
||||
this.hideSIMInfoScreen();
|
||||
Navigation.back();
|
||||
},
|
||||
|
||||
simUnlockBack: function sm_simUnlockBack() {
|
||||
if (this.icc0 && this.icc0.skipped) {
|
||||
this.icc0.skipped = false;
|
||||
this.hideScreen();
|
||||
this.handleCardState();
|
||||
} else {
|
||||
this.back();
|
||||
}
|
||||
},
|
||||
|
||||
unlock: function sm_unlock() {
|
||||
var icc = this._unlockingIcc;
|
||||
if (!icc) {
|
||||
throw new Error('Cannot unlock SIM, no current ICC');
|
||||
}
|
||||
this.clearFields();
|
||||
|
||||
switch (icc.mozIcc.cardState) {
|
||||
case 'pinRequired':
|
||||
this.unlockPin(icc);
|
||||
break;
|
||||
case 'pukRequired':
|
||||
this.unlockPuk(icc);
|
||||
break;
|
||||
case 'networkLocked':
|
||||
case 'corporateLocked':
|
||||
case 'serviceProviderLocked':
|
||||
case 'network1Locked':
|
||||
case 'network2Locked':
|
||||
case 'hrpdNetworkLocked':
|
||||
case 'ruimCorporateLocked':
|
||||
case 'ruimServiceProviderLocked':
|
||||
this.unlockXck(icc);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
unlockPin: function sm_unlockPin(icc) {
|
||||
var pin = UIManager.pinInput.value;
|
||||
if (pin.length < 4 || pin.length > 8) {
|
||||
UIManager.pinError.
|
||||
querySelector('.main').setAttribute('data-l10n-id', 'pinValidation');
|
||||
UIManager.pinInput.classList.add('onerror');
|
||||
UIManager.pinError.classList.remove('hidden');
|
||||
UIManager.pinInput.focus();
|
||||
return;
|
||||
} else {
|
||||
UIManager.pinInput.classList.remove('onerror');
|
||||
UIManager.pinError.classList.add('hidden');
|
||||
}
|
||||
|
||||
// Unlock SIM
|
||||
this.attemptUnlock(icc, {lockType: 'pin', pin: pin });
|
||||
},
|
||||
|
||||
clearFields: function sm_clearFields() {
|
||||
UIManager.pukInput.classList.remove('onerror');
|
||||
UIManager.pukError.classList.add('hidden');
|
||||
|
||||
UIManager.newpinInput.classList.remove('onerror');
|
||||
UIManager.newpinError.classList.add('hidden');
|
||||
|
||||
UIManager.confirmNewpinInput.classList.remove('onerror');
|
||||
UIManager.confirmNewpinError.classList.add('hidden');
|
||||
|
||||
UIManager.pinError.classList.add('hidden');
|
||||
UIManager.pinError.querySelector('.main').classList.add('hidden');
|
||||
UIManager.pinError.querySelector('.lastchance').classList.add('hidden');
|
||||
UIManager.pinRetriesLeft.classList.add('hidden');
|
||||
|
||||
UIManager.pukError.classList.add('hidden');
|
||||
UIManager.pukError.querySelector('.main').classList.add('hidden');
|
||||
UIManager.pukError.querySelector('.lastchance').classList.add('hidden');
|
||||
UIManager.pukRetriesLeft.classList.add('hidden');
|
||||
|
||||
UIManager.xckError.classList.add('hidden');
|
||||
UIManager.xckError.querySelector('.main').classList.add('hidden');
|
||||
UIManager.xckError.querySelector('.lastchance').classList.add('hidden');
|
||||
UIManager.xckRetriesLeft.classList.add('hidden');
|
||||
},
|
||||
|
||||
unlockPuk: function sm_unlockPuk(icc) {
|
||||
var pukCode = UIManager.pukInput.value;
|
||||
if (pukCode.length !== 8) {
|
||||
UIManager.pukError.querySelector('.main')
|
||||
.setAttribute('data-l10n-id', 'pukValidation');
|
||||
UIManager.pukError.classList.remove('hidden');
|
||||
UIManager.pukInfo.classList.add('hidden');
|
||||
UIManager.pukInput.classList.add('onerror');
|
||||
UIManager.pukError.focus();
|
||||
return;
|
||||
}
|
||||
var newpinCode = UIManager.newpinInput.value;
|
||||
var confirmNewpin = UIManager.confirmNewpinInput.value;
|
||||
if (newpinCode.length < 4 || newpinCode.length > 8) {
|
||||
UIManager.newpinError.setAttribute('data-l10n-id', 'pinValidation');
|
||||
UIManager.newpinError.classList.remove('hidden');
|
||||
UIManager.newpinInput.classList.add('onerror');
|
||||
UIManager.newpinError.focus();
|
||||
return;
|
||||
}
|
||||
if (newpinCode != confirmNewpin) {
|
||||
UIManager.confirmNewpinError.setAttribute('data-l10n-id',
|
||||
'newpinConfirmation');
|
||||
UIManager.confirmNewpinError.classList.remove('hidden');
|
||||
UIManager.newpinInput.classList.add('onerror');
|
||||
UIManager.confirmNewpinInput.classList.add('onerror');
|
||||
UIManager.confirmNewpinError.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// Unlock SIM with PUK and new PIN
|
||||
var options = {lockType: 'puk', puk: pukCode, newPin: newpinCode };
|
||||
this.attemptUnlock(icc, options);
|
||||
},
|
||||
|
||||
unlockXck: function sm_unlockXck(icc) {
|
||||
var xck = UIManager.xckInput.value;
|
||||
var lockType;
|
||||
switch (icc.mozIcc.cardState) {
|
||||
case 'networkLocked':
|
||||
lockType = 'nck';
|
||||
break;
|
||||
case 'corporateLocked':
|
||||
lockType = 'cck';
|
||||
break;
|
||||
case 'serviceProviderLocked':
|
||||
lockType = 'spck';
|
||||
break;
|
||||
case 'network1Locked':
|
||||
lockType = 'nck1';
|
||||
break;
|
||||
case 'network2Locked':
|
||||
lockType = 'nck2';
|
||||
break;
|
||||
case 'hrpdNetworkLocked':
|
||||
lockType = 'hnck';
|
||||
break;
|
||||
case 'ruimCorporateLocked':
|
||||
lockType = 'rcck';
|
||||
break;
|
||||
case 'ruimServiceProviderLocked':
|
||||
lockType = 'rspck';
|
||||
break;
|
||||
}
|
||||
if (xck.length < 8 || xck.length > 16) {
|
||||
UIManager.xckInput.classList.add('onerror');
|
||||
UIManager.xckError.classList.remove('hidden');
|
||||
UIManager.xckError.querySelector('.main').
|
||||
setAttribute('data-l10n-id', lockType + 'Validation');
|
||||
UIManager.xckInput.focus();
|
||||
return;
|
||||
} else {
|
||||
UIManager.pinInput.classList.remove('onerror');
|
||||
UIManager.pinError.classList.add('hidden');
|
||||
}
|
||||
|
||||
// Unlock SIM
|
||||
var options = {lockType: lockType, pin: xck };
|
||||
this.attemptUnlock(icc, options);
|
||||
},
|
||||
|
||||
attemptUnlock: function sm_attemptUnlock(icc, options) {
|
||||
var req = icc.mozIcc.unlockCardLock(options);
|
||||
req.onsuccess = (function sm_unlockSuccess() {
|
||||
icc.unlocked = true;
|
||||
this.resetForm();
|
||||
this.hideScreen();
|
||||
this.handleCardState();
|
||||
}).bind(this);
|
||||
req.onerror = (function sm_unlockError() {
|
||||
this.handleUnlockError(options.lockType, req.error.retryCount);
|
||||
}).bind(this);
|
||||
},
|
||||
|
||||
// Try to infer whats the default SIM
|
||||
guessIcc: function guessIcc() {
|
||||
var tempIcc = null;
|
||||
if (navigator.mozMobileConnections) {
|
||||
// New multi-sim api, use mozMobileConnections to guess
|
||||
// the first inserted sim
|
||||
for (var i = 0;
|
||||
i < navigator.mozMobileConnections.length && tempIcc === null; i++) {
|
||||
if (navigator.mozMobileConnections[i] !== null &&
|
||||
navigator.mozMobileConnections[i].iccId !== null) {
|
||||
tempIcc = navigator.mozIccManager.getIccById(
|
||||
navigator.mozMobileConnections[i].iccId);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tempIcc = navigator.mozIccManager;
|
||||
}
|
||||
|
||||
return tempIcc;
|
||||
},
|
||||
|
||||
importContacts: function sm_importContacts() {
|
||||
// Delay for showing feedback to the user after importing
|
||||
var DELAY_FEEDBACK = 300;
|
||||
UIManager.navBar.setAttribute('aria-disabled', 'true');
|
||||
var progress = utils.overlay.show('simContacts-reading',
|
||||
'activityBar');
|
||||
|
||||
var importButton = UIManager.simImportButton;
|
||||
var cancelled = false,
|
||||
contactsRead = false;
|
||||
var importer = new SimContactsImporter(SimManager.guessIcc());
|
||||
utils.overlay.showMenu();
|
||||
utils.overlay.oncancel = function oncancel() {
|
||||
cancelled = true;
|
||||
importer.finish();
|
||||
if (contactsRead) {
|
||||
// A message about canceling will be displayed while the current chunk
|
||||
// is being cooked
|
||||
progress.setClass('activityBar');
|
||||
utils.overlay.hideMenu();
|
||||
progress.setHeaderMsg('messageCanceling');
|
||||
} else {
|
||||
importer.onfinish(); // Early return while reading contacts
|
||||
}
|
||||
};
|
||||
var importedContacts = 0;
|
||||
|
||||
importer.onread = function sim_import_read(n) {
|
||||
contactsRead = true;
|
||||
if (n > 0) {
|
||||
progress.setClass('progressBar');
|
||||
progress.setHeaderMsg('simContacts-importing');
|
||||
progress.setTotal(n);
|
||||
}
|
||||
};
|
||||
|
||||
importer.onimported = function imported_contact() {
|
||||
importedContacts++;
|
||||
if (!cancelled) {
|
||||
progress.update();
|
||||
}
|
||||
};
|
||||
|
||||
importer.onfinish = function sim_import_finish(numDupsMerged, iccId) {
|
||||
window.setTimeout(function do_sim_import_finish() {
|
||||
UIManager.navBar.removeAttribute('aria-disabled');
|
||||
utils.overlay.hide();
|
||||
if (importedContacts > 0) {
|
||||
utils.misc.setTimestamp('sim-' + iccId);
|
||||
}
|
||||
if (!cancelled) {
|
||||
SimManager.alreadyImported = true;
|
||||
importButton.setAttribute('disabled', 'disabled');
|
||||
utils.status.show({
|
||||
id: 'simContacts-imported3',
|
||||
args: {n: importedContacts}
|
||||
});
|
||||
}
|
||||
}, DELAY_FEEDBACK);
|
||||
|
||||
importer.onfinish = null;
|
||||
};
|
||||
|
||||
importer.onerror = function sim_import_error() {
|
||||
UIManager.navBar.removeAttribute('aria-disabled');
|
||||
utils.overlay.hide();
|
||||
// Just in case the user decides to do so later
|
||||
importButton.removeAttribute('disabled');
|
||||
|
||||
// Showing error message allowing user to retry
|
||||
var cancel = {
|
||||
title: 'cancel',
|
||||
callback: function() {
|
||||
ConfirmDialog.hide();
|
||||
}
|
||||
};
|
||||
var retry = {
|
||||
title: 'retry',
|
||||
isRecommend: true,
|
||||
callback: function() {
|
||||
ConfirmDialog.hide();
|
||||
// And now the action is reproduced one more time
|
||||
importButton.click();
|
||||
}
|
||||
};
|
||||
ConfirmDialog.show(null, 'simContacts-error', cancel, retry);
|
||||
}; // importer.onerror
|
||||
|
||||
importer.start();
|
||||
}
|
||||
};
|
||||
})();
|
||||
35
gaia/apps/ftu/js/time.js
Executable file
35
gaia/apps/ftu/js/time.js
Executable file
|
|
@ -0,0 +1,35 @@
|
|||
/* exported TimeManager */
|
||||
'use strict';
|
||||
|
||||
var TimeManager = {
|
||||
elementIDs: [
|
||||
'tz-region',
|
||||
'tz-city',
|
||||
'date-configuration',
|
||||
'time-configuration'
|
||||
],
|
||||
|
||||
init: function tm_init() {
|
||||
if (window.navigator.mozTime) {
|
||||
this.time = window.navigator.mozTime;
|
||||
} else {
|
||||
console.log('There is no mozTime available in window');
|
||||
}
|
||||
|
||||
this.elementIDs.forEach(function(elementID) {
|
||||
document.getElementById(elementID + '-button').addEventListener('click',
|
||||
function(evt) {
|
||||
evt.preventDefault();
|
||||
var input = document.getElementById(elementID);
|
||||
// Inspired by app/clock/js/form_button.js
|
||||
// It seems like setting focus instantly does not trigger the input/
|
||||
// select dialog.
|
||||
setTimeout(input.focus.bind(input), 10);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
set: function tm_set(date) {
|
||||
this.time.set(date);
|
||||
}
|
||||
};
|
||||
383
gaia/apps/ftu/js/tutorial.js
Executable file
383
gaia/apps/ftu/js/tutorial.js
Executable file
|
|
@ -0,0 +1,383 @@
|
|||
/* global ScreenLayout, Promise,
|
||||
Utils, FinishScreen, LazyLoader, TutorialUtils */
|
||||
/* exported Tutorial */
|
||||
|
||||
(function(exports) {
|
||||
'use strict';
|
||||
|
||||
// default layout
|
||||
var currentLayout = 'tiny';
|
||||
|
||||
// Most used DOM elements
|
||||
var dom = {};
|
||||
|
||||
/**
|
||||
* Manages and controls the configuration, content and state of the tutorial
|
||||
* @module Tutorial
|
||||
*/
|
||||
var elementIDs = [
|
||||
'tutorial',
|
||||
'tutorial-step-title',
|
||||
'tutorial-step-media',
|
||||
'tutorial-step-image',
|
||||
'tutorial-step-video',
|
||||
'forward-tutorial',
|
||||
'back-tutorial'
|
||||
];
|
||||
|
||||
var Tutorial = {
|
||||
// A configuration object.
|
||||
config: null,
|
||||
|
||||
// Config for the current tutorial steps
|
||||
_stepsConfig: {},
|
||||
|
||||
// Which tutorial steps to use
|
||||
_stepsKey: '',
|
||||
|
||||
// Keeps track of the current step
|
||||
_currentStep: 1,
|
||||
|
||||
// Track initialized state of tutorial
|
||||
_initialized: false,
|
||||
|
||||
/**
|
||||
* Initialize the tutorial. This is async as a config file must be loaded.
|
||||
* A Sequence (array of sync or async functions) is used to manage the init
|
||||
* tasks.
|
||||
* When complete, the tutorial is ready to be shown via the 'start' method
|
||||
*
|
||||
* @param {String} stepsKey a key into the tutorial config object, i
|
||||
* i.e. a version or version delta like 1.3..2.0
|
||||
* @param {Function} onLoaded optional callback for when init is complete
|
||||
* @memberof Tutorial
|
||||
*/
|
||||
init: function(stepsKey, onLoaded) {
|
||||
// init is async
|
||||
// need to load config, then load the first step and its assets.
|
||||
if (this._initialized || this._initialization) {
|
||||
// init already underway
|
||||
return;
|
||||
}
|
||||
|
||||
var initTasks = this._initialization = new TutorialUtils.Sequence(
|
||||
// config should load or already be loaded.
|
||||
// failure should abort
|
||||
this.loadConfig.bind(this),
|
||||
this._initWithConfig.bind(this, stepsKey)
|
||||
);
|
||||
initTasks.onabort = this._onAbortInitialization.bind(this);
|
||||
initTasks.oncomplete =
|
||||
this._onCompleteInitialization.bind(this, onLoaded);
|
||||
|
||||
// first time here: cache DOM elements
|
||||
elementIDs.forEach(function(name) {
|
||||
dom[Utils.camelCase(name)] = document.getElementById(name);
|
||||
if (!dom[Utils.camelCase(name)]) {
|
||||
console.error('Cache DOM elements: couldnt cache: ' + name);
|
||||
}
|
||||
}, this);
|
||||
|
||||
initTasks.next();
|
||||
|
||||
// watch a ton of video events as context for the tutorial
|
||||
// media loading/playing
|
||||
var mediaEvents = this._mediaEvents = ['abort',
|
||||
'canplay',
|
||||
'canplaythrough',
|
||||
'durationchange',
|
||||
'emptied',
|
||||
'ended',
|
||||
'error',
|
||||
'interruptbegin',
|
||||
'interruptend',
|
||||
'loadeddata',
|
||||
'loadedmetadata',
|
||||
'loadstart',
|
||||
'mozaudioavailable',
|
||||
'pause',
|
||||
'play',
|
||||
'playing',
|
||||
'stalled',
|
||||
'suspend',
|
||||
'waiting'];
|
||||
|
||||
this._debugEventHandler = {
|
||||
handleEvent: function(evt) {
|
||||
var errCodes = {
|
||||
'1': 'MEDIA_ERR_ABORTED',
|
||||
'2': 'MEDIA_ERR_NETWORK',
|
||||
'3': 'MEDIA_ERR_DECODE',
|
||||
'4': 'MEDIA_ERR_SRC_NOT_SUPPORTED'
|
||||
};
|
||||
console.log('TUTORIAL: media-event: '+ evt.type);
|
||||
if (evt.type == 'error') {
|
||||
var errtype = errCodes[evt.target.error.code] || 'unknown';
|
||||
console.log('TUTORIAL: media-event, ' + errtype + ' error');
|
||||
}
|
||||
}
|
||||
};
|
||||
mediaEvents.forEach(name => {
|
||||
dom.tutorialStepVideo.addEventListener(name, this._debugEventHandler);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Show the tutorial and play the first step
|
||||
* May be called during or after the init process
|
||||
* We defer rasing the 'tutorialinitialized' event until this point
|
||||
* as it signals the tutorial step content is loaded and displayed
|
||||
*
|
||||
* @param {Function} onReady optional callback for when start is complete
|
||||
* @memberof Tutorial
|
||||
*/
|
||||
start: function(onReady) {
|
||||
var sequence;
|
||||
var initInProgress = false;
|
||||
if (this._initialization) {
|
||||
// init still underway, tack steps onto existing sequence
|
||||
sequence = this._initialization;
|
||||
initInProgress = true;
|
||||
} else {
|
||||
// init already complete, create new sequence
|
||||
this._initialization = sequence = new TutorialUtils.Sequence();
|
||||
sequence.onabort = this._onAbortInitialization.bind(this);
|
||||
sequence.oncomplete =
|
||||
this._onCompleteInitialization.bind(this);
|
||||
}
|
||||
sequence.push(function setInitialStep() {
|
||||
// setStep should return promise given by _loadMedia
|
||||
console.log('TUTORIAL: setInitialStep');
|
||||
return this._setStep();
|
||||
}.bind(this));
|
||||
|
||||
sequence.push(function showTutorialAndFinishInit() {
|
||||
// Show the panel
|
||||
dom.tutorial.classList.add('show');
|
||||
// Custom event that can be used to apply (screen reader) visibility
|
||||
// changes.
|
||||
console.log('TUTORIAL: dispatching tutorialinitialized');
|
||||
window.dispatchEvent(new CustomEvent('tutorialinitialized'));
|
||||
});
|
||||
|
||||
if(typeof onReady === 'function') {
|
||||
sequence.push(onReady);
|
||||
}
|
||||
|
||||
if (!initInProgress) {
|
||||
// init done, starting start sequence
|
||||
sequence.next();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Continue initialization once config data is loaded
|
||||
* Called as a part of the init sequence
|
||||
*
|
||||
* @param {String} stepsKey a key into the tutorial config object, i
|
||||
* i.e. a version or version delta like 1.3..2.0
|
||||
* @memberof Tutorial
|
||||
*/
|
||||
_initWithConfig: function(stepsKey) {
|
||||
stepsKey = stepsKey || 'default';
|
||||
this._stepsKey = stepsKey;
|
||||
this._stepsConfig = this.config[stepsKey] || this.config['default'];
|
||||
|
||||
// Add event listeners
|
||||
dom.forwardTutorial.addEventListener('click', this);
|
||||
dom.backTutorial.addEventListener('click', this);
|
||||
|
||||
// Set the first step
|
||||
this._currentStep = 1;
|
||||
},
|
||||
_onAbortInitialization: function() {
|
||||
this._initialization = null;
|
||||
},
|
||||
_onCompleteInitialization: function(onReady) {
|
||||
this._initialization = null;
|
||||
this._initialized = true;
|
||||
if(typeof onReady === 'function') {
|
||||
onReady();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Advance the tutorial to the given step (first step if no value given)
|
||||
*
|
||||
* @param {Number} value number of the step to show (1-based)
|
||||
* @memberof Tutorial
|
||||
*/
|
||||
_setStep: function (value) {
|
||||
// If value is bigger than the max, show finish screen
|
||||
value = (typeof value === 'number') ? value : this._currentStep;
|
||||
var stepIndex = value - 1;
|
||||
if (stepIndex >= this._stepsConfig.steps.length) {
|
||||
return Promise.resolve().then(function() {
|
||||
Tutorial.done();
|
||||
});
|
||||
}
|
||||
|
||||
var stepData = this._stepsConfig.steps[stepIndex];
|
||||
if (!stepData) {
|
||||
return Promise.reject('No data for step: ' + value);
|
||||
}
|
||||
// Set the step
|
||||
dom.tutorial.dataset.step = this._currentStep;
|
||||
|
||||
// Internationalize
|
||||
document.l10n.setAttributes(
|
||||
dom.tutorialStepTitle,
|
||||
stepData.l10nKey
|
||||
);
|
||||
|
||||
// Update the image/video
|
||||
var imgElement = dom.tutorialStepImage,
|
||||
videoElement = dom.tutorialStepVideo;
|
||||
|
||||
var stepPromise;
|
||||
if (stepData.video) {
|
||||
stepPromise = TutorialUtils.getBestAssetForDirection(stepData.video)
|
||||
.then((bestSrc) => {
|
||||
return TutorialUtils.loadAndPlayMedia(videoElement, bestSrc);
|
||||
});
|
||||
videoElement.hidden = false;
|
||||
imgElement.hidden = true;
|
||||
} else {
|
||||
imgElement.hidden = false;
|
||||
stepPromise = TutorialUtils.loadMedia(imgElement, stepData.image);
|
||||
imgElement.hidden = false;
|
||||
videoElement.hidden = true;
|
||||
}
|
||||
return stepPromise;
|
||||
},
|
||||
|
||||
/**
|
||||
* DOM Event handler
|
||||
*
|
||||
* @param {DOMEvent} evt Event object
|
||||
* @memberof Tutorial
|
||||
*/
|
||||
handleEvent: function(evt) {
|
||||
if (evt.type === 'click') {
|
||||
switch(evt.target) {
|
||||
case dom.forwardTutorial:
|
||||
this.next(evt);
|
||||
break;
|
||||
case dom.backTutorial:
|
||||
this.back(evt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Advance to the next step in the tutorial
|
||||
* @memberof Tutorial
|
||||
*/
|
||||
next: function() {
|
||||
return this._setStep(++this._currentStep);
|
||||
},
|
||||
|
||||
/**
|
||||
* Go back to the previous step in the tutorial
|
||||
* @memberof Tutorial
|
||||
*/
|
||||
back: function() {
|
||||
return this._setStep(--this._currentStep);
|
||||
},
|
||||
|
||||
/**
|
||||
* Tutorial complete
|
||||
* @memberof Tutorial
|
||||
*/
|
||||
done: function() {
|
||||
var isUpgrade = this._stepsKey && this._stepsKey !== 'default';
|
||||
FinishScreen.init(isUpgrade);
|
||||
dom.tutorial.classList.remove('show');
|
||||
dom.tutorialStepVideo.removeAttribute('src');
|
||||
dom.tutorialStepImage.removeAttribute('src');
|
||||
},
|
||||
|
||||
/**
|
||||
* Load the config with steps and associated resources for the tutorial
|
||||
* We have different config files for each screen category:
|
||||
* phone is 'tiny', tablet is 'large'
|
||||
* and corresponding tiny.json, large.json
|
||||
*
|
||||
* @returns {Promise}
|
||||
* @memberof Tutorial
|
||||
*/
|
||||
loadConfig: function() {
|
||||
if (!this._configPromise) {
|
||||
// Update the value of the layout if needed
|
||||
// 'ScreenLayout' give us 4 different values
|
||||
// tiny: '(max-width: 767px)',
|
||||
// small: '(min-width: 768px) and (max-width: 991px)',
|
||||
// medium: '(min-width: 992px) and (max-width: 1200px)',
|
||||
// large: '(min-width: 1201px)',
|
||||
//
|
||||
// Currently we are taking into account only 'tiny', and we are
|
||||
// going to consider 'tablet' as 'large'. If we want to add more
|
||||
// or specific features for 'small' & 'medium', we should add more
|
||||
// logic here.
|
||||
|
||||
currentLayout = ScreenLayout.getCurrentLayout() === 'tiny' ?
|
||||
'tiny' : 'large';
|
||||
|
||||
var configUrl = '/config/' + currentLayout + '.json';
|
||||
|
||||
this._configPromise = LazyLoader.getJSON(configUrl)
|
||||
.then(function(json) {
|
||||
Tutorial.config = json;
|
||||
return Tutorial.config;
|
||||
}, function() {
|
||||
return new Error('Tutorial config failed to load from: ' + configUrl);
|
||||
});
|
||||
}
|
||||
return this._configPromise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Test helper to reset the tutorial to its pre-initialized state
|
||||
* to allow init to be called again
|
||||
* @memberof Tutorial
|
||||
*/
|
||||
reset: function() {
|
||||
var resetPromise = Promise.resolve();
|
||||
if (dom.tutorialStepVideo) {
|
||||
this._mediaEvents.forEach(name => {
|
||||
dom.tutorialStepVideo.removeEventListener(
|
||||
name,
|
||||
this._debugEventHandler
|
||||
);
|
||||
});
|
||||
dom.tutorialStepVideo.hidden = true;
|
||||
if (dom.tutorialStepVideo.src) {
|
||||
resetPromise = new Promise((resolve, reject) => {
|
||||
dom.tutorialStepVideo.addEventListener('emptied', () => {
|
||||
resolve();
|
||||
});
|
||||
dom.tutorialStepVideo.removeAttribute('src');
|
||||
dom.tutorialStepVideo.load();
|
||||
});
|
||||
}
|
||||
}
|
||||
if (this._initialization) {
|
||||
this._initialization.abort();
|
||||
this._initialization = null;
|
||||
}
|
||||
this._configPromise = null;
|
||||
this._currentStep = 1;
|
||||
this._stepsConfig = this.config = null;
|
||||
if (this._initialized) {
|
||||
dom.tutorial.classList.remove('show');
|
||||
this._initialized = false;
|
||||
}
|
||||
document.getElementById('tutorial').classList.remove('show');
|
||||
return resetPromise;
|
||||
}
|
||||
};
|
||||
|
||||
exports.Tutorial = Tutorial;
|
||||
|
||||
})(this);
|
||||
179
gaia/apps/ftu/js/tutorial_utils.js
Executable file
179
gaia/apps/ftu/js/tutorial_utils.js
Executable file
|
|
@ -0,0 +1,179 @@
|
|||
(function(exports) {
|
||||
'use strict';
|
||||
|
||||
var TutorialUtils = {
|
||||
/**
|
||||
* Helper function to load images and video
|
||||
* @param {DOMNode} mediaElement video or image to assign new src to
|
||||
* @param {String} src URL for video/image resource
|
||||
* @returns {Promise}
|
||||
*/
|
||||
loadMedia: function (mediaElement, src) {
|
||||
console.log('loadMedia, got mediaElement: ', mediaElement);
|
||||
var isVideo = (mediaElement.nodeName === 'VIDEO');
|
||||
console.log('TutorialUtils.loadMedia, isVideo: ',
|
||||
isVideo, mediaElement.nodeName);
|
||||
return new Promise(function(resolve, reject) {
|
||||
function onMediaLoadOrError(evt) {
|
||||
console.log('TutorialUtils.loadMedia, onMediaLoadOrError for ' +
|
||||
'evt.type: ' + (evt && evt.type));
|
||||
evt.target.removeEventListener('error', onMediaLoadOrError);
|
||||
if (isVideo) {
|
||||
evt.target.removeEventListener('canplay', onMediaLoadOrError);
|
||||
evt.target.removeEventListener('abort', onMediaLoadOrError);
|
||||
} else {
|
||||
evt.target.removeEventListener('load', onMediaLoadOrError);
|
||||
}
|
||||
// Dont block progress on failure to load media
|
||||
if (evt.type === 'error') {
|
||||
console.error('Failed to load tutorial media: ' + src);
|
||||
} else if (evt.type === 'abort') {
|
||||
console.error('Loading of tutorial media aborted: ' + src);
|
||||
}
|
||||
resolve(evt);
|
||||
}
|
||||
function onVideoUnloaded(evt) {
|
||||
console.log('TutorialUtils.loadMedia, onVideoUnloaded for evt.type: ' +
|
||||
(evt && evt.type));
|
||||
mediaElement.removeEventListener('emptied', onVideoUnloaded);
|
||||
mediaElement.addEventListener('canplay', onMediaLoadOrError);
|
||||
mediaElement.addEventListener('abort', onMediaLoadOrError);
|
||||
mediaElement.addEventListener('error', onMediaLoadOrError);
|
||||
console.log('TutorialUtils.loadMedia, onVideoUnloaded, ' +
|
||||
'assigning src: ' + src);
|
||||
mediaElement.src = src;
|
||||
mediaElement.load();
|
||||
}
|
||||
if (isVideo) {
|
||||
// must unload video and force load before switching to new source
|
||||
if (mediaElement.src) {
|
||||
console.log('TutorialUtils.loadMedia, removing src attribute');
|
||||
mediaElement.removeAttribute('src');
|
||||
mediaElement.addEventListener('emptied', onVideoUnloaded, false);
|
||||
console.log('TutorialUtils.loadMedia, calling load');
|
||||
mediaElement.load();
|
||||
} else {
|
||||
console.log('TutorialUtils.loadMedia, no src, ' +
|
||||
'just calling onVideoUnloaded');
|
||||
onVideoUnloaded();
|
||||
}
|
||||
} else {
|
||||
console.log('TutorialUtils.loadMedia, not a video, ' +
|
||||
'listen for load/error only');
|
||||
mediaElement.addEventListener('load', onMediaLoadOrError, false);
|
||||
mediaElement.addEventListener('error', onMediaLoadOrError, false);
|
||||
mediaElement.src = src;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Convenience to load and then play video
|
||||
* @param {DOMNode} mediaElement video or image to assign new src to
|
||||
* @param {String} src URL for video/image resource
|
||||
* @returns {Promise}
|
||||
*/
|
||||
loadAndPlayMedia: function(mediaElement, src) {
|
||||
return this.loadMedia(mediaElement, src).then((evt) => {
|
||||
if (evt.type !== 'error' && mediaElement.nodeName === 'VIDEO') {
|
||||
mediaElement.play();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper to check (via HEAD request) a given resource exists
|
||||
* The returned promise resolves with a true|false value
|
||||
*
|
||||
* @param {String} src URL for video/image resource
|
||||
* @returns {Promise}
|
||||
*/
|
||||
fileExists: function (src) {
|
||||
return new Promise((resolve) => {
|
||||
window.fetch(src, { method: 'HEAD' }).then((response) => {
|
||||
resolve(response.ok);
|
||||
}).catch(() => {
|
||||
resolve(false);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper to get best-match resource url for the current document.dir
|
||||
* @param {String} src URL for video/image resource
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getBestAssetForDirection: function(src) {
|
||||
var dir = document.dir || 'ltr';
|
||||
var re =
|
||||
/(-rtl|ltr)?\.(png|gif|jpg|webm|mp4|m4v|ogg|ogv)$/;
|
||||
var directionSrc = src.replace(re, '-'+dir+'.$2');
|
||||
return this.fileExists(directionSrc).then((result) => {
|
||||
return result ? directionSrc : src;
|
||||
}).catch((e) => {
|
||||
console.log('Exception from getBestAssetForDirection: ', e);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Private helper class to manage a series of sync or async functions
|
||||
*
|
||||
* The array may be manipulated using standard array methods while the
|
||||
* sequence runs. The sequence completes when there are no more functions or
|
||||
* an exception is raised.
|
||||
* At the end of the sequence, any 'oncomplete' assigned will be called with
|
||||
* the return value from the last function
|
||||
* Functions may return a 'thenable' to indicate async return
|
||||
* Exceptions will be passed into the oncomplete function
|
||||
* A Sequence may be cleanly aborted by calling abort() - no callbacks will
|
||||
* be fired
|
||||
* @class Sequence
|
||||
*/
|
||||
Sequence: function() {
|
||||
var sequence = Array.slice(arguments);
|
||||
var aborted = false;
|
||||
sequence.abort = function() {
|
||||
aborted = true;
|
||||
this.length = 0;
|
||||
if (typeof this.onabort === 'function') {
|
||||
this.onabort();
|
||||
}
|
||||
};
|
||||
sequence.complete = function(result) {
|
||||
if(!aborted && typeof this.oncomplete === 'function') {
|
||||
this.oncomplete(result);
|
||||
}
|
||||
};
|
||||
sequence.fail = function(reason) {
|
||||
this.complete(reason);
|
||||
};
|
||||
sequence.next = function(previousTaskResult) {
|
||||
var result, exception;
|
||||
if (aborted) {
|
||||
return;
|
||||
}
|
||||
var task = this.shift();
|
||||
if (task) {
|
||||
try {
|
||||
result = task.apply(null, arguments);
|
||||
} catch(e) {
|
||||
exception = e;
|
||||
}
|
||||
if (exception) {
|
||||
this.fail(exception);
|
||||
} else if (result && typeof result.then === 'function') {
|
||||
result.then(this.next.bind(this), this.fail.bind(this));
|
||||
} else {
|
||||
this.next(result);
|
||||
}
|
||||
} else {
|
||||
this.complete(previousTaskResult);
|
||||
}
|
||||
};
|
||||
return sequence;
|
||||
}
|
||||
};
|
||||
|
||||
exports.TutorialUtils = TutorialUtils;
|
||||
|
||||
})(window);
|
||||
650
gaia/apps/ftu/js/ui.js
Executable file
650
gaia/apps/ftu/js/ui.js
Executable file
|
|
@ -0,0 +1,650 @@
|
|||
/* global utils, tzSelect, FinishScreen,
|
||||
Basket, ConfirmDialog, ScreenLayout,
|
||||
DataMobile, SimManager, SdManager,
|
||||
Tutorial, TimeManager, WifiManager,
|
||||
WifiUI, WifiHelper, FxAccountsIACHelper, SettingsListener */
|
||||
/* exported UIManager */
|
||||
'use strict';
|
||||
|
||||
var UIManager = {
|
||||
// As in other Gaia apps, we store all the dom selectors in one
|
||||
// place and then camelCase them and attach to the main object,
|
||||
// eg. instead of calling document.getElementById('splash-screen')
|
||||
// we can access this.splashScreen in our code.
|
||||
domSelectors: [
|
||||
'container',
|
||||
'splash-screen',
|
||||
'activation-screen',
|
||||
'finish-screen',
|
||||
'update-screen',
|
||||
'nav-bar',
|
||||
'main-title',
|
||||
// Unlock SIM Screen
|
||||
'unlock-sim-screen',
|
||||
'unlock-sim-header',
|
||||
'unlock-sim-action',
|
||||
// PIN Screen
|
||||
'pincode-screen',
|
||||
'pin-label',
|
||||
'pin-retries-left',
|
||||
'pin-input',
|
||||
'back-sim-button',
|
||||
'pin-error',
|
||||
'skip-pin-button',
|
||||
'unlock-sim-button',
|
||||
// PUK Screen
|
||||
'pukcode-screen',
|
||||
'puk-label',
|
||||
'puk-retries-left',
|
||||
'puk-input',
|
||||
'puk-info',
|
||||
'puk-error',
|
||||
'newpin-input',
|
||||
'newpin-error',
|
||||
'confirm-newpin-input',
|
||||
'confirm-newpin-error',
|
||||
// XCK Screen
|
||||
'xckcode-screen',
|
||||
'xck-label',
|
||||
'xck-retries-left',
|
||||
'xck-input',
|
||||
'xck-error',
|
||||
// SIM info
|
||||
'sim-info-screen',
|
||||
'sim-info-back',
|
||||
'sim-info-forward',
|
||||
'sim-info-1',
|
||||
'sim-info-2',
|
||||
'sim-number-1',
|
||||
'sim-number-2',
|
||||
'sim-carrier-1',
|
||||
'sim-carrier-2',
|
||||
// Import contacts
|
||||
'sim-import',
|
||||
'sim-import-button',
|
||||
'no-sim',
|
||||
'sd-import-button',
|
||||
'no-memorycard',
|
||||
// Fxa Intro
|
||||
'fxa-create-account',
|
||||
'fxa-intro',
|
||||
// Wifi
|
||||
'networks',
|
||||
'wifi-refresh-button',
|
||||
'wifi-join-button',
|
||||
'join-hidden-button',
|
||||
// Hidden Wifi
|
||||
'hidden-wifi-authentication',
|
||||
'hidden-wifi-ssid',
|
||||
'hidden-wifi-security',
|
||||
'hidden-wifi-password',
|
||||
'hidden-wifi-password-box',
|
||||
'hidden-wifi-identity',
|
||||
'hidden-wifi-identity-box',
|
||||
'hidden-wifi-show-password',
|
||||
//Date & Time
|
||||
'date-configuration',
|
||||
'time-configuration',
|
||||
'date-configuration-label',
|
||||
'time-configuration-label',
|
||||
'time-form',
|
||||
// 3G
|
||||
'data-connection-switch',
|
||||
// Geolocation
|
||||
'geolocation-switch',
|
||||
// Tutorial
|
||||
'lets-go-button',
|
||||
'update-lets-go-button',
|
||||
'skip-tutorial-button',
|
||||
'update-skip-tutorial-button',
|
||||
// Privacy Settings
|
||||
'metrics-basic',
|
||||
'metrics-enhanced',
|
||||
'metrics-none',
|
||||
'offline-error-dialog',
|
||||
// Browser privacy newsletter subscription
|
||||
'newsletter-form',
|
||||
'newsletter-input',
|
||||
'newsletter-success-screen',
|
||||
'offline-newsletter-error-dialog',
|
||||
'invalid-email-error-dialog'
|
||||
],
|
||||
|
||||
dataConnectionChangedByUsr: false,
|
||||
timeZoneNeedsConfirmation: true,
|
||||
DARK_THEME: '#242d33',
|
||||
LIGHT_THEME: '#eeeeee',
|
||||
|
||||
init: function ui_init() {
|
||||
// Initialization of the DOM selectors
|
||||
this.domSelectors.forEach(function createElementRef(name) {
|
||||
this[toCamelCase(name)] = document.getElementById(name);
|
||||
}.bind(this));
|
||||
|
||||
// Setup settings observers
|
||||
this._settingsObserveHandler = {
|
||||
'geolocation.enabled': {
|
||||
// the "checked" attribute in the DOM is currently the source of truth
|
||||
// for default value, when the setting is not initially defined
|
||||
defaultValue: this.geolocationSwitch.checked,
|
||||
callback: function(value) {
|
||||
var isEnabled = !!value;
|
||||
if (this.geolocationSwitch.checked !== isEnabled) {
|
||||
this.geolocationSwitch.checked = isEnabled;
|
||||
}
|
||||
}.bind(this)
|
||||
}
|
||||
};
|
||||
|
||||
for (var name in this._settingsObserveHandler) {
|
||||
SettingsListener.observe(
|
||||
name,
|
||||
this._settingsObserveHandler[name].defaultValue,
|
||||
this._settingsObserveHandler[name].callback
|
||||
);
|
||||
}
|
||||
|
||||
var currentDate = new Date();
|
||||
this.timeConfigurationLabel.textContent = currentDate.toLocaleString(
|
||||
navigator.languages,
|
||||
{
|
||||
hour12: navigator.mozHour12,
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
second: 'numeric'
|
||||
}
|
||||
);
|
||||
this.dateConfigurationLabel.textContent = currentDate.toLocaleString(
|
||||
navigator.languages,
|
||||
{
|
||||
year: 'numeric',
|
||||
month: 'numeric',
|
||||
day: 'numeric'
|
||||
}
|
||||
);
|
||||
|
||||
// Add events to DOM
|
||||
this.simImportButton.addEventListener('click', this);
|
||||
this.sdImportButton.addEventListener('click', this);
|
||||
this.skipPinButton.addEventListener('click', this);
|
||||
this.backSimButton.addEventListener('click', this);
|
||||
this.unlockSimButton.addEventListener('click', this);
|
||||
this.unlockSimAction.addEventListener('action', this);
|
||||
this.simInfoBack.addEventListener('click', this);
|
||||
this.simInfoForward.addEventListener('click', this);
|
||||
|
||||
this.dataConnectionSwitch.addEventListener('change', this);
|
||||
|
||||
this.wifiRefreshButton.addEventListener('click', this);
|
||||
this.wifiJoinButton.addEventListener('click', this);
|
||||
this.networks.addEventListener('click', this);
|
||||
|
||||
this.joinHiddenButton.addEventListener('click', this);
|
||||
this.hiddenWifiSecurity.addEventListener('change', this);
|
||||
this.wifiJoinButton.disabled = true;
|
||||
|
||||
var checkHiddenWifiJoin = function() {
|
||||
this.wifiJoinButton.disabled = this.hiddenWifiSsid.value === '' ||
|
||||
!WifiHelper.isValidInput(this.hiddenWifiSecurity.value,
|
||||
this.hiddenWifiPassword.value,
|
||||
this.hiddenWifiIdentity.value
|
||||
);
|
||||
}.bind(this);
|
||||
|
||||
this.hiddenWifiSsid.addEventListener('keyup', checkHiddenWifiJoin);
|
||||
this.hiddenWifiIdentity.addEventListener('keyup', checkHiddenWifiJoin);
|
||||
this.hiddenWifiPassword.addEventListener('keyup', checkHiddenWifiJoin);
|
||||
this.hiddenWifiSecurity.addEventListener('change', checkHiddenWifiJoin);
|
||||
|
||||
this.hiddenWifiShowPassword.onchange = function togglePasswordVisibility() {
|
||||
UIManager.hiddenWifiPassword.type = this.checked ? 'text' : 'password';
|
||||
};
|
||||
|
||||
this.timeConfiguration.addEventListener('input', this);
|
||||
this.dateConfiguration.addEventListener('input', this);
|
||||
|
||||
this.geolocationSwitch.addEventListener('change', this);
|
||||
|
||||
this.fxaCreateAccount.addEventListener('click', this);
|
||||
|
||||
// Prevent form submit in case something tries to send it
|
||||
this.timeForm.addEventListener('submit', function(event) {
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
// Input scroll workaround
|
||||
this.newsletterInput.addEventListener('focus', function() {
|
||||
window.addEventListener('resize', function resize() {
|
||||
window.removeEventListener('resize', resize);
|
||||
// Need to wait till resize is done
|
||||
setTimeout(function() {
|
||||
var page = document.getElementById('browser_privacy');
|
||||
UIManager.scrollToElement(page, UIManager.newsletterInput);
|
||||
}, 30);
|
||||
});
|
||||
});
|
||||
|
||||
this.offlineNewsletterErrorDialog
|
||||
.querySelector('button')
|
||||
.addEventListener('click',
|
||||
function offlineDialogClick() {
|
||||
this.offlineNewsletterErrorDialog.classList.remove('visible');
|
||||
}.bind(this));
|
||||
|
||||
this.invalidEmailErrorDialog
|
||||
.querySelector('button')
|
||||
.addEventListener('click',
|
||||
function invalidEmailDialogClick() {
|
||||
this.invalidEmailErrorDialog.classList.remove('visible');
|
||||
}.bind(this));
|
||||
|
||||
var skipTutorialAction = function() {
|
||||
// Stop Wifi Manager
|
||||
WifiManager.finish();
|
||||
// For tiny devices
|
||||
if (ScreenLayout.getCurrentLayout() === 'tiny') {
|
||||
window.close();
|
||||
} else {
|
||||
// for large devices
|
||||
FinishScreen.init();
|
||||
}
|
||||
};
|
||||
|
||||
var startTutorialAction = function(evt) {
|
||||
// Stop Wifi Manager
|
||||
WifiManager.finish();
|
||||
|
||||
// Play the tutorial steps as soon as config is done loading
|
||||
Tutorial.start(function onTutorialLoaded() {
|
||||
UIManager.activationScreen.classList.remove('show');
|
||||
UIManager.updateScreen.classList.remove('show');
|
||||
UIManager.finishScreen.classList.remove('show');
|
||||
});
|
||||
};
|
||||
|
||||
this.skipTutorialButton.addEventListener('click', skipTutorialAction);
|
||||
this.updateSkipTutorialButton.addEventListener('click', skipTutorialAction);
|
||||
|
||||
this.letsGoButton.addEventListener('click', startTutorialAction);
|
||||
this.updateLetsGoButton.addEventListener('click', startTutorialAction);
|
||||
|
||||
// Enable saving metrics level choice to settings.
|
||||
this.metricsBasic.addEventListener('change', this);
|
||||
this.metricsEnhanced.addEventListener('change', this);
|
||||
this.metricsNone.addEventListener('change', this);
|
||||
var button = this.offlineErrorDialog.querySelector('button');
|
||||
button.addEventListener('click',
|
||||
this.onOfflineDialogButtonClick.bind(this));
|
||||
|
||||
// Handle activation screen visibility.
|
||||
['confirmdialogshowing',
|
||||
'loadingoverlayshowing',
|
||||
'tutorialinitialized'].forEach(function(event) {
|
||||
window.addEventListener(event,
|
||||
this.hideActivationScreenFromScreenReader.bind(this));
|
||||
}, this);
|
||||
|
||||
['confirmdialoghiding',
|
||||
'loadingoverlayhiding'].forEach(function(event) {
|
||||
window.addEventListener(event,
|
||||
this.showActivationScreenToScreenReader.bind(this));
|
||||
}, this);
|
||||
|
||||
this.checkInitialFxAStatus();
|
||||
},
|
||||
|
||||
scrollToElement: function ui_scrollToElement(container, element) {
|
||||
container.scrollTop = element.offsetTop;
|
||||
},
|
||||
|
||||
sendNewsletter: function ui_sendNewsletter(callback) {
|
||||
var self = this;
|
||||
var emailValue = self.newsletterInput.value;
|
||||
if (emailValue === '') {
|
||||
return callback(true);
|
||||
} else {
|
||||
utils.overlay.show('email-loading', 'spinner');
|
||||
if (self.newsletterInput.checkValidity()) {
|
||||
if (window.navigator.onLine) {
|
||||
Basket.send(emailValue, function emailSent(err, data) {
|
||||
if (err) {
|
||||
if (err.code && err.code === Basket.errors.INVALID_EMAIL) {
|
||||
ConfirmDialog.show('invalid-email-dialog-title',
|
||||
'invalid-email-dialog-text',
|
||||
{
|
||||
title: 'cancel',
|
||||
callback: function ok() {
|
||||
ConfirmDialog.hide();
|
||||
}
|
||||
});
|
||||
utils.overlay.hide();
|
||||
return callback(false);
|
||||
} else {
|
||||
Basket.store(emailValue);
|
||||
}
|
||||
}
|
||||
utils.overlay.hide();
|
||||
return callback(true);
|
||||
});
|
||||
} else {
|
||||
Basket.store(emailValue, function stored(errorStoring) {
|
||||
utils.overlay.hide();
|
||||
return callback(!errorStoring);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
utils.overlay.hide();
|
||||
ConfirmDialog.show('invalid-email-dialog-title',
|
||||
'invalid-email-dialog-text',
|
||||
{
|
||||
title: 'cancel',
|
||||
callback: function ok() {
|
||||
ConfirmDialog.hide();
|
||||
}
|
||||
});
|
||||
return callback(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
initTZ: function ui_initTZ() {
|
||||
// Initialize the timezone selector, see /shared/js/tz_select.js
|
||||
var self = this;
|
||||
var tzRegion = document.getElementById('tz-region');
|
||||
var tzCity = document.getElementById('tz-city');
|
||||
return new Promise(function(resolve) {
|
||||
var onchange = self.setTimeZone.bind(self);
|
||||
var onload = function() {
|
||||
self.setTimeZone.apply(self, arguments);
|
||||
resolve();
|
||||
};
|
||||
tzSelect(tzRegion, tzCity, onchange, onload);
|
||||
});
|
||||
},
|
||||
|
||||
handleEvent: function ui_handleEvent(event) {
|
||||
switch (event.target.id) {
|
||||
// SIM
|
||||
case 'skip-pin-button':
|
||||
SimManager.skip();
|
||||
break;
|
||||
case 'back-sim-button':
|
||||
case 'sim-info-back':
|
||||
SimManager.back();
|
||||
break;
|
||||
case 'unlock-sim-button':
|
||||
SimManager.unlock();
|
||||
break;
|
||||
case 'unlock-sim-action':
|
||||
SimManager.simUnlockBack();
|
||||
break;
|
||||
case 'sim-info-forward':
|
||||
SimManager.finish();
|
||||
break;
|
||||
case 'sim-import-button':
|
||||
// Needed to give the browser the opportunity to properly refresh the UI
|
||||
// Particularly the button toggling cycle (from inactive to active)
|
||||
window.setTimeout(SimManager.importContacts, 0);
|
||||
break;
|
||||
case 'sd-import-button':
|
||||
// Needed to give the browser the opportunity to properly refresh the UI
|
||||
// Particularly the button toggling cycle (from inactive to active)
|
||||
window.setTimeout(SdManager.importContacts, 0);
|
||||
break;
|
||||
// 3G
|
||||
case 'data-connection-switch':
|
||||
this.dataConnectionChangedByUsr = true;
|
||||
var status = event.target.checked;
|
||||
DataMobile.toggle(status);
|
||||
break;
|
||||
// WIFI
|
||||
case 'wifi-refresh-button':
|
||||
WifiManager.scan(WifiUI.renderNetworks);
|
||||
break;
|
||||
case 'wifi-join-button':
|
||||
if (window.location.hash === '#hidden-wifi-authentication') {
|
||||
WifiUI.joinHiddenNetwork();
|
||||
} else {
|
||||
WifiUI.joinNetwork();
|
||||
}
|
||||
break;
|
||||
case 'join-hidden-button':
|
||||
WifiUI.addHiddenNetwork();
|
||||
break;
|
||||
case 'hidden-wifi-security':
|
||||
// Assuming that [0] is None, we prefer '' for collision on translations
|
||||
var securityType = event.target.selectedIndex ? event.target.value : '';
|
||||
WifiUI.handleHiddenWifiSecurity(securityType);
|
||||
break;
|
||||
// Date & Time
|
||||
case 'time-configuration':
|
||||
this.setTime();
|
||||
break;
|
||||
case 'date-configuration':
|
||||
this.setDate();
|
||||
break;
|
||||
// Geolocation
|
||||
case 'geolocation-switch':
|
||||
this.updateSetting(event.target.name, event.target.checked);
|
||||
break;
|
||||
// Privacy
|
||||
case 'metrics-enhanced':
|
||||
this.updateSetting('hud.hide', (event.target.value === 'Enhanced'));
|
||||
this.updateSetting('devtools.overlay',
|
||||
(event.target.value === 'Enhanced'));
|
||||
/* falls through */
|
||||
case 'metrics-basic':
|
||||
case 'metrics-none':
|
||||
this.updateSetting('metrics.selectedMetrics.level', event.target.value);
|
||||
break;
|
||||
case 'fxa-create-account':
|
||||
this.createFirefoxAccount();
|
||||
break;
|
||||
default:
|
||||
// wifi selection
|
||||
if (event.target.parentNode.id === 'networks-list') {
|
||||
WifiUI.chooseNetwork(event);
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
updateSetting: function ui_updateSetting(name, value) {
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
var cset = {}; cset[name] = value;
|
||||
return SettingsListener.getSettingsLock().set(cset);
|
||||
},
|
||||
|
||||
setForwardButtonLabel: function ui_setForwardButtonLabel(label) {
|
||||
var nextButton = document.getElementById('forward');
|
||||
nextButton.setAttribute('data-l10n-id', label);
|
||||
},
|
||||
|
||||
checkInitialFxAStatus: function ui_checkInitialFxAStatus() {
|
||||
// It is possible that we enter the FTU after the user aborted a FTU
|
||||
// session where she logged into her FxA. In that case, we show the
|
||||
// information of her account if possible. If there is any reason why we
|
||||
// can't get the account information, we try to log her out.
|
||||
// It is quite unlikely that logging out fails, but in that case, we simply
|
||||
// hide the FxA panel to avoid potential errors such as the one reported on
|
||||
// bug 1113551. In any case, the user should be able to manage her account
|
||||
// from the Settings app afterwards.
|
||||
this.skipFxA = true;
|
||||
FxAccountsIACHelper.getAccount((account) => {
|
||||
this.skipFxA = false;
|
||||
this.onFxALogin(account);
|
||||
}, () => {
|
||||
FxAccountsIACHelper.logout(() => {
|
||||
this.skipFxA = false;
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
createFirefoxAccount: function ui_createFirefoxAccount() {
|
||||
FxAccountsIACHelper.openFlow(UIManager.onFxAFlowDone,
|
||||
UIManager.onFxAError);
|
||||
},
|
||||
|
||||
onFxAFlowDone: function ui_onFxAFlowDone() {
|
||||
FxAccountsIACHelper.getAccount((account) => {
|
||||
if (!account) {
|
||||
return;
|
||||
}
|
||||
UIManager.onFxALogin(account);
|
||||
UIManager.setForwardButtonLabel('navbar-next');
|
||||
}, UIManager.onFxAError);
|
||||
},
|
||||
|
||||
onFxALogin: function ui_onFxALogin(account) {
|
||||
if (!account) {
|
||||
return;
|
||||
}
|
||||
// Update the email
|
||||
UIManager.newsletterInput.value = account.email;
|
||||
// Update the string
|
||||
UIManager.fxaIntro.innerHTML = '';
|
||||
document.l10n.setAttributes(
|
||||
UIManager.fxaIntro,
|
||||
account.verified ? 'fxa-signed-in' : 'fxa-email-sent',
|
||||
{
|
||||
email: account.email
|
||||
}
|
||||
);
|
||||
// Disable the button
|
||||
UIManager.fxaCreateAccount.disabled = true;
|
||||
},
|
||||
|
||||
onFxAError: function ui_onFxAError(response) {
|
||||
console.error('Create FxA Error: ' + JSON.stringify(response));
|
||||
// Clean fields
|
||||
UIManager.newsletterInput.value = '';
|
||||
// Reset the field
|
||||
document.l10n.setAttributes(
|
||||
UIManager.fxaIntro,
|
||||
'fxa-upsell'
|
||||
);
|
||||
// Enable the button
|
||||
UIManager.fxaCreateAccount.disabled = false;
|
||||
// Change the forward button label
|
||||
UIManager.setForwardButtonLabel('skip');
|
||||
},
|
||||
|
||||
displayOfflineDialog: function ui_displayOfflineDialog(href, title) {
|
||||
var dialog = this.offlineErrorDialog;
|
||||
document.l10n.setAttributes(dialog.querySelector('small'),
|
||||
'offline-dialog-text', { url: href });
|
||||
dialog.classList.add('visible');
|
||||
this.hideActivationScreenFromScreenReader();
|
||||
},
|
||||
|
||||
onOfflineDialogButtonClick: function ui_onOfflineDialogButtonClick(e) {
|
||||
this.offlineErrorDialog.classList.remove('visible');
|
||||
this.showActivationScreenToScreenReader();
|
||||
},
|
||||
|
||||
hideActivationScreenFromScreenReader:
|
||||
function ui_hideActivationScreenFromScreenReader() {
|
||||
this.activationScreen.setAttribute('aria-hidden', true);
|
||||
},
|
||||
|
||||
showActivationScreenToScreenReader:
|
||||
function ui_showActivationScreenToScreenReader() {
|
||||
this.activationScreen.setAttribute('aria-hidden', false);
|
||||
},
|
||||
|
||||
setDate: function ui_sd() {
|
||||
if (!!this.lock) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Current time
|
||||
var now = new Date();
|
||||
// Format: 2012-09-01
|
||||
var currentDate = this.dateConfiguration.value;
|
||||
var currentTime = now.toLocaleFormat('%H:%M');
|
||||
var timeToSet = new Date(currentDate + 'T' + currentTime);
|
||||
TimeManager.set(timeToSet);
|
||||
this.dateConfigurationLabel.textContent = timeToSet.toLocaleString(
|
||||
navigator.languages,
|
||||
{
|
||||
year: 'numeric',
|
||||
month: 'numeric',
|
||||
day: 'numeric'
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
setTime: function ui_st() {
|
||||
if (!!this.lock) {
|
||||
return;
|
||||
}
|
||||
var timeLabel = document.getElementById('time-configuration-label');
|
||||
// Current time
|
||||
var now = new Date();
|
||||
// Format: 2012-09-01
|
||||
var currentTime = document.getElementById('time-configuration').value;
|
||||
if (currentTime.indexOf(':') === 1) { // Format: 8:05 --> 08:05
|
||||
currentTime = '0' + currentTime;
|
||||
}
|
||||
var currentDate = now.toLocaleFormat('%Y-%m-%d');
|
||||
var timeToSet = new Date(currentDate + 'T' + currentTime);
|
||||
// Set date through API
|
||||
TimeManager.set(timeToSet);
|
||||
// Set DATE properly
|
||||
timeLabel.textContent = timeToSet.toLocaleString(
|
||||
navigator.languages,
|
||||
{
|
||||
hour12: navigator.mozHour12,
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
second: 'numeric'
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
setTimeZone: function ui_stz(timezone, needsConfirmation) {
|
||||
this.timeZoneNeedsConfirmation = !!needsConfirmation;
|
||||
|
||||
var utcOffset = timezone.utcOffset;
|
||||
document.getElementById('time_zone_overlay').className =
|
||||
'UTC' + utcOffset.replace(/[+:]/g, '');
|
||||
var timezoneTitle = document.getElementById('time-zone-title');
|
||||
document.l10n.setAttributes(timezoneTitle, 'timezoneTitle', {
|
||||
utcOffset: utcOffset,
|
||||
region: timezone.region,
|
||||
city: timezone.city
|
||||
});
|
||||
document.getElementById('tz-region-label').textContent = timezone.region;
|
||||
document.getElementById('tz-city-label').textContent = timezone.city;
|
||||
|
||||
var now = new Date();
|
||||
var timeLabel = document.getElementById('time-configuration-label');
|
||||
timeLabel.textContent = now.toLocaleString(
|
||||
navigator.languages,
|
||||
{
|
||||
hour12: navigator.mozHour12,
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
second: 'numeric'
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
updateDataConnectionStatus: function ui_udcs(status) {
|
||||
this.dataConnectionSwitch.checked = status;
|
||||
},
|
||||
|
||||
changeStatusBarColor: function ui_csbc(color) {
|
||||
var themeMeta = document.head.querySelector('meta[name="theme-color"]');
|
||||
themeMeta.setAttribute('content', color);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
function toCamelCase(str) {
|
||||
return str.replace(/\-(.)/g, function replacer(str, p1) {
|
||||
return p1.toUpperCase();
|
||||
});
|
||||
}
|
||||
16
gaia/apps/ftu/js/utils.js
Executable file
16
gaia/apps/ftu/js/utils.js
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
(function(exports) {
|
||||
'use strict';
|
||||
|
||||
var rdashes = /-(.)/g;
|
||||
|
||||
var Utils = {
|
||||
camelCase: function ut_camelCase(str) {
|
||||
return str.replace(rdashes, function(str, p1) {
|
||||
return p1.toUpperCase();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.Utils = Utils;
|
||||
|
||||
}(this));
|
||||
485
gaia/apps/ftu/js/wifi.js
Executable file
485
gaia/apps/ftu/js/wifi.js
Executable file
|
|
@ -0,0 +1,485 @@
|
|||
/* global utils, UIManager, WifiHelper, WifiUI:true */
|
||||
/* exported WifiManager, WifiUI */
|
||||
(function(exports) {
|
||||
'use strict';
|
||||
|
||||
const WIFI_STATUS = {
|
||||
connecting: 'connecting',
|
||||
associated: 'associated',
|
||||
connected: 'connected',
|
||||
connectingfailed: 'connectingfailed',
|
||||
disconnected: 'disconnected'
|
||||
};
|
||||
|
||||
var WifiManager = {
|
||||
STATUS: Object.freeze(WIFI_STATUS),
|
||||
init: function wn_init() {
|
||||
this.api = WifiHelper.getWifiManager();
|
||||
this.changeStatus();
|
||||
// Ensure that wifi is on.
|
||||
if (navigator.mozSettings) {
|
||||
var lock = window.navigator.mozSettings.createLock();
|
||||
this.enable(lock);
|
||||
this.enableDebugging(lock);
|
||||
}
|
||||
|
||||
this.gCurrentNetwork = this.api ? this.api.connection.network : null;
|
||||
|
||||
if (this.gCurrentNetwork !== null) {
|
||||
this.api.forget(this.gCurrentNetwork);
|
||||
this.gCurrentNetwork = null;
|
||||
}
|
||||
},
|
||||
|
||||
getNetworks: function wn_getNetworks(callback) {
|
||||
this.networks ? callback(this.networks) : this.scan(callback);
|
||||
},
|
||||
|
||||
scan: function wn_scan(callback) {
|
||||
if (this._scanning) {
|
||||
return;
|
||||
}
|
||||
this._scanning = true;
|
||||
utils.overlay.show('scanningNetworks', 'spinner');
|
||||
var SCAN_TIMEOUT = 10000;
|
||||
|
||||
var self = this;
|
||||
self.onScan = callback;
|
||||
|
||||
var req = this.api ? this.api.getNetworks() : null;
|
||||
if (!req) {
|
||||
// When no wifi API is available (ie. shimless B2G-desktop),
|
||||
// we will yield to the event loop before calling the callback
|
||||
// to prevent a race condition with the wifi overlay.
|
||||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=957769#c5
|
||||
setTimeout(function() {
|
||||
self._scanning = false;
|
||||
callback();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
var handleRequest = function handleRequest() {
|
||||
self._scanning = false;
|
||||
clearTimeout(self.scanTimeout);
|
||||
self.scanTimeout = null;
|
||||
self.onScan(self.networks);
|
||||
self.onScan = null;
|
||||
};
|
||||
|
||||
req.onsuccess = function onScanSuccess() {
|
||||
self.networks = req.result;
|
||||
handleRequest();
|
||||
};
|
||||
|
||||
req.onerror = function onScanError() {
|
||||
console.error('Error reading networks: ' + req.error.name);
|
||||
self.networks = [];
|
||||
handleRequest();
|
||||
};
|
||||
|
||||
// Timeout in case of scanning errors not thrown by the API
|
||||
// We can't block the user in the screen (bug 889623)
|
||||
if (!self.scanTimeout) {
|
||||
self.scanTimeout = setTimeout(function() {
|
||||
self._scanning = false;
|
||||
console.warn('Timeout while reading networks');
|
||||
self.onScan();
|
||||
}, SCAN_TIMEOUT);
|
||||
}
|
||||
},
|
||||
|
||||
enable: function wn_enable(lock) {
|
||||
lock.set({'wifi.enabled': true});
|
||||
},
|
||||
|
||||
enableDebugging: function wn_enableDebugging(lock) {
|
||||
// For bug 819947: turn on wifi debugging output to help track down a bug
|
||||
// in wifi. We turn on wifi output only while the FTU app is active.
|
||||
this._prevDebuggingValue = false;
|
||||
var req = lock.get('wifi.debugging.enabled');
|
||||
req.onsuccess = function wn_getDebuggingSuccess() {
|
||||
this._prevDebuggingValue = req.result['wifi.debugging.enabled'];
|
||||
};
|
||||
lock.set({ 'wifi.debugging.enabled': true });
|
||||
},
|
||||
|
||||
finish: function wn_finish() {
|
||||
if (!this._prevDebuggingValue && navigator.mozSettings) {
|
||||
var resetLock = window.navigator.mozSettings.createLock();
|
||||
resetLock.set({'wifi.debugging.enabled': false});
|
||||
}
|
||||
},
|
||||
|
||||
getNetwork: function wm_gn(ssid) {
|
||||
var list = this.networks;
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
if (list[i].ssid === ssid) {
|
||||
return list[i];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
connect: function wn_connect(ssid, password, user) {
|
||||
var network = this.getNetwork(ssid);
|
||||
if (!network) {
|
||||
console.error('Network not found');
|
||||
return;
|
||||
}
|
||||
this.ssid = ssid;
|
||||
// TODO: Hardcoded for resolving bug 1019146, replace hardcoded eap
|
||||
// method with user selected eap method after bug 1036829.
|
||||
WifiHelper.setPassword(network, password, user, 'PEAP');
|
||||
this.gCurrentNetwork = network;
|
||||
this.api.associate(network);
|
||||
},
|
||||
|
||||
changeStatus: function wn_cs(callback) {
|
||||
/**
|
||||
* mozWifiManager status
|
||||
* see dom/wifi/nsIWifi.idl -- the 4 possible statuses are:
|
||||
* - connecting:
|
||||
* fires when we start the process of connecting to a network.
|
||||
* - associated:
|
||||
* fires when we have connected to an access point but do not yet
|
||||
* have an IP address.
|
||||
* - connected:
|
||||
* fires once we are fully connected to an access point.
|
||||
* - connectingfailed:
|
||||
* fires when we fail to connect to an access point.
|
||||
* - disconnected:
|
||||
* fires when we were connected to a network but have been
|
||||
* disconnected.
|
||||
*/
|
||||
var self = this;
|
||||
if (WifiManager.api) {
|
||||
WifiManager.api.onstatuschange = function(event) {
|
||||
if (event.status === WifiManager.STATUS.disconnected && self.onScan) {
|
||||
self.scan(self.onScan);
|
||||
} else {
|
||||
WifiUI.updateNetworkStatus(event.network.ssid, event.status);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var WifiUI = {
|
||||
joinNetwork: function wui_jn() {
|
||||
var password = document.getElementById('wifi_password').value;
|
||||
var user = document.getElementById('wifi_user').value;
|
||||
var ssid = document.getElementById('wifi_ssid').value;
|
||||
WifiUI.connect(ssid, password, user);
|
||||
window.history.back();
|
||||
},
|
||||
|
||||
joinHiddenNetwork: function wui_jhn() {
|
||||
var password = UIManager.hiddenWifiPassword.value;
|
||||
var user = UIManager.hiddenWifiIdentity.value;
|
||||
var ssid = UIManager.hiddenWifiSsid.value;
|
||||
var security = UIManager.hiddenWifiSecurity.value;
|
||||
var network;
|
||||
|
||||
if (ssid.length) {
|
||||
if (!Array.isArray(WifiManager.networks)) {
|
||||
WifiManager.networks = [];
|
||||
}
|
||||
network = new window.MozWifiNetwork({
|
||||
ssid: ssid,
|
||||
capabilities: [],
|
||||
security: [security],
|
||||
relSignalStrength: 0,
|
||||
hidden: true
|
||||
});
|
||||
WifiManager.networks.push(network);
|
||||
this.renderNetworks(WifiManager.networks);
|
||||
WifiUI.connect(ssid, password, user);
|
||||
}
|
||||
|
||||
// like in Settings: if we don't provide correct
|
||||
// network data it just get back to the wifi screen
|
||||
window.history.back();
|
||||
},
|
||||
|
||||
_selectNetworkItem: function(ssid, isSelected=true) {
|
||||
var elem = document.getElementById(ssid);
|
||||
if (!elem) {
|
||||
return;
|
||||
}
|
||||
if (isSelected) {
|
||||
// We should update the state to 'connecting' in here to show the change
|
||||
// to the user, instead of waiting for the api (takes longer).
|
||||
// But currently
|
||||
// there's no feedback from the api when connecting to a hidden wifi fails
|
||||
// so we skip the visual changes
|
||||
// see https://bugzilla.mozilla.org/show_bug.cgi?id=1107431#c25
|
||||
elem.setAttribute('data-wifi-selected', true);
|
||||
} else {
|
||||
var icon = elem.querySelector('aside');
|
||||
elem.removeAttribute('data-wifi-selected');
|
||||
elem.classList.remove('connected');
|
||||
icon.classList.remove('connecting');
|
||||
icon.classList.remove('connected');
|
||||
|
||||
var security = elem.dataset.security;
|
||||
var securityLevelDOM = elem.querySelector('p[data-security-level]');
|
||||
securityLevelDOM.setAttribute('data-l10n-id',
|
||||
security ? security : 'securityOpen');
|
||||
}
|
||||
},
|
||||
|
||||
connect: function wui_connect(ssid, password, user) {
|
||||
// First we check if there is a previous selected network
|
||||
// and we remove their status
|
||||
// And then end we update the selected network
|
||||
var newWifi = document.getElementById(ssid);
|
||||
Array.from(document.querySelectorAll('li[data-wifi-selected]'))
|
||||
.forEach((networkSelected) => {
|
||||
this._selectNetworkItem(networkSelected,
|
||||
networkSelected === newWifi);
|
||||
});
|
||||
|
||||
// Finally we try to connect to the network
|
||||
WifiManager.connect(ssid, password, user);
|
||||
},
|
||||
|
||||
chooseNetwork: function wui_cn(event) {
|
||||
// Retrieve SSID from dataset
|
||||
var ssid = event.target.dataset.ssid;
|
||||
var selectedNetwork = WifiManager.getNetwork(ssid);
|
||||
if (!selectedNetwork) {
|
||||
console.error('Network not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Do we need to type password?
|
||||
if (WifiHelper.isOpen(selectedNetwork)) {
|
||||
WifiUI.connect(ssid);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove refresh option
|
||||
UIManager.activationScreen.classList.add('no-options');
|
||||
// Update title
|
||||
UIManager.mainTitle.textContent = ssid;
|
||||
|
||||
// Update network values
|
||||
var ssidHeader = document.getElementById('wifi_ssid');
|
||||
var userInput = document.getElementById('wifi_user');
|
||||
var passwordInput = document.getElementById('wifi_password');
|
||||
var showPassword = document.querySelector(
|
||||
'gaia-checkbox[name=show_password]');
|
||||
var joinButton = UIManager.wifiJoinButton;
|
||||
|
||||
joinButton.disabled = true;
|
||||
passwordInput.addEventListener('input', function validatePassword() {
|
||||
// disable the "Join" button if the password is on wrong format
|
||||
joinButton.disabled =
|
||||
!WifiHelper.isValidInput(WifiHelper.getKeyManagement(selectedNetwork),
|
||||
passwordInput.value, userInput.value);
|
||||
});
|
||||
|
||||
// Show / Hide password
|
||||
passwordInput.type = 'password';
|
||||
passwordInput.value = '';
|
||||
showPassword.checked = false;
|
||||
showPassword.addEventListener('change', e => {
|
||||
passwordInput.type = e.target.checked ? 'text' : 'password';
|
||||
});
|
||||
|
||||
// Update form
|
||||
passwordInput.value = '';
|
||||
ssidHeader.value = ssid;
|
||||
|
||||
// Activate secondary menu
|
||||
UIManager.navBar.classList.add('secondary-menu');
|
||||
// Update changes in form
|
||||
if (WifiHelper.isEap(selectedNetwork)) {
|
||||
userInput.parentNode.classList.remove('hidden');
|
||||
} else {
|
||||
userInput.parentNode.classList.add('hidden');
|
||||
}
|
||||
|
||||
// Change hash
|
||||
window.location.hash = '#configure_network';
|
||||
},
|
||||
|
||||
addHiddenNetwork: function wui_addHiddenNetwork() {
|
||||
// Remove refresh option
|
||||
UIManager.activationScreen.classList.add('no-options');
|
||||
// Update title
|
||||
UIManager.mainTitle.setAttribute('data-l10n-id', 'authentication');
|
||||
UIManager.navBar.classList.add('secondary-menu');
|
||||
// Clean input contents
|
||||
UIManager.hiddenWifiSsid.value = '';
|
||||
UIManager.hiddenWifiPassword.value = '';
|
||||
UIManager.hiddenWifiIdentity.value = '';
|
||||
// Reset join button state
|
||||
UIManager.wifiJoinButton.disabled = true;
|
||||
window.location.hash = '#hidden-wifi-authentication';
|
||||
},
|
||||
|
||||
handleHiddenWifiSecurity: function wui_handleSecurity(securityType) {
|
||||
// no need for password if network is open
|
||||
if (securityType === '') {
|
||||
UIManager.hiddenWifiPasswordBox.classList.add('hidden');
|
||||
} else {
|
||||
UIManager.hiddenWifiPasswordBox.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// need of username is security is WPA-EAP
|
||||
if (securityType.indexOf('EAP') !== -1) {
|
||||
UIManager.hiddenWifiIdentityBox.classList.remove('hidden');
|
||||
} else {
|
||||
UIManager.hiddenWifiIdentityBox.classList.add('hidden');
|
||||
}
|
||||
},
|
||||
|
||||
renderNetworks: function wui_rn(networks) {
|
||||
var networksDOM = document.getElementById('networks');
|
||||
networksDOM.innerHTML = '';
|
||||
var networksList;
|
||||
|
||||
if (!networks || networks.length === 0) {
|
||||
var noResult = '<div id="no-result-container">' +
|
||||
' <div id="no-result-message">' +
|
||||
' <p data-l10n-id="noWifiFound3"></p>' +
|
||||
' </div>' +
|
||||
'</div>';
|
||||
networksDOM.innerHTML = noResult;
|
||||
} else {
|
||||
networksList = document.createElement('ul');
|
||||
networksList.id = 'networks-list';
|
||||
networksList.setAttribute('role', 'listbox');
|
||||
networksList.setAttribute('data-l10n-id', 'networksList');
|
||||
var networksShown = [];
|
||||
networks.sort(function(a, b) {
|
||||
return b.relSignalStrength - a.relSignalStrength;
|
||||
});
|
||||
// Add detected networks
|
||||
for (var i = 0, max = networks.length; i < max; i++) {
|
||||
// Retrieve the network
|
||||
var network = networks[i];
|
||||
// Check if is shown
|
||||
if (networksShown.indexOf(network.ssid) === -1) {
|
||||
// Create dom elements
|
||||
var li = document.createElement('li');
|
||||
var icon = document.createElement('aside');
|
||||
var ssidp = document.createElement('p');
|
||||
ssidp.setAttribute('dir', 'auto');
|
||||
var small = document.createElement('p');
|
||||
|
||||
small.dataset.securityLevel = true;
|
||||
|
||||
// Set Icon
|
||||
icon.classList.add('pack-end');
|
||||
icon.classList.add('wifi-icon');
|
||||
var level = Math.min(Math.floor(network.relSignalStrength / 20), 4);
|
||||
icon.classList.add('level-' + level);
|
||||
document.l10n.setAttributes(icon, 'wifiLevel', {level: level});
|
||||
icon.setAttribute('role', 'presentation');
|
||||
// Set SSID
|
||||
ssidp.textContent = network.ssid;
|
||||
li.dataset.ssid = network.ssid;
|
||||
// Show authentication method
|
||||
var keys = WifiHelper.getSecurity(network);
|
||||
|
||||
li.dataset.security = keys;
|
||||
if (!WifiHelper.isOpen(network)) {
|
||||
small.textContent = keys.join(', ');
|
||||
icon.classList.add('secured');
|
||||
} else {
|
||||
small.setAttribute('data-l10n-id', 'securityOpen');
|
||||
}
|
||||
// Show connection status
|
||||
icon.classList.add('wifi-signal');
|
||||
var connectionStatus = WifiHelper.getNetworkStatus(network) ||
|
||||
WifiManager.STATUS.disconnected;
|
||||
li.dataset.status = connectionStatus;
|
||||
|
||||
if (connectionStatus === WifiManager.STATUS.connected) {
|
||||
small.setAttribute('data-l10n-id', 'shortStatus-connected');
|
||||
small.removeAttribute('aria-label');
|
||||
icon.classList.add('connected');
|
||||
li.classList.add('connected');
|
||||
li.dataset.wifiSelected = true;
|
||||
}
|
||||
|
||||
// Update list of shown networks
|
||||
networksShown.push(network.ssid);
|
||||
// Append the elements to li
|
||||
li.setAttribute('id', network.ssid);
|
||||
li.setAttribute('role', 'option');
|
||||
li.setAttribute('aria-live', true);
|
||||
li.setAttribute('aria-relevant', 'text');
|
||||
li.appendChild(ssidp);
|
||||
li.appendChild(small);
|
||||
li.appendChild(icon);
|
||||
// Append to DOM
|
||||
if (WifiHelper.isConnected(network)) {
|
||||
networksList.insertBefore(li, networksList.firstChild);
|
||||
} else {
|
||||
networksList.appendChild(li);
|
||||
}
|
||||
}
|
||||
}
|
||||
networksList.dataset.type = 'list';
|
||||
networksDOM.appendChild(networksList);
|
||||
}
|
||||
utils.overlay.hide();
|
||||
},
|
||||
|
||||
updateNetworkStatus: function wui_uns(ssid, status) {
|
||||
var element = document.getElementById(ssid);
|
||||
// Check if element exists and it's the selected network
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
if (status === WifiManager.STATUS.connecting && ssid !== WifiManager.ssid) {
|
||||
// bogus event, see bug 1179703/1216221
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the element
|
||||
element.dataset.status = status;
|
||||
if (status !== WifiManager.STATUS.disconnected) {
|
||||
element.querySelector('p[data-security-level]').setAttribute(
|
||||
'data-l10n-id', 'shortStatus-' + status);
|
||||
} else {
|
||||
var security = element.dataset.security || 'Open';
|
||||
|
||||
element.querySelector('p[data-security-level]').setAttribute(
|
||||
'data-l10n-id', 'security' + security);
|
||||
element.classList.remove('connected');
|
||||
}
|
||||
|
||||
// Animate icon if connecting, stop animation if
|
||||
// failed/connected/disconnected
|
||||
var icon = element.querySelector('aside');
|
||||
switch (status) {
|
||||
case WifiManager.STATUS.connecting:
|
||||
case WifiManager.STATUS.associated:
|
||||
icon.classList.add('connecting');
|
||||
break;
|
||||
case WifiManager.STATUS.connected:
|
||||
icon.classList.remove('connecting');
|
||||
var networksList = document.getElementById('networks-list');
|
||||
icon.classList.add('connected');
|
||||
element.classList.add('connected');
|
||||
networksList.removeChild(element);
|
||||
networksList.insertBefore(element, networksList.firstChild);
|
||||
break;
|
||||
default:
|
||||
icon.classList.remove('connecting');
|
||||
icon.classList.remove('connected');
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.WifiManager = WifiManager;
|
||||
exports.WifiUI = WifiUI;
|
||||
|
||||
})(window);
|
||||
Loading…
Reference in a new issue