gecko-dev/dom/mobileconnection/gonk/MobileConnectionService.js
2014-09-22 01:36:00 -04:00

1256 lines
39 KiB
JavaScript

/* -*- 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 TELEPHONYCALLBACK_CID =
Components.ID("{6e1af17e-37f3-11e4-aed3-60a44c237d2b}");
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";
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 CallForwardingOptions(aOptions) {
this.active = aOptions.active;
this.action = aOptions.action;
this.reason = aOptions.reason;
this.number = aOptions.number;
this.timeSeconds = aOptions.timeSeconds;
this.serviceClass = aOptions.serviceClass;
}
CallForwardingOptions.prototype = {
__exposedProps__ : {active: 'r',
action: 'r',
reason: 'r',
number: 'r',
timeSeconds: 'r',
serviceClass: 'r'},
};
function MMIResult(aOptions) {
this.serviceCode = aOptions.serviceCode;
this.statusMessage = aOptions.statusMessage;
this.additionalInformation = aOptions.additionalInformation;
}
MMIResult.prototype = {
__exposedProps__ : {serviceCode: 'r',
statusMessage: 'r',
additionalInformation: 'r'},
};
/**
* Wrap a MobileConnectionCallback to a TelephonyCallback.
*/
function TelephonyCallback(aCallback) {
this.callback = aCallback;
}
TelephonyCallback.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsITelephonyCallback]),
classID: TELEPHONYCALLBACK_CID,
notifyDialMMI: function(mmiServiceCode) {
this.serviceCode = mmiServiceCode;
},
notifyDialMMISuccess: function(result) {
this.callback.notifySendCancelMmiSuccess(result);
},
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: null,
radioState: null,
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(",");
}
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);
}
supportedNetworkTypes =
RIL.GECKO_SUPPORTED_NETWORK_TYPES_DEFAULT.split(",");
break;
}
}
if (DEBUG) {
this._debug("Supported Network Types: " + supportedNetworkTypes);
}
return supportedNetworkTypes;
},
/**
* Helper for guarding us against invalid reason values for call forwarding.
*/
_isValidCallForwardingReason: function(aReason) {
switch (aReason) {
case Ci.nsIMobileConnection.CALL_FORWARD_REASON_UNCONDITIONAL:
case Ci.nsIMobileConnection.CALL_FORWARD_REASON_MOBILE_BUSY:
case Ci.nsIMobileConnection.CALL_FORWARD_REASON_NO_REPLY:
case Ci.nsIMobileConnection.CALL_FORWARD_REASON_NOT_REACHABLE:
case Ci.nsIMobileConnection.CALL_FORWARD_REASON_ALL_CALL_FORWARDING:
case Ci.nsIMobileConnection.CALL_FORWARD_REASON_ALL_CONDITIONAL_CALL_FORWARDING:
return true;
default:
return false;
}
},
/**
* Helper for guarding us against invalid action values for call forwarding.
*/
_isValidCallForwardingAction: function(aAction) {
switch (aAction) {
case Ci.nsIMobileConnection.CALL_FORWARD_ACTION_DISABLE:
case Ci.nsIMobileConnection.CALL_FORWARD_ACTION_ENABLE:
case Ci.nsIMobileConnection.CALL_FORWARD_ACTION_REGISTRATION:
case Ci.nsIMobileConnection.CALL_FORWARD_ACTION_ERASURE:
return true;
default:
return false;
}
},
/**
* Helper for guarding us against invalid program values for call barring.
*/
_isValidCallBarringProgram: function(aProgram) {
switch (aProgram) {
case Ci.nsIMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING:
case Ci.nsIMobileConnection.CALL_BARRING_PROGRAM_OUTGOING_INTERNATIONAL:
case Ci.nsIMobileConnection.CALL_BARRING_PROGRAM_OUTGOING_INTERNATIONAL_EXCEPT_HOME:
case Ci.nsIMobileConnection.CALL_BARRING_PROGRAM_ALL_INCOMING:
case Ci.nsIMobileConnection.CALL_BARRING_PROGRAM_INCOMING_ROAMING:
return true;
default:
return false;
}
},
/**
* Helper for guarding us against invalid options for call barring.
*/
_isValidCallBarringOptions: function(aOptions, aUsedForSetting) {
if (!aOptions || aOptions.serviceClass == null ||
!this._isValidCallBarringProgram(aOptions.program)) {
return false;
}
// For setting callbarring options, |enabled| and |password| are required.
if (aUsedForSetting &&
(aOptions.enabled == null || aOptions.password == null)) {
return false;
}
return true;
},
/**
* 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;
},
_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 (aSrcInfo.cell == null) {
if (aDestInfo.cell != null) {
isUpdated = true;
aDestInfo.cell = null;
}
} else {
if (aDestInfo.cell == null) {
aDestInfo.cell = new MobileCellInfo();
}
isUpdated = this._updateInfo(aDestInfo.cell, aSrcInfo.cell) || isUpdated;
}
}
// Check roaming state
isUpdated = this._checkRoamingBetweenOperators(aDestInfo) || isUpdated;
return isUpdated;
},
_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 CallForwardingOptions(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",
[true, 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 !== RIL.GECKO_RADIOSTATE_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 !== RIL.GECKO_RADIOSTATE_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.notifySuccessWithString(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.notifySuccessWithString(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 telephonyCallback = new TelephonyCallback(aCallback);
gGonkTelephonyService.dialMMI(this._clientId, aMmi, telephonyCallback);
},
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(aOptions, aCallback) {
if (!aOptions ||
!this._isValidCallForwardingReason(aOptions.reason) ||
!this._isValidCallForwardingAction(aOptions.action)){
this._dispatchNotifyError(aCallback, RIL.GECKO_ERROR_INVALID_PARAMETER);
return;
}
let options = {
active: aOptions.active,
action: aOptions.action,
reason: aOptions.reason,
number: aOptions.number,
timeSeconds: aOptions.timeSeconds,
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) {
if (!this._isValidCallForwardingReason(aReason)){
this._dispatchNotifyError(aCallback, RIL.GECKO_ERROR_INVALID_PARAMETER);
return;
}
this._radioInterface.sendWorkerMessage("queryCallForwardStatus",
{reason: aReason},
(function(aResponse) {
if (aResponse.errorMsg) {
aCallback.notifyError(aResponse.errorMsg);
return false;
}
aCallback.notifyGetCallForwardingSuccess(
this._rulesToCallForwardingOptions(aResponse.rules));
return false;
}).bind(this));
},
setCallBarring: function(aOptions, aCallback) {
if (!this._isValidCallBarringOptions(aOptions, true)) {
this._dispatchNotifyError(aCallback, RIL.GECKO_ERROR_INVALID_PARAMETER);
return;
}
let options = {
program: aOptions.program,
enabled: aOptions.enabled,
password: aOptions.password,
serviceClass: aOptions.serviceClass
};
this._radioInterface.sendWorkerMessage("setCallBarring", options,
(function(aResponse) {
if (aResponse.errorMsg) {
aCallback.notifyError(aResponse.errorMsg);
return false;
}
aCallback.notifySuccess();
return false;
}).bind(this));
},
getCallBarring: function(aOptions, aCallback) {
if (!this._isValidCallBarringOptions(aOptions)) {
this._dispatchNotifyError(aCallback, RIL.GECKO_ERROR_INVALID_PARAMETER);
return;
}
let options = {
program: aOptions.program,
password: aOptions.password,
serviceClass: aOptions.serviceClass
};
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(aOptions, aCallback) {
// Checking valid PIN for supplementary services. See TS.22.004 clause 5.2.
if (aOptions.pin == null || !aOptions.pin.match(/^\d{4}$/) ||
aOptions.newPin == null || !aOptions.newPin.match(/^\d{4}$/)) {
this._dispatchNotifyError(aCallback, "InvalidPassword");
return;
}
let options = {
pin: aOptions.pin,
newPin: aOptions.newPin
};
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 !== RIL.GECKO_RADIOSTATE_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 !== RIL.GECKO_RADIOSTATE_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));
},
};
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) {}
},
/**
* 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]);
let info = {
message: aMessage,
sessionEnded: aSessionEnded,
serviceId: aClientId
};
gSystemMessenger.broadcastMessage("ussd-received", info);
},
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);
},
/**
* 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]);