gecko/dom/mobileconnection/gonk/MobileConnectionService.js

1500 lines
46 KiB
JavaScript
Raw Normal View History

/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/systemlibs.js");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
var RIL = {};
Cu.import("resource://gre/modules/ril_consts.js", RIL);
const GONK_MOBILECONNECTIONSERVICE_CONTRACTID =
"@mozilla.org/mobileconnection/gonkmobileconnectionservice;1";
const GONK_MOBILECONNECTIONSERVICE_CID =
Components.ID("{0c9c1a96-2c72-4c55-9e27-0ca73eb16f63}");
const MOBILECONNECTIONINFO_CID =
Components.ID("{8162b3c0-664b-45f6-96cd-f07b4e193b0e}");
const MOBILENETWORKINFO_CID =
Components.ID("{a6c8416c-09b4-46d1-bf29-6520d677d085}");
const MOBILECELLINFO_CID =
Components.ID("{0635d9ab-997e-4cdf-84e7-c1883752dff3}");
const MOBILECALLFORWARDINGOPTIONS_CID =
Components.ID("{e0cf4463-ee63-4b05-ab2e-d94bf764836c}");
const TELEPHONYDIALCALLBACK_CID =
Components.ID("{c2af1a5d-3649-44ef-a1ff-18e9ac1dec51}");
const NEIGHBORINGCELLINFO_CID =
Components.ID("{6078cbf1-f34c-44fa-96f8-11a88d4bfdd3}");
const GSMCELLINFO_CID =
Components.ID("{e3cf3aa0-f992-48fe-967b-ec98a28c8535}");
const WCDMACELLINFO_CID =
Components.ID("{62e2c83c-b535-4068-9762-8039fac48106}");
const CDMACELLINFO_CID =
Components.ID("{40f491f0-dd8b-42fd-af32-aef5b002749a}");
const LTECELLINFO_CID =
Components.ID("{715e2c76-3b08-41e4-8ea5-e60c5ce6393e}");
const NS_XPCOM_SHUTDOWN_OBSERVER_ID = "xpcom-shutdown";
const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
const NS_NETWORK_ACTIVE_CHANGED_TOPIC_ID = "network-active-changed";
const kPrefRilDebuggingEnabled = "ril.debugging.enabled";
const INT32_MAX = 2147483647;
const UNKNOWN_RSSI = 99;
XPCOMUtils.defineLazyServiceGetter(this, "gSystemMessenger",
"@mozilla.org/system-message-internal;1",
"nsISystemMessagesInternal");
XPCOMUtils.defineLazyServiceGetter(this, "gNetworkManager",
"@mozilla.org/network/manager;1",
"nsINetworkManager");
XPCOMUtils.defineLazyServiceGetter(this, "gRadioInterfaceLayer",
"@mozilla.org/ril;1",
"nsIRadioInterfaceLayer");
XPCOMUtils.defineLazyServiceGetter(this, "gGonkTelephonyService",
"@mozilla.org/telephony/telephonyservice;1",
"nsIGonkTelephonyService");
let DEBUG = RIL.DEBUG_RIL;
function debug(s) {
dump("MobileConnectionService: " + s + "\n");
}
function MobileNetworkInfo() {
this.shortName = null;
this.longName = null;
this.mcc = null;
this.mnc = null;
this.stat = null;
}
MobileNetworkInfo.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIMobileNetworkInfo]),
classID: MOBILENETWORKINFO_CID,
classInfo: XPCOMUtils.generateCI({
classID: MOBILENETWORKINFO_CID,
classDescription: "MobileNetworkInfo",
interfaces: [Ci.nsIMobileNetworkInfo]
})
};
function MobileCellInfo() {
this.gsmLocationAreaCode = -1;
this.gsmCellId = -1;
this.cdmaBaseStationId = -1;
this.cdmaBaseStationLatitude = -2147483648;
this.cdmaBaseStationLongitude = -2147483648;
this.cdmaSystemId = -1;
this.cdmaNetworkId = -1;
}
MobileCellInfo.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIMobileCellInfo]),
classID: MOBILECELLINFO_CID,
classInfo: XPCOMUtils.generateCI({
classID: MOBILECELLINFO_CID,
classDescription: "MobileCellInfo",
interfaces: [Ci.nsIMobileCellInfo]
})
};
function MobileConnectionInfo() {}
MobileConnectionInfo.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIMobileConnectionInfo]),
classID: MOBILECONNECTIONINFO_CID,
classInfo: XPCOMUtils.generateCI({
classID: MOBILECONNECTIONINFO_CID,
classDescription: "MobileConnectionInfo",
interfaces: [Ci.nsIMobileConnectionInfo]
}),
state: null,
connected: false,
emergencyCallsOnly: false,
roaming: false,
network: null,
cell: null,
type: null,
signalStrength: null,
relSignalStrength: null
};
function MobileCallForwardingOptions(aOptions) {
for (let key in aOptions) {
this[key] = aOptions[key];
}
}
MobileCallForwardingOptions.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIMobileCallForwardingOptions]),
classID: MOBILECALLFORWARDINGOPTIONS_CID,
classInfo: XPCOMUtils.generateCI({
classID: MOBILECALLFORWARDINGOPTIONS_CID,
classDescription: "MobileCallForwardingOptions",
interfaces: [Ci.nsIMobileCallForwardingOptions]
}),
// nsIMobileForwardingOptions
active: false,
action: Ci.nsIMobileConnection.CALL_FORWARD_ACTION_UNKNOWN,
reason: Ci.nsIMobileConnection.CALL_FORWARD_REASON_UNKNOWN,
number: null,
timeSeconds: -1,
serviceClass: Ci.nsIMobileConnection.ICC_SERVICE_CLASS_NONE
}
function NeighboringCellInfo() {}
NeighboringCellInfo.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsINeighboringCellInfo]),
classID: NEIGHBORINGCELLINFO_CID,
classInfo: XPCOMUtils.generateCI({
classID: NEIGHBORINGCELLINFO_CID,
classDescription: "NeighboringCellInfo",
interfaces: [Ci.nsINeighboringCellInfo]
}),
// nsINeighboringCellInfo
networkType: null,
gsmLocationAreaCode: -1,
gsmCellId: -1,
wcdmaPsc: -1,
signalStrength: UNKNOWN_RSSI
};
function CellInfo() {}
CellInfo.prototype = {
// nsICellInfo
type: null,
registered: false,
timestampType: Ci.nsICellInfo.TIMESTAMP_TYPE_UNKNOWN,
timestamp: 0
};
function GsmCellInfo() {}
GsmCellInfo.prototype = {
__proto__: CellInfo.prototype,
QueryInterface: XPCOMUtils.generateQI([Ci.nsICellInfo,
Ci.nsIGsmCellInfo]),
classID: GSMCELLINFO_CID,
classInfo: XPCOMUtils.generateCI({
classID: GSMCELLINFO_CID,
classDescription: "GsmCellInfo",
interfaces: [Ci.nsIGsmCellInfo]
}),
// nsIGsmCellInfo
mcc: INT32_MAX,
mnc: INT32_MAX,
lac: INT32_MAX,
cid: INT32_MAX,
signalStrength: UNKNOWN_RSSI,
bitErrorRate: UNKNOWN_RSSI
};
function WcdmaCellInfo() {}
WcdmaCellInfo.prototype = {
__proto__: CellInfo.prototype,
QueryInterface: XPCOMUtils.generateQI([Ci.nsICellInfo,
Ci.nsIWcdmaCellInfo]),
classID: WCDMACELLINFO_CID,
classInfo: XPCOMUtils.generateCI({
classID: WCDMACELLINFO_CID,
classDescription: "WcdmaCellInfo",
interfaces: [Ci.nsIWcdmaCellInfo]
}),
// nsIWcdmaCellInfo
mcc: INT32_MAX,
mnc: INT32_MAX,
lac: INT32_MAX,
cid: INT32_MAX,
psc: INT32_MAX,
signalStrength: UNKNOWN_RSSI,
bitErrorRate: UNKNOWN_RSSI
};
function LteCellInfo() {}
LteCellInfo.prototype = {
__proto__: CellInfo.prototype,
QueryInterface: XPCOMUtils.generateQI([Ci.nsICellInfo,
Ci.nsILteCellInfo]),
classID: LTECELLINFO_CID,
classInfo: XPCOMUtils.generateCI({
classID: LTECELLINFO_CID,
classDescription: "LteCellInfo",
interfaces: [Ci.nsILteCellInfo]
}),
// nsILteCellInfo
mcc: INT32_MAX,
mnc: INT32_MAX,
cid: INT32_MAX,
pcid: INT32_MAX,
tac: INT32_MAX,
signalStrength: UNKNOWN_RSSI,
rsrp: INT32_MAX,
rsrq: INT32_MAX,
rssnr: INT32_MAX,
cqi: INT32_MAX,
timingAdvance: INT32_MAX
};
function CdmaCellInfo() {}
CdmaCellInfo.prototype = {
__proto__: CellInfo.prototype,
QueryInterface: XPCOMUtils.generateQI([Ci.nsICellInfo,
Ci.nsICdmaCellInfo]),
classID: CDMACELLINFO_CID,
classInfo: XPCOMUtils.generateCI({
classID: CDMACELLINFO_CID,
classDescription: "CdmaCellInfo",
interfaces: [Ci.nsICdmaCellInfo]
}),
// nsICdmaCellInfo
networkId: INT32_MAX,
systemId: INT32_MAX,
baseStationId: INT32_MAX,
longitude: INT32_MAX,
latitude: INT32_MAX,
cdmaDbm: INT32_MAX,
cdmaEcio: INT32_MAX,
evdoDbm: INT32_MAX,
evdoEcio: INT32_MAX,
evdoSnr: INT32_MAX
};
/**
* Wrap a MobileConnectionCallback to a TelephonyDialCallback.
*/
function TelephonyDialCallback(aCallback) {
this.callback = aCallback;
}
TelephonyDialCallback.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsITelephonyDialCallback]),
classID: TELEPHONYDIALCALLBACK_CID,
notifyDialMMI: function(mmiServiceCode) {
this.serviceCode = mmiServiceCode;
},
notifyDialMMISuccess: function(statusMessage) {
this.callback.notifySendCancelMmiSuccess(this.serviceCode, statusMessage);
},
notifyDialMMISuccessWithInteger: function(statusMessage, additionalInfo) {
this.callback.notifySendCancelMmiSuccessWithInteger(this.serviceCode,
statusMessage,
additionalInfo);
},
notifyDialMMISuccessWithStrings: function(statusMessage, count, additionalInfo) {
this.callback.notifySendCancelMmiSuccessWithStrings(this.serviceCode,
statusMessage,
count,
additionalInfo);
},
notifyDialMMISuccessWithCallForwardingOptions: function(statusMessage, count, additionalInfo) {
this.callback.notifySendCancelMmiSuccessWithCallForwardingOptions(
this.serviceCode,
statusMessage,
count,
additionalInfo);
},
notifyDialMMIError: function(error) {
this.callback.notifyError(error, "", this.serviceCode);
},
notifyDialMMIErrorWithInfo: function(error, info) {
this.callback.notifyError(error, "", this.serviceCode, info);
},
notifyDialError: function() {
throw Cr.NS_ERROR_UNEXPECTED;
},
notifyDialSuccess: function() {
throw Cr.NS_ERROR_UNEXPECTED;
},
};
function MobileConnectionProvider(aClientId, aRadioInterface) {
this._clientId = aClientId;
this._radioInterface = aRadioInterface;
this._operatorInfo = new MobileNetworkInfo();
// An array of nsIMobileConnectionListener instances.
this._listeners = [];
this.supportedNetworkTypes = this._getSupportedNetworkTypes();
this.voice = new MobileConnectionInfo();
this.data = new MobileConnectionInfo();
}
MobileConnectionProvider.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIMobileConnection]),
_clientId: null,
_radioInterface: null,
_operatorInfo: null,
_listeners: null,
/**
* The networks that are currently trying to be selected (or "automatic").
* This helps ensure that only one network per client is selected at a time.
*/
_selectingNetwork: null,
voice: null,
data: null,
iccId: null,
networkSelectionMode: Ci.nsIMobileConnection.NETWORK_SELECTION_MODE_UNKNOWN,
radioState: Ci.nsIMobileConnection.MOBILE_RADIO_STATE_UNKNOWN,
lastKnownNetwork: null,
lastKnownHomeNetwork: null,
supportedNetworkTypes: null,
/**
* A utility function to dump debug message.
*/
_debug: function(aMessage) {
dump("MobileConnectionProvider[" + this._clientId + "]: " + aMessage + "\n");
},
/**
* A utility function to get supportedNetworkTypes from system property.
*/
_getSupportedNetworkTypes: function() {
let key = "ro.moz.ril." + this._clientId + ".network_types";
let supportedNetworkTypes = libcutils.property_get(key, "").split(",");
// If mozRIL system property is not available, fallback to AOSP system
// property for support network types.
if (supportedNetworkTypes.length === 1 && supportedNetworkTypes[0] === "") {
key = "ro.telephony.default_network";
let indexString = libcutils.property_get(key, "");
let index = parseInt(indexString, 10);
if (DEBUG) this._debug("Fallback to " + key + ": " + index);
let networkTypes = RIL.RIL_PREFERRED_NETWORK_TYPE_TO_GECKO[index];
supportedNetworkTypes = networkTypes ?
networkTypes.replace("-auto", "", "g").split("/") :
RIL.GECKO_SUPPORTED_NETWORK_TYPES_DEFAULT.split(",");
}
let enumNetworkTypes = [];
for (let type of supportedNetworkTypes) {
// If the value in system property is not valid, use the default one which
// is defined in ril_consts.js.
if (RIL.GECKO_SUPPORTED_NETWORK_TYPES.indexOf(type) < 0) {
if (DEBUG) {
this._debug("Unknown network type: " + type);
}
RIL.GECKO_SUPPORTED_NETWORK_TYPES_DEFAULT.split(",").forEach(aType => {
enumNetworkTypes.push(RIL.GECKO_SUPPORTED_NETWORK_TYPES.indexOf(aType));
});
break;
}
enumNetworkTypes.push(RIL.GECKO_SUPPORTED_NETWORK_TYPES.indexOf(type));
}
if (DEBUG) {
this._debug("Supported Network Types: " + enumNetworkTypes);
}
return enumNetworkTypes;
},
/**
* Helper for guarding us against invalid mode for clir.
*/
_isValidClirMode: function(aMode) {
switch (aMode) {
case Ci.nsIMobileConnection.CLIR_DEFAULT:
case Ci.nsIMobileConnection.CLIR_INVOCATION:
case Ci.nsIMobileConnection.CLIR_SUPPRESSION:
return true;
default:
return false;
}
},
/**
* Fix the roaming. RIL can report roaming in some case it is not
* really the case. See bug 787967
*/
_checkRoamingBetweenOperators: function(aNetworkInfo) {
// TODO: Bug 864489 - B2G RIL: use ipdl as IPC in MozIccManager
// Should get iccInfo from GonkIccProvider.
let iccInfo = this._radioInterface.rilContext.iccInfo;
let operator = aNetworkInfo.network;
let state = aNetworkInfo.state;
if (!iccInfo || !operator ||
state !== RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED) {
return false;
}
let spn = iccInfo.spn && iccInfo.spn.toLowerCase();
let longName = operator.longName && operator.longName.toLowerCase();
let shortName = operator.shortName && operator.shortName.toLowerCase();
let equalsLongName = longName && (spn == longName);
let equalsShortName = shortName && (spn == shortName);
let equalsMcc = iccInfo.mcc == operator.mcc;
let newRoaming = aNetworkInfo.roaming &&
!(equalsMcc && (equalsLongName || equalsShortName));
if (newRoaming === aNetworkInfo.roaming) {
return false;
}
aNetworkInfo.roaming = newRoaming;
return true;
},
/**
* The design of this updating function is to update the attribute in
* |aDestInfo| *only if* new data (e.g. aSrcInfo) contains the same attribute.
* Thus, for the attribute in |aDestInfo| that isn't showed in |aSrcInfo|, it
* should just keep the original value unchanged.
*/
_updateConnectionInfo: function(aDestInfo, aSrcInfo) {
let isUpdated = false;
for (let key in aSrcInfo) {
if (key === "network" || key === "cell") {
// nsIMobileNetworkInfo and nsIMobileCellInfo are handled explicitly below.
continue;
}
if (aDestInfo[key] !== aSrcInfo[key]) {
isUpdated = true;
aDestInfo[key] = aSrcInfo[key];
}
}
// Make sure we also reset the operator and signal strength information
// if we drop off the network.
if (aDestInfo.state !== RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED) {
aDestInfo.cell = null;
aDestInfo.network = null;
aDestInfo.signalStrength = null;
aDestInfo.relSignalStrength = null;
} else {
aDestInfo.network = this._operatorInfo;
// If no new cell data is passed, we should just keep the original cell
// data unchanged.
if (aSrcInfo.cell) {
if (!aDestInfo.cell) {
aDestInfo.cell = new MobileCellInfo();
}
isUpdated = this._updateInfo(aDestInfo.cell, aSrcInfo.cell) || isUpdated;
}
}
// Check roaming state
isUpdated = this._checkRoamingBetweenOperators(aDestInfo) || isUpdated;
return isUpdated;
},
/**
* The design of this updating function is to update the attribute in
* |aDestInfo| *only if* new data (e.g. aSrcInfo) contains the same attribute.
* Thus, for the attribute in |aDestInfo| that isn't showed in |aSrcInfo|, it
* should just keep the original value unchanged.
*/
_updateInfo: function(aDestInfo, aSrcInfo) {
let isUpdated = false;
for (let key in aSrcInfo) {
if (aDestInfo[key] !== aSrcInfo[key]) {
isUpdated = true;
aDestInfo[key] = aSrcInfo[key];
}
}
return isUpdated;
},
_rulesToCallForwardingOptions: function(aRules) {
return aRules.map(rule => new MobileCallForwardingOptions(rule));
},
_dispatchNotifyError: function(aCallback, aErrorMsg) {
Services.tm.currentThread.dispatch(() => aCallback.notifyError(aErrorMsg),
Ci.nsIThread.DISPATCH_NORMAL);
},
registerListener: function(aListener) {
if (this._listeners.indexOf(aListener) >= 0) {
throw Cr.NS_ERROR_UNEXPECTED;
}
this._listeners.push(aListener);
},
unregisterListener: function(aListener) {
let index = this._listeners.indexOf(aListener);
if (index >= 0) {
this._listeners.splice(index, 1);
}
},
deliverListenerEvent: function(aName, aArgs) {
let listeners = this._listeners.slice();
for (let listener of listeners) {
if (listeners.indexOf(listener) === -1) {
continue;
}
let handler = listener[aName];
if (typeof handler != "function") {
throw new Error("No handler for " + aName);
}
try {
handler.apply(listener, aArgs);
} catch (e) {
if (DEBUG) {
this._debug("listener for " + aName + " threw an exception: " + e);
}
}
}
},
updateVoiceInfo: function(aNewInfo, aBatch = false) {
let isUpdated = this._updateConnectionInfo(this.voice, aNewInfo);
if (isUpdated && !aBatch) {
this.deliverListenerEvent("notifyVoiceChanged");
}
},
updateDataInfo: function(aNewInfo, aBatch = false) {
// For the data connection, the `connected` flag indicates whether
// there's an active data call. We get correct `connected` state here.
let active = gNetworkManager.active;
aNewInfo.connected = false;
if (active &&
active.type === Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE &&
active.serviceId === this._clientId) {
aNewInfo.connected = true;
}
let isUpdated = this._updateConnectionInfo(this.data, aNewInfo);
if (isUpdated && !aBatch) {
this.deliverListenerEvent("notifyDataChanged");
}
},
updateOperatorInfo: function(aNewInfo, aBatch = false) {
let isUpdated = this._updateInfo(this._operatorInfo, aNewInfo);
// Update lastKnownNetwork
if (this._operatorInfo.mcc && this._operatorInfo.mnc) {
let network = this._operatorInfo.mcc + "-" + this._operatorInfo.mnc;
if (this.lastKnownNetwork !== network) {
if (DEBUG) {
this._debug("lastKnownNetwork now is " + network);
}
this.lastKnownNetwork = network;
this.deliverListenerEvent("notifyLastKnownNetworkChanged");
}
}
// If the voice is unregistered, no need to send notification.
if (this.voice.state !== RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED &&
isUpdated && !aBatch) {
this.deliverListenerEvent("notifyVoiceChanged");
}
// If the data is unregistered, no need to send notification.
if (this.data.state !== RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED &&
isUpdated && !aBatch) {
this.deliverListenerEvent("notifyDataChanged");
}
},
updateSignalInfo: function(aNewInfo, aBatch = false) {
// If the voice is not registered, no need to update signal information.
if (this.voice.state === RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED) {
if (this._updateInfo(this.voice, aNewInfo.voice) && !aBatch) {
this.deliverListenerEvent("notifyVoiceChanged");
}
}
// If the data is not registered, no need to update signal information.
if (this.data.state === RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED) {
if (this._updateInfo(this.data, aNewInfo.data) && !aBatch) {
this.deliverListenerEvent("notifyDataChanged");
}
}
},
updateIccId: function(aIccId) {
if (this.iccId === aIccId) {
return;
}
this.iccId = aIccId;
this.deliverListenerEvent("notifyIccChanged");
},
updateRadioState: function(aRadioState) {
if (this.radioState === aRadioState) {
return;
}
this.radioState = aRadioState;
this.deliverListenerEvent("notifyRadioStateChanged");
},
notifyCFStateChanged: function(aAction, aReason, aNumber, aTimeSeconds,
aServiceClass) {
this.deliverListenerEvent("notifyCFStateChanged",
[aAction, aReason, aNumber, aTimeSeconds,
aServiceClass]);
},
getSupportedNetworkTypes: function(aTypes) {
aTypes.value = this.supportedNetworkTypes.slice();
return aTypes.value.length;
},
getNetworks: function(aCallback) {
this._radioInterface.sendWorkerMessage("getAvailableNetworks", null,
(function(aResponse) {
if (aResponse.errorMsg) {
aCallback.notifyError(aResponse.errorMsg);
return false;
}
let networks = aResponse.networks;
for (let i = 0; i < networks.length; i++) {
let info = new MobileNetworkInfo();
this._updateInfo(info, networks[i]);
networks[i] = info;
}
aCallback.notifyGetNetworksSuccess(networks.length, networks);
return false;
}).bind(this));
},
selectNetwork: function(aNetwork, aCallback) {
if (!aNetwork ||
isNaN(parseInt(aNetwork.mcc, 10)) ||
isNaN(parseInt(aNetwork.mnc, 10))) {
this._dispatchNotifyError(aCallback, RIL.GECKO_ERROR_INVALID_PARAMETER);
return;
}
if (this._selectingNetwork) {
this._dispatchNotifyError(aCallback, "AlreadySelectingANetwork");
return;
}
let options = {mcc: aNetwork.mcc, mnc: aNetwork.mnc};
this._selectingNetwork = options;
this._radioInterface.sendWorkerMessage("selectNetwork", options,
(function(aResponse) {
this._selectingNetwork = null;
if (aResponse.errorMsg) {
aCallback.notifyError(aResponse.errorMsg);
return false;
}
aCallback.notifySuccess();
return false;
}).bind(this));
},
selectNetworkAutomatically: function(aCallback) {
if (this._selectingNetwork) {
this._dispatchNotifyError(aCallback, "AlreadySelectingANetwork");
return;
}
this._selectingNetwork = "automatic";
this._radioInterface.sendWorkerMessage("selectNetworkAuto", null,
(function(aResponse) {
this._selectingNetwork = null;
if (aResponse.errorMsg) {
aCallback.notifyError(aResponse.errorMsg);
return false;
}
aCallback.notifySuccess();
return false;
}).bind(this));
},
setPreferredNetworkType: function(aType, aCallback) {
if (this.radioState !== Ci.nsIMobileConnection.MOBILE_RADIO_STATE_ENABLED) {
this._dispatchNotifyError(aCallback, RIL.GECKO_ERROR_RADIO_NOT_AVAILABLE);
return;
}
this._radioInterface.sendWorkerMessage("setPreferredNetworkType",
{type: aType},
(function(aResponse) {
if (aResponse.errorMsg) {
aCallback.notifyError(aResponse.errorMsg);
return false;
}
aCallback.notifySuccess();
return false;
}).bind(this));
},
getPreferredNetworkType: function(aCallback) {
if (this.radioState !== Ci.nsIMobileConnection.MOBILE_RADIO_STATE_ENABLED) {
this._dispatchNotifyError(aCallback, RIL.GECKO_ERROR_RADIO_NOT_AVAILABLE);
return;
}
this._radioInterface.sendWorkerMessage("getPreferredNetworkType", null,
(function(aResponse) {
if (aResponse.errorMsg) {
aCallback.notifyError(aResponse.errorMsg);
return false;
}
aCallback.notifyGetPreferredNetworkTypeSuccess(aResponse.type);
return false;
}).bind(this));
},
setRoamingPreference: function(aMode, aCallback) {
this._radioInterface.sendWorkerMessage("setRoamingPreference",
{mode: aMode},
(function(aResponse) {
if (aResponse.errorMsg) {
aCallback.notifyError(aResponse.errorMsg);
return false;
}
aCallback.notifySuccess();
return false;
}).bind(this));
},
getRoamingPreference: function(aCallback) {
this._radioInterface.sendWorkerMessage("queryRoamingPreference", null,
(function(aResponse) {
if (aResponse.errorMsg) {
aCallback.notifyError(aResponse.errorMsg);
return false;
}
aCallback.notifyGetRoamingPreferenceSuccess(aResponse.mode);
return false;
}).bind(this));
},
setVoicePrivacyMode: function(aEnabled, aCallback) {
this._radioInterface.sendWorkerMessage("setVoicePrivacyMode",
{enabled: aEnabled},
(function(aResponse) {
if (aResponse.errorMsg) {
aCallback.notifyError(aResponse.errorMsg);
return false;
}
aCallback.notifySuccess();
return false;
}).bind(this));
},
getVoicePrivacyMode: function(aCallback) {
this._radioInterface.sendWorkerMessage("queryVoicePrivacyMode", null,
(function(aResponse) {
if (aResponse.errorMsg) {
aCallback.notifyError(aResponse.errorMsg);
return false;
}
aCallback.notifySuccessWithBoolean(aResponse.enabled);
return false;
}).bind(this));
},
sendMMI: function(aMmi, aCallback) {
let callback = new TelephonyDialCallback(aCallback);
gGonkTelephonyService.dialMMI(this._clientId, aMmi, callback);
},
cancelMMI: function(aCallback) {
this._radioInterface.sendWorkerMessage("cancelUSSD", null,
(function(aResponse) {
if (aResponse.errorMsg) {
aCallback.notifyError(aResponse.errorMsg);
return false;
}
aCallback.notifySuccess();
return false;
}).bind(this));
},
setCallForwarding: function(aAction, aReason, aNumber, aTimeSeconds,
aServiceClass, aCallback) {
let options = {
action: aAction,
reason: aReason,
number: aNumber,
timeSeconds: aTimeSeconds,
serviceClass: RIL.ICC_SERVICE_CLASS_VOICE
};
this._radioInterface.sendWorkerMessage("setCallForward", options,
(function(aResponse) {
if (aResponse.errorMsg) {
aCallback.notifyError(aResponse.errorMsg);
return false;
}
this.notifyCFStateChanged(aResponse.action, aResponse.reason,
aResponse.number, aResponse.timeSeconds,
aResponse.serviceClass);
aCallback.notifySuccess();
return false;
}).bind(this));
},
getCallForwarding: function(aReason, aCallback) {
this._radioInterface.sendWorkerMessage("queryCallForwardStatus",
{reason: aReason},
(function(aResponse) {
if (aResponse.errorMsg) {
aCallback.notifyError(aResponse.errorMsg);
return false;
}
let infos = this._rulesToCallForwardingOptions(aResponse.rules);
aCallback.notifyGetCallForwardingSuccess(infos.length, infos);
return false;
}).bind(this));
},
setCallBarring: function(aProgram, aEnabled, aPassword, aServiceClass,
aCallback) {
let options = {
program: aProgram,
enabled: aEnabled,
password: aPassword,
serviceClass: aServiceClass
};
this._radioInterface.sendWorkerMessage("setCallBarring", options,
(function(aResponse) {
if (aResponse.errorMsg) {
aCallback.notifyError(aResponse.errorMsg);
return false;
}
aCallback.notifySuccess();
return false;
}).bind(this));
},
getCallBarring: function(aProgram, aPassword, aServiceClass, aCallback) {
let options = {
program: aProgram,
password: aPassword,
serviceClass: aServiceClass
};
this._radioInterface.sendWorkerMessage("queryCallBarringStatus", options,
(function(aResponse) {
if (aResponse.errorMsg) {
aCallback.notifyError(aResponse.errorMsg);
return false;
}
aCallback.notifyGetCallBarringSuccess(aResponse.program,
aResponse.enabled,
aResponse.serviceClass);
return false;
}).bind(this));
},
changeCallBarringPassword: function(aPin, aNewPin, aCallback) {
let options = {
pin: aPin,
newPin: aNewPin
};
this._radioInterface.sendWorkerMessage("changeCallBarringPassword", options,
(function(aResponse) {
if (aResponse.errorMsg) {
aCallback.notifyError(aResponse.errorMsg);
return false;
}
aCallback.notifySuccess();
return false;
}).bind(this));
},
setCallWaiting: function(aEnabled, aCallback) {
this._radioInterface.sendWorkerMessage("setCallWaiting",
{enabled: aEnabled},
(function(aResponse) {
if (aResponse.errorMsg) {
aCallback.notifyError(aResponse.errorMsg);
return false;
}
aCallback.notifySuccess();
return false;
}).bind(this));
},
getCallWaiting: function(aCallback) {
this._radioInterface.sendWorkerMessage("queryCallWaiting", null,
(function(aResponse) {
if (aResponse.errorMsg) {
aCallback.notifyError(aResponse.errorMsg);
return false;
}
aCallback.notifySuccessWithBoolean(aResponse.enabled);
return false;
}).bind(this));
},
setCallingLineIdRestriction: function(aMode, aCallback) {
if (!this._isValidClirMode(aMode)) {
this._dispatchNotifyError(aCallback, RIL.GECKO_ERROR_INVALID_PARAMETER);
return;
}
if (this.radioState !== Ci.nsIMobileConnection.MOBILE_RADIO_STATE_ENABLED) {
this._dispatchNotifyError(aCallback, RIL.GECKO_ERROR_RADIO_NOT_AVAILABLE);
return;
}
this._radioInterface.sendWorkerMessage("setCLIR", {clirMode: aMode},
(function(aResponse) {
if (aResponse.errorMsg) {
aCallback.notifyError(aResponse.errorMsg);
return false;
}
this.deliverListenerEvent("notifyClirModeChanged", [aResponse.mode]);
aCallback.notifySuccess();
return false;
}).bind(this));
},
getCallingLineIdRestriction: function(aCallback) {
if (this.radioState !== Ci.nsIMobileConnection.MOBILE_RADIO_STATE_ENABLED) {
this._dispatchNotifyError(aCallback, RIL.GECKO_ERROR_RADIO_NOT_AVAILABLE);
return;
}
this._radioInterface.sendWorkerMessage("getCLIR", null,
(function(aResponse) {
if (aResponse.errorMsg) {
aCallback.notifyError(aResponse.errorMsg);
return false;
}
aCallback.notifyGetClirStatusSuccess(aResponse.n, aResponse.m);
return false;
}).bind(this));
},
exitEmergencyCbMode: function(aCallback) {
this._radioInterface.sendWorkerMessage("exitEmergencyCbMode", null,
(function(aResponse) {
if (aResponse.errorMsg) {
aCallback.notifyError(aResponse.errorMsg);
return false;
}
aCallback.notifySuccess();
return false;
}).bind(this));
},
setRadioEnabled: function(aEnabled, aCallback) {
this._radioInterface.sendWorkerMessage("setRadioEnabled",
{enabled: aEnabled},
(function(aResponse) {
if (aResponse.errorMsg) {
aCallback.notifyError(aResponse.errorMsg);
return true;
}
aCallback.notifySuccess();
return true;
}).bind(this));
},
getCellInfoList: function(aCallback) {
this._radioInterface.sendWorkerMessage("getCellInfoList",
null,
function(aResponse) {
if (aResponse.errorMsg) {
aCallback.notifyGetCellInfoListFailed(aResponse.errorMsg);
return;
}
let cellInfoList = [];
let count = aResponse.result.length;
for (let i = 0; i < count; i++) {
let srcCellInfo = aResponse.result[i];
let cellInfo;
switch (srcCellInfo.type) {
case RIL.CELL_INFO_TYPE_GSM:
cellInfo = new GsmCellInfo();
break;
case RIL.CELL_INFO_TYPE_WCDMA:
cellInfo = new WcdmaCellInfo();
break;
case RIL.CELL_INFO_TYPE_LTE:
cellInfo = new LteCellInfo();
break;
case RIL.CELL_INFO_TYPE_CDMA:
cellInfo = new CdmaCellInfo();
break;
}
if (!cellInfo) {
continue;
}
this._updateInfo(cellInfo, srcCellInfo);
cellInfoList.push(cellInfo);
}
aCallback.notifyGetCellInfoList(count, cellInfoList);
}.bind(this));
},
getNeighboringCellIds: function(aCallback) {
this._radioInterface.sendWorkerMessage("getNeighboringCellIds",
null,
function(aResponse) {
if (aResponse.errorMsg) {
aCallback.notifyGetNeighboringCellIdsFailed(aResponse.errorMsg);
return;
}
let neighboringCellIds = [];
let count = aResponse.result.length;
for (let i = 0; i < count; i++) {
let srcCellInfo = aResponse.result[i];
let cellInfo = new NeighboringCellInfo();
this._updateInfo(cellInfo, srcCellInfo);
neighboringCellIds.push(cellInfo);
}
aCallback.notifyGetNeighboringCellIds(count, neighboringCellIds);
}.bind(this));
},
};
function MobileConnectionService() {
this._providers = [];
let numClients = gRadioInterfaceLayer.numRadioInterfaces;
for (let i = 0; i < numClients; i++) {
let radioInterface = gRadioInterfaceLayer.getRadioInterface(i);
let provider = new MobileConnectionProvider(i, radioInterface);
this._providers.push(provider);
}
Services.prefs.addObserver(kPrefRilDebuggingEnabled, this, false);
Services.obs.addObserver(this, NS_NETWORK_ACTIVE_CHANGED_TOPIC_ID, false);
Services.obs.addObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
debug("init complete");
}
MobileConnectionService.prototype = {
classID: GONK_MOBILECONNECTIONSERVICE_CID,
classInfo: XPCOMUtils.generateCI({classID: GONK_MOBILECONNECTIONSERVICE_CID,
contractID: GONK_MOBILECONNECTIONSERVICE_CONTRACTID,
classDescription: "MobileConnectionService",
interfaces: [Ci.nsIGonkMobileConnectionService,
Ci.nsIMobileConnectionService],
flags: Ci.nsIClassInfo.SINGLETON}),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIGonkMobileConnectionService,
Ci.nsIMobileConnectionService,
Ci.nsIObserver]),
// An array of MobileConnectionProvider instances.
_providers: null,
_shutdown: function() {
Services.prefs.removeObserver(kPrefRilDebuggingEnabled, this);
Services.obs.removeObserver(this, NS_NETWORK_ACTIVE_CHANGED_TOPIC_ID);
Services.obs.removeObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
},
_updateDebugFlag: function() {
try {
DEBUG = RIL.DEBUG_RIL ||
Services.prefs.getBoolPref(kPrefRilDebuggingEnabled);
} catch (e) {}
},
_broadcastCdmaInfoRecordSystemMessage: function(aMessage) {
// TODO: Bug 1072808, Broadcast System Message with proxy.
gSystemMessenger.broadcastMessage("cdma-info-rec-received", aMessage);
},
/**
* nsIMobileConnectionService interface.
*/
get numItems() {
return this._providers.length;
},
getItemByServiceId: function(aServiceId) {
let provider = this._providers[aServiceId];
if (!provider) {
throw Cr.NS_ERROR_UNEXPECTED;
}
return provider;
},
/**
* nsIGonkMobileConnectionService interface.
*/
notifyVoiceInfoChanged: function(aClientId, aVoiceInfo) {
if (DEBUG) {
debug("notifyVoiceInfoChanged for " + aClientId + ": " +
JSON.stringify(aVoiceInfo));
}
this.getItemByServiceId(aClientId).updateVoiceInfo(aVoiceInfo);
},
notifyDataInfoChanged: function(aClientId, aDataInfo) {
if (DEBUG) {
debug("notifyDataInfoChanged for " + aClientId + ": " +
JSON.stringify(aDataInfo));
}
this.getItemByServiceId(aClientId).updateDataInfo(aDataInfo);
},
notifyUssdReceived: function(aClientId, aMessage, aSessionEnded) {
if (DEBUG) {
debug("notifyUssdReceived for " + aClientId + ": " +
aMessage + " (sessionEnded : " + aSessionEnded + ")");
}
this.getItemByServiceId(aClientId)
.deliverListenerEvent("notifyUssdReceived", [aMessage, aSessionEnded]);
},
notifyDataError: function(aClientId, aMessage) {
if (DEBUG) {
debug("notifyDataError for " + aClientId + ": " + aMessage);
}
this.getItemByServiceId(aClientId)
.deliverListenerEvent("notifyDataError", [aMessage]);
},
notifyEmergencyCallbackModeChanged: function(aClientId, aActive, aTimeoutMs) {
if (DEBUG) {
debug("notifyEmergencyCbModeChanged for " + aClientId + ": " +
JSON.stringify({active: aActive, timeoutMs: aTimeoutMs}));
}
this.getItemByServiceId(aClientId)
.deliverListenerEvent("notifyEmergencyCbModeChanged",
[aActive, aTimeoutMs]);
},
notifyOtaStatusChanged: function(aClientId, aStatus) {
if (DEBUG) {
debug("notifyOtaStatusChanged for " + aClientId + ": " + aStatus);
}
this.getItemByServiceId(aClientId)
.deliverListenerEvent("notifyOtaStatusChanged", [aStatus]);
},
notifyIccChanged: function(aClientId, aIccId) {
if (DEBUG) {
debug("notifyIccChanged for " + aClientId + ": " + aIccId);
}
this.getItemByServiceId(aClientId).updateIccId(aIccId);
},
notifyRadioStateChanged: function(aClientId, aRadioState) {
if (DEBUG) {
debug("notifyRadioStateChanged for " + aClientId + ": " + aRadioState);
}
this.getItemByServiceId(aClientId).updateRadioState(aRadioState);
},
notifyNetworkInfoChanged: function(aClientId, aNetworkInfo) {
if (DEBUG) {
debug("notifyNetworkInfoChanged for " + aClientId + ": " +
JSON.stringify(aNetworkInfo));
}
let provider = this.getItemByServiceId(aClientId);
let isVoiceUpdated = false;
let isDataUpdated = false;
let operatorMessage = aNetworkInfo[RIL.NETWORK_INFO_OPERATOR];
let voiceMessage = aNetworkInfo[RIL.NETWORK_INFO_VOICE_REGISTRATION_STATE];
let dataMessage = aNetworkInfo[RIL.NETWORK_INFO_DATA_REGISTRATION_STATE];
let signalMessage = aNetworkInfo[RIL.NETWORK_INFO_SIGNAL];
let selectionMessage = aNetworkInfo[RIL.NETWORK_INFO_NETWORK_SELECTION_MODE];
// Batch the *InfoChanged messages together
if (operatorMessage) {
provider.updateOperatorInfo(operatorMessage, true);
}
if (voiceMessage) {
provider.updateVoiceInfo(voiceMessage, true);
}
if (dataMessage) {
provider.updateDataInfo(dataMessage, true);
}
if (signalMessage) {
provider.updateSignalInfo(signalMessage, true);
}
if (selectionMessage) {
this.notifyNetworkSelectModeChanged(aClientId, selectionMessage.mode);
}
if (voiceMessage || operatorMessage || signalMessage) {
provider.deliverListenerEvent("notifyVoiceChanged");
}
if (dataMessage || operatorMessage || signalMessage) {
provider.deliverListenerEvent("notifyDataChanged");
}
},
notifySignalStrengthChanged: function(aClientId, aSignal) {
if (DEBUG) {
debug("notifySignalStrengthChanged for " + aClientId + ": " +
JSON.stringify(aSignal));
}
this.getItemByServiceId(aClientId).updateSignalInfo(aSignal);
},
notifyOperatorChanged: function(aClientId, aOperator) {
if (DEBUG) {
debug("notifyOperatorChanged for " + aClientId + ": " +
JSON.stringify(aOperator));
}
this.getItemByServiceId(aClientId).updateOperatorInfo(aOperator);
},
notifyNetworkSelectModeChanged: function(aClientId, aMode) {
if (DEBUG) {
debug("notifyNetworkSelectModeChanged for " + aClientId + ": " + aMode);
}
let provider = this.getItemByServiceId(aClientId);
if (provider.networkSelectionMode === aMode) {
return;
}
provider.networkSelectionMode = aMode;
provider.deliverListenerEvent("notifyNetworkSelectionModeChanged");
},
notifySpnAvailable: function(aClientId) {
if (DEBUG) {
debug("notifySpnAvailable for " + aClientId);
}
let provider = this.getItemByServiceId(aClientId);
// Update voice roaming state
provider.updateVoiceInfo({});
// Update data roaming state
provider.updateDataInfo({});
},
notifyLastHomeNetworkChanged: function(aClientId, aNetwork) {
if (DEBUG) {
debug("notifyLastHomeNetworkChanged for " + aClientId + ": " + aNetwork);
}
let provider = this.getItemByServiceId(aClientId);
if (provider.lastKnownHomeNetwork === aNetwork) {
return;
}
provider.lastKnownHomeNetwork = aNetwork;
provider.deliverListenerEvent("notifyLastKnownHomeNetworkChanged");
},
notifyCFStateChanged: function(aClientId, aAction, aReason, aNumber,
aTimeSeconds, aServiceClass) {
if (DEBUG) {
debug("notifyCFStateChanged for " + aClientId);
}
let provider = this.getItemByServiceId(aClientId);
provider.notifyCFStateChanged(aAction, aReason, aNumber, aTimeSeconds,
aServiceClass);
},
notifyCdmaInfoRecDisplay: function(aClientId, aDisplay) {
this._broadcastCdmaInfoRecordSystemMessage({
clientId: aClientId,
display: aDisplay
});
},
notifyCdmaInfoRecCalledPartyNumber: function(aClientId, aType, aPlan, aNumber,
aPi, aSi) {
this._broadcastCdmaInfoRecordSystemMessage({
clientId: aClientId,
calledNumber: {
type: aType,
plan: aPlan,
number: aNumber,
pi: aPi,
si: aSi
}
});
},
notifyCdmaInfoRecCallingPartyNumber: function(aClientId, aType, aPlan, aNumber,
aPi, aSi) {
this._broadcastCdmaInfoRecordSystemMessage({
clientId: aClientId,
callingNumber: {
type: aType,
plan: aPlan,
number: aNumber,
pi: aPi,
si: aSi
}
});
},
notifyCdmaInfoRecConnectedPartyNumber: function(aClientId, aType, aPlan, aNumber,
aPi, aSi) {
this._broadcastCdmaInfoRecordSystemMessage({
clientId: aClientId,
connectedNumber: {
type: aType,
plan: aPlan,
number: aNumber,
pi: aPi,
si: aSi
}
});
},
notifyCdmaInfoRecSignal: function(aClientId, aType, aAlertPitch, aSignal){
this._broadcastCdmaInfoRecordSystemMessage({
clientId: aClientId,
signal: {
type: aType,
alertPitch: aAlertPitch,
signal: aSignal
}
});
},
notifyCdmaInfoRecRedirectingNumber: function(aClientId, aType, aPlan, aNumber,
aPi, aSi, aReason) {
this._broadcastCdmaInfoRecordSystemMessage({
clientId: aClientId,
redirect: {
type: aType,
plan: aPlan,
number: aNumber,
pi: aPi,
si: aSi,
reason: aReason
}
});
},
notifyCdmaInfoRecLineControl: function(aClientId, aPolarityIncluded, aToggle,
aReverse, aPowerDenial) {
this._broadcastCdmaInfoRecordSystemMessage({
clientId: aClientId,
lineControl: {
polarityIncluded: aPolarityIncluded,
toggle: aToggle,
reverse: aReverse,
powerDenial: aPowerDenial
}
});
},
notifyCdmaInfoRecClir: function(aClientId, aCause) {
this._broadcastCdmaInfoRecordSystemMessage({
clientId: aClientId,
clirCause: aCause
});
},
notifyCdmaInfoRecAudioControl: function(aClientId, aUplink, aDownLink) {
this._broadcastCdmaInfoRecordSystemMessage({
clientId: aClientId,
audioControl: {
upLink: aUplink,
downLink: aDownLink
}
});
},
/**
* nsIObserver interface.
*/
observe: function(aSubject, aTopic, aData) {
switch (aTopic) {
case NS_NETWORK_ACTIVE_CHANGED_TOPIC_ID:
for (let i = 0; i < this.numItems; i++) {
let provider = this._providers[i];
// Update connected flag only.
provider.updateDataInfo({});
}
break;
case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID:
if (aData === kPrefRilDebuggingEnabled) {
this._updateDebugFlag();
}
break;
case NS_XPCOM_SHUTDOWN_OBSERVER_ID:
this._shutdown();
break;
}
}
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MobileConnectionService]);