Bug 744344 - B2G RIL: Add DOM API for getting the list of available networks. r=philikon sr=sicking

Also added a new MobileOperatorInfo type that exposes operator name, MNC, MCC, and operator/network status, and accompanying emulator Marionette tests.
This commit is contained in:
Marshall Culpepper 2012-06-01 14:10:39 -07:00
parent ab044e7b8e
commit 60c62fa867
8 changed files with 281 additions and 7 deletions

View File

@ -32,7 +32,11 @@ interface nsIDOMMozMobileConnection : nsIDOMEventTarget
/** /**
* Search for available networks. * Search for available networks.
* *
* If successful, the request result will be an array of operator names. * If successful, the request's onsuccess will be called, and the request's
* result will be an array of nsIDOMMozMobileOperatorInfo.
*
* Otherwise, the request's onerror will be called, and the request's error
* will be either 'RadioNotAvailable', 'RequestNotSupported', or 'GenericFailure'.
*/ */
nsIDOMDOMRequest getNetworks(); nsIDOMDOMRequest getNetworks();
@ -208,3 +212,34 @@ interface nsIDOMMozMobileConnectionInfo : nsISupports
readonly attribute jsval relSignalStrength; readonly attribute jsval relSignalStrength;
}; };
[scriptable, uuid(79217f7a-4401-4d75-9654-3b28bba698c9)]
interface nsIDOMMozMobileOperatorInfo : nsISupports
{
/**
* Short name of the network operator
*/
readonly attribute DOMString shortName;
/**
* Long name of the network operator
*/
readonly attribute DOMString longName;
/**
* Mobile Country Code (MCC) of the network operator
*/
readonly attribute unsigned short mcc;
/**
* Mobile Network Code (MNC) of the network operator
*/
readonly attribute unsigned short mnc;
/**
* State of this network operator.
*
* Possible values: 'available', 'connected', 'forbidden', or null (unknown)
*/
readonly attribute DOMString state;
};

View File

@ -0,0 +1,5 @@
[test_mobile_networks.js]
b2g = true
browser = false
qemu = true

View File

@ -0,0 +1,52 @@
/* 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/. */
// getNetworks() can take some time..
MARIONETTE_TIMEOUT = 30000;
const WHITELIST_PREF = "dom.mobileconnection.whitelist";
let uriPrePath = window.location.protocol + "//" + window.location.host;
SpecialPowers.setCharPref(WHITELIST_PREF, uriPrePath);
ok(navigator.mozMobileConnection instanceof MozMobileConnection,
"mozMobileConnection is instanceof " + navigator.mozMobileConnection.constructor);
let request = navigator.mozMobileConnection.getNetworks();
ok(request instanceof DOMRequest,
"request is instanceof " + request.constructor);
request.onerror = function() {
ok(false, request.error);
cleanUp();
};
request.onsuccess = function() {
ok('result' in request, "Request did not contain a result");
let networks = request.result;
// The emulator RIL server should always return 2 networks:
// {"longName":"Android","shortName":"Android","mcc":310,"mnc":260,"state":"available"}
// {"longName":"TelKila","shortName":"TelKila","mcc":310,"mnc":295,"state":"available"}
is(networks.length, 2);
let network1 = networks[0];
is(network1.longName, "Android");
is(network1.shortName, "Android");
is(network1.mcc, 310);
is(network1.mnc, 260);
is(network1.state, "available");
let network2 = networks[1];
is(network2.longName, "TelKila");
is(network2.shortName, "TelKila");
is(network2.mcc, 310);
is(network2.mnc, 295);
is(network2.state, "available");
cleanUp();
};
function cleanUp() {
SpecialPowers.clearUserPref(WHITELIST_PREF);
finish();
}

View File

@ -19,12 +19,15 @@ const RILCONTENTHELPER_CID =
Components.ID("{472816e1-1fd6-4405-996c-806f9ea68174}"); Components.ID("{472816e1-1fd6-4405-996c-806f9ea68174}");
const MOBILECONNECTIONINFO_CID = const MOBILECONNECTIONINFO_CID =
Components.ID("{a35cfd39-2d93-4489-ac7d-396475dacb27}"); Components.ID("{a35cfd39-2d93-4489-ac7d-396475dacb27}");
const MOBILEOPERATORINFO_CID =
Components.ID("{a6c8416c-09b4-46d1-bf29-6520d677d085}");
const RIL_IPC_MSG_NAMES = [ const RIL_IPC_MSG_NAMES = [
"RIL:CardStateChanged", "RIL:CardStateChanged",
"RIL:VoiceInfoChanged", "RIL:VoiceInfoChanged",
"RIL:DataInfoChanged", "RIL:DataInfoChanged",
"RIL:EnumerateCalls", "RIL:EnumerateCalls",
"RIL:GetAvailableNetworks",
"RIL:CallStateChanged", "RIL:CallStateChanged",
"RIL:CallError", "RIL:CallError",
"RIL:GetCardLock:Return:OK", "RIL:GetCardLock:Return:OK",
@ -65,6 +68,25 @@ MobileConnectionInfo.prototype = {
relSignalStrength: null relSignalStrength: null
}; };
function MobileOperatorInfo() {}
MobileOperatorInfo.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMMozMobileOperatorInfo]),
classID: MOBILEOPERATORINFO_CID,
classInfo: XPCOMUtils.generateCI({
classID: MOBILEOPERATORINFO_CID,
classDescription: "MobileOperatorInfo",
flags: Ci.nsIClassInfo.DOM_OBJECT,
interfaces: [Ci.nsIDOMMozMobileOperatorInfo]
}),
// nsIDOMMozMobileOperatorInfo
shortName: null,
longName: null,
mcc: 0,
mnc: 0,
state: null
};
function RILContentHelper() { function RILContentHelper() {
this.voiceConnectionInfo = new MobileConnectionInfo(); this.voiceConnectionInfo = new MobileConnectionInfo();
@ -108,8 +130,16 @@ RILContentHelper.prototype = {
dataConnectionInfo: null, dataConnectionInfo: null,
getNetworks: function getNetworks(window) { getNetworks: function getNetworks(window) {
//TODO bug 744344 if (window == null) {
throw Components.Exception("Not implemented", Cr.NS_ERROR_NOT_IMPLEMENTED); throw Components.Exception("Can't get window object",
Cr.NS_ERROR_UNEXPECTED);
}
let request = Services.DOMRequest.createRequest(window);
let requestId = this.getRequestId(request);
cpmm.sendAsyncMessage("RIL:GetAvailableNetworks", requestId);
return request;
}, },
getCardLock: function getCardLock(window, lockType) { getCardLock: function getCardLock(window, lockType) {
@ -269,6 +299,9 @@ RILContentHelper.prototype = {
case "RIL:EnumerateCalls": case "RIL:EnumerateCalls":
this.handleEnumerateCalls(msg.json); this.handleEnumerateCalls(msg.json);
break; break;
case "RIL:GetAvailableNetworks":
this.handleGetAvailableNetworks(msg.json);
break;
case "RIL:CallStateChanged": case "RIL:CallStateChanged":
this._deliverTelephonyCallback("callStateChanged", this._deliverTelephonyCallback("callStateChanged",
[msg.json.callIndex, msg.json.state, [msg.json.callIndex, msg.json.state,
@ -276,9 +309,9 @@ RILContentHelper.prototype = {
break; break;
case "RIL:CallError": case "RIL:CallError":
this._deliverTelephonyCallback("notifyError", this._deliverTelephonyCallback("notifyError",
[msg.json.callIndex, [msg.json.callIndex,
msg.json.error]); msg.json.error]);
break; break;
case "RIL:GetCardLock:Return:OK": case "RIL:GetCardLock:Return:OK":
case "RIL:SetCardLock:Return:OK": case "RIL:SetCardLock:Return:OK":
case "RIL:UnlockCardLock:Return:OK": case "RIL:UnlockCardLock:Return:OK":
@ -319,6 +352,37 @@ RILContentHelper.prototype = {
} }
}, },
handleGetAvailableNetworks: function handleGetAvailableNetworks(message) {
debug("handleGetAvailableNetworks: " + JSON.stringify(message));
let requestId = message.requestId;
let request = this.takeRequest(requestId);
if (!request) {
debug("no DOMRequest found with request ID: " + requestId);
return;
}
if (message.error) {
debug("Received error from getAvailableNetworks: " + message.error);
Services.DOMRequest.fireError(request, message.error);
return;
}
let networks = message.networks;
for (let i = 0; i < networks.length; i++) {
let network = networks[i];
let info = new MobileOperatorInfo();
for (let key in network) {
info[key] = network[key];
}
networks[i] = info;
}
Services.DOMRequest.fireSuccess(request, networks);
},
_deliverTelephonyCallback: function _deliverTelephonyCallback(name, args) { _deliverTelephonyCallback: function _deliverTelephonyCallback(name, args) {
if (!this._telephonyCallbacks) { if (!this._telephonyCallbacks) {
return; return;

View File

@ -44,6 +44,7 @@ const RIL_IPC_MSG_NAMES = [
"RIL:RejectCall", "RIL:RejectCall",
"RIL:HoldCall", "RIL:HoldCall",
"RIL:ResumeCall", "RIL:ResumeCall",
"RIL:GetAvailableNetworks",
"RIL:GetCardLock", "RIL:GetCardLock",
"RIL:UnlockCardLock", "RIL:UnlockCardLock",
"RIL:SetCardLock" "RIL:SetCardLock"
@ -235,6 +236,9 @@ RadioInterfaceLayer.prototype = {
case "RIL:ResumeCall": case "RIL:ResumeCall":
this.resumeCall(msg.json); this.resumeCall(msg.json);
break; break;
case "RIL:GetAvailableNetworks":
this.getAvailableNetworks(msg.json);
break;
case "RIL:GetCardLock": case "RIL:GetCardLock":
this.getCardLock(msg.json); this.getCardLock(msg.json);
break; break;
@ -279,6 +283,9 @@ RadioInterfaceLayer.prototype = {
case "callError": case "callError":
this.handleCallError(message); this.handleCallError(message);
break; break;
case "getAvailableNetworks":
this.handleGetAvailableNetworks(message);
break;
case "voiceregistrationstatechange": case "voiceregistrationstatechange":
this.updateVoiceConnection(message); this.updateVoiceConnection(message);
break; break;
@ -560,6 +567,15 @@ RadioInterfaceLayer.prototype = {
ppmm.sendAsyncMessage("RIL:EnumerateCalls", calls); ppmm.sendAsyncMessage("RIL:EnumerateCalls", calls);
}, },
/**
* Handle available networks returned by the 'getAvailableNetworks' request.
*/
handleGetAvailableNetworks: function handleGetAvailableNetworks(message) {
debug("handleGetAvailableNetworks: " + JSON.stringify(message));
ppmm.sendAsyncMessage("RIL:GetAvailableNetworks", message);
},
/** /**
* Handle call error. * Handle call error.
*/ */
@ -834,6 +850,10 @@ RadioInterfaceLayer.prototype = {
this.worker.postMessage({type: "resumeCall", callIndex: callIndex}); this.worker.postMessage({type: "resumeCall", callIndex: callIndex});
}, },
getAvailableNetworks: function getAvailableNetworks(requestId) {
this.worker.postMessage({type: "getAvailableNetworks", requestId: requestId});
},
get microphoneMuted() { get microphoneMuted() {
return gAudioManager.microphoneMuted; return gAudioManager.microphoneMuted;
}, },

View File

@ -207,6 +207,17 @@ const ERROR_SS_MODIFIED_TO_USSD = 24;
const ERROR_SS_MODIFIED_TO_SS = 25; const ERROR_SS_MODIFIED_TO_SS = 25;
const ERROR_SUBSCRIPTION_NOT_SUPPORTED = 26; const ERROR_SUBSCRIPTION_NOT_SUPPORTED = 26;
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 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;
// 3GPP 23.040 clause 9.2.3.6 TP-Message-Reference(TP-MR): // 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 // The number of times the MS automatically repeats the SMS-SUBMIT shall be in
// the range 1 to 3 but the precise number is an implementation matter. // the range 1 to 3 but the precise number is an implementation matter.
@ -282,6 +293,11 @@ const CARD_APPTYPE_ISIM = 5;
const CARD_MAX_APPS = 8; const CARD_MAX_APPS = 8;
const NETWORK_STATE_UNKNOWN = "unknown";
const NETWORK_STATE_AVAILABLE = "available";
const NETWORK_STATE_CONNECTED = "connected";
const NETWORK_STATE_FORBIDDEN = "forbidden";
const NETWORK_SELECTION_MODE_AUTOMATIC = 0; const NETWORK_SELECTION_MODE_AUTOMATIC = 0;
const NETWORK_SELECTION_MODE_MANUAL = 1; const NETWORK_SELECTION_MODE_MANUAL = 1;
@ -1351,6 +1367,9 @@ const GECKO_NETWORK_STATE_SUSPENDED = 2;
const GECKO_NETWORK_STATE_DISCONNECTING = 3; const GECKO_NETWORK_STATE_DISCONNECTING = 3;
const GECKO_NETWORK_STATE_DISCONNECTED = 4; const GECKO_NETWORK_STATE_DISCONNECTED = 4;
// Used for QUERY_AVAILABLE_NETWORKS status of "unknown"
const GECKO_QAN_STATE_UNKNOWN = null;
const CALL_FAIL_UNOBTAINABLE_NUMBER = 1; const CALL_FAIL_UNOBTAINABLE_NUMBER = 1;
const CALL_FAIL_NORMAL = 16; const CALL_FAIL_NORMAL = 16;
const CALL_FAIL_BUSY = 17; const CALL_FAIL_BUSY = 17;

View File

@ -1391,6 +1391,15 @@ let RIL = {
this.getNetworkSelectionMode(); this.getNetworkSelectionMode();
}, },
/**
* Get the available networks
*/
getAvailableNetworks: function getAvailableNetworks(options) {
if (DEBUG) debug("Getting available networks");
Buf.newParcel(REQUEST_QUERY_AVAILABLE_NETWORKS, options);
Buf.sendParcel();
},
/** /**
* Get current calls. * Get current calls.
*/ */
@ -2186,6 +2195,66 @@ let RIL = {
} }
}, },
_processNetworks: function _processNetworks() {
let strings = Buf.readStringList();
let networks = [];
for (let i = 0; i < strings.length; i += 4) {
let network = {
longName: strings[i],
shortName: strings[i + 1],
mcc: 0, mnc: 0,
state: null
};
let networkTuple = strings[i + 2];
try {
this._processNetworkTuple(networkTuple, network);
} catch (e) {
debug("Error processing operator tuple: " + e);
}
let state = strings[i + 3];
if (state === NETWORK_STATE_UNKNOWN) {
// TODO: looks like this might conflict in style with
// GECKO_NETWORK_STYLE_UNKNOWN / nsINetworkManager
state = GECKO_QAN_STATE_UNKNOWN;
}
network.state = state;
networks.push(network);
}
return networks;
},
/**
* The "numeric" portion of the operator info is a tuple
* containing MCC (country code) and MNC (network code).
* AFAICT, MCC should always be 3 digits, making the remaining
* portion the MNC.
*/
_processNetworkTuple: function _processNetworkTuple(networkTuple, network) {
let tupleLen = networkTuple.length;
let mcc = 0, mnc = 0;
if (tupleLen == 5 || tupleLen == 6) {
mcc = parseInt(networkTuple.substr(0, 3), 10);
if (isNaN(mcc)) {
throw new Error("MCC could not be parsed from network tuple: " + networkTuple );
}
mnc = parseInt(networkTuple.substr(3), 10);
if (isNaN(mnc)) {
throw new Error("MNC could not be parsed from network tuple: " + networkTuple);
}
} else {
throw new Error("Invalid network tuple (should be 5 or 6 digits): " + networkTuple);
}
network.mcc = mcc;
network.mnc = mnc;
},
/** /**
* Helper for processing received SMS parcel data. * Helper for processing received SMS parcel data.
* *
@ -2938,7 +3007,16 @@ RIL[REQUEST_QUERY_NETWORK_SELECTION_MODE] = function REQUEST_QUERY_NETWORK_SELEC
}; };
RIL[REQUEST_SET_NETWORK_SELECTION_AUTOMATIC] = null; RIL[REQUEST_SET_NETWORK_SELECTION_AUTOMATIC] = null;
RIL[REQUEST_SET_NETWORK_SELECTION_MANUAL] = null; RIL[REQUEST_SET_NETWORK_SELECTION_MANUAL] = null;
RIL[REQUEST_QUERY_AVAILABLE_NETWORKS] = 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];
this.sendDOMMessage(options);
return;
}
options.networks = this._processNetworks();
this.sendDOMMessage(options);
};
RIL[REQUEST_DTMF_START] = null; RIL[REQUEST_DTMF_START] = null;
RIL[REQUEST_DTMF_STOP] = null; RIL[REQUEST_DTMF_STOP] = null;
RIL[REQUEST_BASEBAND_VERSION] = function REQUEST_BASEBAND_VERSION(length, options) { RIL[REQUEST_BASEBAND_VERSION] = function REQUEST_BASEBAND_VERSION(length, options) {

View File

@ -15,6 +15,7 @@ skip = false
[include:../../../../../dom/telephony/test/marionette/manifest.ini] [include:../../../../../dom/telephony/test/marionette/manifest.ini]
[include:../../../../../dom/battery/test/marionette/manifest.ini] [include:../../../../../dom/battery/test/marionette/manifest.ini]
[include:../../../../../dom/sms/tests/marionette/manifest.ini] [include:../../../../../dom/sms/tests/marionette/manifest.ini]
[include:../../../../../dom/network/tests/marionette/manifest.ini]
; marionette unit tests ; marionette unit tests
[include:unit/unit-tests.ini] [include:unit/unit-tests.ini]