Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
492 lines
16 KiB
JavaScript
492 lines
16 KiB
JavaScript
/* global debug, ConfigManager, CostControl, Formatting,
|
|
SimManager, Common, ChartUtils */
|
|
/* jshint -W120 */
|
|
|
|
/*
|
|
* The data usage tab is in charge of usage charts of mobile and wi-fi networks.
|
|
*
|
|
* It has several canvas areas layered one above the others.
|
|
*/
|
|
'use strict';
|
|
var DataUsageTab = (function() {
|
|
|
|
var graphicArea, graphicPattern;
|
|
var backgroundLayer, wifiLayer, mobileLayer, axisLayer, todayLayer,
|
|
warningLayer, limitsLayer;
|
|
var wifiOverview, mobileOverview;
|
|
var wifiToggle, mobileToggle;
|
|
var appList, noData;
|
|
|
|
var costcontrol, initialized, model;
|
|
|
|
function setupTab() {
|
|
if (initialized) {
|
|
return;
|
|
}
|
|
|
|
CostControl.getInstance(function _onCostControl(instance) {
|
|
costcontrol = instance;
|
|
|
|
// HTML entities
|
|
graphicArea = document.getElementById('graphic-area');
|
|
graphicPattern = document.getElementById('graphic-pattern');
|
|
backgroundLayer = document.getElementById('background-layer');
|
|
wifiLayer = document.getElementById('wifi-layer');
|
|
mobileLayer = document.getElementById('mobile-layer');
|
|
axisLayer = document.getElementById('axis-layer');
|
|
todayLayer = document.getElementById('today-layer');
|
|
warningLayer = document.getElementById('warning-layer');
|
|
limitsLayer = document.getElementById('limits-layer');
|
|
wifiOverview = document.getElementById('wifiOverview');
|
|
mobileOverview = document.getElementById('mobileOverview');
|
|
wifiToggle = document.getElementById('wifiCheck');
|
|
mobileToggle = document.getElementById('mobileCheck');
|
|
appList = document.getElementById('app-usage-list');
|
|
noData = document.getElementById('app-usage-no-data');
|
|
|
|
window.addEventListener('localized', localize);
|
|
|
|
// Update and chart visibility
|
|
document.addEventListener('visibilitychange', updateWhenVisible);
|
|
wifiToggle.addEventListener('change', toggleWifi);
|
|
mobileToggle.addEventListener('change', toggleMobile);
|
|
|
|
// Setup the model
|
|
SimManager.requestDataSimIcc(function(dataSimIcc) {
|
|
ConfigManager.requestSettings(dataSimIcc.iccId,
|
|
function _onSettings(settings) {
|
|
debug('First time setup for model');
|
|
model = {
|
|
height: ChartUtils.toDevicePixels(graphicArea.clientHeight),
|
|
width: ChartUtils.toDevicePixels(graphicArea.clientWidth),
|
|
originX: Math.floor(
|
|
ChartUtils.toDevicePixels(graphicArea.clientWidth) * 0.15),
|
|
endX: Math.floor(
|
|
ChartUtils.toDevicePixels(graphicArea.clientWidth) * 0.95),
|
|
axis: {
|
|
Y: {
|
|
lower: 0,
|
|
margin: 0.20
|
|
},
|
|
X: {
|
|
lower: ChartUtils.calculateLowerDate(settings),
|
|
upper: ChartUtils.calculateUpperDate(settings)
|
|
}
|
|
},
|
|
limits: {
|
|
enabled: settings.dataLimit,
|
|
value: ChartUtils.getLimitInBytes(settings),
|
|
dataLimitValue: settings.dataLimitValue,
|
|
dataLimitUnit: settings.dataLimitUnit
|
|
},
|
|
data: {
|
|
wifi: {
|
|
enabled: true
|
|
},
|
|
mobile: {
|
|
enabled: true
|
|
}
|
|
},
|
|
todayLabel: {}
|
|
};
|
|
ChartUtils.expandModel(model);
|
|
resetButtonState(settings);
|
|
|
|
ConfigManager.observe('dataLimit', toggleDataLimit, true);
|
|
ConfigManager.observe('dataLimitValue', setDataLimit, true);
|
|
ConfigManager.observe('lastCompleteDataReset', updateDataUsage, true);
|
|
ConfigManager.observe('lastDataReset', updateDataUsage, true);
|
|
ConfigManager.observe('nextReset', changeNextReset, true);
|
|
|
|
function finishInit() {
|
|
initialized = true;
|
|
updateDataUsage();
|
|
}
|
|
|
|
Common.loadApps()
|
|
.then(finishInit)
|
|
.catch(function(reason) {
|
|
debug(reason);
|
|
finishInit();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function localize() {
|
|
if (initialized) {
|
|
ChartUtils.drawTodayLayer(todayLayer, model);
|
|
ChartUtils.drawAxisLayer(axisLayer, model, mobileToggle.checked);
|
|
ChartUtils.drawLimits(limitsLayer, model, mobileToggle.checked);
|
|
}
|
|
}
|
|
|
|
function finalize() {
|
|
if (!initialized) {
|
|
return;
|
|
}
|
|
|
|
document.removeEventListener('visibilitychange', updateWhenVisible);
|
|
wifiToggle.removeEventListener('click', toggleWifi);
|
|
mobileToggle.removeEventListener('click', toggleMobile);
|
|
ConfigManager.removeObserver('dataLimit', toggleDataLimit);
|
|
ConfigManager.removeObserver('dataLimitValue', setDataLimit);
|
|
ConfigManager.removeObserver('lastCompleteDataReset', updateDataUsage);
|
|
ConfigManager.removeObserver('lastDataReset', updateDataUsage);
|
|
ConfigManager.removeObserver('nextReset', changeNextReset);
|
|
|
|
initialized = false;
|
|
}
|
|
|
|
function resetButtonState(settings) {
|
|
var isMobileChartVisible = settings.isMobileChartVisible;
|
|
if (typeof isMobileChartVisible === 'undefined') {
|
|
isMobileChartVisible = true;
|
|
}
|
|
if (isMobileChartVisible !== mobileToggle.checked) {
|
|
mobileToggle.checked = isMobileChartVisible;
|
|
toggleMobile();
|
|
}
|
|
|
|
var isWifiChartVisible = settings.isWifiChartVisible;
|
|
if (typeof isWifiChartVisible === 'undefined') {
|
|
isWifiChartVisible = false;
|
|
}
|
|
if (isWifiChartVisible !== wifiToggle.checked) {
|
|
wifiToggle.checked = isWifiChartVisible;
|
|
toggleWifi();
|
|
}
|
|
}
|
|
|
|
// UPDATES
|
|
|
|
// On visibility change
|
|
function updateWhenVisible(evt) {
|
|
if (!document.hidden) {
|
|
updateDataUsage();
|
|
}
|
|
}
|
|
|
|
function requestDataUsage(perApp) {
|
|
return new Promise(function(resolve) {
|
|
SimManager.requestDataSimIcc(function(dataSimIcc) {
|
|
ConfigManager.requestSettings(dataSimIcc.iccId,
|
|
function _onSettings(settings) {
|
|
|
|
var request = { type: 'datausage' };
|
|
if (perApp) {
|
|
request.apps = Common.allApps.map(function(app) {
|
|
return app.manifestURL;
|
|
});
|
|
}
|
|
|
|
costcontrol.request(request, resolve);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function maybeRequestPerAppUsage() {
|
|
if (!model) {
|
|
return;
|
|
}
|
|
|
|
if (!model.data.mobile.total) {
|
|
noData.hidden = false;
|
|
appList.hidden = true;
|
|
// Repeated here to not rely on the deferred `updateApps()` call which
|
|
// would add an annoying delay until clearing the data.
|
|
clearAppList();
|
|
|
|
} else {
|
|
noData.hidden = true;
|
|
appList.hidden = false;
|
|
|
|
// Bug 1064491: request per-app data usage on the next tick of
|
|
// the main loop to avoid performance regression in startup
|
|
setTimeout(function() {
|
|
requestDataUsage(true).then(updateApps);
|
|
}, 0);
|
|
}
|
|
}
|
|
|
|
function updateCharts(result) {
|
|
return new Promise(function(resolve, reject) {
|
|
if (result.status === 'success') {
|
|
SimManager.requestDataSimIcc(function(dataSimIcc) {
|
|
ConfigManager.requestSettings(dataSimIcc.iccId,
|
|
function _onSettings(settings) {
|
|
var modelData = result.data;
|
|
model.data.wifi.samples = modelData.wifi.samples;
|
|
model.data.wifi.total = modelData.wifi.total;
|
|
|
|
model.data.mobile.samples = modelData.mobile.samples;
|
|
model.data.mobile.total = modelData.mobile.total;
|
|
|
|
model.limits.enabled = settings.dataLimit;
|
|
model.limits.value = ChartUtils.getLimitInBytes(settings);
|
|
model.limits.dataLimitValue = settings.dataLimitValue;
|
|
model.axis.X.upper = ChartUtils.calculateUpperDate(settings);
|
|
model.axis.X.lower = ChartUtils.calculateLowerDate(settings);
|
|
ChartUtils.expandModel(model);
|
|
resolve();
|
|
|
|
debug('Rendering');
|
|
drawCharts();
|
|
});
|
|
});
|
|
} else {
|
|
reject(new Error(
|
|
'Error requesting data usage. This should not happen.'));
|
|
}
|
|
});
|
|
}
|
|
|
|
function updateApps(result) {
|
|
if (result.status === 'success') {
|
|
model.data.wifi.apps = result.data.wifi.apps;
|
|
model.data.mobile.apps = result.data.mobile.apps;
|
|
drawApps(model);
|
|
}
|
|
}
|
|
|
|
// OBSERVERS
|
|
|
|
function toggleDataLimit(value) {
|
|
model.limits.enabled = value;
|
|
ChartUtils.drawBackgroundLayer(backgroundLayer, model,
|
|
mobileToggle.checked);
|
|
ChartUtils.drawAxisLayer(axisLayer, model, mobileToggle.checked);
|
|
ChartUtils.drawLimits(limitsLayer, model, mobileToggle.checked);
|
|
ChartUtils.drawWarningLayer(warningLayer, model);
|
|
}
|
|
|
|
function setDataLimit(value, old, key, settings) {
|
|
model.limits.value = ChartUtils.getLimitInBytes(settings);
|
|
model.limits.dataLimitValue = settings.dataLimitValue;
|
|
ChartUtils.expandModel(model);
|
|
drawCharts();
|
|
}
|
|
|
|
function updateDataUsage() {
|
|
requestDataUsage()
|
|
.then(updateCharts)
|
|
.then(maybeRequestPerAppUsage);
|
|
}
|
|
|
|
function changeNextReset(value, old, key, settings) {
|
|
model.axis.X.upper = ChartUtils.calculateUpperDate(settings);
|
|
model.axis.X.lower = ChartUtils.calculateLowerDate(settings);
|
|
ChartUtils.expandModel(model);
|
|
drawCharts();
|
|
}
|
|
|
|
// USER INTERFACE
|
|
|
|
// On tapping on wifi toggle
|
|
function toggleWifi() {
|
|
var isChecked = wifiToggle.checked;
|
|
wifiLayer.hidden = !isChecked;
|
|
// save wifi toggled state
|
|
ConfigManager.setOption({ isWifiChartVisible: isChecked });
|
|
|
|
if (model) {
|
|
drawApps(model);
|
|
}
|
|
}
|
|
|
|
// On tapping on mobile toggle
|
|
function toggleMobile() {
|
|
var isChecked = mobileToggle.checked;
|
|
mobileLayer.hidden = !isChecked;
|
|
warningLayer.hidden = !isChecked;
|
|
limitsLayer.hidden = !isChecked;
|
|
// save wifi toggled state
|
|
ConfigManager.setOption({ isMobileChartVisible: isChecked });
|
|
|
|
if (model) {
|
|
ChartUtils.drawBackgroundLayer(backgroundLayer, model, isChecked);
|
|
ChartUtils.drawAxisLayer(axisLayer, model, isChecked);
|
|
ChartUtils.drawLimits(limitsLayer, model, isChecked);
|
|
drawApps(model);
|
|
}
|
|
}
|
|
|
|
function drawCharts() {
|
|
// Update overview
|
|
var wifiData = Formatting.roundData(model.data.wifi.total);
|
|
var mobileData = Formatting.roundData(model.data.mobile.total);
|
|
wifiOverview.textContent = Formatting.formatData(wifiData);
|
|
mobileOverview.textContent = Formatting.formatData(mobileData);
|
|
|
|
// Render the charts
|
|
ChartUtils.drawBackgroundLayer(backgroundLayer, model,
|
|
mobileToggle.checked);
|
|
ChartUtils.drawTodayLayer(todayLayer, model);
|
|
ChartUtils.drawAxisLayer(axisLayer, model, mobileToggle.checked);
|
|
ChartUtils.drawDataLayer(wifiLayer, model, 'wifi', {
|
|
stroke: ChartUtils.WIFI_CHART_STROKE,
|
|
fill: ChartUtils.WIFI_CHART_FILL
|
|
});
|
|
ChartUtils.drawDataLayer(mobileLayer, model, 'mobile', {
|
|
stroke: ChartUtils.MOBILE_CHART_STROKE,
|
|
fill: ChartUtils.MOBILE_CHART_FILL,
|
|
pattern: graphicPattern
|
|
});
|
|
ChartUtils.drawWarningLayer(warningLayer, model);
|
|
ChartUtils.drawLimits(limitsLayer, model, mobileToggle.checked);
|
|
drawApps(model);
|
|
}
|
|
|
|
var cachedAppItems = {};
|
|
function drawApps(model) {
|
|
|
|
function createAppItem(app) {
|
|
var isSystem = app.manifestURL === Common.SYSTEM_MANIFEST;
|
|
var appElement = document.createElement('li');
|
|
appElement.className = 'app-item';
|
|
|
|
var linkElement = document.createElement('a');
|
|
if (!isSystem) {
|
|
linkElement.href = '##appdetail-view?manifestURL=' + app.manifestURL;
|
|
}
|
|
appElement.appendChild(linkElement);
|
|
|
|
var imgElement = document.createElement('img');
|
|
imgElement.className = 'app-image';
|
|
imgElement.src = Common.getAppIcon(app);
|
|
imgElement.setAttribute('role', 'presentation');
|
|
imgElement.onerror = function() {
|
|
console.warn('Unable to load icon: ' + this.src);
|
|
this.src = '/style/images/app/icons/default.png';
|
|
};
|
|
linkElement.appendChild(imgElement);
|
|
|
|
var appInfoElement = document.createElement('div');
|
|
appInfoElement.className = 'app-info';
|
|
linkElement.appendChild(appInfoElement);
|
|
|
|
var nameElement = document.createElement('div');
|
|
nameElement.className = 'app-info-row app-name';
|
|
nameElement.textContent = Common.getLocalizedAppName(app);
|
|
appInfoElement.appendChild(nameElement);
|
|
|
|
var barElement = document.createElement('div');
|
|
barElement.className = 'app-info-row app-usage-bar';
|
|
barElement.setAttribute('role', 'presentation');
|
|
appInfoElement.appendChild(barElement);
|
|
|
|
var usedBarElement = document.createElement('div');
|
|
usedBarElement.className = 'app-usage-bar-used';
|
|
usedBarElement.setAttribute('role', 'presentation');
|
|
barElement.appendChild(usedBarElement);
|
|
|
|
var usageElement = document.createElement('div');
|
|
usageElement.className = 'app-info-row app-usage-total';
|
|
appInfoElement.appendChild(usageElement);
|
|
|
|
return appElement;
|
|
}
|
|
|
|
function updateAppItemUsage(appItem, total) {
|
|
var usedBarElement =
|
|
appItem.getElementsByClassName('app-usage-bar-used')[0];
|
|
var totalElement =
|
|
appItem.getElementsByClassName('app-usage-total')[0];
|
|
|
|
var barTotal = mobileTotal;
|
|
if (model.limits.enabled && model.limits.value !== null) {
|
|
barTotal = Math.max(barTotal, model.limits.value);
|
|
}
|
|
|
|
var usedPercent = (total / barTotal) * 100;
|
|
usedBarElement.style.width = usedPercent + '%';
|
|
totalElement.textContent = '' + Formatting.formatData(
|
|
Formatting.roundData(total));
|
|
}
|
|
|
|
// Front-end workaround for Bug 1083680: Noticeable difference between by
|
|
// application breakdown totals and the total displayed in chart and widget.
|
|
// This method adds the residual traffic (the traffic that cannot be not
|
|
// allocated to an app) to the System application.
|
|
function fixResidualTraffic() {
|
|
var breakdownTotal = 0;
|
|
mobileApps[Common.SYSTEM_MANIFEST] = {total: 0};
|
|
if (manifests.length > 0) {
|
|
breakdownTotal =
|
|
manifests.reduce(function(accumulatedTraffic, appManifest) {
|
|
return accumulatedTraffic + mobileApps[appManifest].total;
|
|
}, 0);
|
|
}
|
|
var residualTraffic = mobileTotal - breakdownTotal;
|
|
// System traffic is the residual traffic
|
|
if (residualTraffic > 0) {
|
|
mobileApps[Common.SYSTEM_MANIFEST].total = residualTraffic;
|
|
if (!manifests[Common.SYSTEM_MANIFEST]) {
|
|
manifests.push(Common.SYSTEM_MANIFEST);
|
|
}
|
|
}
|
|
}
|
|
|
|
clearAppList();
|
|
|
|
var mobileTotal = model.data.mobile.total;
|
|
var mobileApps = model.data.mobile.apps;
|
|
if (!mobileApps) {
|
|
return;
|
|
}
|
|
|
|
// Filter out apps that have not used any data yet.
|
|
var manifests = Object.keys(mobileApps).filter(function(key) {
|
|
return mobileApps[key].total > 0;
|
|
});
|
|
|
|
// Note: The second premise (mobileTotal === 0) must be removed when the
|
|
// fixResidualTraffic method will be eliminated.
|
|
if (manifests.length === 0 && mobileTotal === 0) {
|
|
return;
|
|
}
|
|
|
|
fixResidualTraffic();
|
|
|
|
// Sort by total data usage, descending
|
|
manifests.sort(function(a, b) {
|
|
return mobileApps[b].total - mobileApps[a].total;
|
|
});
|
|
|
|
var fragment = document.createDocumentFragment();
|
|
manifests.forEach(function(manifestURL) {
|
|
var app = Common.allApps.find(function(app) {
|
|
return app.manifestURL === manifestURL;
|
|
});
|
|
|
|
if (!app) {
|
|
debug('No app with manifest URL: ' + manifestURL);
|
|
return;
|
|
}
|
|
|
|
var appTotal = mobileApps[manifestURL].total;
|
|
var appItem = cachedAppItems[manifestURL];
|
|
if (!appItem) {
|
|
appItem = cachedAppItems[manifestURL] = createAppItem(app);
|
|
}
|
|
|
|
updateAppItemUsage(appItem, appTotal);
|
|
fragment.appendChild(appItem);
|
|
});
|
|
|
|
appList.appendChild(fragment);
|
|
}
|
|
|
|
function clearAppList() {
|
|
appList.innerHTML = '';
|
|
}
|
|
|
|
return {
|
|
initialize: setupTab,
|
|
finalize: finalize
|
|
};
|
|
}());
|
|
|
|
DataUsageTab.initialize();
|