Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
475 lines
16 KiB
JavaScript
Executable file
475 lines
16 KiB
JavaScript
Executable file
/* globals LazyLoader, MmiUI, MobileOperator, NotificationHelper,
|
|
Promise */
|
|
|
|
/* exported MmiManager */
|
|
|
|
'use strict';
|
|
|
|
// As defined in 3GPP TS 22.030 version 10.0.0 Release 10 standard
|
|
// USSD code used to query call barring supplementary service status
|
|
const CALL_BARRING_STATUS_MMI_CODE = '*#33#';
|
|
// USSD code used to query call waiting supplementary service status
|
|
const CALL_WAITING_STATUS_MMI_CODE = '*#43#';
|
|
|
|
var MmiManager = {
|
|
|
|
_: null,
|
|
_conn: null,
|
|
_ready: false,
|
|
// In some cases, the RIL doesn't provide the expected order of events
|
|
// while sending an MMI that triggers an interactive USSD request (specially
|
|
// while roaming), which should be DOMRequest.onsuccess (or .onerror) +
|
|
// ussdreceived. If the first event received is the ussdreceived one, we take
|
|
// the DOMRequest.onsuccess received subsequenty as a closure of the USSD
|
|
// session.
|
|
_pendingRequest: null,
|
|
_session: null,
|
|
|
|
init: function mm_init() {
|
|
if (this._ready) {
|
|
return Promise.resolve();
|
|
}
|
|
|
|
this._ = navigator.mozL10n.get;
|
|
|
|
var self = this;
|
|
var lazyFiles = ['/shared/js/icc_helper.js',
|
|
'/shared/style/input_areas.css',
|
|
'/shared/js/mobile_operator.js'];
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
LazyLoader.load(lazyFiles, function() {
|
|
MmiUI.init();
|
|
self._ready = true;
|
|
resolve();
|
|
});
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Handle dialing an MMI code by starting the UI and following up on the
|
|
* promise in the MMICall object returned by Telephony.dial().
|
|
*
|
|
* @param {Object} conn The MozMobileConnection on which the MMI code was
|
|
* dialed on.
|
|
* @param {String} message The dialed MMI code.
|
|
* @param {Promise} promise The MMICall promise.
|
|
* @returns {Promise} A promise that is resolved once the MMI message
|
|
* response has been handled.
|
|
*/
|
|
handleDialing: function mm_handleDialing(conn, message, promise) {
|
|
var self = this;
|
|
|
|
this._conn = conn;
|
|
this._pendingRequest = promise;
|
|
|
|
return this.init().then(function() {
|
|
self.openUI();
|
|
|
|
/* TODO we are creating this callback instead of just following up on
|
|
* the promise because we need to pass the original mmi code sent. This
|
|
* should be removed when bug 889737 and bug 1049651 are landed as it
|
|
* should be possible to get it in the callback. */
|
|
return promise.then(function(result) {
|
|
if (result.success) {
|
|
self.notifySuccess(result, message);
|
|
} else {
|
|
self.notifyError(result, message);
|
|
}
|
|
});
|
|
});
|
|
},
|
|
|
|
// Passing the sent MMI code because the message displayed to the user
|
|
// could be different depending on the MMI code.
|
|
notifySuccess: function mm_notifySuccess(mmiResult, sentMMI) {
|
|
// Helper function to compose an informative message about a successful
|
|
// request to query the call forwarding status.
|
|
var processCf = (function processCf(result) {
|
|
var voice, data, fax, sms, sync, async, packet, pad;
|
|
|
|
for (var i = 0; i < result.length; i++) {
|
|
if (!result[i].active) {
|
|
continue;
|
|
}
|
|
|
|
for (var serviceClassMask = 1;
|
|
serviceClassMask <= this._conn.ICC_SERVICE_CLASS_MAX;
|
|
serviceClassMask <<= 1) {
|
|
if ((serviceClassMask & result[i].serviceClass) !== 0) {
|
|
switch (serviceClassMask) {
|
|
case this._conn.ICC_SERVICE_CLASS_VOICE:
|
|
voice = result[i].number;
|
|
break;
|
|
case this._conn.ICC_SERVICE_CLASS_DATA:
|
|
data = result[i].number;
|
|
break;
|
|
case this._conn.ICC_SERVICE_CLASS_FAX:
|
|
fax = result[i].number;
|
|
break;
|
|
case this._conn.ICC_SERVICE_CLASS_SMS:
|
|
sms = result[i].number;
|
|
break;
|
|
case this._conn.ICC_SERVICE_CLASS_DATA_SYNC:
|
|
sync = result[i].number;
|
|
break;
|
|
case this._conn.ICC_SERVICE_CLASS_DATA_ASYNC:
|
|
async = result[i].number;
|
|
break;
|
|
case this._conn.ICC_SERVICE_CLASS_PACKET:
|
|
packet = result[i].number;
|
|
break;
|
|
case this._conn.ICC_SERVICE_CLASS_PAD:
|
|
pad = result[i].number;
|
|
break;
|
|
default:
|
|
return this._('call-forwarding-error');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var inactive = this._('call-forwarding-inactive');
|
|
var msg = [
|
|
this._('call-forwarding-status'),
|
|
this._('call-forwarding-voice', { voice: voice || inactive }),
|
|
this._('call-forwarding-data', { data: data || inactive }),
|
|
this._('call-forwarding-fax', { fax: fax || inactive }),
|
|
this._('call-forwarding-sms', { sms: sms || inactive }),
|
|
this._('call-forwarding-sync', { sync: sync || inactive }),
|
|
this._('call-forwarding-async', { async: async || inactive }),
|
|
this._('call-forwarding-packet', { packet: packet || inactive }),
|
|
this._('call-forwarding-pad', { pad: pad || inactive })
|
|
].join('\n');
|
|
|
|
return msg;
|
|
}).bind(this);
|
|
|
|
var ci = this.cardIndexForConnection(this._conn);
|
|
var message = null;
|
|
var title = null;
|
|
var error = null;
|
|
|
|
// We always expect an MMIResult object even for USSD requests.
|
|
if (!mmiResult) {
|
|
MmiUI.error(this._('GenericFailure'));
|
|
return;
|
|
}
|
|
|
|
if (mmiResult.serviceCode) {
|
|
title = this.prependSimNumber(this._(mmiResult.serviceCode), ci);
|
|
}
|
|
|
|
var additionalInformation = mmiResult.additionalInformation;
|
|
|
|
switch (mmiResult.serviceCode) {
|
|
case 'scCall':
|
|
/* Call control, no need to show a result page as the command will
|
|
* already have been executed (hang up a call, etc...). */
|
|
MmiUI.cancel();
|
|
return;
|
|
case 'scUssd':
|
|
// Bail out if there is nothing to show or if we got the .onsuccess
|
|
// event after the .onussdevent.
|
|
if (!mmiResult.statusMessage || this._pendingRequest === null) {
|
|
return;
|
|
}
|
|
|
|
message = mmiResult.statusMessage;
|
|
break;
|
|
case 'scPin':
|
|
case 'scPin2':
|
|
case 'scPuk':
|
|
case 'scPuk2':
|
|
if (mmiResult.statusMessage) {
|
|
message = this._(mmiResult.statusMessage);
|
|
}
|
|
break;
|
|
case 'scCallForwarding':
|
|
if (mmiResult.statusMessage) {
|
|
message = this._(mmiResult.statusMessage);
|
|
// Call forwarding requests via MMI codes might return an array of
|
|
// nsIDOMMozMobileCFInfo objects. In that case we serialize that array
|
|
// into a single string that can be shown on the screen.
|
|
if (additionalInformation) {
|
|
message = processCf(additionalInformation);
|
|
}
|
|
} else {
|
|
error = this._('GenericFailure');
|
|
}
|
|
break;
|
|
case 'scCallBarring':
|
|
case 'scCallWaiting':
|
|
message = this._(mmiResult.statusMessage);
|
|
// If we are just querying the status of the service, we show a
|
|
// different message, so the user knows she hasn't change anything
|
|
if (sentMMI === CALL_BARRING_STATUS_MMI_CODE ||
|
|
sentMMI === CALL_WAITING_STATUS_MMI_CODE) {
|
|
if (mmiResult.statusMessage === 'smServiceEnabled') {
|
|
message = this._('ServiceIsEnabled');
|
|
} else if (mmiResult.statusMessage === 'smServiceDisabled') {
|
|
message = this._('ServiceIsDisabled');
|
|
} else if (mmiResult.statusMessage === 'smServiceEnabledFor') {
|
|
message = this._('ServiceIsEnabledFor');
|
|
}
|
|
}
|
|
// Call barring and call waiting requests via MMI codes might return an
|
|
// array of strings indicating the service it is enabled for or just
|
|
// the disabled status message.
|
|
if (mmiResult.statusMessage === 'smServiceEnabledFor' &&
|
|
additionalInformation &&
|
|
Array.isArray(additionalInformation)) {
|
|
for (var i = 0, l = additionalInformation.length; i < l; i++) {
|
|
message += '\n' + this._(additionalInformation[i]);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
// This would allow carriers and others to implement custom MMI codes
|
|
// with title and statusMessage only.
|
|
if (mmiResult.statusMessage) {
|
|
message = this._(mmiResult.statusMessage) || mmiResult.statusMessage;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (!error) {
|
|
MmiUI.success(message, title);
|
|
} else {
|
|
MmiUI.error(error, title);
|
|
}
|
|
},
|
|
|
|
notifyError: function mm_notifyError(mmiError) {
|
|
var title = null;
|
|
var ci = this.cardIndexForConnection(this._conn);
|
|
|
|
if (mmiError.serviceCode) {
|
|
title = this.prependSimNumber(this._(mmiError.serviceCode), ci);
|
|
}
|
|
|
|
var error = mmiError.statusMessage ? this._(mmiError.statusMessage)
|
|
: this._('GenericFailure');
|
|
|
|
switch (mmiError.serviceCode) {
|
|
case 'scPin':
|
|
case 'scPin2':
|
|
case 'scPuk':
|
|
case 'scPuk2':
|
|
// If the error is related with an incorrect old PIN, we get the
|
|
// number of remainings attempts.
|
|
if (mmiError.additionalInformation &&
|
|
(mmiError.statusMessage === 'emMmiErrorPasswordIncorrect' ||
|
|
mmiError.statusMessage === 'emMmiErrorBadPin' ||
|
|
mmiError.statusMessage === 'emMmiErrorBadPuk')) {
|
|
error += '\n' + this._('emMmiErrorPinPukAttempts', {
|
|
n: mmiError.additionalInformation
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
|
|
MmiUI.error(error, title);
|
|
},
|
|
|
|
openUI: function mm_openUI() {
|
|
this.init().then(function() {
|
|
MmiUI.loading();
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Create a notification/message string by prepending the SIM number if the
|
|
* phone has more than one SIM card.
|
|
*
|
|
* @param text {String} The message text.
|
|
* @param cardIndex {Integer} The SIM card slot index.
|
|
* @return {String} Either the original string alone or with the SIM number
|
|
* prepended to it.
|
|
*/
|
|
prependSimNumber: function mm_prependSimNumber(text, cardIndex) {
|
|
if (window.navigator.mozIccManager &&
|
|
window.navigator.mozIccManager.iccIds.length > 1) {
|
|
var simName = this._('sim-number', { n: +cardIndex + 1 });
|
|
|
|
text = this._(
|
|
'mmi-notification-title-with-sim',
|
|
{ sim: simName, title: text }
|
|
);
|
|
}
|
|
|
|
return text;
|
|
},
|
|
|
|
/**
|
|
* Handles an MMI/USSD message. Pops up the MMI UI and displays the message.
|
|
*
|
|
* @param {String} message An MMI/USSD message.
|
|
* @param {Object} session The object representing the USSD session.
|
|
* @param {Integer} cardIndex The index of the SIM card on which this message
|
|
* was received.
|
|
*/
|
|
handleMMIReceived: function mm_handleMMIReceived(message, session, cardIndex)
|
|
{
|
|
var self = this;
|
|
|
|
return this.init().then(function() {
|
|
self._pendingRequest = null;
|
|
self._session = session;
|
|
// Do not notify the UI if no message to show.
|
|
if (!message && session) {
|
|
return;
|
|
}
|
|
|
|
var conn = navigator.mozMobileConnections[cardIndex || 0];
|
|
var operator = MobileOperator.userFacingInfo(conn).operator;
|
|
var title = self.prependSimNumber(operator || '', cardIndex);
|
|
|
|
MmiUI.received(session, message, title);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Sends a notification for the specified message, returns a promise that is
|
|
* resolved once the operation is finished.
|
|
*
|
|
* @param {String} message An MMI/USSD message.
|
|
* @param {Integer} cardIndex The index of the SIM card on which this message
|
|
* was received.
|
|
* @return {Promise} A promise that is resolved once the operation is
|
|
* finished.
|
|
*/
|
|
sendNotification: function mm_sendNotification(message, cardIndex) {
|
|
var self = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
var request = window.navigator.mozApps.getSelf();
|
|
request.onsuccess = function(evt) {
|
|
var app = evt.target.result;
|
|
|
|
self.init().then(function() {
|
|
LazyLoader.load('/shared/js/notification_helper.js', function() {
|
|
var iconURL = NotificationHelper.getIconURI(app, 'dialer');
|
|
var clickCB = function(evt) {
|
|
self.handleMMIReceived(message, /* session */ null, cardIndex);
|
|
};
|
|
var conn = navigator.mozMobileConnections[cardIndex || 0];
|
|
var operator = MobileOperator.userFacingInfo(conn).operator;
|
|
var titleL10n = self.prependSimNumber(
|
|
operator ? operator : '', cardIndex);
|
|
/* XXX: Bug 1033254 - We put the |ussd-message=1| parameter in the
|
|
* URL string to distinguish this notification from the others.
|
|
* This should be thorought the application possibly by using the
|
|
* tag field. */
|
|
NotificationHelper.send(titleL10n, {
|
|
body: message,
|
|
icon: iconURL + '?ussdMessage=1&cardIndex=' + cardIndex,
|
|
tag: Date.now()
|
|
}).then(notification => {
|
|
notification.addEventListener('click', clickCB);
|
|
resolve();
|
|
});
|
|
});
|
|
});
|
|
};
|
|
request.onerror = function(error) {
|
|
reject(error);
|
|
};
|
|
});
|
|
},
|
|
|
|
reply: function mm_reply(message) {
|
|
var self = this;
|
|
|
|
return this._session.send(message).then(function(request) {
|
|
self._pendingRequest = request;
|
|
});
|
|
},
|
|
|
|
cancel: function mm_cancel() {
|
|
this._pendingRequest = null;
|
|
if (this._session) {
|
|
this._session.cancel();
|
|
this._session = null;
|
|
}
|
|
},
|
|
|
|
cardIndexForConnection: function mm_cardIndexForConnection(conn) {
|
|
for (var i = 0; i < navigator.mozMobileConnections.length; i++) {
|
|
if (conn === navigator.mozMobileConnections[i]) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
},
|
|
|
|
/**
|
|
* Retrieves the IMEI code for the specified SIM card slot.
|
|
*
|
|
* @param {Integer} cardIndex The index of the SIM card slot.
|
|
* @returns {Promise} A promise that resolves to the IMEI code for the slot
|
|
* upon successful completion or rejects upon failure.
|
|
*/
|
|
_getImeiForCard: function mm_getImeiForCard(cardIndex) {
|
|
return navigator.mozTelephony.dial('*#06#', cardIndex).then(
|
|
function(mmiCall) {
|
|
return mmiCall.result.then(function(result) {
|
|
/* We always expect the IMEI, so if we got a successful completion
|
|
* without the IMEI value, we throw an error message. */
|
|
if ((result === null) || (result.serviceCode !== 'scImei') ||
|
|
(result.statusMessage === null)) {
|
|
return Promise.reject(
|
|
new Error('Could not retrieve the IMEI code for SIM' +
|
|
cardIndex));
|
|
}
|
|
|
|
return result.statusMessage;
|
|
});
|
|
}
|
|
);
|
|
},
|
|
|
|
/**
|
|
* Returns true if the specified number is the MMI code used to request the
|
|
* IMEI codes of the phone's SIM slots (*#06#).
|
|
*
|
|
* @param {String} number A phone number or MMI code.
|
|
* @returns {Boolean} true if the passed number is the MMI IMEI request code
|
|
* and false in all other cases.
|
|
*/
|
|
isImei: function mm_isImei(number) {
|
|
return (number === '*#06#');
|
|
},
|
|
|
|
/**
|
|
* Sends the necessary MMI messages to retrieve IMEI codes for all SIM slots
|
|
* and displays the resulting codes on the screen.
|
|
*
|
|
* @returns {Promise} A promise that is resolved when the operation has been
|
|
* completed.
|
|
*/
|
|
showImei: function mm_showImei() {
|
|
var self = this;
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
self.init().then(function() {
|
|
var promises = [];
|
|
|
|
for (var i = 0; i < navigator.mozMobileConnections.length; i++) {
|
|
promises.push(self._getImeiForCard(i));
|
|
}
|
|
|
|
self.openUI();
|
|
|
|
Promise.all(promises).then(function(imeis) {
|
|
MmiUI.success(imeis.join('\n'), self._('scImei'));
|
|
resolve();
|
|
}, function(reason) {
|
|
MmiUI.error(self._('GenericFailure'));
|
|
reject(reason);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
};
|