diff --git a/dom/network/src/NetworkStatsService.jsm b/dom/network/src/NetworkStatsService.jsm index c57e15b0531d..237332f69707 100644 --- a/dom/network/src/NetworkStatsService.jsm +++ b/dom/network/src/NetworkStatsService.jsm @@ -29,6 +29,15 @@ const TOPIC_INTERFACE_UNREGISTERED = "network-interface-unregistered"; const NET_TYPE_WIFI = Ci.nsINetworkInterface.NETWORK_TYPE_WIFI; const NET_TYPE_MOBILE = Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE; +// Networks have different status that NetworkStats API needs to be aware of. +// Network is present and ready, so NetworkManager provides the whole info. +const NETWORK_STATUS_READY = 0; +// Network is present but hasn't established a connection yet (e.g. SIM that has not +// enabled 3G since boot). +const NETWORK_STATUS_STANDBY = 1; +// Network is not present, but stored in database by the previous connections. +const NETWORK_STATUS_AWAY = 2; + // The maximum traffic amount can be saved in the |cachedStats|. const MAX_CACHED_TRAFFIC = 500 * 1000 * 1000; // 500 MB @@ -89,7 +98,8 @@ this.NetworkStatsService = { let netId = this.getNetworkId('0', NET_TYPE_WIFI); this._networks[netId] = { network: { id: '0', type: NET_TYPE_WIFI }, - interfaceName: null }; + interfaceName: null, + status: NETWORK_STATUS_STANDBY }; this.messages = ["NetworkStats:Get", "NetworkStats:Clear", @@ -278,6 +288,7 @@ this.NetworkStatsService = { type: aNetwork.type }; } + this._networks[netId].status = NETWORK_STATUS_READY; this._networks[netId].interfaceName = aNetwork.name; return netId; }, @@ -286,6 +297,46 @@ this.NetworkStatsService = { return aIccId + '' + aNetworkType; }, + /* Function to ensure that one network is valid. The network is valid if its status is + * NETWORK_STATUS_READY, NETWORK_STATUS_STANDBY or NETWORK_STATUS_AWAY. + * + * The result is |netId| or null in case of a non-valid network + * aCallback is signatured as |function(netId)|. + */ + validateNetwork: function validateNetwork(aNetwork, aCallback) { + let netId = this.getNetworkId(aNetwork.id, aNetwork.type); + + if (this._networks[netId]) { + aCallback(netId); + return; + } + + // Check if network is valid (RIL entry) but has not established a connection yet. + // If so add to networks list with empty interfaceName. + let rilNetworks = this.getRilNetworks(); + if (rilNetworks[netId]) { + this._networks[netId] = Object.create(null); + this._networks[netId].network = rilNetworks[netId]; + this._networks[netId].status = NETWORK_STATUS_STANDBY; + this._currentAlarms[netId] = Object.create(null); + aCallback(netId); + return; + } + + // Check if network is available in the DB. + this._db.isNetworkAvailable(aNetwork, function(aError, aResult) { + if (aResult) { + this._networks[netId] = Object.create(null); + this._networks[netId].network = aNetwork; + this._networks[netId].status = NETWORK_STATUS_AWAY; + this._currentAlarms[netId] = Object.create(null); + aCallback(netId); + } + + aCallback(null); + }); + }, + getAvailableNetworks: function getAvailableNetworks(mm, msg) { let self = this; let rilNetworks = this.getRilNetworks(); @@ -368,91 +419,55 @@ this.NetworkStatsService = { let start = new Date(msg.start); let end = new Date(msg.end); - // Check if the network is currently active. If yes, we need to update - // the cached stats first before retrieving stats from the DB. - if (this._networks[netId]) { - this.updateStats(netId, function onStatsUpdated(aResult, aMessage) { - debug("getstats for network " + network.id + " of type " + network.type); - debug("appId: " + appId + " from appManifestURL: " + appManifestURL); - - self.updateCachedStats(function onStatsUpdated(aResult, aMessage) { - self._db.find(function onStatsFound(aError, aResult) { - mm.sendAsyncMessage("NetworkStats:Get:Return", - { id: msg.id, error: aError, result: aResult }); - }, appId, serviceType, network, start, end, appManifestURL); - }); - }); - return; - } - - // Check if the network is available in the DB. If yes, we also - // retrieve the stats for it from the DB. - this._db.isNetworkAvailable(network, function(aError, aResult) { - let toFind = false; - if (aResult) { - toFind = true; - } else if (!aError) { - // Network is not found in the database without any errors. - // Check if network is valid but has not established a connection yet. - let rilNetworks = self.getRilNetworks(); - if (rilNetworks[netId]) { - // find will not get data for network from the database but will format the - // result object in order to make NetworkStatsManager be able to construct a - // nsIDOMMozNetworkStats object. - toFind = true; - } - } - - if (toFind) { - // If network is not active, there is no need to update stats before finding. - self._db.find(function onStatsFound(aError, aResult) { - mm.sendAsyncMessage("NetworkStats:Get:Return", - { id: msg.id, error: aError, result: aResult }); - }, appId, serviceType, network, start, end, appManifestURL); + this.validateNetwork(network, function onValidateNetwork(aNetId) { + if (!aNetId) { + mm.sendAsyncMessage("NetworkStats:Get:Return", + { id: msg.id, error: "Invalid connectionType", result: null }); return; } - if (!aError) { - aError = "Invalid connectionType"; + // If network is currently active we need to update the cached stats first before + // retrieving stats from the DB. + if (self._networks[aNetId].status == NETWORK_STATUS_READY) { + self.updateStats(aNetId, function onStatsUpdated(aResult, aMessage) { + debug("getstats for network " + network.id + " of type " + network.type); + debug("appId: " + appId + " from appManifestURL: " + appManifestURL); + + self.updateCachedStats(function onStatsUpdated(aResult, aMessage) { + self._db.find(function onStatsFound(aError, aResult) { + mm.sendAsyncMessage("NetworkStats:Get:Return", + { id: msg.id, error: aError, result: aResult }); + }, appId, serviceType, network, start, end, appManifestURL); + }); + }); + return; } - mm.sendAsyncMessage("NetworkStats:Get:Return", - { id: msg.id, error: aError, result: null }); + // Network not active, so no need to update + self._db.find(function onStatsFound(aError, aResult) { + mm.sendAsyncMessage("NetworkStats:Get:Return", + { id: msg.id, error: aError, result: aResult }); + }, appId, serviceType, network, start, end, appManifestURL); }); }, clearInterfaceStats: function clearInterfaceStats(mm, msg) { + let self = this; let network = msg.network; - let netId = this.getNetworkId(network.id, network.type); debug("clear stats for network " + network.id + " of type " + network.type); - if (!this._networks[netId]) { - // Check if network is valid but has not established a connection yet. If it is not - // found in RIL networks, it can be a SIM network used in the past having sample - // in the database. - let rilNetworks = this.getRilNetworks(); - if (!rilNetworks[netId]) { - // Check if it is available in the DB. - this._db.isNetworkAvailable(network, function(aError, aResult) { - if (aResult) { - this._db.clearInterfaceStats(network, function onDBCleared(aError, aResult) { - mm.sendAsyncMessage("NetworkStats:Clear:Return", - { id: msg.id, error: aError, result: aResult }); - }); - return; - } - - mm.sendAsyncMessage("NetworkStats:Clear:Return", - { id: msg.id, error: "Invalid networkType", result: null }); - }); + this.validateNetwork(network, function onValidateNetwork(aNetId) { + if (!aNetId) { + mm.sendAsyncMessage("NetworkStats:Clear:Return", + { id: msg.id, error: "Invalid connectionType", result: null }); return; } - } - this._db.clearInterfaceStats(network, function onDBCleared(aError, aResult) { - mm.sendAsyncMessage("NetworkStats:Clear:Return", - { id: msg.id, error: aError, result: aResult }); + self._db.clearInterfaceStats(network, function onDBCleared(aError, aResult) { + mm.sendAsyncMessage("NetworkStats:Clear:Return", + { id: msg.id, error: aError, result: aResult }); + }); }); }, @@ -478,7 +493,7 @@ this.NetworkStatsService = { this.updateCachedStats(); let elements = []; - let lastElement; + let lastElement = null; // For each connectionType create an object containning the type // and the 'queueIndex', the 'queueIndex' is an integer representing @@ -487,14 +502,27 @@ this.NetworkStatsService = { // else it is pushed in 'elements' array, which later will be pushed to // the queue array. for (let netId in this._networks) { + if (this._networks[netId].status != NETWORK_STATUS_READY) { + continue; + } + lastElement = { netId: netId, queueIndex: this.updateQueueIndex(netId)}; if (lastElement.queueIndex == -1) { - elements.push({netId: lastElement.netId, callbacks: []}); + elements.push({ netId: lastElement.netId, callbacks: [] }); } } + if (!lastElement) { + // No elements need to be updated, probably because status is different than + // NETWORK_STATUS_READY. + if (aCallback) { + aCallback(true, "OK"); + } + return; + } + if (elements.length > 0) { // If length of elements is greater than 0, callback is set to // the last element. @@ -800,21 +828,29 @@ this.NetworkStatsService = { }, getAlarms: function getAlarms(mm, msg) { + let self = this; let network = msg.data.network; let manifestURL = msg.data.manifestURL; - let netId = null; if (network) { - netId = this.getNetworkId(network.id, network.type); - if (!this._networks[netId]) { - mm.sendAsyncMessage("NetworkStats:GetAlarms:Return", - { id: msg.id, error: "InvalidInterface", result: null }); - return; - } + this.validateNetwork(network, function onValidateNetwork(aNetId) { + if (!aNetId) { + mm.sendAsyncMessage("NetworkStats:GetAlarms:Return", + { id: msg.id, error: "InvalidInterface", result: null }); + return; + } + + self._getAlarms(mm, msg, aNetId, manifestURL); + }); + return; } + this._getAlarms(mm, msg, null, manifestURL); + }, + + _getAlarms: function _getAlarms(mm, msg, aNetId, aManifestURL) { let self = this; - this._db.getAlarms(netId, manifestURL, function onCompleted(error, result) { + this._db.getAlarms(aNetId, aManifestURL, function onCompleted(error, result) { if (error) { mm.sendAsyncMessage("NetworkStats:GetAlarms:Return", { id: msg.id, error: error, result: result }); @@ -884,48 +920,49 @@ this.NetworkStatsService = { return; } - let netId = this.getNetworkId(network.id, network.type); - if (!this._networks[netId]) { - mm.sendAsyncMessage("NetworkStats:SetAlarm:Return", - { id: msg.id, error: "InvalidiConnectionType", result: null }); - return; - } - - let newAlarm = { - id: null, - networkId: netId, - threshold: threshold, - absoluteThreshold: null, - startTime: options.startTime, - data: options.data, - pageURL: options.pageURL, - manifestURL: options.manifestURL - }; - let self = this; - this._updateThreshold(newAlarm, function onUpdate(error, _threshold) { - if (error) { + this.validateNetwork(network, function onValidateNetwork(aNetId) { + if (!aNetId) { mm.sendAsyncMessage("NetworkStats:SetAlarm:Return", - { id: msg.id, error: error, result: null }); + { id: msg.id, error: "InvalidiConnectionType", result: null }); return; } - newAlarm.absoluteThreshold = _threshold.absoluteThreshold; - self._db.addAlarm(newAlarm, function addSuccessCb(error, newId) { + let newAlarm = { + id: null, + networkId: aNetId, + threshold: threshold, + absoluteThreshold: null, + startTime: options.startTime, + data: options.data, + pageURL: options.pageURL, + manifestURL: options.manifestURL + }; + + self._updateThreshold(newAlarm, function onUpdate(error, _threshold) { if (error) { mm.sendAsyncMessage("NetworkStats:SetAlarm:Return", { id: msg.id, error: error, result: null }); return; } - newAlarm.id = newId; - self._setAlarm(newAlarm, function onSet(error, success) { - mm.sendAsyncMessage("NetworkStats:SetAlarm:Return", - { id: msg.id, error: error, result: newId }); - - if (error == "InvalidStateError") { - self._fireAlarm(newAlarm); + newAlarm.absoluteThreshold = _threshold.absoluteThreshold; + self._db.addAlarm(newAlarm, function addSuccessCb(error, newId) { + if (error) { + mm.sendAsyncMessage("NetworkStats:SetAlarm:Return", + { id: msg.id, error: error, result: null }); + return; } + + newAlarm.id = newId; + self._setAlarm(newAlarm, function onSet(error, success) { + mm.sendAsyncMessage("NetworkStats:SetAlarm:Return", + { id: msg.id, error: error, result: newId }); + + if (error == "InvalidStateError") { + self._fireAlarm(newAlarm); + } + }); }); }); }); @@ -933,8 +970,9 @@ this.NetworkStatsService = { _setAlarm: function _setAlarm(aAlarm, aCallback) { let currentAlarm = this._currentAlarms[aAlarm.networkId]; - if (Object.getOwnPropertyNames(currentAlarm).length !== 0 && - aAlarm.absoluteThreshold > currentAlarm.alarm.absoluteThreshold) { + if ((Object.getOwnPropertyNames(currentAlarm).length !== 0 && + aAlarm.absoluteThreshold > currentAlarm.alarm.absoluteThreshold) || + this._networks[aAlarm.networkId].status != NETWORK_STATUS_READY) { aCallback(null, true); return; } @@ -985,6 +1023,14 @@ this.NetworkStatsService = { return; } + if (!result) { + // There are no stats for the network of the alarm, set them to default 0 in + // order to be able to calculate the offset, systemThreshold and + // absoluteThreshold. + result = { rxTotalBytes: 0, txTotalBytes: 0, + rxSystemBytes: 0, txSystemBytes: 0 }; + } + let offset = aAlarm.threshold - result.rxTotalBytes - result.txTotalBytes; // Alarm set to a threshold lower than current rx/tx bytes.