Bug 759637: Initial implementation of MobileConnection network selection APIs. r=philikon sr=sicking

This commit is contained in:
Marshall Culpepper 2012-06-19 15:52:06 -07:00
parent 3735b460f3
commit dfaa41334c
8 changed files with 717 additions and 56 deletions

View File

@ -30,6 +30,13 @@ interface nsIDOMMozMobileConnection : nsIDOMEventTarget
*/
readonly attribute nsIDOMMozMobileConnectionInfo data;
/**
* The selection mode of the voice and data networks.
*
* Possible values: null (unknown), 'automatic', 'manual'
*/
readonly attribute DOMString networkSelectionMode;
/**
* Search for available networks.
*
@ -37,10 +44,38 @@ interface nsIDOMMozMobileConnection : nsIDOMEventTarget
* result will be an array of nsIDOMMozMobileNetworkInfo.
*
* Otherwise, the request's onerror will be called, and the request's error
* will be either 'RadioNotAvailable', 'RequestNotSupported', or 'GenericFailure'.
* will be either 'RadioNotAvailable', 'RequestNotSupported',
* or 'GenericFailure'.
*/
nsIDOMDOMRequest getNetworks();
/**
* Manually selects the passed in network, overriding the radio's current
* selection.
*
* If successful, the request's onsuccess will be called.
* Note: If the network was actually changed by this request,
* the 'voicechange' and 'datachange' events will also be fired.
*
* Otherwise, the request's onerror will be called, and the request's error
* will be either 'RadioNotAvailable', 'RequestNotSupported',
* 'IllegalSIMorME', or 'GenericFailure'
*/
nsIDOMDOMRequest selectNetwork(in nsIDOMMozMobileNetworkInfo network);
/**
* Tell the radio to automatically select a network.
*
* If successful, the request's onsuccess will be called.
* Note: If the network was actually changed by this request, the
* 'voicechange' and 'datachange' events will also be fired.
*
* Otherwise, the request's onerror will be called, and the request's error
* will be either 'RadioNotAvailable', 'RequestNotSupported',
* 'IllegalSIMorME', or 'GenericFailure'
*/
nsIDOMDOMRequest selectNetworkAutomatically();
/**
* Find out about the status of an ICC lock (e.g. the PIN lock).
*

View File

@ -5,6 +5,7 @@
#include "nsISupports.idl"
interface nsIDOMMozMobileConnectionInfo;
interface nsIDOMMozMobileNetworkInfo;
interface nsIDOMDOMRequest;
interface nsIDOMWindow;
@ -18,8 +19,12 @@ interface nsIMobileConnectionProvider : nsISupports
readonly attribute DOMString cardState;
readonly attribute nsIDOMMozMobileConnectionInfo voiceConnectionInfo;
readonly attribute nsIDOMMozMobileConnectionInfo dataConnectionInfo;
readonly attribute DOMString networkSelectionMode;
nsIDOMDOMRequest getNetworks(in nsIDOMWindow window);
nsIDOMDOMRequest selectNetwork(in nsIDOMWindow window, in nsIDOMMozMobileNetworkInfo network);
nsIDOMDOMRequest selectNetworkAutomatically(in nsIDOMWindow window);
nsIDOMDOMRequest getCardLock(in nsIDOMWindow window, in DOMString lockType);
nsIDOMDOMRequest unlockCardLock(in nsIDOMWindow window, in jsval info);
nsIDOMDOMRequest setCardLock(in nsIDOMWindow window, in jsval info);

View File

@ -170,6 +170,16 @@ MobileConnection::GetData(nsIDOMMozMobileConnectionInfo** data)
return mProvider->GetDataConnectionInfo(data);
}
NS_IMETHODIMP
MobileConnection::GetNetworkSelectionMode(nsAString& networkSelectionMode)
{
if (!mProvider) {
networkSelectionMode.SetIsVoid(true);
return NS_OK;
}
return mProvider->GetNetworkSelectionMode(networkSelectionMode);
}
NS_IMETHODIMP
MobileConnection::GetNetworks(nsIDOMDOMRequest** request)
{
@ -182,6 +192,30 @@ MobileConnection::GetNetworks(nsIDOMDOMRequest** request)
return mProvider->GetNetworks(GetOwner(), request);
}
NS_IMETHODIMP
MobileConnection::SelectNetwork(nsIDOMMozMobileNetworkInfo* network, nsIDOMDOMRequest** request)
{
*request = nsnull;
if (!mProvider) {
return NS_ERROR_FAILURE;
}
return mProvider->SelectNetwork(GetOwner(), network, request);
}
NS_IMETHODIMP
MobileConnection::SelectNetworkAutomatically(nsIDOMDOMRequest** request)
{
*request = nsnull;
if (!mProvider) {
return NS_ERROR_FAILURE;
}
return mProvider->SelectNetworkAutomatically(GetOwner(), request);
}
NS_IMETHODIMP
MobileConnection::GetCardLock(const nsAString& aLockType, nsIDOMDOMRequest** aDomRequest)
{

View File

@ -3,7 +3,7 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
// getNetworks() can take some time..
MARIONETTE_TIMEOUT = 30000;
MARIONETTE_TIMEOUT = 60000;
const WHITELIST_PREF = "dom.mobileconnection.whitelist";
let uriPrePath = window.location.protocol + "//" + window.location.host;
@ -13,6 +13,11 @@ let connection = navigator.mozMobileConnection;
ok(connection instanceof MozMobileConnection,
"connection is instanceof " + connection.constructor);
is(connection.networkSelectionMode, "automatic");
let androidNetwork = null;
let telkilaNetwork = null;
function isAndroidNetwork(network) {
is(network.longName, "Android");
is(network.shortName, "Android");
@ -20,6 +25,13 @@ function isAndroidNetwork(network) {
is(network.mnc, 260);
}
function isTelkilaNetwork(network) {
is(network.longName, "TelKila");
is(network.shortName, "TelKila");
is(network.mcc, 310);
is(network.mnc, 295);
}
function testConnectionInfo() {
let voice = connection.voice;
is(voice.connected, true);
@ -28,7 +40,7 @@ function testConnectionInfo() {
isAndroidNetwork(voice.network);
let data = connection.data;
// TODO Bug 762959: enable these checks when data state updates have been implemented
// TODO Bug 762959: enable these checks when data state updates are implemented
// is(data.connected, true);
// is(data.emergencyCallsOnly, false);
// is(data.roaming, false);
@ -40,11 +52,11 @@ function testConnectionInfo() {
function testGetNetworks() {
let request = connection.getNetworks();
ok(request instanceof DOMRequest,
"request is instanceof " + request.constructor);
"request is instanceof " + request.constructor);
request.onerror = function() {
ok(false, request.error);
cleanUp();
setTimeout(testSelectNetwork, 0);
};
request.onsuccess = function() {
@ -56,16 +68,165 @@ function testGetNetworks() {
// {"longName":"TelKila","shortName":"TelKila","mcc":310,"mnc":295,"state":"available"}
is(networks.length, 2);
let network1 = networks[0];
let network1 = androidNetwork = networks[0];
isAndroidNetwork(network1);
is(network1.state, "available");
let network2 = networks[1];
is(network2.longName, "TelKila");
is(network2.shortName, "TelKila");
is(network2.mcc, 310);
is(network2.mnc, 295);
let network2 = telkilaNetwork = networks[1];
isTelkilaNetwork(network2);
is(network2.state, "available");
setTimeout(testSelectNetwork, 0);
};
}
function testSelectNetwork() {
let request = connection.selectNetwork(telkilaNetwork);
ok(request instanceof DOMRequest,
"request instanceof " + request.constructor);
connection.addEventListener("voicechange", function voiceChange() {
connection.removeEventListener("voicechange", voiceChange);
isTelkilaNetwork(connection.voice.network);
setTimeout(testSelectNetworkAutomatically, 0);
});
request.onsuccess = function() {
is(connection.networkSelectionMode, "manual",
"selectNetwork sets mode to: " + connection.networkSelectionMode);
};
request.onerror = function() {
ok(false, request.error);
setTimeout(testSelectNetworkAutomatically, 0);
};
}
function testSelectNetworkAutomatically() {
let request = connection.selectNetworkAutomatically();
ok(request instanceof DOMRequest,
"request instanceof " + request.constructor);
connection.addEventListener("voicechange", function voiceChange() {
connection.removeEventListener("voicechange", voiceChange);
isAndroidNetwork(connection.voice.network);
setTimeout(testSelectNetworkErrors, 0);
});
request.onsuccess = function() {
is(connection.networkSelectionMode, "automatic",
"selectNetworkAutomatically sets mode to: " +
connection.networkSelectionMode);
};
request.onerror = function() {
ok(false, request.error);
setTimeout(testSelectNetworkErrors, 0);
};
}
function throwsException(fn) {
try {
fn();
ok(false, "function did not throw an exception: " + fn);
} catch (e) {
ok(true, "function succesfully caught exception: " + e);
}
}
function testSelectNetworkErrors() {
throwsException(function() {
connection.selectNetwork(null);
});
throwsException(function() {
connection.selectNetwork({});
});
connection.addEventListener("voicechange", function voiceChange() {
connection.removeEventListener("voicechange", voiceChange);
setTimeout(testSelectExistingNetworkManual, 0);
});
let request1 = connection.selectNetwork(telkilaNetwork);
request1.onerror = function() {
ok(false, request.error);
setTimeout(testSelectExistingNetworkManual, 0);
};
// attempt to selectNetwork while one request has already been sent
throwsException(function() {
connection.selectNetwork(androidNetwork);
});
}
function testSelectExistingNetworkManual() {
// When the current network is selected again, the DOMRequest's onsuccess
// should be called, but the network shouldn't actually change
// Telkila should be the currently selected network
log("Selecting TelKila (should already be selected");
let request = connection.selectNetwork(telkilaNetwork);
let voiceChanged = false;
connection.addEventListener("voicechange", function voiceChange() {
connection.removeEventListener("voicechange", voiceChange);
voiceChanged = true;
});
function nextTest() {
// Switch back to automatic selection to setup the next test
let autoRequest = connection.selectNetworkAutomatically();
autoRequest.onsuccess = function() {
setTimeout(testSelectExistingNetworkAuto, 0);
};
autoRequest.onerror = function() {
ok(false, autoRequest.error);
cleanUp();
};
}
request.onsuccess = function() {
// Give the voicechange event another opportunity to fire
setTimeout(function() {
is(voiceChanged, false,
"voiceNetwork changed while manually selecting Telkila network? " +
voiceChanged);
nextTest();
}, 0);
};
request.onerror = function() {
ok(false, request.error);
nextTest();
};
}
function testSelectExistingNetworkAuto() {
// Now try the same thing but using automatic selection
log("Selecting automatically (should already be auto)");
let request = connection.selectNetworkAutomatically();
let voiceChanged = false;
connection.addEventListener("voicechange", function voiceChange() {
connection.removeEventListener("voicechange", voiceChange);
voiceChanged = true;
});
request.onsuccess = function() {
// Give the voicechange event another opportunity to fire
setTimeout(function() {
is(voiceChanged, false,
"voiceNetwork changed while automatically selecting network? " +
voiceChanged);
cleanUp();
}, 0);
};
request.onerror = function() {
ok(false, request.error);
cleanUp();
};
}

View File

@ -13,7 +13,8 @@ Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
var RIL = {};
Cu.import("resource://gre/modules/ril_consts.js", RIL);
const DEBUG = false; // set to true to see debug messages
// set to true to in ril_consts.js to see debug messages
const DEBUG = RIL.DEBUG_CONTENT_HELPER;
const RILCONTENTHELPER_CID =
Components.ID("{472816e1-1fd6-4405-996c-806f9ea68174}");
@ -28,6 +29,9 @@ const RIL_IPC_MSG_NAMES = [
"RIL:DataInfoChanged",
"RIL:EnumerateCalls",
"RIL:GetAvailableNetworks",
"RIL:NetworkSelectionModeChanged",
"RIL:SelectNetwork",
"RIL:SelectNetworkAuto",
"RIL:CallStateChanged",
"RIL:CallError",
"RIL:GetCardLock:Return:OK",
@ -156,6 +160,13 @@ RILContentHelper.prototype = {
cardState: RIL.GECKO_CARDSTATE_UNAVAILABLE,
voiceConnectionInfo: null,
dataConnectionInfo: null,
networkSelectionMode: RIL.GECKO_NETWORK_SELECTION_UNKNOWN,
/**
* The network that is currently trying to be selected (or "automatic").
* This helps ensure that only one network is selected at a time.
*/
_selectingNetwork: null,
getNetworks: function getNetworks(window) {
if (window == null) {
@ -170,6 +181,79 @@ RILContentHelper.prototype = {
return request;
},
selectNetwork: function selectNetwork(window, network) {
if (window == null) {
throw Components.Exception("Can't get window object",
Cr.NS_ERROR_UNEXPECTED);
}
if (this._selectingNetwork) {
throw new Error("Already selecting a network: " + this._selectingNetwork);
}
if (!network) {
throw new Error("Invalid network provided: " + network);
}
let mnc = network.mnc;
if (!mnc) {
throw new Error("Invalid network MNC: " + mnc);
}
let mcc = network.mcc;
if (!mcc) {
throw new Error("Invalid network MCC: " + mcc);
}
let request = Services.DOMRequest.createRequest(window);
let requestId = this.getRequestId(request);
if (this.networkSelectionMode == RIL.GECKO_NETWORK_SELECTION_MANUAL
&& this.voiceConnectionInfo.network === network) {
// Already manually selected this network, so schedule
// onsuccess to be fired on the next tick
this.dispatchFireRequestSuccess(requestId, null);
return request;
}
this._selectingNetwork = network;
cpmm.sendAsyncMessage("RIL:SelectNetwork", {
requestId: requestId,
mnc: mnc,
mcc: mcc
});
return request;
},
selectNetworkAutomatically: function selectNetworkAutomatically(window) {
if (window == null) {
throw Components.Exception("Can't get window object",
Cr.NS_ERROR_UNEXPECTED);
}
if (this._selectingNetwork) {
throw new Error("Already selecting a network: " + this._selectingNetwork);
}
let request = Services.DOMRequest.createRequest(window);
let requestId = this.getRequestId(request);
if (this.networkSelectionMode == RIL.GECKO_NETWORK_SELECTION_AUTOMATIC) {
// Already using automatic selection mode, so schedule
// onsuccess to be be fired on the next tick
this.dispatchFireRequestSuccess(requestId, null);
return request;
}
this._selectingNetwork = "automatic";
cpmm.sendAsyncMessage("RIL:SelectNetworkAuto", requestId);
return request;
},
getCardLock: function getCardLock(window, lockType) {
if (window == null) {
throw Components.Exception("Can't get window object",
@ -330,28 +414,39 @@ RILContentHelper.prototype = {
let request = this.takeRequest(requestId);
if (!request) {
if (DEBUG) {
debug("not firing success for id: " + requestId + ", result: " + JSON.stringify(result));
debug("not firing success for id: " + requestId +
", result: " + JSON.stringify(result));
}
return;
}
if (DEBUG) {
debug("fire request success, id: " + requestId + ", result: " + JSON.stringify(result));
debug("fire request success, id: " + requestId +
", result: " + JSON.stringify(result));
}
Services.DOMRequest.fireSuccess(request, result);
},
dispatchFireRequestSuccess: function dispatchFireRequestSuccess(requestId, result) {
let currentThread = Services.tm.currentThread;
currentThread.dispatch(this.fireRequestSuccess.bind(this, requestId, result),
Ci.nsIThread.DISPATCH_NORMAL);
},
fireRequestError: function fireRequestError(requestId, error) {
let request = this.takeRequest(requestId);
if (!request) {
if (DEBUG) {
debug("not firing error for id: " + requestId + ", error: " + JSON.stringify(error));
debug("not firing error for id: " + requestId +
", error: " + JSON.stringify(error));
}
return;
}
if (DEBUG) {
debug("fire request error, id: " + requestId + ", result: " + JSON.stringify(error));
debug("fire request error, id: " + requestId +
", result: " + JSON.stringify(error));
}
Services.DOMRequest.fireError(request, error);
},
@ -380,6 +475,17 @@ RILContentHelper.prototype = {
case "RIL:GetAvailableNetworks":
this.handleGetAvailableNetworks(msg.json);
break;
case "RIL:NetworkSelectionModeChanged":
this.networkSelectionMode = msg.json.mode;
break;
case "RIL:SelectNetwork":
this.handleSelectNetwork(msg.json,
RIL.GECKO_NETWORK_SELECTION_MANUAL);
break;
case "RIL:SelectNetworkAuto":
this.handleSelectNetwork(msg.json,
RIL.GECKO_NETWORK_SELECTION_AUTOMATIC);
break;
case "RIL:CallStateChanged":
this._deliverTelephonyCallback("callStateChanged",
[msg.json.callIndex, msg.json.state,
@ -473,6 +579,17 @@ RILContentHelper.prototype = {
Services.DOMRequest.fireSuccess(request, networks);
},
handleSelectNetwork: function handleSelectNetwork(message, mode) {
this._selectingNetwork = null;
this.networkSelectionMode = mode;
if (message.error) {
this.fireRequestError(message.requestId, message.error);
} else {
this.fireRequestSuccess(message.requestId, null);
}
},
_deliverTelephonyCallback: function _deliverTelephonyCallback(name, args) {
if (!this._telephonyCallbacks) {
return;

View File

@ -12,7 +12,8 @@ Cu.import("resource://gre/modules/Services.jsm");
var RIL = {};
Cu.import("resource://gre/modules/ril_consts.js", RIL);
const DEBUG = false; // set to true to see debug messages
// set to true in ril_consts.js to see debug messages
const DEBUG = RIL.DEBUG_RIL;
const RADIOINTERFACELAYER_CID =
Components.ID("{2d831c8d-6017-435b-a80c-e5d422810cea}");
@ -45,6 +46,8 @@ const RIL_IPC_MSG_NAMES = [
"RIL:HoldCall",
"RIL:ResumeCall",
"RIL:GetAvailableNetworks",
"RIL:SelectNetwork",
"RIL:SelectNetworkAuto",
"RIL:GetCardLock",
"RIL:UnlockCardLock",
"RIL:SetCardLock",
@ -249,6 +252,11 @@ RadioInterfaceLayer.prototype = {
case "RIL:GetAvailableNetworks":
this.getAvailableNetworks(msg.json);
break;
case "RIL:SelectNetwork":
this.selectNetwork(msg.json);
break;
case "RIL:SelectNetworkAuto":
this.selectNetworkAuto(msg.json);
case "RIL:GetCardLock":
this.getCardLock(msg.json);
break;
@ -302,6 +310,18 @@ RadioInterfaceLayer.prototype = {
case "getAvailableNetworks":
this.handleGetAvailableNetworks(message);
break;
case "selectNetwork":
this.handleSelectNetwork(message);
break;
case "selectNetworkAuto":
this.handleSelectNetworkAuto(message);
break;
case "networkinfochanged":
this.updateNetworkInfo(message);
break;
case "networkselectionmodechange":
this.updateNetworkSelectionMode(message);
break;
case "voiceregistrationstatechange":
this.updateVoiceConnection(message);
break;
@ -398,10 +418,64 @@ RadioInterfaceLayer.prototype = {
}
},
updateNetworkInfo: function updateNetworkInfo(message) {
let voiceMessage = message[RIL.NETWORK_INFO_VOICE_REGISTRATION_STATE];
let dataMessage = message[RIL.NETWORK_INFO_DATA_REGISTRATION_STATE];
let operatorMessage = message[RIL.NETWORK_INFO_OPERATOR];
let selectionMessage = message[RIL.NETWORK_INFO_NETWORK_SELECTION_MODE];
// Batch the *InfoChanged messages together
let voiceInfoChanged = false;
if (voiceMessage) {
voiceMessage.batch = true;
voiceInfoChanged = this.updateVoiceConnection(voiceMessage);
}
let dataInfoChanged = false;
if (dataMessage) {
dataMessage.batch = true;
dataInfoChanged = this.updateDataConnection(dataMessage);
}
let voice = this.rilContext.voice;
let data = this.rilContext.data;
if (operatorMessage) {
if (this.networkChanged(operatorMessage, voice.network)) {
voiceInfoChanged = true;
voice.network = operatorMessage;
}
if (this.networkChanged(operatorMessage, data.network)) {
dataInfoChanged = true;
data.network = operatorMessage;
}
}
if (voiceInfoChanged) {
ppmm.sendAsyncMessage("RIL:VoiceInfoChanged", voice);
}
if (dataInfoChanged) {
ppmm.sendAsyncMessage("RIL:DataInfoChanged", data);
}
if (selectionMessage) {
this.updateNetworkSelectionMode(selectionMessage);
}
},
/**
* Sends the RIL:VoiceInfoChanged message when the voice
* connection's state has changed.
*
* @param state The new voice connection state. When state.batch is true,
* the RIL:VoiceInfoChanged message will not be sent.
* @return Whether or not this.radioState.voice was updated
*/
updateVoiceConnection: function updateVoiceConnection(state) {
let voiceInfo = this.rilContext.voice;
let regState = state.regState;
voiceInfo.type = "gsm"; //TODO see bug 726098.
if (!state || state.regState == RIL.NETWORK_CREG_STATE_UNKNOWN) {
if (!state || regState == RIL.NETWORK_CREG_STATE_UNKNOWN) {
voiceInfo.connected = false;
voiceInfo.emergencyCallsOnly = false;
voiceInfo.roaming = false;
@ -413,17 +487,30 @@ RadioInterfaceLayer.prototype = {
voiceInfo);
return;
}
voiceInfo.emergencyCallsOnly = state.emergencyCallsOnly;
voiceInfo.connected =
(state.regState == RIL.NETWORK_CREG_STATE_REGISTERED_HOME) ||
(state.regState == RIL.NETWORK_CREG_STATE_REGISTERED_ROAMING);
voiceInfo.roaming =
voiceInfo.connected &&
(state == RIL.NETWORK_CREG_STATE_REGISTERED_ROAMING);
voiceInfo.type =
RIL.GECKO_RADIO_TECH[state.radioTech] || null;
ppmm.sendAsyncMessage("RIL:VoiceInfoChanged", voiceInfo);
let isRoaming = regState == RIL.NETWORK_CREG_STATE_REGISTERED_ROAMING;
let isHome = regState == RIL.NETWORK_CREG_STATE_REGISTERED_HOME;
let isConnected = isRoaming || isHome;
let radioTech = RIL.GECKO_RADIO_TECH[state.radioTech] || null;
// Ensure that we check for changes before sending the message
if (voiceInfo.emergencyCallsOnly != state.emergencyCallsOnly ||
voiceInfo.connected != isConnected ||
voiceInfo.roaming != isRoaming ||
voiceInfo.type != radioTech) {
voiceInfo.emergencyCallsOnly = state.emergencyCallsOnly;
voiceInfo.connected = isConnected;
voiceInfo.roaming = isRoaming;
voiceInfo.type = radioTech;
// When batch is true, hold off on firing VoiceInfoChanged events
if (!state.batch) {
ppmm.sendAsyncMessage("RIL:VoiceInfoChanged", voiceInfo);
}
return true;
}
return false;
},
_isDataEnabled: function _isDataEnabled() {
@ -444,7 +531,7 @@ RadioInterfaceLayer.prototype = {
updateDataConnection: function updateDataConnection(state) {
if (!this._isDataEnabled()) {
return;
return false;
}
let isRegistered =
@ -462,6 +549,7 @@ RadioInterfaceLayer.prototype = {
//TODO need to keep track of some of the state information, and then
// notify the content when state changes (connected, technology
// changes, etc.). This should be done in RILNetworkInterface.
return false;
},
handleSignalStrengthChange: function handleSignalStrengthChange(message) {
@ -613,6 +701,30 @@ RadioInterfaceLayer.prototype = {
ppmm.sendAsyncMessage("RIL:GetAvailableNetworks", message);
},
/**
* Update network selection mode
*/
updateNetworkSelectionMode: function updateNetworkSelectionMode(message) {
debug("updateNetworkSelectionMode: " + JSON.stringify(message));
ppmm.sendAsyncMessage("RIL:NetworkSelectionModeChanged", message);
},
/**
* Handle "manual" network selection request.
*/
handleSelectNetwork: function handleSelectNetwork(message) {
debug("handleSelectNetwork: " + JSON.stringify(message));
ppmm.sendAsyncMessage("RIL:SelectNetwork", message);
},
/**
* Handle "automatic" network selection request.
*/
handleSelectNetworkAuto: function handleSelectNetworkAuto(message) {
debug("handleSelectNetworkAuto: " + JSON.stringify(message));
ppmm.sendAsyncMessage("RIL:SelectNetworkAuto", message);
},
/**
* Handle call error.
*/
@ -951,6 +1063,16 @@ RadioInterfaceLayer.prototype = {
this.worker.postMessage(message);
},
selectNetworkAuto: function selectNetworkAuto(requestId) {
this.worker.postMessage({type: "selectNetworkAuto", requestId: requestId});
},
selectNetwork: function selectNetwork(message) {
message.type = "selectNetwork";
this.worker.postMessage(message);
},
get microphoneMuted() {
return gAudioManager.microphoneMuted;
},

View File

@ -2,6 +2,14 @@
* 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/. */
// Set to true to debug all RIL layers
const DEBUG_ALL = false;
// Set individually to debug specific layers
const DEBUG_WORKER = false || DEBUG_ALL;
const DEBUG_CONTENT_HELPER = false || DEBUG_ALL;
const DEBUG_RIL = false || DEBUG_ALL;
const REQUEST_GET_SIM_STATUS = 1;
const REQUEST_ENTER_SIM_PIN = 2;
const REQUEST_ENTER_SIM_PUK = 3;
@ -211,12 +219,14 @@ const GECKO_ERROR_SUCCESS = null;
const GECKO_ERROR_RADIO_NOT_AVAILABLE = "RadioNotAvailable";
const GECKO_ERROR_GENERIC_FAILURE = "GenericFailure";
const GECKO_ERROR_REQUEST_NOT_SUPPORTED = "RequestNotSupported";
const GECKO_ERROR_ILLEGAL_SIM_OR_ME = "IllegalSIMorME";
const RIL_ERROR_TO_GECKO_ERROR = {};
RIL_ERROR_TO_GECKO_ERROR[ERROR_SUCCESS] = GECKO_ERROR_SUCCESS;
RIL_ERROR_TO_GECKO_ERROR[ERROR_RADIO_NOT_AVAILABLE] = GECKO_ERROR_RADIO_NOT_AVAILABLE;
RIL_ERROR_TO_GECKO_ERROR[ERROR_GENERIC_FAILURE] = GECKO_ERROR_GENERIC_FAILURE;
RIL_ERROR_TO_GECKO_ERROR[ERROR_REQUEST_NOT_SUPPORTED] = GECKO_ERROR_REQUEST_NOT_SUPPORTED;
RIL_ERROR_TO_GECKO_ERROR[ERROR_ILLEGAL_SIM_OR_ME] = GECKO_ERROR_ILLEGAL_SIM_OR_ME;
// 3GPP 23.040 clause 9.2.3.6 TP-Message-Reference(TP-MR):
// The number of times the MS automatically repeats the SMS-SUBMIT shall be in
@ -301,6 +311,17 @@ const NETWORK_STATE_FORBIDDEN = "forbidden";
const NETWORK_SELECTION_MODE_AUTOMATIC = 0;
const NETWORK_SELECTION_MODE_MANUAL = 1;
const NETWORK_INFO_VOICE_REGISTRATION_STATE = "voiceRegistrationState";
const NETWORK_INFO_DATA_REGISTRATION_STATE = "dataRegistrationState";
const NETWORK_INFO_OPERATOR = "operator";
const NETWORK_INFO_NETWORK_SELECTION_MODE = "networkSelectionMode";
const NETWORK_INFO_MESSAGE_TYPES = [
NETWORK_INFO_VOICE_REGISTRATION_STATE,
NETWORK_INFO_DATA_REGISTRATION_STATE,
NETWORK_INFO_OPERATOR,
NETWORK_INFO_NETWORK_SELECTION_MODE
];
const PREFERRED_NETWORK_TYPE_GSM_WCDMA = 0;
const PREFERRED_NETWORK_TYPE_GSM_ONLY = 1;
const PREFERRED_NETWORK_TYPE_WCDMA = 2;
@ -1394,6 +1415,10 @@ const GECKO_CARDSTATE_NETWORK_LOCKED = "network_locked";
const GECKO_CARDSTATE_NOT_READY = null;
const GECKO_CARDSTATE_READY = "ready";
const GECKO_NETWORK_SELECTION_UNKNOWN = null;
const GECKO_NETWORK_SELECTION_AUTOMATIC = "automatic";
const GECKO_NETWORK_SELECTION_MANUAL = "manual";
const GECKO_CALL_ERROR_BAD_NUMBER = "BadNumberError";
const GECKO_CALL_ERROR_NORMAL_CALL_CLEARING = "NormalCallClearingError";
const GECKO_CALL_ERROR_BUSY = "BusyError";

View File

@ -29,10 +29,8 @@
importScripts("ril_consts.js", "systemlibs.js");
// We leave this as 'undefined' instead of setting it to 'false'. That
// way an outer scope can define it to 'true' (e.g. for testing purposes)
// without us overriding that here.
let DEBUG;
// set to true in ril_consts.js to see debug messages
let DEBUG = DEBUG_WORKER;
const INT32_MAX = 2147483647;
const UINT8_SIZE = 1;
@ -520,6 +518,7 @@ let Buf = {
*/
newParcel: function newParcel(type, options) {
if (DEBUG) debug("New outgoing parcel of type " + type);
// We're going to leave room for the parcel size at the beginning.
this.outgoingIndex = PARCEL_SIZE_SIZE;
this.writeUint32(type);
@ -616,11 +615,6 @@ let RIL = {
*/
basebandVersion: null,
/**
* Network selection mode. 0 for automatic, 1 for manual selection.
*/
networkSelectionMode: null,
/**
* Valid calls.
*/
@ -648,6 +642,17 @@ let RIL = {
*/
_pendingSentSmsMap: {},
/**
* Whether or not the multiple requests in requestNetworkInfo() are currently
* being processed
*/
_processingNetworkInfo: false,
/**
* Pending messages to be send in batch from requestNetworkInfo()
*/
_pendingNetworkInfo: {type: "networkinfochanged"},
/**
* Mute or unmute the radio.
*/
@ -1362,14 +1367,6 @@ let RIL = {
Buf.simpleRequest(REQUEST_OPERATOR);
},
getNetworkSelectionMode: function getNetworkSelectionMode() {
Buf.simpleRequest(REQUEST_QUERY_NETWORK_SELECTION_MODE);
},
setNetworkSelectionAutomatic: function setNetworkSelectionAutomatic() {
Buf.simpleRequest(REQUEST_SET_NETWORK_SELECTION_AUTOMATIC);
},
/**
* Set the preferred network type.
*
@ -1386,7 +1383,17 @@ let RIL = {
* Request various states about the network.
*/
requestNetworkInfo: function requestNetworkInfo() {
if (DEBUG) debug("Requesting phone state");
if (this._processingNetworkInfo) {
if (DEBUG) {
debug("Already requesting network info: " +
JSON.stringify(this._pendingNetworkInfo))
}
return;
}
if (DEBUG) debug("Requesting network info");
this._processingNetworkInfo = true;
this.getVoiceRegistrationState();
this.getDataRegistrationState(); //TODO only GSM
this.getOperator();
@ -1402,6 +1409,36 @@ let RIL = {
Buf.sendParcel();
},
/**
* Request the radio's network selection mode
*/
getNetworkSelectionMode: function getNetworkSelectionMode(options) {
if (DEBUG) debug("Getting network selection mode");
Buf.simpleRequest(REQUEST_QUERY_NETWORK_SELECTION_MODE, options);
},
/**
* Tell the radio to automatically choose a voice/data network
*/
selectNetworkAuto: function selectNetworkAuto(options) {
if (DEBUG) debug("Setting automatic network selection");
Buf.simpleRequest(REQUEST_SET_NETWORK_SELECTION_AUTOMATIC, options);
},
/**
* Tell the radio to choose a specific voice/data network
*/
selectNetwork: function selectNetwork(options) {
if (DEBUG) {
debug("Setting manual network selection: " + options.mcc + options.mnc);
}
let numeric = String(options.mcc) + options.mnc;
Buf.newParcel(REQUEST_SET_NETWORK_SELECTION_MANUAL, options);
Buf.writeString(numeric);
Buf.sendParcel();
},
/**
* Get current calls.
*/
@ -1848,9 +1885,6 @@ let RIL = {
newCardState = GECKO_CARDSTATE_NETWORK_LOCKED;
break;
case CARD_APPSTATE_READY:
this.requestNetworkInfo();
this.getSignalStrength();
this.fetchICCRecords();
newCardState = GECKO_CARDSTATE_READY;
break;
case CARD_APPSTATE_UNKNOWN:
@ -1862,6 +1896,12 @@ let RIL = {
if (this.cardState == newCardState) {
return;
}
// This was moved down from CARD_APPSTATE_READY
this.requestNetworkInfo();
this.getSignalStrength();
this.fetchICCRecords();
this.cardState = newCardState;
this.sendDOMMessage({type: "cardstatechange",
cardState: this.cardState});
@ -1978,6 +2018,82 @@ let RIL = {
}
},
// We combine all of the NETWORK_INFO_MESSAGE_TYPES into one "networkinfochange"
// message to the RadioInterfaceLayer, so we can avoid sending multiple
// VoiceInfoChanged events for both operator / voice_data_registration
//
// State management here is a little tricky. We need to know both:
// 1. Whether or not a response was received for each of the
// NETWORK_INFO_MESSAGE_TYPES
// 2. The outbound message that corresponds with that response -- but this
// only happens when internal state changes (i.e. it isn't guaranteed)
//
// To collect this state, each message response function first calls
// _receivedNetworkInfo, to mark the response as received. When the
// final response is received, a call to _sendPendingNetworkInfo is placed
// on the next tick of the worker thread.
//
// Since the original call to _receivedNetworkInfo happens at the top
// of the response handler, this gives the final handler a chance to
// queue up it's "changed" message by calling _sendNetworkInfoMessage if/when
// the internal state has actually changed.
_sendNetworkInfoMessage: function _sendNetworkInfoMessage(type, message) {
if (!this._processingNetworkInfo) {
// We only combine these messages in the case of the combined request
// in requestNetworkInfo()
this.sendDOMMessage(message);
return;
}
this._pendingNetworkInfo[type] = message;
},
_receivedNetworkInfo: function _receivedNetworkInfo(type) {
if (!this._processingNetworkInfo) {
return;
}
let pending = this._pendingNetworkInfo;
// We still need to track states for events that aren't fired
if (!(type in pending)) {
pending[type] = true;
}
// Pending network info is ready to be sent when no more messages
// are waiting for responses, but the combined payload hasn't been sent.
for (let i = 0; i < NETWORK_INFO_MESSAGE_TYPES.length; i++) {
let type = NETWORK_INFO_MESSAGE_TYPES[i];
if (!(type in pending)) {
return;
}
}
// Do a pass to clean up the processed messages that didn't create
// a response message, so we don't have unused keys in the outbound
// networkinfochanged message
let keys = Object.keys(pending);
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
if (pending[key] === true) {
delete pending[key];
}
}
// Send the message on the next tick of the worker's loop, so we give the
// last message a chance to call _sendNetworkInfoMessage first.
setTimeout(this._sendPendingNetworkInfo.bind(this), 0);
},
_sendPendingNetworkInfo: function _sendPendingNetworkInfo() {
this.sendDOMMessage(this._pendingNetworkInfo);
this._processingNetworkInfo = false;
for (let i = 0; i < NETWORK_INFO_MESSAGE_TYPES.length; i++) {
delete this._pendingNetworkInfo[NETWORK_INFO_MESSAGE_TYPES[i]];
}
},
_processVoiceRegistrationState: function _processVoiceRegistrationState(state) {
this.initRILQuirks();
@ -2052,7 +2168,7 @@ let RIL = {
if (stateChanged) {
rs.type = "voiceregistrationstatechange";
this.sendDOMMessage(rs);
this._sendNetworkInfoMessage(NETWORK_INFO_VOICE_REGISTRATION_STATE, rs);
}
},
@ -2074,7 +2190,7 @@ let RIL = {
if (stateChanged) {
rs.type = "dataregistrationstatechange";
this.sendDOMMessage(rs);
this._sendNetworkInfoMessage(NETWORK_INFO_DATA_REGISTRATION_STATE, rs);
}
},
@ -2827,15 +2943,20 @@ RIL[REQUEST_SIGNAL_STRENGTH] = function REQUEST_SIGNAL_STRENGTH(length, options)
this.sendDOMMessage(obj);
};
RIL[REQUEST_VOICE_REGISTRATION_STATE] = function REQUEST_VOICE_REGISTRATION_STATE(length, options) {
this._receivedNetworkInfo(NETWORK_INFO_VOICE_REGISTRATION_STATE);
if (options.rilRequestError) {
return;
}
let state = Buf.readStringList();
if (DEBUG) debug("voice registration state: " + state);
this._processVoiceRegistrationState(state);
};
RIL[REQUEST_DATA_REGISTRATION_STATE] = function REQUEST_DATA_REGISTRATION_STATE(length, options) {
this._receivedNetworkInfo(NETWORK_INFO_DATA_REGISTRATION_STATE);
if (options.rilRequestError) {
return;
}
@ -2844,11 +2965,14 @@ RIL[REQUEST_DATA_REGISTRATION_STATE] = function REQUEST_DATA_REGISTRATION_STATE(
this._processDataRegistrationState(state);
};
RIL[REQUEST_OPERATOR] = function REQUEST_OPERATOR(length, options) {
this._receivedNetworkInfo(NETWORK_INFO_OPERATOR);
if (options.rilRequestError) {
return;
}
let operator = Buf.readStringList();
if (DEBUG) debug("Operator data: " + operator);
if (operator.length < 3) {
if (DEBUG) debug("Expected at least 3 strings for operator.");
@ -2875,7 +2999,7 @@ RIL[REQUEST_OPERATOR] = function REQUEST_OPERATOR(length, options) {
debug("Error processing operator tuple: " + e);
}
this.sendDOMMessage(this.operator);
this._sendNetworkInfoMessage(NETWORK_INFO_OPERATOR, this.operator);
}
};
RIL[REQUEST_RADIO_POWER] = null;
@ -3050,15 +3174,53 @@ RIL[REQUEST_SET_FACILITY_LOCK] = function REQUEST_SET_FACILITY_LOCK(length, opti
};
RIL[REQUEST_CHANGE_BARRING_PASSWORD] = null;
RIL[REQUEST_QUERY_NETWORK_SELECTION_MODE] = function REQUEST_QUERY_NETWORK_SELECTION_MODE(length, options) {
this._receivedNetworkInfo(NETWORK_INFO_NETWORK_SELECTION_MODE);
if (options.rilRequestError) {
options.error = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
this.sendDOMMessage(options);
return;
}
let mode = Buf.readUint32List();
this.networkSelectionMode = mode[0];
let selectionMode;
switch (mode[0]) {
case NETWORK_SELECTION_MODE_AUTOMATIC:
selectionMode = GECKO_NETWORK_SELECTION_AUTOMATIC;
break;
case NETWORK_SELECTION_MODE_MANUAL:
selectionMode = GECKO_NETWORK_SELECTION_MANUAL;
break;
default:
selectionMode = GECKO_NETWORK_SELECTION_UNKNOWN;
break;
}
if (this.mode != selectionMode) {
this.mode = options.mode = selectionMode;
options.type = "networkselectionmodechange";
this._sendNetworkInfoMessage(NETWORK_INFO_NETWORK_SELECTION_MODE, options);
}
};
RIL[REQUEST_SET_NETWORK_SELECTION_AUTOMATIC] = function REQUEST_SET_NETWORK_SELECTION_AUTOMATIC(length, options) {
if (options.rilRequestError) {
options.error = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
this.sendDOMMessage(options);
return;
}
this.sendDOMMessage(options);
};
RIL[REQUEST_SET_NETWORK_SELECTION_MANUAL] = function REQUEST_SET_NETWORK_SELECTION_MANUAL(length, options) {
if (options.rilRequestError) {
options.error = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
this.sendDOMMessage(options);
return;
}
this.sendDOMMessage(options);
};
RIL[REQUEST_SET_NETWORK_SELECTION_AUTOMATIC] = null;
RIL[REQUEST_SET_NETWORK_SELECTION_MANUAL] = null;
RIL[REQUEST_QUERY_AVAILABLE_NETWORKS] = function REQUEST_QUERY_AVAILABLE_NETWORKS(length, options) {
if (options.rilRequestError) {
options.error = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];