Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
475 lines
16 KiB
JavaScript
475 lines
16 KiB
JavaScript
/* 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);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
};
|