Skip to content

Instantly share code, notes, and snippets.

@marshall
Created June 18, 2012 22:41
Show Gist options
  • Save marshall/2951208 to your computer and use it in GitHub Desktop.
Save marshall/2951208 to your computer and use it in GitHub Desktop.
networkSelectionModes.patch - v4
# HG changeset patch
# User Marshall Culpepper <[email protected]>
Bug 759637: Initial implementation of MobileConnection network selection APIs. r=philikon sr=sicking
diff --git a/dom/network/interfaces/nsIDOMMobileConnection.idl b/dom/network/interfaces/nsIDOMMobileConnection.idl
index 1df7131..ef434cd 100644
--- a/dom/network/interfaces/nsIDOMMobileConnection.idl
+++ b/dom/network/interfaces/nsIDOMMobileConnection.idl
@@ -31,17 +31,52 @@ 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.
*
* If successful, the request's onsuccess will be called, and the request's
* 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).
*
* @param lockType
diff --git a/dom/network/interfaces/nsIMobileConnectionProvider.idl b/dom/network/interfaces/nsIMobileConnectionProvider.idl
index 7458052..91d694f 100644
--- a/dom/network/interfaces/nsIMobileConnectionProvider.idl
+++ b/dom/network/interfaces/nsIMobileConnectionProvider.idl
@@ -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);
diff --git a/dom/network/src/MobileConnection.cpp b/dom/network/src/MobileConnection.cpp
index b2407e4..803a67e 100644
--- a/dom/network/src/MobileConnection.cpp
+++ b/dom/network/src/MobileConnection.cpp
@@ -171,6 +171,16 @@ MobileConnection::GetData(nsIDOMMozMobileConnectionInfo** 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)
{
*request = nsnull;
@@ -183,6 +193,30 @@ MobileConnection::GetNetworks(nsIDOMDOMRequest** 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)
{
*aDomRequest = nsnull;
diff --git a/dom/network/tests/marionette/test_mobile_networks.js b/dom/network/tests/marionette/test_mobile_networks.js
index c44cc91..520c640 100644
--- a/dom/network/tests/marionette/test_mobile_networks.js
+++ b/dom/network/tests/marionette/test_mobile_networks.js
@@ -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();
};
}
diff --git a/dom/system/gonk/RILContentHelper.js b/dom/system/gonk/RILContentHelper.js
index fb1298a..996ab14 100644
--- a/dom/system/gonk/RILContentHelper.js
+++ b/dom/system/gonk/RILContentHelper.js
@@ -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;
diff --git a/dom/system/gonk/RadioInterfaceLayer.js b/dom/system/gonk/RadioInterfaceLayer.js
index 403c4f5..6022603 100644
--- a/dom/system/gonk/RadioInterfaceLayer.js
+++ b/dom/system/gonk/RadioInterfaceLayer.js
@@ -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.radioState.voice;
+ let data = this.radioState.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.radioState.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) {
@@ -614,6 +702,30 @@ RadioInterfaceLayer.prototype = {
},
/**
+ * 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.
*/
handleCallError: function handleCallError(message) {
@@ -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;
},
diff --git a/dom/system/gonk/ril_consts.js b/dom/system/gonk/ril_consts.js
index f406fe8..128324f 100644
--- a/dom/system/gonk/ril_consts.js
+++ b/dom/system/gonk/ril_consts.js
@@ -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,15 @@ 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_MESSAGES = [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 +1413,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";
diff --git a/dom/system/gonk/ril_worker.js b/dom/system/gonk/ril_worker.js
index 5b6dc97..4a275d7 100644
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -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);
@@ -617,11 +616,6 @@ let RIL = {
basebandVersion: null,
/**
- * Network selection mode. 0 for automatic, 1 for manual selection.
- */
- networkSelectionMode: null,
-
- /**
* Valid calls.
*/
currentCalls: {},
@@ -649,6 +643,11 @@ let RIL = {
_pendingSentSmsMap: {},
/**
+ * Whether or not the multiple requests in requestNetworkInfo() are currently being processed
+ */
+ _processingNetworkInfo: false,
+
+ /**
* Mute or unmute the radio.
*/
_muted: true,
@@ -1360,14 +1359,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.
*
@@ -1384,7 +1375,19 @@ 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 should match the number of parcels sent below
+ this._pendingNetworkInfo = {};
+ this._processingNetworkInfo = true;
+
this.getVoiceRegistrationState();
this.getDataRegistrationState(); //TODO only GSM
this.getOperator();
@@ -1401,6 +1404,36 @@ let RIL = {
},
/**
+ * 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.
*/
getCurrentCalls: function getCurrentCalls() {
@@ -1846,9 +1879,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:
@@ -1860,6 +1890,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});
@@ -1976,6 +2012,61 @@ let RIL = {
}
},
+ // We combine all of the network info messages into one "networkinfochange"
+ // message to the RadioInterfaceLayer, so we can avoid sending multiple
+ // VoiceInfoChanged events for both operator / voice_data_registration
+ _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;
+ },
+
+ _processNetworkInfoResult: function _processNetworkInfoResult(type) {
+ let pending = this._pendingNetworkInfo;
+ if (!pending || !this._processingNetworkInfo) {
+ return;
+ }
+
+ // We still need to track states for events that aren't fired
+ if (!(type in pending)) {
+ pending[type] = null;
+ }
+
+ // 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_MESSAGES.length; i++) {
+ let message = NETWORK_INFO_MESSAGES[i];
+ if (!(message in pending)) {
+ return;
+ }
+ }
+
+ this._pendingNetworkInfo.type = "networkinfochanged";
+
+ // Clean up the dead-weight events
+ let keys = Object.keys(pending);
+ for (let i = 0; i < keys.length; i++) {
+ let key = keys[i];
+ if (!pending[key]) {
+ 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.
+ let self = this;
+ setTimeout(function() {
+ self.sendDOMMessage(self._pendingNetworkInfo);
+ self._processingNetworkInfo = false;
+ self._pendingNetworkInfo = null;
+ }, 0);
+ },
+
_processVoiceRegistrationState: function _processVoiceRegistrationState(state) {
this.initRILQuirks();
@@ -2048,7 +2139,7 @@ let RIL = {
if (stateChanged) {
rs.type = "voiceregistrationstatechange";
- this.sendDOMMessage(rs);
+ this._sendNetworkInfoMessage(NETWORK_INFO_VOICE_REGISTRATION_STATE, rs);
}
},
@@ -2070,7 +2161,7 @@ let RIL = {
if (stateChanged) {
rs.type = "dataregistrationstatechange";
- this.sendDOMMessage(rs);
+ this._sendNetworkInfoMessage(NETWORK_INFO_DATA_REGISTRATION_STATE, rs);
}
},
@@ -2823,15 +2914,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._processNetworkInfoResult(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._processNetworkInfoResult(NETWORK_INFO_DATA_REGISTRATION_STATE);
+
if (options.rilRequestError) {
return;
}
@@ -2840,11 +2936,14 @@ RIL[REQUEST_DATA_REGISTRATION_STATE] = function REQUEST_DATA_REGISTRATION_STATE(
this._processDataRegistrationState(state);
};
RIL[REQUEST_OPERATOR] = function REQUEST_OPERATOR(length, options) {
+ this._processNetworkInfoResult(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.");
@@ -2871,7 +2970,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;
@@ -3046,15 +3145,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._processNetworkInfoResult(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];
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment