VulpesOS/gaia/apps/costcontrol/js/app.js
OverSu b38dea0597 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.
2026-09-30 23:13:03 +02:00

538 lines
17 KiB
JavaScript
Executable file

/* global BalanceTab, ConfigManager, Common, NonReadyScreen, SimManager,
debug, CostControl, TelephonyTab, ViewManager, LazyLoader,
AirplaneModeHelper, setNextReset, asyncStorage */
/* exported CostControlApp */
'use strict';
/*
* The application is in charge of display detailed information about the usage.
* Application has three tabs configured independently plus a settings view.
*
* This module is only in charge of manage the different tabs but the specific
* behaviour is delegated to each tab (in /js/views directory).
*
* It is important to explain how the URL schema works. Cost Control
* uses the concept of card to refer a tab or a view. There are two main layers:
* the tab layer and the overlay layer. The first one is used to display tabs,
* the second one is used to display settings or other overlaying dialogs.
*
* The URL schema is:
* #tab-id[#overlay-id]
*
* So changing the hash to:
* #balance-tab#settings-view
* makes Cost Control to change to the balance view and show the settings.
*
* We can close the overlay layer by setting only the balance layer:
* #datausage-tab
* this makes Cost Control to change to the datausage tab at the same time it
* closes the card in the overlay layer.
*
* If you want to preserve a layer but changing the other, use the empty string
* as the id of the layer you want to preserve.
* For instance, you want to only close the overlay layer but not affecting the
* tab layer:
* #
*
* Or you want to change tab layer but without affecting the overlay, use:
* #telephony-tab#
*
* Remember: you cannot close the tab layer!
*/
var CostControlApp = (function() {
var costcontrol,
initialized = false,
hashFromLastRun,
hashFromNotification;
var vmanager;
// Set the application in waiting for SIM mode. During this mode, the
// application shows a dialog informing about the current situation of the
// SIM. Once ready, callback is executed.
function waitForSIMReady(callback) {
SimManager.requestDataSimIcc(function _onIccId(dataSim) {
var dataSimIcc = dataSim.icc;
var cardState = dataSimIcc.cardState;
// SIM not ready
if (cardState !== 'ready') {
showNonReadyScreen(cardState);
debug('SIM not ready:', cardState);
dataSimIcc.oncardstatechange = function() {
waitForSIMReady(callback);
};
// SIM is ready
} else {
hideNotReadyScreen();
debug('SIM ready. ICCID:', dataSim.iccId);
dataSimIcc.oncardstatechange = undefined;
callback && callback();
}
// In case we can not get a valid ICCID.
}, function _errorNoSim() {
console.warn('Error when trying to get the ICC, SIM not detected.');
LazyLoader.load(['/shared/js/airplane_mode_helper.js'], function() {
AirplaneModeHelper.ready(function() {
var fakeState = null;
if (AirplaneModeHelper.getStatus() === 'enabled') {
console.warn('The airplaneMode is enabled.');
fakeState = 'airplaneMode';
window.addEventListener('airplaneModeDisabled',
function _onAirplanemodeDisabled(evt) {
if (evt.detail && evt.detail.serviceId === 'data') {
waitForSIMReady(startApp);
}
}
);
}
showNonReadyScreen(fakeState);
});
});
});
}
// Displays a faked modal dialog that can be automatically close when the SIM
// is ready. A second call if it is already shown will only update the
// message.
var nonReadyScreen;
function showNonReadyScreen(cardState) {
if (isApplicationLocalized) {
realshowNonReadyScreen(cardState);
} else {
window.addEventListener('localized', function _onlocalized() {
window.removeEventListener('localized', _onlocalized);
realshowNonReadyScreen(cardState);
});
}
function realshowNonReadyScreen(messageId) {
debug('Showing non-ready screen.');
if (!nonReadyScreen) {
nonReadyScreen =
new NonReadyScreen(document.getElementById('non-ready-screen'));
}
nonReadyScreen.updateForState(cardState);
vmanager.changeViewTo(nonReadyScreen.id);
}
}
function hideNotReadyScreen(status) {
debug('Hiding non-ready screen.');
if (vmanager.getCurrentView() === 'non-ready-screen') {
vmanager.closeCurrentView();
}
return;
}
// XXX: See the module documentation for details about URL schema
var tabmanager, settingsVManager;
function _onHashChange(evt) {
var parser = document.createElement('a');
parser.href = evt.oldURL;
var oldHash = parser.hash.split('#');
parser.href = evt.newURL;
var newHash = parser.hash.split('#');
if (newHash.length > 3) {
console.error('Cost Control bad URL schema');
return;
}
debug('URL schema before normalizing:', newHash);
var normalized = false;
if (!newHash[1] && oldHash[1]) {
newHash[1] = oldHash[1];
normalized = true;
}
if (newHash.length === 3 && newHash[2] === '') {
if (oldHash.length === 3) {
newHash[2] = oldHash[2];
} else {
newHash = newHash.slice(0, 2);
}
normalized = true;
}
if (normalized) {
debug('URL schema after normalization:', newHash);
window.location.hash = newHash.join('#');
return;
}
if (newHash[1]) {
tabmanager.changeViewTo(newHash[1]);
}
if (newHash.length < 3) {
vmanager.closeCurrentView();
} else {
vmanager.changeViewTo(newHash[2], '#tabpanel');
}
}
function setupCardHandler() {
// View managers for dialogs and settings
tabmanager = new ViewManager(
['balance-tab', 'telephony-tab', { id: 'datausage-tab', tab: 'right' }]
);
settingsVManager = new ViewManager();
// View handler
window.addEventListener('hashchange', _onHashChange);
}
function loadMessageHandler() {
var messageHandlerFrame = document.getElementById('message-handler');
var thereIsNextReset = ConfigManager.option('nextReset');
var alreadyLoaded = messageHandlerFrame.src
.contains('message_handler.html');
if (alreadyLoaded && thereIsNextReset) {
setNextReset(ConfigManager.option('nextReset'));
return;
}
if (thereIsNextReset) {
window.addEventListener('messagehandlerready', function _setNextReset() {
window.removeEventListener('messagehandlerready', _setNextReset);
setNextReset(ConfigManager.option('nextReset'));
});
}
messageHandlerFrame.src = 'message_handler.html';
}
function _onDataSimChange() {
// Before restarting the app, it's necessary remove the cached values of
// costcontrol and config to force an update.
CostControl.reset();
ConfigManager.setConfig(null);
SimManager.requestDataSimIcc(startApp);
}
function startApp(callback) {
// If customMode is not ready and it was activated on previous execution,
// we have to change the setup plan to no plan.
if (!ConfigManager.supportCustomizeMode) {
SimManager.requestDataSimIcc(function(dataSim) {
ConfigManager.requestSettings(dataSim.iccId, function(settings) {
if (settings.trackingPeriod === 'custom') {
ConfigManager.setOption({ trackingPeriod: 'never' });
// Removing manually if a nextReset alarm exists
// XXX: This is not part of configuration by SIM so we bypass
// ConfigManager
asyncStorage.getItem('nextResetAlarm', function(id) {
// There is already an alarm, remove it
debug('Current nextResetAlarm', id + '.', id ? 'Removing.' : '');
if (id) {
navigator.mozAlarms.remove(id);
}
asyncStorage.setItem('nextResetAlarm', null, function() {
ConfigManager.setOption({ nextReset: null });
});
});
}
});
});
}
if (SimManager.isMultiSim()) {
window.addEventListener('dataSlotChange', _onDataSimChange);
}
CostControl.getInstance(function _onCostControlReady(instance) {
if (ConfigManager.option('fte')) {
startFTE();
return;
}
loadMessageHandler();
costcontrol = instance;
if (!initialized) {
setupApp(callback);
} else {
loadSettings();
updateUI(callback);
}
});
}
var isApplicationLocalized = false;
window.addEventListener('localized', function _onLocalize() {
isApplicationLocalized = true;
if (initialized) {
updateUI();
loadSettings();
}
});
function setupApp(callback) {
setupCardHandler();
// Configure settings buttons
var settingsButtons = document.querySelectorAll('.settings-button');
Array.prototype.forEach.call(settingsButtons,
function _eachSettingsButton(button) {
button.addEventListener('click', function _showSettings() {
window.location.hash = '##settings-view';
});
}
);
// Handle 'open activity' sent by the user via the widget
navigator.mozSetMessageHandler('activity',
function _handleActivity(activityRequest) {
var name = activityRequest.source.name;
switch (name) {
case 'costcontrol/balance':
window.location.hash = '#balance-tab';
break;
case 'costcontrol/telephony':
window.location.hash = '#telephony-tab';
break;
case 'costcontrol/data_usage':
window.location.hash = '#datausage-tab';
break;
}
}
);
// When a notification is received
window.navigator.mozSetMessageHandler('notification',
function _onNotification(notification) {
if (!notification.clicked) {
return;
}
debug('Notification was clicked!');
navigator.mozApps.getSelf().onsuccess = function _onAppReady(evt) {
var app = evt.target.result;
app.launch();
var type = notification.data;
debug('Notification type:', type);
handleNotification(type);
};
}
);
// Check card state when visible
document.addEventListener('visibilitychange',
function _onVisibilityChange(evt) {
if (!document.hidden && initialized) {
waitForSIMReady();
}
}
);
updateUI(callback);
ConfigManager.observe('plantype', updateUI, true);
initialized = true;
loadSettings();
}
// Load settings in background
function loadSettings() {
window.performance.mark('loadSettingsStart');
document.getElementById('settings-view-placeholder').src = 'settings.html';
}
function handleNotification(type) {
switch (type) {
case 'topUpError':
BalanceTab.topUpWithCode(true);
break;
case 'lowBalance':
case 'zeroBalance':
window.location.hash = '#balance-tab';
hashFromNotification = '#balance-tab';
break;
case 'dataUsage':
hashFromNotification = '#datausage-tab';
tabmanager.changeViewTo('datausage-tab');
break;
}
}
var currentMode;
function updateUI(callback) {
SimManager.requestDataSimIcc(function(dataSim) {
ConfigManager.requestSettings(dataSim.iccId,
function _onSettings(settings) {
var mode = ConfigManager.getApplicationMode();
var newHash = window.location.hash;
var tabs = document.getElementById('tabs');
var dataUsageTab = document.getElementById('datausage-tab');
debug('App UI mode: ', mode);
// Layout
if (mode !== currentMode) {
currentMode = mode;
// Stand alone mode when data usage only
if (mode === 'DATA_USAGE_ONLY') {
tabs.hidden = true;
dataUsageTab.classList.add('standalone');
newHash = '#datausage-tab';
// Two tabs mode
} else {
dataUsageTab.classList.remove('standalone');
tabs.hidden = false;
document.getElementById('balance-tab-filter')
.hidden = (mode !== 'PREPAID');
document.getElementById('telephony-tab-filter')
.hidden = (mode !== 'POSTPAID');
// If it was showing the left tab, force changing to the
// proper left view
if (!isDataUsageTabShown()) {
newHash = (mode === 'PREPAID') ? '#balance-tab#' :
'#telephony-tab#';
}
}
window.location.hash = hashFromNotification ||
hashFromLastRun ||
newHash;
hashFromLastRun = hashFromNotification = null;
// XXX: Break initialization to allow Gecko to render the animation on
// time.
requestAnimationFrame(function continueLoading() {
document.getElementById('main').classList.remove('non-ready');
if (mode === 'PREPAID') {
if (typeof TelephonyTab !== 'undefined') {
TelephonyTab.finalize();
}
if (typeof BalanceTab !== 'undefined') {
BalanceTab.initialize();
}
} else if (mode === 'POSTPAID') {
if (typeof BalanceTab !== 'undefined') {
BalanceTab.finalize();
}
if (typeof TelephonyTab !== 'undefined') {
TelephonyTab.initialize();
}
}
});
}
(typeof callback === 'function') && callback();
});
});
}
function isDataUsageTabShown() {
return window.location.hash.split('#')[1] === 'datausage-tab';
}
function onFteFinished(e) {
if (e.origin !== Common.COST_CONTROL_APP) {
return;
}
var type = e.data.type;
if (type === 'fte_finished') {
window.removeEventListener('message', onFteFinished);
document.getElementById('splash_section').hidden = 'true';
// Only hide the FTE view when everything in the UI is ready
ConfigManager.requestAll(function() {
startApp(Common.closeFTE);
});
}
}
function startFTE() {
window.addEventListener('message', onFteFinished);
var mode = ConfigManager.getApplicationMode();
Common.startFTE(mode);
}
function initApp() {
// The location hash keeps its current value even if the app is killed by
// the oom killer.
hashFromLastRun = window.location.hash;
window.location.hash = '';
vmanager = new ViewManager();
waitForSIMReady(startApp);
}
return {
init: function() {
var SCRIPTS_NEEDED = [
'js/utils/debug.js',
'js/common.js',
'js/views/NonReadyScreen.js',
'js/utils/toolkit.js',
'js/view_manager.js'
];
// Check if the mandatory APIs to work exist.
if (!window.navigator.mozMobileConnections ||
!window.navigator.mozIccManager ||
!window.navigator.mozNetworkStats) {
LazyLoader.load(SCRIPTS_NEEDED, function _showError() {
vmanager = new ViewManager();
showNonReadyScreen(null);
});
} else {
SCRIPTS_NEEDED = [
'js/sim_manager.js',
'js/utils/debug.js',
'shared/js/date_time_helper.js',
'js/utils/chart.js',
'js/utils/formatting.js',
'js/utils/toolkit.js',
'js/settings/networkUsageAlarm.js',
'js/common.js',
'js/costcontrol.js',
'js/config/config_manager.js',
'js/views/NonReadyScreen.js',
'js/view_manager.js'
];
LazyLoader.load(SCRIPTS_NEEDED, initApp);
}
// Tell audio channel manager that we want to adjust the notification
// channel if the user press the volumeup/volumedown buttons in Usage.
if (navigator.mozAudioChannelManager) {
navigator.mozAudioChannelManager.volumeControlChannel = 'notification';
}
},
reset: function() {
costcontrol = null;
initialized = false;
vmanager = null;
tabmanager = null;
settingsVManager = null;
currentMode = null;
isApplicationLocalized = false;
window.removeEventListener('dataSlotChange', _onDataSimChange);
window.removeEventListener('hashchange', _onHashChange);
window.removeEventListener('message', onFteFinished);
window.location.hash = '';
nonReadyScreen = null;
},
showBalanceTab: function _showBalanceTab() {
window.location.hash = '#balance-tab';
},
showDataUsageTab: function _showDataUsageTab() {
window.location.hash = '#datausage-tab';
},
showAppDetailView: function _showAppDetailView() {
window.location.hash = '##appdetail-view';
},
_onHashChange: _onHashChange
};
}());