Bug 889737 - Part 09: Dial MMI (ril). r=vicamo

This commit is contained in:
Szu-Yu Chen [:aknow] 2014-09-22 01:36:00 -04:00
parent bbd007d9bd
commit 0488a241d4
5 changed files with 449 additions and 365 deletions

View File

@ -25,6 +25,8 @@ const MOBILENETWORKINFO_CID =
Components.ID("{a6c8416c-09b4-46d1-bf29-6520d677d085}"); Components.ID("{a6c8416c-09b4-46d1-bf29-6520d677d085}");
const MOBILECELLINFO_CID = const MOBILECELLINFO_CID =
Components.ID("{0635d9ab-997e-4cdf-84e7-c1883752dff3}"); 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_XPCOM_SHUTDOWN_OBSERVER_ID = "xpcom-shutdown";
const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed"; const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
@ -44,6 +46,10 @@ XPCOMUtils.defineLazyServiceGetter(this, "gRadioInterfaceLayer",
"@mozilla.org/ril;1", "@mozilla.org/ril;1",
"nsIRadioInterfaceLayer"); "nsIRadioInterfaceLayer");
XPCOMUtils.defineLazyServiceGetter(this, "gGonkTelephonyService",
"@mozilla.org/telephony/telephonyservice;1",
"nsIGonkTelephonyService");
let DEBUG = RIL.DEBUG_RIL; let DEBUG = RIL.DEBUG_RIL;
function debug(s) { function debug(s) {
dump("MobileConnectionService: " + s + "\n"); dump("MobileConnectionService: " + s + "\n");
@ -134,6 +140,41 @@ MMIResult.prototype = {
additionalInformation: '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) { function MobileConnectionProvider(aClientId, aRadioInterface) {
this._clientId = aClientId; this._clientId = aClientId;
this._radioInterface = aRadioInterface; this._radioInterface = aRadioInterface;
@ -188,7 +229,7 @@ MobileConnectionProvider.prototype = {
key = "ro.telephony.default_network"; key = "ro.telephony.default_network";
let indexString = libcutils.property_get(key, ""); let indexString = libcutils.property_get(key, "");
let index = parseInt(indexString, 10); let index = parseInt(indexString, 10);
if (DEBUG) this._debug("Fallback to " + key + ": " + index) if (DEBUG) this._debug("Fallback to " + key + ": " + index);
let networkTypes = RIL.RIL_PREFERRED_NETWORK_TYPE_TO_GECKO[index]; let networkTypes = RIL.RIL_PREFERRED_NETWORK_TYPE_TO_GECKO[index];
supportedNetworkTypes = networkTypes ? supportedNetworkTypes = networkTypes ?
@ -382,10 +423,7 @@ MobileConnectionProvider.prototype = {
}, },
_rulesToCallForwardingOptions: function(aRules) { _rulesToCallForwardingOptions: function(aRules) {
for (let i = 0; i < aRules.length; i++) { return aRules.map(rule => new CallForwardingOptions(rule));
let info = new CallForwardingOptions(aRules[i]);
aRules[i] = info;
}
}, },
_dispatchNotifyError: function(aCallback, aErrorMsg) { _dispatchNotifyError: function(aCallback, aErrorMsg) {
@ -515,6 +553,13 @@ MobileConnectionProvider.prototype = {
this.deliverListenerEvent("notifyRadioStateChanged"); this.deliverListenerEvent("notifyRadioStateChanged");
}, },
notifyCFStateChanged: function(aAction, aReason, aNumber, aTimeSeconds,
aServiceClass) {
this.deliverListenerEvent("notifyCFStateChanged",
[true, aAction, aReason, aNumber, aTimeSeconds,
aServiceClass]);
},
getSupportedNetworkTypes: function(aTypes) { getSupportedNetworkTypes: function(aTypes) {
aTypes.value = this.supportedNetworkTypes.slice(); aTypes.value = this.supportedNetworkTypes.slice();
return aTypes.value.length; return aTypes.value.length;
@ -680,49 +725,8 @@ MobileConnectionProvider.prototype = {
}, },
sendMMI: function(aMmi, aCallback) { sendMMI: function(aMmi, aCallback) {
this._radioInterface.sendWorkerMessage("sendMMI", {mmi: aMmi}, let telephonyCallback = new TelephonyCallback(aCallback);
(function(aResponse) { gGonkTelephonyService.dialMMI(this._clientId, aMmi, telephonyCallback);
aResponse.serviceCode = aResponse.mmiServiceCode || "";
// We expect to have an IMEI at this point if the request was supposed
// to query for the IMEI, so getting a successful reply from the RIL
// without containing an actual IMEI number is considered an error.
if (aResponse.serviceCode === RIL.MMI_KS_SC_IMEI &&
!aResponse.statusMessage) {
aResponse.errorMsg = aResponse.errorMsg ||
RIL.GECKO_ERROR_GENERIC_FAILURE;
}
if (aResponse.errorMsg) {
if (aResponse.additionalInformation) {
aCallback.notifyError(aResponse.errorMsg, "",
aResponse.serviceCode,
aResponse.additionalInformation);
} else {
aCallback.notifyError(aResponse.errorMsg, "",
aResponse.serviceCode);
}
return false;
}
if (aResponse.isSetCallForward) {
this.deliverListenerEvent("notifyCFStateChanged",
[!aResponse.errorMsg, aResponse.action,
aResponse.reason, aResponse.number,
aResponse.timeSeconds, aResponse.serviceClass]);
}
// MMI query call forwarding options request returns a set of rules that
// will be exposed in the form of an array of MozCallForwardingOptions
// instances.
if (aResponse.serviceCode === RIL.MMI_KS_SC_CALL_FORWARDING &&
aResponse.additionalInformation) {
this._rulesToCallForwardingOptions(aResponse.additionalInformation);
}
let mmiResult = new MMIResult(aResponse);
aCallback.notifySendCancelMmiSuccess(mmiResult);
return false;
}).bind(this));
}, },
cancelMMI: function(aCallback) { cancelMMI: function(aCallback) {
@ -762,11 +766,9 @@ MobileConnectionProvider.prototype = {
return false; return false;
} }
this.deliverListenerEvent("notifyCFStateChanged", this.notifyCFStateChanged(aResponse.action, aResponse.reason,
[!aResponse.errorMsg, aResponse.action, aResponse.number, aResponse.timeSeconds,
aResponse.reason, aResponse.number, aResponse.serviceClass);
aResponse.timeSeconds, aResponse.serviceClass]);
aCallback.notifySuccess(); aCallback.notifySuccess();
return false; return false;
}).bind(this)); }).bind(this));
@ -786,9 +788,8 @@ MobileConnectionProvider.prototype = {
return false; return false;
} }
let infos = aResponse.rules; aCallback.notifyGetCallForwardingSuccess(
this._rulesToCallForwardingOptions(infos); this._rulesToCallForwardingOptions(aResponse.rules));
aCallback.notifyGetCallForwardingSuccess(infos);
return false; return false;
}).bind(this)); }).bind(this));
}, },
@ -1216,6 +1217,17 @@ MobileConnectionService.prototype = {
provider.deliverListenerEvent("notifyLastKnownHomeNetworkChanged"); 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. * nsIObserver interface.
*/ */

View File

@ -9,7 +9,7 @@
"@mozilla.org/mobileconnection/gonkmobileconnectionservice;1" "@mozilla.org/mobileconnection/gonkmobileconnectionservice;1"
%} %}
[scriptable, uuid(e54fa0a4-d357-48ef-9a1e-ffc9705b44b1)] [scriptable, uuid(b0310517-e7f6-4fa5-a52e-fa6ff35c8fc1)]
interface nsIGonkMobileConnectionService : nsIMobileConnectionService interface nsIGonkMobileConnectionService : nsIMobileConnectionService
{ {
void notifyNetworkInfoChanged(in unsigned long clientId, in jsval networkInfo); void notifyNetworkInfoChanged(in unsigned long clientId, in jsval networkInfo);
@ -46,4 +46,11 @@ interface nsIGonkMobileConnectionService : nsIMobileConnectionService
void notifyLastHomeNetworkChanged(in unsigned long clientId, void notifyLastHomeNetworkChanged(in unsigned long clientId,
in DOMString network); in DOMString network);
void notifyCFStateChanged(in unsigned long clientId,
in unsigned short action,
in unsigned short reason,
in DOMString number,
in unsigned short timeSeconds,
in unsigned short serviceClass);
}; };

View File

@ -33,7 +33,7 @@ function testInvalidMMICode() {
ok(true, MMI_CODE + " fail"); ok(true, MMI_CODE + " fail");
is(aError.name, "emMmiError", "MMI error name"); is(aError.name, "emMmiError", "MMI error name");
is(aError.message, "", "No message"); is(aError.message, "", "No message");
is(aError.serviceCode, "", "No serviceCode"); is(aError.serviceCode, "scUssd", "Service code USSD");
is(aError.additionalInformation, null, "No additional information"); is(aError.additionalInformation, null, "No additional information");
}); });
} }

View File

@ -57,20 +57,6 @@ const EMERGENCY_CB_MODE_TIMEOUT_MS = 300000; // 5 mins = 300000 ms.
const ICC_MAX_LINEAR_FIXED_RECORDS = 0xfe; const ICC_MAX_LINEAR_FIXED_RECORDS = 0xfe;
// MMI match groups
const MMI_MATCH_GROUP_FULL_MMI = 1;
const MMI_MATCH_GROUP_PROCEDURE = 2;
const MMI_MATCH_GROUP_SERVICE_CODE = 3;
const MMI_MATCH_GROUP_SIA = 4;
const MMI_MATCH_GROUP_SIB = 5;
const MMI_MATCH_GROUP_SIC = 6;
const MMI_MATCH_GROUP_PWD_CONFIRM = 7;
const MMI_MATCH_GROUP_DIALING_NUMBER = 8;
const MMI_MAX_LENGTH_SHORT_CODE = 2;
const MMI_END_OF_USSD = "#";
const GET_CURRENT_CALLS_RETRY_MAX = 3; const GET_CURRENT_CALLS_RETRY_MAX = 3;
let RILQUIRKS_CALLSTATE_EXTRA_UINT32; let RILQUIRKS_CALLSTATE_EXTRA_UINT32;
@ -2402,208 +2388,11 @@ RilObject.prototype = {
{callback: callback}); {callback: callback});
}, },
/**
* Parse the dial number to extract its mmi code part.
*
* @param number
* Phone number to be parsed
*/
parseMMIFromDialNumber: function(options) {
// We don't have to parse mmi in cdma.
if (!this._isCdma) {
options.mmi = this._parseMMI(options.number);
}
this.sendChromeMessage(options);
},
/**
* Helper to parse MMI/USSD string. TS.22.030 Figure 3.5.3.2.
*/
_parseMMI: function(mmiString) {
if (!mmiString || !mmiString.length) {
return null;
}
let matches = this._getMMIRegExp().exec(mmiString);
if (matches) {
return {
fullMMI: matches[MMI_MATCH_GROUP_FULL_MMI],
procedure: matches[MMI_MATCH_GROUP_PROCEDURE],
serviceCode: matches[MMI_MATCH_GROUP_SERVICE_CODE],
sia: matches[MMI_MATCH_GROUP_SIA],
sib: matches[MMI_MATCH_GROUP_SIB],
sic: matches[MMI_MATCH_GROUP_SIC],
pwd: matches[MMI_MATCH_GROUP_PWD_CONFIRM],
dialNumber: matches[MMI_MATCH_GROUP_DIALING_NUMBER]
};
}
if (this._isPoundString(mmiString) || this._isMMIShortString(mmiString)) {
return {
fullMMI: mmiString
};
}
return null;
},
/**
* Build the regex to parse MMI string.
*
* The resulting groups after matching will be:
* 1 = full MMI string that might be used as a USSD request.
* 2 = MMI procedure.
* 3 = Service code.
* 4 = SIA.
* 5 = SIB.
* 6 = SIC.
* 7 = Password registration.
* 8 = Dialing number.
*
* @see TS.22.030 Figure 3.5.3.2.
*/
_buildMMIRegExp: function() {
// The general structure of the codes is as follows:
// - Activation (*SC*SI#).
// - Deactivation (#SC*SI#).
// - Interrogation (*#SC*SI#).
// - Registration (**SC*SI#).
// - Erasure (##SC*SI#).
//
// where SC = Service Code (2 or 3 digits) and SI = Supplementary Info
// (variable length).
// MMI procedure, which could be *, #, *#, **, ##
let procedure = "(\\*[*#]?|##?)";
// MMI Service code, which is a 2 or 3 digits that uniquely specifies the
// Supplementary Service associated with the MMI code.
let serviceCode = "(\\d{2,3})";
// MMI Supplementary Information SIA, SIB and SIC. SIA may comprise e.g. a
// PIN code or Directory Number, SIB may be used to specify the tele or
// bearer service and SIC to specify the value of the "No Reply Condition
// Timer". Where a particular service request does not require any SI,
// "*SI" is not entered. The use of SIA, SIB and SIC is optional and shall
// be entered in any of the following formats:
// - *SIA*SIB*SIC#
// - *SIA*SIB#
// - *SIA**SIC#
// - *SIA#
// - **SIB*SIC#
// - ***SIC#
//
// Also catch the additional NEW_PASSWORD for the case of a password
// registration procedure. Ex:
// - * 03 * ZZ * OLD_PASSWORD * NEW_PASSWORD * NEW_PASSWORD #
// - ** 03 * ZZ * OLD_PASSWORD * NEW_PASSWORD * NEW_PASSWORD #
// - * 03 ** OLD_PASSWORD * NEW_PASSWORD * NEW_PASSWORD #
// - ** 03 ** OLD_PASSWORD * NEW_PASSWORD * NEW_PASSWORD #
let si = "\\*([^*#]*)";
let allSi = "";
for (let i = 0; i < 4; ++i) {
allSi = "(?:" + si + allSi + ")?";
}
let fullmmi = "(" + procedure + serviceCode + allSi + "#)";
// dial string after the #.
let dialString = "([^#]*)";
return new RegExp(fullmmi + dialString);
},
/**
* Provide the regex to parse MMI string.
*/
_getMMIRegExp: function() {
if (!this._mmiRegExp) {
this._mmiRegExp = this._buildMMIRegExp();
}
return this._mmiRegExp;
},
/**
* Helper to parse # string. TS.22.030 Figure 3.5.3.2.
*/
_isPoundString: function(mmiString) {
return (mmiString.charAt(mmiString.length - 1) === MMI_END_OF_USSD);
},
/**
* Helper to parse short string. TS.22.030 Figure 3.5.3.2.
*/
_isMMIShortString: function(mmiString) {
if (mmiString.length > 2) {
return false;
}
// TODO: Should take care of checking if the string is an emergency number
// in Bug 889737. See Bug 1023141 for more background.
// In a call case.
if (Object.getOwnPropertyNames(this.currentCalls).length > 0) {
return true;
}
// Input string is 2 digits starting with a "1"
if ((mmiString.length == 2) && (mmiString.charAt(0) === '1')) {
return false;
}
return true;
},
_serviceCodeToKeyString: function(serviceCode) {
switch (serviceCode) {
case MMI_SC_CFU:
case MMI_SC_CF_BUSY:
case MMI_SC_CF_NO_REPLY:
case MMI_SC_CF_NOT_REACHABLE:
case MMI_SC_CF_ALL:
case MMI_SC_CF_ALL_CONDITIONAL:
return MMI_KS_SC_CALL_FORWARDING;
case MMI_SC_PIN:
return MMI_KS_SC_PIN;
case MMI_SC_PIN2:
return MMI_KS_SC_PIN2;
case MMI_SC_PUK:
return MMI_KS_SC_PUK;
case MMI_SC_PUK2:
return MMI_KS_SC_PUK2;
case MMI_SC_IMEI:
return MMI_KS_SC_IMEI;
case MMI_SC_CLIP:
return MMI_KS_SC_CLIP;
case MMI_SC_CLIR:
return MMI_KS_SC_CLIR;
case MMI_SC_BAOC:
case MMI_SC_BAOIC:
case MMI_SC_BAOICxH:
case MMI_SC_BAIC:
case MMI_SC_BAICr:
case MMI_SC_BA_ALL:
case MMI_SC_BA_MO:
case MMI_SC_BA_MT:
return MMI_KS_SC_CALL_BARRING;
case MMI_SC_CALL_WAITING:
return MMI_KS_SC_CALL_WAITING;
default:
return MMI_KS_SC_USSD;
}
},
sendMMI: function(options) { sendMMI: function(options) {
if (DEBUG) { if (DEBUG) {
this.context.debug("SendMMI " + JSON.stringify(options)); this.context.debug("SendMMI " + JSON.stringify(options));
} }
let mmi = this._parseMMI(options.mmi);
if (DEBUG) {
this.context.debug("MMI " + JSON.stringify(mmi));
}
let _sendMMIError = (function(errorMsg) { let _sendMMIError = (function(errorMsg) {
options.success = false; options.success = false;
options.errorMsg = errorMsg; options.errorMsg = errorMsg;
@ -2611,14 +2400,12 @@ RilObject.prototype = {
}).bind(this); }).bind(this);
// It's neither a valid mmi code nor an ongoing ussd. // It's neither a valid mmi code nor an ongoing ussd.
let mmi = options.mmi;
if (!mmi && !this._ussdSession) { if (!mmi && !this._ussdSession) {
_sendMMIError(MMI_ERROR_KS_ERROR); _sendMMIError(MMI_ERROR_KS_ERROR);
return; return;
} }
options.mmiServiceCode = mmi ?
this._serviceCodeToKeyString(mmi.serviceCode) : MMI_KS_SC_USSD;
function _isValidPINPUKRequest() { function _isValidPINPUKRequest() {
// The only allowed MMI procedure for ICC PIN, PIN2, PUK and PUK2 handling // The only allowed MMI procedure for ICC PIN, PIN2, PUK and PUK2 handling
// is "Registration" (**). // is "Registration" (**).
@ -3576,36 +3363,34 @@ RilObject.prototype = {
return; return;
} }
let mmiServiceCode = options.mmiServiceCode; let serviceCode = options.mmi.serviceCode;
if (options.success) { if (options.success) {
switch (mmiServiceCode) { switch (serviceCode) {
case MMI_KS_SC_PIN: case MMI_SC_PIN:
options.statusMessage = MMI_SM_KS_PIN_CHANGED; options.statusMessage = MMI_SM_KS_PIN_CHANGED;
break; break;
case MMI_KS_SC_PIN2: case MMI_SC_PIN2:
options.statusMessage = MMI_SM_KS_PIN2_CHANGED; options.statusMessage = MMI_SM_KS_PIN2_CHANGED;
break; break;
case MMI_KS_SC_PUK: case MMI_SC_PUK:
options.statusMessage = MMI_SM_KS_PIN_UNBLOCKED; options.statusMessage = MMI_SM_KS_PIN_UNBLOCKED;
break; break;
case MMI_KS_SC_PUK2: case MMI_SC_PUK2:
options.statusMessage = MMI_SM_KS_PIN2_UNBLOCKED; options.statusMessage = MMI_SM_KS_PIN2_UNBLOCKED;
break; break;
} }
} else { } else {
if (options.retryCount <= 0) { if (options.retryCount <= 0) {
if (mmiServiceCode === MMI_KS_SC_PUK) { if (serviceCode === MMI_SC_PUK) {
options.errorMsg = MMI_ERROR_KS_SIM_BLOCKED; options.errorMsg = MMI_ERROR_KS_SIM_BLOCKED;
} else if (mmiServiceCode === MMI_KS_SC_PIN) { } else if (serviceCode === MMI_SC_PIN) {
options.errorMsg = MMI_ERROR_KS_NEEDS_PUK; options.errorMsg = MMI_ERROR_KS_NEEDS_PUK;
} }
} else { } else {
if (mmiServiceCode === MMI_KS_SC_PIN || if (serviceCode === MMI_SC_PIN || serviceCode === MMI_SC_PIN2) {
mmiServiceCode === MMI_KS_SC_PIN2) {
options.errorMsg = MMI_ERROR_KS_BAD_PIN; options.errorMsg = MMI_ERROR_KS_BAD_PIN;
} else if (mmiServiceCode === MMI_KS_SC_PUK || } else if (serviceCode === MMI_SC_PUK || serviceCode === MMI_SC_PUK2) {
mmiServiceCode === MMI_KS_SC_PUK2) {
options.errorMsg = MMI_ERROR_KS_BAD_PUK; options.errorMsg = MMI_ERROR_KS_BAD_PUK;
} }
if (options.retryCount !== undefined) { if (options.retryCount !== undefined) {
@ -6030,7 +5815,7 @@ RilObject.prototype[REQUEST_QUERY_CALL_FORWARD_STATUS] =
this.sendChromeMessage(options); this.sendChromeMessage(options);
}; };
RilObject.prototype[REQUEST_SET_CALL_FORWARD] = RilObject.prototype[REQUEST_SET_CALL_FORWARD] =
function REQUEST_SET_CALL_FORWARD(length, options) { function REQUEST_SET_CALL_FORWARD(length, options) {
options.success = (options.rilRequestError === 0); options.success = (options.rilRequestError === 0);
if (!options.success) { if (!options.success) {
options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];

View File

@ -54,6 +54,16 @@ const AUDIO_STATE_NAME = [
const DEFAULT_EMERGENCY_NUMBERS = ["112", "911"]; const DEFAULT_EMERGENCY_NUMBERS = ["112", "911"];
// MMI match groups
const MMI_MATCH_GROUP_FULL_MMI = 1;
const MMI_MATCH_GROUP_PROCEDURE = 2;
const MMI_MATCH_GROUP_SERVICE_CODE = 3;
const MMI_MATCH_GROUP_SIA = 4;
const MMI_MATCH_GROUP_SIB = 5;
const MMI_MATCH_GROUP_SIC = 6;
const MMI_MATCH_GROUP_PWD_CONFIRM = 7;
const MMI_MATCH_GROUP_DIALING_NUMBER = 8;
let DEBUG; let DEBUG;
function debug(s) { function debug(s) {
dump("TelephonyService: " + s + "\n"); dump("TelephonyService: " + s + "\n");
@ -99,15 +109,52 @@ XPCOMUtils.defineLazyServiceGetter(this, "gSystemMessenger",
"@mozilla.org/system-message-internal;1", "@mozilla.org/system-message-internal;1",
"nsISystemMessagesInternal"); "nsISystemMessagesInternal");
XPCOMUtils.defineLazyServiceGetter(this, "gGonkMobileConnectionService",
"@mozilla.org/mobileconnection/mobileconnectionservice;1",
"nsIGonkMobileConnectionService");
XPCOMUtils.defineLazyGetter(this, "gPhoneNumberUtils", function() { XPCOMUtils.defineLazyGetter(this, "gPhoneNumberUtils", function() {
let ns = {}; let ns = {};
Cu.import("resource://gre/modules/PhoneNumberUtils.jsm", ns); Cu.import("resource://gre/modules/PhoneNumberUtils.jsm", ns);
return ns.PhoneNumberUtils; return ns.PhoneNumberUtils;
}); });
function MMIResult(aMmiServiceCode, aOptions) {
this.serviceCode = aMmiServiceCode;
this.statusMessage = aOptions.statusMessage;
this.additionalInformation = aOptions.additionalInformation;
}
MMIResult.prototype = {
__exposedProps__ : {serviceCode: 'r',
statusMessage: 'r',
additionalInformation: 'r'},
};
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 TelephonyService() { function TelephonyService() {
this._numClients = gRadioInterfaceLayer.numRadioInterfaces; this._numClients = gRadioInterfaceLayer.numRadioInterfaces;
this._listeners = []; this._listeners = [];
this._mmiRegExp = null;
this._isDialing = false;
this._cachedDialRequest = null;
this._currentCalls = {}; this._currentCalls = {};
this._cdmaCallWaitingNumber = null; this._cdmaCallWaitingNumber = null;
@ -300,6 +347,10 @@ TelephonyService.prototype = {
} }
}, },
_rulesToCallForwardingOptions: function(aRules) {
return aRules.map(rule => new CallForwardingOptions(rule));
},
_updateDebugFlag: function() { _updateDebugFlag: function() {
try { try {
DEBUG = RIL.DEBUG_RIL || DEBUG = RIL.DEBUG_RIL ||
@ -499,36 +550,51 @@ TelephonyService.prototype = {
this.notifyCallStateChanged(aClientId, parentCall); this.notifyCallStateChanged(aClientId, parentCall);
}, },
_composeDialRequest: function(aClientId, aNumber) {
return new Promise((resolve, reject) => {
this._sendToRilWorker(aClientId, "parseMMIFromDialNumber",
{number: aNumber}, response => {
let options = {};
let mmi = response.mmi;
if (!mmi) {
resolve({
number: aNumber
});
} else if (this._isTemporaryCLIR(mmi)) {
resolve({
number: mmi.dialNumber,
clirMode: this._getTemporaryCLIRMode(mmi.procedure)
});
} else {
reject(DIAL_ERROR_BAD_NUMBER);
}
});
});
},
cachedDialRequest: null,
isDialing: false,
dial: function(aClientId, aNumber, aIsDialEmergency, aCallback) { dial: function(aClientId, aNumber, aIsDialEmergency, aCallback) {
if (DEBUG) debug("Dialing " + (aIsDialEmergency ? "emergency " : "") + aNumber); if (DEBUG) debug("Dialing " + (aIsDialEmergency ? "emergency " : "") + aNumber);
if (this.isDialing) { // We don't try to be too clever here, as the phone is probably in the
// locked state. Let's just check if it's a number without normalizing
if (!aIsDialEmergency) {
aNumber = gPhoneNumberUtils.normalize(aNumber);
}
// Validate the number.
// Note: isPlainPhoneNumber also accepts USSD and SS numbers
if (!gPhoneNumberUtils.isPlainPhoneNumber(aNumber)) {
if (DEBUG) debug("Error: Number '" + aNumber + "' is not viable. Drop.");
aCallback.notifyDialError(DIAL_ERROR_BAD_NUMBER);
return;
}
let mmi = this._parseMMI(aClientId, aNumber);
if (!mmi) {
this._dialCall(aClientId,
{ number: aNumber,
isDialEmergency: aIsDialEmergency }, aCallback);
} else if (this._isTemporaryCLIR(mmi)) {
this._dialCall(aClientId,
{ number: mmi.dialNumber,
clirMode: this._getTemporaryCLIRMode(mmi.procedure),
isDialEmergency: aIsDialEmergency }, aCallback);
} else {
// Reject MMI code from dialEmergency api.
if (aIsDialEmergency) {
aCallback.notifyDialError(DIAL_ERROR_BAD_NUMBER);
return;
}
this._dialMMI(aClientId, mmi, aCallback);
}
},
/**
* @param aOptions.number
* @param aOptions.clirMode (optional)
* @param aOptions.isDialEmergency
*/
_dialCall: function(aClientId, aOptions, aCallback) {
if (this._isDialing) {
if (DEBUG) debug("Error: Already has a dialing call."); if (DEBUG) debug("Error: Already has a dialing call.");
aCallback.notifyDialError(DIAL_ERROR_INVALID_STATE_ERROR); aCallback.notifyDialError(DIAL_ERROR_INVALID_STATE_ERROR);
return; return;
@ -549,63 +615,43 @@ TelephonyService.prototype = {
return; return;
} }
// We don't try to be too clever here, as the phone is probably in the aOptions.isEmergency = this._isEmergencyNumber(aOptions.number);
// locked state. Let's just check if it's a number without normalizing if (aOptions.isEmergency) {
if (!aIsDialEmergency) { // Automatically select a proper clientId for emergency call.
aNumber = gPhoneNumberUtils.normalize(aNumber); aClientId = gRadioInterfaceLayer.getClientIdForEmergencyCall() ;
} if (aClientId === -1) {
if (DEBUG) debug("Error: No client is avaialble for emergency call.");
// Validate the number. aCallback.notifyDialError(DIAL_ERROR_INVALID_STATE_ERROR);
// Note: isPlainPhoneNumber also accepts USSD and SS numbers return;
if (!gPhoneNumberUtils.isPlainPhoneNumber(aNumber)) {
if (DEBUG) debug("Error: Number '" + aNumber + "' is not viable. Drop.");
aCallback.notifyDialError(DIAL_ERROR_BAD_NUMBER);
return;
}
this._composeDialRequest(aClientId, aNumber).then(options => {
options.isEmergency = this._isEmergencyNumber(options.number);
options.isDialEmergency = aIsDialEmergency;
if (options.isEmergency) {
// Automatically select a proper clientId for emergency call.
aClientId = gRadioInterfaceLayer.getClientIdForEmergencyCall() ;
if (aClientId === -1) {
if (DEBUG) debug("Error: No client is avaialble for emergency call.");
aCallback.notifyDialError(DIAL_ERROR_INVALID_STATE_ERROR);
return;
}
} }
}
// Before we dial, we have to hold the active call first. // Before we dial, we have to hold the active call first.
let activeCall = this._getOneActiveCall(aClientId); let activeCall = this._getOneActiveCall(aClientId);
if (!activeCall) { if (!activeCall) {
this._dialInternal(aClientId, options, aCallback); this._sendDialCallRequest(aClientId, aOptions, aCallback);
} else {
if (DEBUG) debug("There is an active call. Hold it first before dial.");
this._cachedDialRequest = {
clientId: aClientId,
options: aOptions,
callback: aCallback
};
if (activeCall.isConference) {
this.holdConference(aClientId);
} else { } else {
if (DEBUG) debug("There is an active call. Hold it first before dial."); this.holdCall(aClientId, activeCall.callIndex);
this.cachedDialRequest = {
clientId: aClientId,
options: options,
callback: aCallback
};
if (activeCall.isConference) {
this.holdConference(aClientId);
} else {
this.holdCall(aClientId, activeCall.callIndex);
}
} }
}, cause => { }
aCallback.notifyDialError(DIAL_ERROR_BAD_NUMBER);
});
}, },
_dialInternal: function(aClientId, aOptions, aCallback) { _sendDialCallRequest: function(aClientId, aOptions, aCallback) {
this.isDialing = true; this._isDialing = true;
this._sendToRilWorker(aClientId, "dial", aOptions, response => { this._sendToRilWorker(aClientId, "dial", aOptions, response => {
this.isDialing = false; this._isDialing = false;
if (!response.success) { if (!response.success) {
aCallback.notifyDialError(response.errorMsg); aCallback.notifyDialError(response.errorMsg);
@ -625,6 +671,235 @@ TelephonyService.prototype = {
}); });
}, },
/**
* @param aMmi
* Parsed MMI structure.
*/
_dialMMI: function(aClientId, aMmi, aCallback) {
let mmiServiceCode = aMmi ?
this._serviceCodeToKeyString(aMmi.serviceCode) : RIL.MMI_KS_SC_USSD;
aCallback.notifyDialMMI(mmiServiceCode);
this._sendToRilWorker(aClientId, "sendMMI", { mmi: aMmi }, response => {
if (DEBUG) debug("MMI response: " + JSON.stringify(response));
if (!response.success) {
if (response.additionalInformation != null) {
aCallback.notifyDialMMIErrorWithInfo(response.errorMsg,
response.additionalInformation);
} else {
aCallback.notifyDialMMIError(response.errorMsg);
}
return;
}
// We expect to have an IMEI at this point if the request was supposed
// to query for the IMEI, so getting a successful reply from the RIL
// without containing an actual IMEI number is considered an error.
if (mmiServiceCode === RIL.MMI_KS_SC_IMEI &&
!response.statusMessage) {
aCallback.notifyDialMMIError(RIL.GECKO_ERROR_GENERIC_FAILURE);
return;
}
// MMI query call forwarding options request returns a set of rules that
// will be exposed in the form of an array of MozCallForwardingOptions
// instances.
if (mmiServiceCode === RIL.MMI_KS_SC_CALL_FORWARDING) {
if (response.isSetCallForward) {
gGonkMobileConnectionService.notifyCFStateChanged(aClientId,
response.action,
response.reason,
response.number,
response.timeSeconds,
response.serviceClass);
}
if (response.additionalInformation != null) {
response.additionalInformation =
this._rulesToCallForwardingOptions(response.additionalInformation);
}
}
let result = new MMIResult(mmiServiceCode, response);
aCallback.notifyDialMMISuccess(result);
});
},
/**
* Build the regex to parse MMI string. TS.22.030
*
* The resulting groups after matching will be:
* 1 = full MMI string that might be used as a USSD request.
* 2 = MMI procedure.
* 3 = Service code.
* 4 = SIA.
* 5 = SIB.
* 6 = SIC.
* 7 = Password registration.
* 8 = Dialing number.
*/
_buildMMIRegExp: function() {
// The general structure of the codes is as follows:
// - Activation (*SC*SI#).
// - Deactivation (#SC*SI#).
// - Interrogation (*#SC*SI#).
// - Registration (**SC*SI#).
// - Erasure (##SC*SI#).
//
// where SC = Service Code (2 or 3 digits) and SI = Supplementary Info
// (variable length).
// Procedure, which could be *, #, *#, **, ##
let procedure = "(\\*[*#]?|##?)";
// Service code, which is a 2 or 3 digits that uniquely specifies the
// Supplementary Service associated with the MMI code.
let serviceCode = "(\\d{2,3})";
// Supplementary Information SIA, SIB and SIC. SIA may comprise e.g. a PIN
// code or Directory Number, SIB may be used to specify the tele or bearer
// service and SIC to specify the value of the "No Reply Condition Timer".
// Where a particular service request does not require any SI, "*SI" is
// not entered. The use of SIA, SIB and SIC is optional and shall be
// entered in any of the following formats:
// - *SIA*SIB*SIC#
// - *SIA*SIB#
// - *SIA**SIC#
// - *SIA#
// - **SIB*SIC#
// - ***SIC#
//
// Also catch the additional NEW_PASSWORD for the case of a password
// registration procedure. Ex:
// - * 03 * ZZ * OLD_PASSWORD * NEW_PASSWORD * NEW_PASSWORD #
// - ** 03 * ZZ * OLD_PASSWORD * NEW_PASSWORD * NEW_PASSWORD #
// - * 03 ** OLD_PASSWORD * NEW_PASSWORD * NEW_PASSWORD #
// - ** 03 ** OLD_PASSWORD * NEW_PASSWORD * NEW_PASSWORD #
let si = "\\*([^*#]*)";
let allSi = "";
for (let i = 0; i < 4; ++i) {
allSi = "(?:" + si + allSi + ")?";
}
let fullmmi = "(" + procedure + serviceCode + allSi + "#)";
// Dial string after the #.
let dialString = "([^#]*)";
return new RegExp(fullmmi + dialString);
},
/**
* Provide the regex to parse MMI string.
*/
_getMMIRegExp: function() {
if (!this._mmiRegExp) {
this._mmiRegExp = this._buildMMIRegExp();
}
return this._mmiRegExp;
},
/**
* Helper to parse # string. TS.22.030 Figure 3.5.3.2.
*/
_isPoundString: function(aMmiString) {
return (aMmiString.charAt(aMmiString.length - 1) === "#");
},
/**
* Helper to parse short string. TS.22.030 Figure 3.5.3.2.
*/
_isShortString: function(aClientId, aMmiString) {
if (aMmiString.length > 2) {
return false;
}
// In a call case.
if (Object.getOwnPropertyNames(this._currentCalls[aClientId]).length > 0) {
return true;
}
// Input string is
// - emergency number or
// - 2 digits starting with a "1"
if (this._isEmergencyNumber(aMmiString) ||
(aMmiString.length == 2) && (aMmiString.charAt(0) === '1')) {
return false;
}
return true;
},
/**
* Helper to parse MMI/USSD string. TS.22.030 Figure 3.5.3.2.
*/
_parseMMI: function(aClientId, aMmiString) {
let matches = this._getMMIRegExp().exec(aMmiString);
if (matches) {
return {
fullMMI: matches[MMI_MATCH_GROUP_FULL_MMI],
procedure: matches[MMI_MATCH_GROUP_PROCEDURE],
serviceCode: matches[MMI_MATCH_GROUP_SERVICE_CODE],
sia: matches[MMI_MATCH_GROUP_SIA],
sib: matches[MMI_MATCH_GROUP_SIB],
sic: matches[MMI_MATCH_GROUP_SIC],
pwd: matches[MMI_MATCH_GROUP_PWD_CONFIRM],
dialNumber: matches[MMI_MATCH_GROUP_DIALING_NUMBER]
};
}
if (this._isPoundString(aMmiString) ||
this._isShortString(aClientId, aMmiString)) {
return {
fullMMI: aMmiString
};
}
return null;
},
_serviceCodeToKeyString: function(aServiceCode) {
switch (aServiceCode) {
case RIL.MMI_SC_CFU:
case RIL.MMI_SC_CF_BUSY:
case RIL.MMI_SC_CF_NO_REPLY:
case RIL.MMI_SC_CF_NOT_REACHABLE:
case RIL.MMI_SC_CF_ALL:
case RIL.MMI_SC_CF_ALL_CONDITIONAL:
return RIL.MMI_KS_SC_CALL_FORWARDING;
case RIL.MMI_SC_PIN:
return RIL.MMI_KS_SC_PIN;
case RIL.MMI_SC_PIN2:
return RIL.MMI_KS_SC_PIN2;
case RIL.MMI_SC_PUK:
return RIL.MMI_KS_SC_PUK;
case RIL.MMI_SC_PUK2:
return RIL.MMI_KS_SC_PUK2;
case RIL.MMI_SC_IMEI:
return RIL.MMI_KS_SC_IMEI;
case RIL.MMI_SC_CLIP:
return RIL.MMI_KS_SC_CLIP;
case RIL.MMI_SC_CLIR:
return RIL.MMI_KS_SC_CLIR;
case RIL.MMI_SC_BAOC:
case RIL.MMI_SC_BAOIC:
case RIL.MMI_SC_BAOICxH:
case RIL.MMI_SC_BAIC:
case RIL.MMI_SC_BAICr:
case RIL.MMI_SC_BA_ALL:
case RIL.MMI_SC_BA_MO:
case RIL.MMI_SC_BA_MT:
return RIL.MMI_KS_SC_CALL_BARRING;
case RIL.MMI_SC_CALL_WAITING:
return RIL.MMI_KS_SC_CALL_WAITING;
default:
return RIL.MMI_KS_SC_USSD;
}
},
hangUp: function(aClientId, aCallIndex) { hangUp: function(aClientId, aCallIndex) {
let parentId = this._currentCalls[aClientId][aCallIndex].parentId; let parentId = this._currentCalls[aClientId][aCallIndex].parentId;
if (parentId) { if (parentId) {
@ -932,12 +1207,12 @@ TelephonyService.prototype = {
} }
// Handle cached dial request. // Handle cached dial request.
if (this.cachedDialRequest && !this._getOneActiveCall()) { if (this._cachedDialRequest && !this._getOneActiveCall()) {
if (DEBUG) debug("All calls held. Perform the cached dial request."); if (DEBUG) debug("All calls held. Perform the cached dial request.");
let request = this.cachedDialRequest; let request = this._cachedDialRequest;
this._dialInternal(request.clientId, request.options, request.callback); this._sendDialCallRequest(request.clientId, request.options, request.callback);
this.cachedDialRequest = null; this._cachedDialRequest = null;
} }
this._notifyAllListeners("callStateChanged", [aClientId, this._notifyAllListeners("callStateChanged", [aClientId,
@ -987,6 +1262,11 @@ TelephonyService.prototype = {
this._notifyAllListeners("conferenceCallStateChanged", [aState]); this._notifyAllListeners("conferenceCallStateChanged", [aState]);
}, },
dialMMI: function(aClientId, aMmiString, aCallback) {
let mmi = this._parseMMI(aClientId, aMmiString);
this._dialMMI(aClientId, mmi, aCallback);
},
/** /**
* nsIObserver interface. * nsIObserver interface.
*/ */