Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
263
gaia/shared/js/dialer/contacts.js
Executable file
263
gaia/shared/js/dialer/contacts.js
Executable file
|
|
@ -0,0 +1,263 @@
|
|||
'use strict';
|
||||
/* global
|
||||
asyncStorage,
|
||||
fb,
|
||||
LazyLoader,
|
||||
MozActivity,
|
||||
SimplePhoneMatcher
|
||||
*/
|
||||
/* exported Contacts */
|
||||
|
||||
var Contacts = {
|
||||
|
||||
_FB_FILES: [
|
||||
'/shared/js/fb/fb_request.js',
|
||||
'/shared/js/fb/fb_data_reader.js',
|
||||
'/shared/js/fb/fb_reader_utils.js'
|
||||
],
|
||||
|
||||
// The mozContact API stores a revision of its database that allow us to know
|
||||
// if we have a proper and updated contact cache.
|
||||
getRevision: function getRevision(callback) {
|
||||
var mozContacts = navigator.mozContacts;
|
||||
if (!mozContacts) {
|
||||
callback(null);
|
||||
return;
|
||||
}
|
||||
|
||||
var req = mozContacts.getRevision();
|
||||
req.onsuccess = function onsuccess(event) {
|
||||
callback(event.target.result);
|
||||
};
|
||||
req.onerror = function onerror(event) {
|
||||
console.log('Error ' + event.target.error);
|
||||
callback(null);
|
||||
};
|
||||
},
|
||||
|
||||
findByNumber: function findByNumber(number, callback) {
|
||||
LazyLoader.load(this._FB_FILES,
|
||||
this._findByNumber.bind(this, number, callback));
|
||||
},
|
||||
|
||||
_findByNumber: function _findByNumber(number, callback) {
|
||||
if (!number) {
|
||||
callback(null);
|
||||
return;
|
||||
}
|
||||
|
||||
var options;
|
||||
var variants;
|
||||
|
||||
// Based on E.164 (http://en.wikipedia.org/wiki/E.164)
|
||||
// if length < 7 we're dealing with a short number
|
||||
// so no need for variants
|
||||
if (number.length < 7) {
|
||||
variants = [number];
|
||||
options = {
|
||||
filterBy: ['tel'],
|
||||
filterOp: 'equals',
|
||||
filterValue: number
|
||||
};
|
||||
} else {
|
||||
// get the phone number variants (for the Facebook lookup)
|
||||
variants = SimplePhoneMatcher.generateVariants(number);
|
||||
var sanitizedNumber = SimplePhoneMatcher.sanitizedNumber(number);
|
||||
|
||||
options = {
|
||||
filterBy: ['tel'],
|
||||
filterOp: 'match',
|
||||
filterValue: sanitizedNumber
|
||||
};
|
||||
}
|
||||
|
||||
var mozContacts = navigator.mozContacts;
|
||||
if (!mozContacts) {
|
||||
callback(null);
|
||||
return;
|
||||
}
|
||||
|
||||
var request = mozContacts.find(options);
|
||||
request.onsuccess = function findCallback() {
|
||||
var contacts = request.result;
|
||||
if (contacts.length === 0) {
|
||||
// It is only necessary to search for one variant as FB takes care
|
||||
fb.getContactByNumber(number, function fb_ready(finalContact) {
|
||||
var objMatching = null;
|
||||
if (finalContact) {
|
||||
objMatching = {
|
||||
value: number,
|
||||
// Facebook telephone are always of type personal
|
||||
type: 'personal',
|
||||
// We don't know the carrier from FB phones
|
||||
carrier: null
|
||||
};
|
||||
}
|
||||
callback(finalContact, objMatching);
|
||||
}, function fb_err(err) {
|
||||
callback(null);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// formatting the matches as an array (contacts) of arrays (phone numbers)
|
||||
var matches = contacts.map(function getTels(contact) {
|
||||
return contact.tel.map(function getNumber(tel) {
|
||||
return tel.value;
|
||||
});
|
||||
});
|
||||
|
||||
// Finding the best match
|
||||
var matchResult = SimplePhoneMatcher.bestMatch(variants, matches);
|
||||
|
||||
var contact = contacts[matchResult.bestMatchIndex];
|
||||
var contactsWithSameNumber;
|
||||
if (contacts.length > 1) {
|
||||
contactsWithSameNumber = contacts.length - 1;
|
||||
}
|
||||
|
||||
var matchingTel = contact.tel[matchResult.localIndex];
|
||||
if (fb.isFbLinked(contact)) {
|
||||
// Merge with the FB data
|
||||
var req = fb.getData(contact);
|
||||
req.onsuccess = function() {
|
||||
callback(req.result, matchingTel, contactsWithSameNumber);
|
||||
};
|
||||
req.onerror = function() {
|
||||
window.console.error('Error while getting FB Data');
|
||||
callback(contact, matchingTel, contactsWithSameNumber);
|
||||
};
|
||||
}
|
||||
else {
|
||||
callback(contact, matchingTel, contactsWithSameNumber);
|
||||
}
|
||||
};
|
||||
request.onerror = function findError() {
|
||||
callback(null);
|
||||
};
|
||||
},
|
||||
|
||||
_mergeOneFbContact: function _mergeOneFbContact(contacts, index) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var fbReq = fb.getData(contacts[index]);
|
||||
|
||||
fbReq.onsuccess = function() {
|
||||
// We don't have to merge phone numbers from FB in order to avoid
|
||||
// duplicate contacts in suggesstion bar because it also searches
|
||||
// matchings in facebook datastore. It means that this method
|
||||
// returns mozContacts merged but without FB's phone numbers (just
|
||||
// local ones)
|
||||
var originalTel = contacts[index].tel;
|
||||
contacts[index] = fbReq.result;
|
||||
contacts[index].tel = originalTel;
|
||||
resolve();
|
||||
};
|
||||
|
||||
fbReq.onerror = function() {
|
||||
console.error('Could not merge Facebook data for', contacts[index].id);
|
||||
reject();
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
_mergeFbContacts: function _mergeFbContacts(contacts, callback) {
|
||||
if (!callback || !(callback instanceof Function)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!contacts) {
|
||||
callback(null);
|
||||
}
|
||||
|
||||
LazyLoader.load(this._FB_FILES, function() {
|
||||
var pending = contacts.length;
|
||||
|
||||
var onContactMerged = function() {
|
||||
if (--pending === 0) {
|
||||
callback(contacts);
|
||||
}
|
||||
};
|
||||
|
||||
for (var i = 0, length = contacts.length; i < length; i++) {
|
||||
if (fb.isFbContact(contacts[i])) {
|
||||
this._mergeOneFbContact(contacts, i).then(onContactMerged,
|
||||
onContactMerged);
|
||||
} else {
|
||||
onContactMerged();
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
_findContacts: function _findContacts(options, callback) {
|
||||
if (!callback || !(callback instanceof Function)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!navigator.mozContacts || !options) {
|
||||
callback(null);
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var req = navigator.mozContacts.find(options);
|
||||
req.onsuccess = function onsuccess() {
|
||||
var contacts = req.result;
|
||||
if (!contacts.length) {
|
||||
callback(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have contacts data from Facebook, we need to merge it with the
|
||||
// one from the Contacts API db.
|
||||
self._mergeFbContacts(contacts, callback);
|
||||
};
|
||||
|
||||
req.onerror = function onerror() {
|
||||
console.error('Contact finding error. Error: ' + req.errorCode);
|
||||
callback(null);
|
||||
};
|
||||
},
|
||||
|
||||
findListByNumber: function findListByNumber(number, limit, callback) {
|
||||
var self = this;
|
||||
asyncStorage.getItem('order.lastname', function(value) {
|
||||
var sortKey = value ? 'familyName' : 'givenName';
|
||||
|
||||
var options = {
|
||||
filterBy: ['tel'],
|
||||
filterOp: 'contains',
|
||||
filterValue: number,
|
||||
sortBy: sortKey,
|
||||
sortOrder: 'ascending',
|
||||
filterLimit: limit
|
||||
};
|
||||
|
||||
self._findContacts(options, callback);
|
||||
});
|
||||
},
|
||||
|
||||
getLength: function getLength(prop) {
|
||||
if (!prop || !prop.length) {
|
||||
return 0;
|
||||
}
|
||||
return prop.length;
|
||||
},
|
||||
|
||||
sendEmailOrPick: function sendEmailOrPick(address) {
|
||||
/* jshint nonew: false */
|
||||
try {
|
||||
// We don't check the email format, lets the email
|
||||
// app do that
|
||||
new MozActivity({
|
||||
name: 'new',
|
||||
data: {
|
||||
type: 'mail',
|
||||
URI: 'mailto:' + address
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('WebActivities unavailable? : ' + e);
|
||||
}
|
||||
}
|
||||
};
|
||||
52
gaia/shared/js/dialer/dtmf_tone.js
Executable file
52
gaia/shared/js/dialer/dtmf_tone.js
Executable file
|
|
@ -0,0 +1,52 @@
|
|||
/* exported DtmfTone */
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* DTMF tone constructor, providing the SIM on which this tone will be played
|
||||
* is mandatory.
|
||||
*
|
||||
* @param {String} tone The tone to be played.
|
||||
* @param {Boolean} short True if this will be a short tone, false otherwise.
|
||||
* @param {Integer} serviceId The ID of the SIM card on which to play the tone.
|
||||
*/
|
||||
function DtmfTone(tone, short, serviceId) {
|
||||
this.tone = tone;
|
||||
this.short = short;
|
||||
this.serviceId = serviceId;
|
||||
this.timer = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Length of a short DTMF tone, currently 120ms.
|
||||
*/
|
||||
DtmfTone.kShortToneLength = 120;
|
||||
|
||||
DtmfTone.prototype = {
|
||||
/**
|
||||
* Starts playing the tone, if this is a short tone it will stop automatically
|
||||
* after kShortToneLength milliseconds, otherwise it will play until stopped.
|
||||
*/
|
||||
play: function dt_play() {
|
||||
clearTimeout(this.timer);
|
||||
|
||||
// Stop previous tone before dispatching a new one
|
||||
navigator.mozTelephony.stopTone(this.serviceId);
|
||||
navigator.mozTelephony.startTone(this.tone, this.serviceId);
|
||||
|
||||
if (this.short) {
|
||||
this.timer = window.setTimeout(function dt_stopTone(serviceId) {
|
||||
navigator.mozTelephony.stopTone(serviceId);
|
||||
}, DtmfTone.kShortToneLength, this.serviceId);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop the DTMF tone, this is safe to call even if the DTMF tone has already
|
||||
* stopped.
|
||||
*/
|
||||
stop: function dt_stop() {
|
||||
clearTimeout(this.timer);
|
||||
navigator.mozTelephony.stopTone(this.serviceId);
|
||||
}
|
||||
};
|
||||
152
gaia/shared/js/dialer/font_size_manager.js
Executable file
152
gaia/shared/js/dialer/font_size_manager.js
Executable file
|
|
@ -0,0 +1,152 @@
|
|||
/* globals FontSizeUtils */
|
||||
|
||||
/* exported FontSizeManager */
|
||||
|
||||
'use strict';
|
||||
|
||||
var FontSizeManager = (function fontSizeManager() {
|
||||
/**
|
||||
* Possible scenarios in which the dialer and call screen can be in.
|
||||
*/
|
||||
var DIAL_PAD = 0,
|
||||
SINGLE_CALL = 1,
|
||||
CALL_WAITING = 2,
|
||||
STATUS_BAR = 3,
|
||||
SECOND_INCOMING_CALL = 4;
|
||||
|
||||
/**
|
||||
* The following font sizes are in rems.
|
||||
*/
|
||||
var _sizes = {};
|
||||
_sizes[DIAL_PAD] = {min: 2.6, max: 4.1};
|
||||
_sizes[SINGLE_CALL] = {min: 2.3, max: 3.4, line: 3.4};
|
||||
_sizes[CALL_WAITING] = {min: 2.3, max: 2.5, line: 1.3};
|
||||
_sizes[STATUS_BAR] = {min: 1.7, max: 1.7};
|
||||
_sizes[SECOND_INCOMING_CALL] = {min: 2.3, max: 2.5, line: 3.7};
|
||||
|
||||
var _defaultFontSize;
|
||||
|
||||
function _getRootFontSize() {
|
||||
_defaultFontSize = _defaultFontSize ||
|
||||
parseInt(window.getComputedStyle(document.body, null).
|
||||
getPropertyValue('font-size'));
|
||||
return _defaultFontSize;
|
||||
}
|
||||
|
||||
function _getMinFontSize(scenario) {
|
||||
return Math.round(_sizes[scenario].min * _getRootFontSize());
|
||||
}
|
||||
|
||||
function _getMaxFontSize(scenario) {
|
||||
return Math.round(_sizes[scenario].max * _getRootFontSize());
|
||||
}
|
||||
|
||||
// Returns an array containing each number between scenario.minsize and
|
||||
// scenario.maxsize in steps of 4,
|
||||
// e.g.[minsize, minsize + 4, minsize + 8, ..., maxsize]
|
||||
function _getAllowedFontSizes(scenario) {
|
||||
var minFontSize = _getMinFontSize(scenario);
|
||||
var maxFontSize = _getMaxFontSize(scenario);
|
||||
var allowedSizes = [];
|
||||
for (var size = minFontSize; size <= maxFontSize; size +=4) {
|
||||
allowedSizes.push(size);
|
||||
}
|
||||
if (allowedSizes.indexOf(maxFontSize) === -1) {
|
||||
allowedSizes.push(maxFontSize);
|
||||
}
|
||||
return allowedSizes;
|
||||
}
|
||||
|
||||
/* The font size of the view will be modified to fit its width. The scenario
|
||||
provides the sizing contraints.
|
||||
If the content is still too large within the size contraints, it will add
|
||||
an ellipsis */
|
||||
function adaptToSpace(scenario, view, forceMaxFontSize, ellipsisSide) {
|
||||
// Bug 1082139 - JavascriptException: JavascriptException: TypeError:
|
||||
// window.getComputedStyle(...) is null at://
|
||||
// app://callscreen.gaiamobile.org/gaia_build_defer_index.js line: 146
|
||||
var computedStyle = window.getComputedStyle(view);
|
||||
if ((!view.value && !view.textContent) || !computedStyle) {
|
||||
// We don't care about the font size of empty views
|
||||
return;
|
||||
}
|
||||
|
||||
var viewWidth = view.getBoundingClientRect().width;
|
||||
var viewFont = computedStyle.fontFamily;
|
||||
|
||||
var allowedSizes;
|
||||
if (forceMaxFontSize) {
|
||||
allowedSizes = [_getMaxFontSize(scenario)];
|
||||
} else {
|
||||
allowedSizes = _getAllowedFontSizes(scenario);
|
||||
}
|
||||
|
||||
var infos = FontSizeUtils.getMaxFontSizeInfo(
|
||||
view.value || view.textContent, allowedSizes, viewFont, viewWidth);
|
||||
var newFontSize = infos.fontSize;
|
||||
if (view.style.fontSize !== newFontSize) {
|
||||
view.style.fontSize = newFontSize + 'px';
|
||||
}
|
||||
|
||||
if (infos && infos.overflow) {
|
||||
var overflowCount = FontSizeUtils.getOverflowCount(
|
||||
view.value || view.textContent, newFontSize, viewFont, viewWidth);
|
||||
|
||||
// Add two characters to the overflowcount to account for "…" width
|
||||
overflowCount += 2;
|
||||
_useEllipsis(view, overflowCount, ellipsisSide);
|
||||
view.dataset.ellipsedCharacters = overflowCount;
|
||||
} else {
|
||||
view.dataset.ellipsedCharacters = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function _useEllipsis(view, overflowCount, ellipsisSide) {
|
||||
var side = ellipsisSide || 'begin';
|
||||
var localizedSide = (side === 'begin' ? 'left' : 'right');
|
||||
|
||||
var value = view.value || view.textContent;
|
||||
if (localizedSide == 'left') {
|
||||
value = '\u2026' + value.substr(-value.length + overflowCount);
|
||||
} else if (localizedSide == 'right') {
|
||||
value = value.substr(0, value.length - overflowCount) + '\u2026';
|
||||
}
|
||||
|
||||
if (view.value) {
|
||||
view.value = value;
|
||||
} else {
|
||||
var el = view.querySelector('bdi') || view;
|
||||
el.textContent = value;
|
||||
}
|
||||
}
|
||||
|
||||
function ensureFixedBaseline(scenario, view) {
|
||||
var initialLineHeight = _sizes[scenario].line;
|
||||
if (!initialLineHeight) {
|
||||
return;
|
||||
}
|
||||
|
||||
var maxFontSize = _getMaxFontSize(scenario);
|
||||
var fontSize = parseInt(window.getComputedStyle(view).fontSize, 10);
|
||||
// Taking the line height for the maximum font size case for each
|
||||
// scenario as the reference, we need to adjust it adding half of
|
||||
// the decrease to the final calculated font size in pixels.
|
||||
view.style.lineHeight = initialLineHeight * _getRootFontSize() +
|
||||
(maxFontSize - fontSize) / 2 + 'px';
|
||||
}
|
||||
|
||||
function resetFixedBaseline(view) {
|
||||
view.style.removeProperty('line-height');
|
||||
}
|
||||
|
||||
return {
|
||||
DIAL_PAD: DIAL_PAD,
|
||||
SINGLE_CALL: SINGLE_CALL,
|
||||
CALL_WAITING: CALL_WAITING,
|
||||
STATUS_BAR: STATUS_BAR,
|
||||
SECOND_INCOMING_CALL: SECOND_INCOMING_CALL,
|
||||
adaptToSpace: adaptToSpace,
|
||||
ensureFixedBaseline: ensureFixedBaseline,
|
||||
resetFixedBaseline: resetFixedBaseline
|
||||
};
|
||||
})();
|
||||
963
gaia/shared/js/dialer/keypad.js
Executable file
963
gaia/shared/js/dialer/keypad.js
Executable file
|
|
@ -0,0 +1,963 @@
|
|||
/* exported KeypadManager */
|
||||
|
||||
/* globals AddContactMenu, CallHandler, CallLogDBManager, CallsHandler,
|
||||
CallScreen, ConfirmDialog, CustomDialog, DtmfTone, FontSizeManager,
|
||||
LazyLoader, MultiSimActionButton, Promise, SimSettingsHelper,
|
||||
SettingsListener, TonePlayer */
|
||||
|
||||
'use strict';
|
||||
|
||||
// Frequencies coming from http://en.wikipedia.org/wiki/Telephone_keypad
|
||||
var gTonesFrequencies = {
|
||||
'1': [697, 1209], '2': [697, 1336], '3': [697, 1477],
|
||||
'4': [770, 1209], '5': [770, 1336], '6': [770, 1477],
|
||||
'7': [852, 1209], '8': [852, 1336], '9': [852, 1477],
|
||||
'*': [941, 1209], '0': [941, 1336], '#': [941, 1477]
|
||||
};
|
||||
|
||||
var KeypadManager = {
|
||||
|
||||
kMaxDigits: 50,
|
||||
|
||||
_phoneNumber: '',
|
||||
_onCall: false,
|
||||
|
||||
_keypadSoundIsEnabled: false,
|
||||
_shortTone: false,
|
||||
_vibrationEnabled: false,
|
||||
|
||||
// Keep in sync with Lockscreen and keyboard vibration
|
||||
kVibrationDuration: 50, // ms
|
||||
|
||||
// Due to the ellipsis capabilities of the number view, the cursor position
|
||||
// in the input element, does not match the point where new digits should be
|
||||
// added to the current number or where the deletion operation should take
|
||||
// place. This property holds the position of the edition point for the
|
||||
// current number (_phoneNumber property).
|
||||
_insertPosition: null,
|
||||
|
||||
onValueChanged: null,
|
||||
|
||||
get phoneNumberView() {
|
||||
delete this.phoneNumberView;
|
||||
this.phoneNumberView = document.getElementById('phone-number-view');
|
||||
return this.phoneNumberView;
|
||||
},
|
||||
|
||||
get phoneNumberViewContainer() {
|
||||
delete this.phoneNumberViewContainer;
|
||||
this.phoneNumberViewContainer =
|
||||
document.getElementById('phone-number-view-container');
|
||||
return this.phoneNumberViewContainer;
|
||||
},
|
||||
|
||||
get keypad() {
|
||||
delete this.keypad;
|
||||
this.keypad = document.getElementById('keypad');
|
||||
return this.keypad;
|
||||
},
|
||||
|
||||
get callBar() {
|
||||
delete this.callBar;
|
||||
this.callBar =
|
||||
document.getElementById('keypad-callbar');
|
||||
return this.callBar;
|
||||
},
|
||||
|
||||
get hideBar() {
|
||||
delete this.hideBar;
|
||||
this.hideBar = document.getElementById('keypad-hidebar');
|
||||
return this.hideBar;
|
||||
},
|
||||
|
||||
get callBarAddContact() {
|
||||
delete this.callBarAddContact;
|
||||
this.callBarAddContact =
|
||||
document.getElementById('keypad-callbar-add-contact');
|
||||
return this.callBarAddContact;
|
||||
},
|
||||
|
||||
get callBarCallAction() {
|
||||
delete this.callBarCallAction;
|
||||
this.callBarCallAction =
|
||||
document.getElementById('keypad-callbar-call-action');
|
||||
return this.callBarCallAction;
|
||||
},
|
||||
|
||||
get callBarCancelAction() {
|
||||
delete this.callBarCancelAction;
|
||||
this.callBarCancelAction =
|
||||
document.getElementById('keypad-callbar-cancel');
|
||||
return this.callBarCancelAction;
|
||||
},
|
||||
|
||||
get deleteButton() {
|
||||
delete this.deleteButton;
|
||||
this.deleteButton = document.getElementById('keypad-delete');
|
||||
return this.deleteButton;
|
||||
},
|
||||
|
||||
get hideBarHangUpAction() {
|
||||
delete this.hideBarHangUpAction;
|
||||
this.hideBarHangUpAction =
|
||||
document.getElementById('keypad-hidebar-hang-up-action-wrapper');
|
||||
return this.hideBarHangUpAction;
|
||||
},
|
||||
|
||||
get hideBarHideAction() {
|
||||
delete this.hideBarHideAction;
|
||||
this.hideBarHideAction =
|
||||
document.getElementById('keypad-hidebar-hide-keypad-action');
|
||||
return this.hideBarHideAction;
|
||||
},
|
||||
|
||||
multiSimActionButton: null,
|
||||
|
||||
/**
|
||||
* Initializes the keypad manager, registers all the appropriate event
|
||||
* handlers and instances the necessary sound infrastructure so that the
|
||||
* keypad will be fully functional once this method has been called.
|
||||
*
|
||||
* @param oncall {Boolean} True if the keypad manager will be used during a
|
||||
* call and will play sounds on the "telephony" channel. False if it
|
||||
* will be used outside of a call and should use the "content" channel
|
||||
* instead. We should be using the "notification" channel but we can't
|
||||
* due to bug 1092346.
|
||||
*/
|
||||
init: function kh_init(oncall) {
|
||||
|
||||
this._onCall = !!oncall;
|
||||
this._lastPressedKey = null;
|
||||
|
||||
this.phoneNumberView.value = '';
|
||||
this.phoneNumberView
|
||||
.addEventListener('click', this._updateInsertPosition.bind(this));
|
||||
this._phoneNumber = '';
|
||||
|
||||
var keyHandler = this.keyHandler.bind(this);
|
||||
this.keypad.addEventListener('contextmenu', keyHandler);
|
||||
|
||||
this.keypad.addEventListener('touchstart', keyHandler, true);
|
||||
this.keypad.addEventListener('touchmove', keyHandler, true);
|
||||
this.keypad.addEventListener('touchend', keyHandler, true);
|
||||
this.keypad.addEventListener('touchcancel', keyHandler, true);
|
||||
|
||||
this.deleteButton.addEventListener('touchstart', keyHandler);
|
||||
this.deleteButton.addEventListener('touchend', keyHandler);
|
||||
// The keypad add contact bar is only included in the normal version of
|
||||
// the keypad.
|
||||
if (this.callBarAddContact) {
|
||||
this.callBarAddContact.addEventListener('click',
|
||||
this.addContact.bind(this));
|
||||
}
|
||||
|
||||
// The keypad call bar is only included in the normal version and
|
||||
// the emergency call version of the keypad.
|
||||
if (this.callBarCallAction) {
|
||||
if (typeof MultiSimActionButton !== 'undefined') {
|
||||
this.multiSimActionButton =
|
||||
new MultiSimActionButton(this.callBarCallAction,
|
||||
CallHandler.call,
|
||||
'ril.telephony.defaultServiceId',
|
||||
this.phoneNumber.bind(this));
|
||||
}
|
||||
this.callBarCallAction.addEventListener('click',
|
||||
this.fetchLastCalled.bind(this));
|
||||
}
|
||||
|
||||
// The keypad cancel bar is only the emergency call version of the keypad.
|
||||
if (this.callBarCancelAction) {
|
||||
this.callBarCancelAction.addEventListener('click', function() {
|
||||
window.parent.LockScreen.switchPanel();
|
||||
});
|
||||
}
|
||||
|
||||
// The keypad hide bar is only included in the on call version of the
|
||||
// keypad.
|
||||
if (this.hideBarHideAction) {
|
||||
this.hideBarHideAction.addEventListener('click',
|
||||
this.callbarBackAction);
|
||||
}
|
||||
|
||||
if (this.hideBarHangUpAction) {
|
||||
this.hideBarHangUpAction.addEventListener('click',
|
||||
this.hangUpCallFromKeypad);
|
||||
}
|
||||
|
||||
// According to the UX sound spec, we should use the "system" type for the
|
||||
// dialer pad. See the attachment of the bug1068219.
|
||||
TonePlayer.init('system');
|
||||
|
||||
this.render();
|
||||
LazyLoader.load(['/shared/style/action_menu.css',
|
||||
'/dialer/js/suggestion_bar.js']);
|
||||
|
||||
this._observePreferences();
|
||||
},
|
||||
|
||||
render: function hk_render(layoutType) {
|
||||
if (layoutType == 'oncall') {
|
||||
if (CallsHandler.activeCall) {
|
||||
var activeCall = CallsHandler.activeCall.call;
|
||||
this._phoneNumber = activeCall.id.number;
|
||||
}
|
||||
this._isKeypadClicked = false;
|
||||
this.phoneNumberViewContainer.classList.add('keypad-visible');
|
||||
if (this.callBar) {
|
||||
this.callBar.classList.add('hide');
|
||||
}
|
||||
|
||||
if (this.hideBar) {
|
||||
this.hideBar.classList.remove('hide');
|
||||
}
|
||||
|
||||
this.deleteButton.classList.add('hide');
|
||||
} else {
|
||||
this.phoneNumberViewContainer.classList.remove('keypad-visible');
|
||||
|
||||
if (this.callBar) {
|
||||
this.callBar.classList.remove('hide');
|
||||
}
|
||||
|
||||
if (this.hideBar) {
|
||||
this.hideBar.classList.add('hide');
|
||||
}
|
||||
|
||||
this.deleteButton.classList.remove('hide');
|
||||
}
|
||||
},
|
||||
|
||||
phoneNumber: function hk_phoneNumber() {
|
||||
return this._phoneNumber;
|
||||
},
|
||||
|
||||
fetchLastCalled: function hk_fetchLastCalled() {
|
||||
if (this._phoneNumber !== '') {
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
CallLogDBManager.getGroupAtPosition(1, 'lastEntryDate', true, 'dialing',
|
||||
function hk_ggap_callback(result) {
|
||||
if (result && (typeof result === 'object') && result.number) {
|
||||
self.updatePhoneNumber(result.number);
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
addContact: function hk_addContact(event) {
|
||||
var number = this._phoneNumber;
|
||||
if (!number) {
|
||||
return;
|
||||
}
|
||||
LazyLoader.load(['/dialer/js/add_contact_menu.js'], function() {
|
||||
AddContactMenu.show(number);
|
||||
});
|
||||
},
|
||||
|
||||
callbarBackAction: function hk_callbarBackAction(event) {
|
||||
CallScreen.hideKeypad();
|
||||
},
|
||||
|
||||
hangUpCallFromKeypad: function hk_hangUpCallFromKeypad(event) {
|
||||
CallScreen.hideKeypad();
|
||||
CallsHandler.end();
|
||||
},
|
||||
|
||||
_lastPressedKey: null,
|
||||
_dtmfTone: null,
|
||||
|
||||
_playDtmfTone: function kh_playDtmfTone(key) {
|
||||
var serviceId = 0;
|
||||
|
||||
if (!this._onCall) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (CallsHandler.activeCall) {
|
||||
// Single call
|
||||
serviceId = CallsHandler.activeCall.call.serviceId;
|
||||
} else {
|
||||
// Conference call
|
||||
serviceId = navigator.mozTelephony.active.calls[0].serviceId;
|
||||
}
|
||||
|
||||
if (this._dtmfTone) {
|
||||
this._dtmfTone.stop();
|
||||
this._dtmfTone = null;
|
||||
}
|
||||
|
||||
this._dtmfTone = new DtmfTone(key, this._shortTone, serviceId);
|
||||
this._dtmfTone.play();
|
||||
},
|
||||
|
||||
_stopDtmfTone: function kh_stopDtmfTone() {
|
||||
if (!this._dtmfTone) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._dtmfTone.stop();
|
||||
this._dtmfTone = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Function used to respond to touchstart events over the keypad. Reacts to
|
||||
* the first key that has been pressed by playing the appropriate tone and
|
||||
* sets up the necessary timers to react to long presses.
|
||||
*
|
||||
* @param {String} key The key that was hit by this touchstart event.
|
||||
*/
|
||||
_touchStart: function kh_touchStart(key) {
|
||||
|
||||
this._longPress = false;
|
||||
this._lastPressedKey = key;
|
||||
|
||||
// If user input number more 50 digits, app shouldn't accept.
|
||||
// The limit only applies while not on a call - there is no
|
||||
// limit while on a call (bug 917630).
|
||||
if (key !== 'delete' && !this._onCall &&
|
||||
(this._phoneNumber.length >= this.kMaxDigits)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (key != 'delete') {
|
||||
if (this._keypadSoundIsEnabled) {
|
||||
// We do not support long press if not on a call
|
||||
TonePlayer.start(
|
||||
gTonesFrequencies[key], !this._onCall || this._shortTone);
|
||||
}
|
||||
|
||||
if (this._vibrationEnabled) {
|
||||
navigator.vibrate(this.kVibrationDuration);
|
||||
}
|
||||
|
||||
this._playDtmfTone(key);
|
||||
}
|
||||
|
||||
// Manage long press
|
||||
if (((key == '0' || key == '*') && !this._onCall) || key == 'delete') {
|
||||
this._holdTimer = setTimeout(function(self) {
|
||||
self.restoreCaretPosition();
|
||||
|
||||
if (key == 'delete') {
|
||||
self._clearPhoneNumber();
|
||||
} else if (key === '0') {
|
||||
self._replaceLastKey('+');
|
||||
} else if (key === '*') {
|
||||
var isAtTheEnd = self._insertPosition === null;
|
||||
var isFirst = self._insertPosition === 1 ||
|
||||
(isAtTheEnd && self._phoneNumber.length === 1);
|
||||
// ',' DTMF separator can't be the first
|
||||
if (!isFirst) {
|
||||
self._replaceLastKey(',');
|
||||
} else {
|
||||
self._deleteAtInsertPosition();
|
||||
}
|
||||
}
|
||||
|
||||
self._longPress = true;
|
||||
self._updatePhoneNumberView('begin', false);
|
||||
}, 400, this);
|
||||
}
|
||||
|
||||
// Voicemail long press (only if first digit pressed)
|
||||
if (key === '1' && this._phoneNumber === '') {
|
||||
this._holdTimer = setTimeout(function vm_call(self) {
|
||||
self._longPress = true;
|
||||
self._callVoicemail();
|
||||
|
||||
self._phoneNumber = '';
|
||||
self._updatePhoneNumberView('begin', false);
|
||||
}, 400, this);
|
||||
}
|
||||
|
||||
if (key == 'delete') {
|
||||
this._deleteAtInsertPosition();
|
||||
} else if (this.phoneNumberViewContainer.classList.
|
||||
contains('keypad-visible')) {
|
||||
|
||||
if (!this._isKeypadClicked) {
|
||||
this._isKeypadClicked = true;
|
||||
this._phoneNumber = key;
|
||||
this.replaceAdditionalContactInfo('');
|
||||
} else {
|
||||
this._insertAtCaret(key);
|
||||
}
|
||||
} else {
|
||||
this._insertAtCaret(key);
|
||||
}
|
||||
|
||||
// Per certification requirements abbreviated dialing codes need to be
|
||||
// called immediately after the user enters the '#' key. This covers
|
||||
// retrieving the device's IMEI codes as well as speed dialing.
|
||||
if (key === '#' && !this._onCall) {
|
||||
if (this._phoneNumber === '*#06#') {
|
||||
this.multiSimActionButton.performAction();
|
||||
return;
|
||||
} else if (this._isSpeedDialNumber(this._phoneNumber)) {
|
||||
// Remove the trailing '#' to get the index
|
||||
this._handleSpeedDial(this._phoneNumber.slice(0, -1));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(function(self) {
|
||||
self._updatePhoneNumberView('begin', false);
|
||||
}, 0, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Function used to respond to touchmove events over the keypad. Stops playing
|
||||
* the tone associated with the last pressed key and resets it if the target
|
||||
* goes outside its area.
|
||||
*
|
||||
* @param {Object} touch Touch position object for this move.
|
||||
*/
|
||||
_touchMove: function kh_touchMove(touch) {
|
||||
var target = document.elementFromPoint(touch.pageX, touch.pageY);
|
||||
var key = (target && target.dataset) ? target.dataset.value : null;
|
||||
|
||||
if (key !== this._lastPressedKey || key === 'delete') {
|
||||
this._stopDtmfTone();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Function used to respond to touchend events over the keypad. Stops playing
|
||||
* tones and resets timers associated with the key press.
|
||||
*
|
||||
* @param {String} key The key over which the tap finished.
|
||||
*/
|
||||
_touchEnd: function kh_touchEnd(key) {
|
||||
this._lastPressedKey = null;
|
||||
|
||||
if (key !== 'delete') {
|
||||
this._stopDtmfTone();
|
||||
}
|
||||
|
||||
if (this._keypadSoundIsEnabled) {
|
||||
TonePlayer.stop();
|
||||
}
|
||||
|
||||
this.restoreCaretPosition();
|
||||
|
||||
// If it was a long press our work is already done
|
||||
if (this._longPress) {
|
||||
this._longPress = false;
|
||||
this._holdTimer = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._holdTimer) {
|
||||
clearTimeout(this._holdTimer);
|
||||
}
|
||||
},
|
||||
|
||||
keyHandler: function kh_keyHandler(event) {
|
||||
// Avoid the keys to get focus.
|
||||
event.preventDefault();
|
||||
|
||||
// When long pressing on the voicemail button, if a menu pops up on top of
|
||||
// the 1 button, a click will go through and target that button unless we
|
||||
// preventDefault the contextmenu event.
|
||||
if (event.type == 'contextmenu') {
|
||||
return;
|
||||
}
|
||||
|
||||
var key = event.target.dataset.value;
|
||||
|
||||
// We could receive this event from an element that
|
||||
// doesn't have the dataset value. Got the last key
|
||||
// pressed and assing this value to continue with the
|
||||
// proccess.
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.stopPropagation();
|
||||
|
||||
switch (event.type) {
|
||||
case 'touchstart':
|
||||
if (this._lastPressedKey) {
|
||||
return; // Ignore multi-taps
|
||||
}
|
||||
|
||||
event.target.classList.add('active');
|
||||
this._touchStart(key);
|
||||
break;
|
||||
case 'touchmove':
|
||||
this._touchMove(event.touches[0]);
|
||||
break;
|
||||
case 'touchend':
|
||||
case 'touchcancel':
|
||||
if (key !== this._lastPressedKey) {
|
||||
return; // Ignore multi-taps
|
||||
}
|
||||
|
||||
event.target.classList.remove('active');
|
||||
this._touchEnd(key);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if the number is a speed dial code as described in
|
||||
* 3GPP TS 22.030 6.6.4. Speed dial codes are in the N(N)(N)# format.
|
||||
*/
|
||||
_isSpeedDialNumber: function(number) {
|
||||
return !!number.match(/^[0-9][0-9]{0,2}\#$/);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the telephony number corresponding to the specified index. Speed
|
||||
* dial numbers are retrieved from the SIM contacts list and not from the
|
||||
* regular contacts.
|
||||
*
|
||||
* @param {Integer} index The index of the speed dial number.
|
||||
* @returns {Promise} A promise that resolves to the corresponding number.
|
||||
*/
|
||||
_getSpeedDialNumber: function(index) {
|
||||
var self = this;
|
||||
var cardIndex;
|
||||
|
||||
index--; // Speed dial indexes are 1-based
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
LazyLoader.load(['/shared/js/sim_settings_helper.js'], function() {
|
||||
SimSettingsHelper.getCardIndexFrom('outgoingCall',
|
||||
function(defaultCardIndex) {
|
||||
if (defaultCardIndex == SimSettingsHelper.ALWAYS_ASK_OPTION_VALUE) {
|
||||
LazyLoader.load(['/shared/js/component_utils.js',
|
||||
'/shared/elements/gaia_sim_picker/script.js'],
|
||||
function() {
|
||||
var simPicker = document.getElementById('sim-picker');
|
||||
simPicker.getOrPick(defaultCardIndex, null,
|
||||
function(pickedCardIndex) {
|
||||
cardIndex = pickedCardIndex;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
cardIndex = defaultCardIndex;
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}).then(function() {
|
||||
return self._getSimContactsList(cardIndex).then(
|
||||
function(simContactsList) {
|
||||
if ((index >= 0) && (index < simContactsList.length)) {
|
||||
return simContactsList[index].number;
|
||||
} else {
|
||||
return Promise.reject('noContactsWereFound');
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles a speed dial number, replaces the phone number with the one
|
||||
* at the specified index or displays an error message if none was found.
|
||||
*
|
||||
* @param {Integer} index The index of the speed dial number.
|
||||
*/
|
||||
_handleSpeedDial: function(index) {
|
||||
var self = this;
|
||||
|
||||
this.updatePhoneNumber('', 'begin', false);
|
||||
this._getSpeedDialNumber(+index).then(
|
||||
function(number) {
|
||||
self.updatePhoneNumber(number, 'begin', false);
|
||||
}, function(error) {
|
||||
/* Do not display an error message if the user explicitly
|
||||
* cancelled the speed dial operation. */
|
||||
if (error) {
|
||||
ConfirmDialog.show(error, null, {
|
||||
title: 'noContactsFoundDialogOk',
|
||||
callback: ConfirmDialog.hide
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates an array of contacts populated using the ADN contacts retrieved
|
||||
* from a SIM card. Every contact will contain only the ID and first
|
||||
* telephone number and the array will be sorted by ID. This array is then
|
||||
* suitable to be used to pick speed dial numbers.
|
||||
*
|
||||
* @param {Array} contacts An array of mozContact elements.
|
||||
* @returns {Array} An array of telephone numbers / ID couples sorted by ID.
|
||||
*/
|
||||
_createSimContactList: function(contacts) {
|
||||
var numbers = new Array(contacts.length);
|
||||
|
||||
for (var i = 0; i < contacts.length; i++) {
|
||||
numbers[i] = {
|
||||
id: contacts[i].id,
|
||||
number: contacts[i].tel[0].value,
|
||||
};
|
||||
}
|
||||
|
||||
numbers.sort(function(a, b) {
|
||||
if (a.id.length == b.id.length) {
|
||||
return (a.id > b.id) ? 1 : 0;
|
||||
} else {
|
||||
return (a.id.length > b.id.length) ? 1 : 0;
|
||||
}
|
||||
});
|
||||
|
||||
return numbers;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the SIM contacts list for the specified SIM card.
|
||||
*
|
||||
* @param {Integer} cardIndex The SIM card index.
|
||||
* @returns {Promise} A promise that is resolved with the contacts list.
|
||||
*/
|
||||
_getSimContactsList: function(cardIndex) {
|
||||
var self = this;
|
||||
var canceled = false;
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
LazyLoader.load(['/shared/style/confirm.css',
|
||||
'/shared/js/confirm.js',
|
||||
document.getElementById('confirmation-message')],
|
||||
function() {
|
||||
var iccId = navigator.mozIccManager.iccIds[cardIndex];
|
||||
var icc = navigator.mozIccManager.getIccById(iccId);
|
||||
|
||||
if (!icc) {
|
||||
reject('noSimCardToLoadContactsFrom');
|
||||
return;
|
||||
}
|
||||
|
||||
var req = icc.readContacts('adn');
|
||||
|
||||
req.onsuccess = function(event) {
|
||||
var adnContacts = event.target.result;
|
||||
var contacts = self._createSimContactList(adnContacts);
|
||||
|
||||
if (!canceled) {
|
||||
ConfirmDialog.hide();
|
||||
}
|
||||
|
||||
resolve(contacts);
|
||||
};
|
||||
req.onerror = function(error) {
|
||||
console.error('Could not retrieve the ADN contacts from SIM card ' +
|
||||
cardIndex + ', got error ' + error.name);
|
||||
reject('noContactsWereFound');
|
||||
};
|
||||
|
||||
ConfirmDialog.show('loadingSimContacts', null, {
|
||||
title: 'cancel',
|
||||
callback: function() {
|
||||
canceled = true;
|
||||
ConfirmDialog.hide();
|
||||
reject();
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
sanitizePhoneNumber: function(number) {
|
||||
return number.replace(/\s+/g, '');
|
||||
},
|
||||
|
||||
updatePhoneNumber: function kh_updatePhoneNumber(number, ellipsisSide,
|
||||
forceMaxFontSize) {
|
||||
number = this.sanitizePhoneNumber(number);
|
||||
this._phoneNumber = number;
|
||||
this._updatePhoneNumberView(ellipsisSide, forceMaxFontSize);
|
||||
},
|
||||
|
||||
press: function(value) {
|
||||
this._playDtmfTone(value);
|
||||
TonePlayer.start(gTonesFrequencies[value], true);
|
||||
setTimeout((function nextTick() {
|
||||
TonePlayer.stop();
|
||||
this._stopDtmfTone();
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
_updatePhoneNumberView: function kh_updatePhoneNumberview(ellipsisSide,
|
||||
forceMaxFontSize) {
|
||||
var phoneNumber = this._phoneNumber;
|
||||
|
||||
// If there are digits in the phone number, show the delete button
|
||||
// and enable the add contact button
|
||||
if (this._onCall) {
|
||||
this.renderPhoneNumber(phoneNumber, ellipsisSide);
|
||||
} else {
|
||||
var visibility;
|
||||
if (phoneNumber.length > 0) {
|
||||
visibility = 'visible';
|
||||
this.callBarAddContact.removeAttribute('disabled');
|
||||
} else {
|
||||
visibility = 'hidden';
|
||||
this.callBarAddContact.setAttribute('disabled', 'disabled');
|
||||
}
|
||||
this.deleteButton.style.visibility = visibility;
|
||||
this.phoneNumberView.blur(); // update avoiding caret flickering
|
||||
this.phoneNumberView.value = phoneNumber;
|
||||
|
||||
FontSizeManager.adaptToSpace(
|
||||
FontSizeManager.DIAL_PAD, this.phoneNumberView, forceMaxFontSize,
|
||||
ellipsisSide);
|
||||
}
|
||||
|
||||
if (this.onValueChanged) {
|
||||
this.onValueChanged(this._phoneNumber);
|
||||
}
|
||||
},
|
||||
|
||||
renderPhoneNumber:
|
||||
function kh_replacePhoneNumber(phoneNumber, ellipsisSide) {
|
||||
if (this._onCall && CallsHandler.activeCall) {
|
||||
CallsHandler.activeCall.
|
||||
renderPhoneNumber({raw: phoneNumber} , ellipsisSide);
|
||||
}
|
||||
},
|
||||
|
||||
restorePhoneNumber:
|
||||
function kh_restorePhoneNumber() {
|
||||
if (this._onCall && CallsHandler.activeCall) {
|
||||
CallsHandler.activeCall.restorePhoneNumber();
|
||||
}
|
||||
},
|
||||
|
||||
replaceAdditionalContactInfo:
|
||||
function kh_replaceAdditionalContactInfo(additionalContactInfo) {
|
||||
var call = CallsHandler.activeCall;
|
||||
if (this._onCall && call) {
|
||||
call.replaceAdditionalContactInfo(additionalContactInfo);
|
||||
}
|
||||
},
|
||||
|
||||
restoreAdditionalContactInfo: function kh_restoreAdditionalContactInfo() {
|
||||
if (this._onCall && CallsHandler.activeCall) {
|
||||
CallsHandler.activeCall.restoreAdditionalContactInfo();
|
||||
}
|
||||
},
|
||||
|
||||
_callVoicemail: function() {
|
||||
if (navigator.mozIccManager.iccIds.length <= 1) {
|
||||
this._callVoicemailForSim(0);
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var key = 'ril.voicemail.defaultServiceId';
|
||||
var req = navigator.mozSettings.createLock().get(key);
|
||||
req.onsuccess = function() {
|
||||
LazyLoader.load(['/shared/js/component_utils.js',
|
||||
'/shared/elements/gaia_sim_picker/script.js'],
|
||||
function() {
|
||||
var simPicker = document.getElementById('sim-picker');
|
||||
navigator.mozL10n.formatValue('voiceMail').then(string => {
|
||||
simPicker.getOrPick(req.result[key], string,
|
||||
self._callVoicemailForSim.bind(self));
|
||||
});
|
||||
});
|
||||
};
|
||||
},
|
||||
|
||||
_callVoicemailForSim: function(cardIndex) {
|
||||
var settings = navigator.mozSettings;
|
||||
if (!settings) {
|
||||
return;
|
||||
}
|
||||
var transaction = settings.createLock();
|
||||
var request = transaction.get('ril.iccInfo.mbdn');
|
||||
request.onsuccess = (function() {
|
||||
var numbers = request.result['ril.iccInfo.mbdn'];
|
||||
var number;
|
||||
if (typeof numbers == 'string') {
|
||||
number = numbers;
|
||||
} else {
|
||||
number = numbers && numbers[cardIndex];
|
||||
}
|
||||
var voicemail = navigator.mozVoicemail;
|
||||
if (!number && voicemail) {
|
||||
number = voicemail.getNumber(cardIndex);
|
||||
}
|
||||
if (number) {
|
||||
CallHandler.call(number, cardIndex);
|
||||
} else {
|
||||
this._showNoVoicemailDialog();
|
||||
}
|
||||
}).bind(this);
|
||||
request.onerror = function() {};
|
||||
},
|
||||
|
||||
_showNoVoicemailDialog: function hk_showNoVoicemailDialog() {
|
||||
|
||||
var voicemailDialog = {
|
||||
title: 'voicemailNoNumberTitle',
|
||||
text: 'voicemailNoNumberText',
|
||||
confirm: {
|
||||
title: 'voicemailNoNumberSettings',
|
||||
recommend: true,
|
||||
callback: this.showVoicemailSettings
|
||||
},
|
||||
cancel: {
|
||||
title: 'voicemailNoNumberCancel',
|
||||
callback: this._hideNoVoicemailDialog
|
||||
}
|
||||
};
|
||||
|
||||
LazyLoader.load(['/shared/js/custom_dialog.js'], function() {
|
||||
CustomDialog.show(
|
||||
voicemailDialog.title, voicemailDialog.text,
|
||||
voicemailDialog.cancel, voicemailDialog.confirm);
|
||||
});
|
||||
},
|
||||
|
||||
_hideNoVoicemailDialog: function kh_hideNoVoicemailDialog() {
|
||||
CustomDialog.hide();
|
||||
},
|
||||
|
||||
showVoicemailSettings: function kh_showVoicemailSettings() {
|
||||
var activity = new window.MozActivity({
|
||||
name: 'configure',
|
||||
data: {
|
||||
target: 'device',
|
||||
section: 'call'
|
||||
}
|
||||
});
|
||||
|
||||
activity.onerror = function() {
|
||||
console.warn('Configure activity error:', activity.error.name);
|
||||
};
|
||||
},
|
||||
|
||||
_observePreferences: function kh_observePreferences() {
|
||||
var self = this;
|
||||
LazyLoader.load('/shared/js/settings_listener.js', function() {
|
||||
SettingsListener.observe('phone.ring.keypad', false, function(value) {
|
||||
self._keypadSoundIsEnabled = !!value;
|
||||
});
|
||||
|
||||
SettingsListener.observe('phone.dtmf.type', 'long', function(value) {
|
||||
self._shortTone = (value === 'short');
|
||||
});
|
||||
|
||||
SettingsListener.observe('keyboard.vibration', false, function(value) {
|
||||
self._vibrationEnabled = !!value;
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
_updateInsertPosition: function kh_updateInsertPosition() {
|
||||
this._insertPosition = this._realStartPosition();
|
||||
},
|
||||
|
||||
_replaceLastKey: function kh_replaceLastKey(newKey) {
|
||||
this._deleteAtInsertPosition();
|
||||
this.restoreCaretPosition();
|
||||
this._insertAtCaret(newKey);
|
||||
},
|
||||
|
||||
_insertAtCaret: function kh_insertAtCaret(key) {
|
||||
if (this._insertPosition === null) {
|
||||
this._phoneNumber += key;
|
||||
} else {
|
||||
var start = this._realStartPosition();
|
||||
var end = this._realEndPosition();
|
||||
this._phoneNumber = this._phoneNumber.substring(0, start) + key +
|
||||
this._phoneNumber.substring(end);
|
||||
this._insertPosition = start + 1;
|
||||
}
|
||||
},
|
||||
|
||||
_deleteAtInsertPosition: function kh_deleteAtInsertPosition() {
|
||||
if (this._insertPosition === null) {
|
||||
this._phoneNumber = this._phoneNumber.slice(0, -1);
|
||||
} else {
|
||||
var start = this._realStartPosition();
|
||||
var end = this._realEndPosition();
|
||||
if (start > 0 && start === end) {
|
||||
start = end - 1;
|
||||
}
|
||||
this._phoneNumber = this._phoneNumber.substring(0, start) +
|
||||
this._phoneNumber.substring(end);
|
||||
this._insertPosition = this._phoneNumber ? start : null;
|
||||
}
|
||||
},
|
||||
|
||||
_clearPhoneNumber: function kh_clearPhoneNumber() {
|
||||
if (this._insertPosition === null) {
|
||||
this._phoneNumber = '';
|
||||
} else {
|
||||
var start = this._realStartPosition();
|
||||
this._phoneNumber = this._phoneNumber.substring(start);
|
||||
this._insertPosition = this._phoneNumber ? 0 : null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the caret position inside the phone number input of the dialer
|
||||
* according to the insert position (which is the real place where the caret
|
||||
* should be inside the complete number) taking into account the number of
|
||||
* ellipsed characters.
|
||||
*/
|
||||
restoreCaretPosition: function kh_restoreCaretPosition() {
|
||||
if (this._insertPosition !== null) {
|
||||
var caretPosition = this._caretPosition(this._insertPosition);
|
||||
this.phoneNumberView.selectionStart = caretPosition;
|
||||
this.phoneNumberView.selectionEnd = caretPosition;
|
||||
this.phoneNumberView.focus();
|
||||
} else {
|
||||
this.phoneNumberView.blur();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the real start index for a selection in the phone view considering
|
||||
* the ellipsed characters.
|
||||
*/
|
||||
_realStartPosition: function kh_realStartPosition() {
|
||||
var start = this.phoneNumberView.selectionStart;
|
||||
return this._realPosition(this.phoneNumberView, start);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the real end index for a selection in the phone view considering
|
||||
* the ellipsed characters.
|
||||
*/
|
||||
_realEndPosition: function kh_realEndPosition() {
|
||||
var end = this.phoneNumberView.selectionEnd;
|
||||
return this._realPosition(this.phoneNumberView, end);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the position where the cursor should be placed inside the phone
|
||||
* view (considering the ellipsed scenaries) from the real edition point.
|
||||
*/
|
||||
_caretPosition: function kh_caretPosition(realPosition) {
|
||||
return realPosition - this._ellipsisOffset(this.phoneNumberView);
|
||||
},
|
||||
|
||||
/**
|
||||
* Utility to calculate the real cursor position given an input with
|
||||
* ellipsed characters and a position inside the current value.
|
||||
*/
|
||||
_realPosition: function kh_realPosition(input, position) {
|
||||
return position + this._ellipsisOffset(this.phoneNumberView);
|
||||
},
|
||||
|
||||
/**
|
||||
* Calculates the amount of characters that are not visible in the current
|
||||
* view. This includes all the ellipsed characters and the ellipsis
|
||||
* character itself.
|
||||
*/
|
||||
_ellipsisOffset: function kh_ellipsisOffset(input) {
|
||||
var ellipsedCharacters = +input.dataset.ellipsedCharacters;
|
||||
return ellipsedCharacters ? (ellipsedCharacters - 1) : 0;
|
||||
}
|
||||
};
|
||||
134
gaia/shared/js/dialer/telephony_messages.js
Executable file
134
gaia/shared/js/dialer/telephony_messages.js
Executable file
|
|
@ -0,0 +1,134 @@
|
|||
'use strict';
|
||||
|
||||
/* globals ConfirmDialog, LazyLoader */
|
||||
/* exported TelephonyMessages */
|
||||
|
||||
var TelephonyMessages = {
|
||||
/**
|
||||
* Constants for what message to display when we get a bad number error.
|
||||
* Depends on the situation.
|
||||
*/
|
||||
// Display the regular "invalid number" message, as this is a regular call.
|
||||
REGULAR_CALL: 0,
|
||||
// We have no network and/or no SIM, so we can only make emergency calls.
|
||||
// Display the "no network" message.
|
||||
NO_NETWORK: 1,
|
||||
// We are making a call from an emergency call view, so calls can only be
|
||||
// placed to emergency and ICE numbers. Display the "not an emergency number"
|
||||
// message.
|
||||
EMERGENCY_ONLY: 2,
|
||||
|
||||
displayMessage: function(message, number) {
|
||||
var dialogTitle, dialogBody;
|
||||
|
||||
switch (message) {
|
||||
case 'BadNumber':
|
||||
dialogTitle = 'invalidNumberToDialTitle';
|
||||
dialogBody = 'invalidNumberToDialMessage';
|
||||
break;
|
||||
case 'FlightMode':
|
||||
dialogTitle = 'callAirplaneModeTitle';
|
||||
dialogBody = 'callAirplaneModeMessage';
|
||||
break;
|
||||
case 'NoNetwork':
|
||||
dialogTitle = 'emergencyDialogTitle';
|
||||
dialogBody = 'emergencyDialogBodyBadNumber';
|
||||
break;
|
||||
case 'DeviceNotAccepted':
|
||||
dialogTitle = 'emergencyDialogTitle';
|
||||
dialogBody = 'emergencyDialogBodyDeviceNotAccepted';
|
||||
break;
|
||||
case 'UnableToCall':
|
||||
dialogTitle = 'unableToCallTitle';
|
||||
dialogBody = 'unableToCallMessage';
|
||||
break;
|
||||
case 'NumberIsBusy':
|
||||
dialogTitle = 'numberIsBusyTitle';
|
||||
dialogBody = 'numberIsBusyMessage';
|
||||
break;
|
||||
case 'FixedDialingNumbers':
|
||||
dialogTitle = 'fdnIsActiveTitle';
|
||||
dialogBody = 'fdnIsActiveMessage';
|
||||
break;
|
||||
case 'OtherConnectionInUse':
|
||||
dialogTitle = 'otherConnectionInUseTitle';
|
||||
dialogBody = 'otherConnectionInUseMessage';
|
||||
break;
|
||||
case 'OperatorDeterminedBarring':
|
||||
dialogTitle = 'unableToCallTitle';
|
||||
dialogBody = 'callBarredByTheOperatorMessage';
|
||||
break;
|
||||
case 'EmergencyCallOnly':
|
||||
dialogTitle = 'emergency-call-only';
|
||||
dialogBody = 'emergency-call-error';
|
||||
break;
|
||||
default:
|
||||
console.error('Invalid message argument'); // Should never happen
|
||||
return;
|
||||
}
|
||||
|
||||
LazyLoader.load(['/shared/style/confirm.css',
|
||||
'/shared/js/confirm.js',
|
||||
document.getElementById('confirmation-message')],
|
||||
function() {
|
||||
ConfirmDialog.show(
|
||||
dialogTitle,
|
||||
{id: dialogBody, args: {number: number}},
|
||||
{
|
||||
title: 'emergencyDialogBtnOk', // Just 'ok' would be better.
|
||||
callback: function() {
|
||||
ConfirmDialog.hide();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
handleDisconnect: function(reason, number, emergencyOptions) {
|
||||
if (reason === 'NormalCallClearing') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.handleError(reason + 'Error', number, emergencyOptions);
|
||||
},
|
||||
|
||||
handleError: function(errorName, number, emergencyOptions) {
|
||||
// We ignore this error because some networks generate this with
|
||||
// STK CallControl when calling Voicemail pilots (e.g. TMobile)
|
||||
if (errorName === 'ModifiedDialError') {
|
||||
console.log('ModifiedDialError');
|
||||
return;
|
||||
}
|
||||
|
||||
if (errorName === 'BadNumberError') {
|
||||
if (emergencyOptions === this.REGULAR_CALL) {
|
||||
this.displayMessage('BadNumber');
|
||||
} else if (emergencyOptions === this.NO_NETWORK) {
|
||||
this.displayMessage('NoNetwork');
|
||||
} else if (emergencyOptions === this.EMERGENCY_ONLY) {
|
||||
this.displayMessage('EmergencyCallOnly', number);
|
||||
} else {
|
||||
console.error('Unexpected emergency options: ' + emergencyOptions);
|
||||
}
|
||||
} else if (errorName === 'DeviceNotAcceptedError') {
|
||||
this.displayMessage('DeviceNotAccepted');
|
||||
} else if (errorName === 'RadioNotAvailable') {
|
||||
this.displayMessage('FlightMode');
|
||||
} else if (errorName === 'BusyError') {
|
||||
this.displayMessage('NumberIsBusy');
|
||||
} else if (errorName === 'FDNBlockedError' ||
|
||||
errorName === 'FdnCheckFailure') {
|
||||
this.displayMessage('FixedDialingNumbers', number);
|
||||
} else if (errorName === 'OtherConnectionInUse') {
|
||||
this.displayMessage('OtherConnectionInUse');
|
||||
} else if (errorName === 'OperatorDeterminedBarringError') {
|
||||
this.displayMessage('OperatorDeterminedBarring');
|
||||
} else {
|
||||
// If the call failed for some other reason we should still
|
||||
// display something to the user. See bug 846403.
|
||||
console.error('Unexpected error: ', errorName);
|
||||
this.displayMessage('UnableToCall');
|
||||
}
|
||||
},
|
||||
};
|
||||
217
gaia/shared/js/dialer/tone_player.js
Executable file
217
gaia/shared/js/dialer/tone_player.js
Executable file
|
|
@ -0,0 +1,217 @@
|
|||
/* globals AudioContext */
|
||||
|
||||
/* exported TonePlayer */
|
||||
|
||||
'use strict';
|
||||
|
||||
const kMasterVolume = 0.5;
|
||||
const kToneVolume = 0.7;
|
||||
const kShortPressDuration = 0.15;
|
||||
const kAttackDuration = 0.025;
|
||||
const kDecayDuration = 0.025;
|
||||
const kReleaseDuration = 0.05;
|
||||
|
||||
var TonePlayer = {
|
||||
_audioContext: null,
|
||||
_channel: null,
|
||||
_gainNode: null,
|
||||
_playingNodes: [],
|
||||
_maybeTrashAudio: null,
|
||||
_initialized: false,
|
||||
|
||||
/**
|
||||
* Initializes the tone player by specifying which channel will be used to
|
||||
* play sounds. The TonePlayer will lazily create an AudioContext to play
|
||||
* sounds when needed and automatically dispose of it when the application is
|
||||
* hidden. However if the 'telephony' channel is used then we'll keep the
|
||||
* AudioContext around as long as there's an active call.
|
||||
*
|
||||
* @param channel {String} The default channel used to play sounds.
|
||||
*/
|
||||
init: function tp_init(channel) {
|
||||
var telephony = navigator.mozTelephony;
|
||||
|
||||
this._reset();
|
||||
this._channel = channel;
|
||||
this._maybeTrashAudio = (function tp_maybeTrashAudio() {
|
||||
var callIsActive = telephony && (telephony.calls.length ||
|
||||
telephony.conferenceGroup.calls.length);
|
||||
|
||||
/* If the application is hidden dispose of the audio context unless we're
|
||||
* in a call and we're using the 'telephony' channel. */
|
||||
if (document.hidden &&
|
||||
!((this._channel === 'telephony') && callIsActive)) {
|
||||
this._trashAudio();
|
||||
}
|
||||
}).bind(this);
|
||||
|
||||
window.addEventListener('visibilitychange', this._maybeTrashAudio);
|
||||
telephony && telephony.addEventListener('callschanged',
|
||||
this._maybeTrashAudio);
|
||||
|
||||
this._initialized = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Tears down the tone player and removes the registered event listeners,
|
||||
* mostly used for unit-testing.
|
||||
*/
|
||||
teardown: function tp_teardown() {
|
||||
var telephony = navigator.mozTelephony;
|
||||
|
||||
telephony && telephony.removeEventListener('callschanged',
|
||||
this._maybeTrashAudio);
|
||||
window.removeEventListener('visibilitychange', this._maybeTrashAudio);
|
||||
this._reset();
|
||||
this._initialized = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset all internal state to its default value.
|
||||
*/
|
||||
_reset: function tp_reset() {
|
||||
this._audioContext = null;
|
||||
this._channel = null;
|
||||
this._gainNode = null;
|
||||
this._playingNodes = [];
|
||||
this._maybeTrashAudio = null;
|
||||
},
|
||||
|
||||
_ensureAudio: function tp_ensureAudio() {
|
||||
if (this._audioContext || !this._initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._audioContext = new AudioContext();
|
||||
},
|
||||
|
||||
_trashAudio: function tp_trashAudio() {
|
||||
this.stop();
|
||||
this._audioContext = null;
|
||||
},
|
||||
|
||||
// Pass 0.0 for |when| to play as soon as possible.
|
||||
// Pass 0.0 for |duration| to make the tone play until stop is called.
|
||||
_startAt: function tp_startAt(frequencies, when, duration) {
|
||||
var context = this._audioContext;
|
||||
var sampleRate = context.sampleRate;
|
||||
var envelope =
|
||||
context.createBuffer(1, (duration ? duration : 0.05) * sampleRate,
|
||||
sampleRate);
|
||||
// ADSR
|
||||
for (var i = 0; i < envelope.length; i++) {
|
||||
var factor = kToneVolume;
|
||||
var t = i / sampleRate;
|
||||
if (t <= kAttackDuration) {
|
||||
factor = t / kAttackDuration;
|
||||
} else if (t - kAttackDuration <= kDecayDuration) {
|
||||
factor = 1.0 - (1.0 - kToneVolume) *
|
||||
(t - kAttackDuration) / kDecayDuration;
|
||||
}
|
||||
if (!duration) {
|
||||
// The envelope buffer contains the difference from the sustain value
|
||||
factor -= kToneVolume;
|
||||
} else if (t > duration - kReleaseDuration) {
|
||||
factor *= (duration - t) / kReleaseDuration;
|
||||
}
|
||||
envelope.getChannelData(0)[i] = factor * kMasterVolume;
|
||||
}
|
||||
|
||||
var gainNode = context.createGain();
|
||||
gainNode.connect(context.destination);
|
||||
if (!duration) {
|
||||
// For long presses, the gainNode will be used to release the tone.
|
||||
this._gainNode = gainNode;
|
||||
}
|
||||
|
||||
var envelopeNode = context.createBufferSource();
|
||||
envelopeNode.buffer = envelope;
|
||||
envelopeNode.start(when);
|
||||
envelopeNode.connect(gainNode.gain);
|
||||
// Set the gain which will be summed with the envelope buffer values
|
||||
// and will be the constant gain at the end of the tone envelope. For
|
||||
// tones with duration, the envelope covers the entire tone, so the gain
|
||||
// at the end is zero. For tones with no duration, the envelope covers
|
||||
// only the attack and delay phases after which the gain is the sustain
|
||||
// value.
|
||||
gainNode.gain.setValueAtTime(duration ? 0.0 : kToneVolume * kMasterVolume,
|
||||
0.0);
|
||||
|
||||
for (i = 0; i < frequencies.length; ++i) {
|
||||
var oscNode = this._audioContext.createOscillator();
|
||||
oscNode.type = 'sine';
|
||||
oscNode.frequency.value = frequencies[i];
|
||||
oscNode.start(when);
|
||||
if (duration) {
|
||||
// If starting immediately, then add some extra time to allow the tone
|
||||
// to start, so that the tone doesn't stop short of the end of the
|
||||
// envelope.
|
||||
oscNode.stop(Math.max(when, context.currentTime + 0.5) + duration);
|
||||
} else {
|
||||
this._playingNodes.push(oscNode);
|
||||
}
|
||||
oscNode.connect(gainNode);
|
||||
}
|
||||
},
|
||||
|
||||
start: function tp_start(frequencies, shortPress) {
|
||||
this._ensureAudio();
|
||||
this._startAt(frequencies, 0, shortPress ? kShortPressDuration : 0);
|
||||
},
|
||||
|
||||
stop: function tp_stop() {
|
||||
if (!this._gainNode) {
|
||||
return;
|
||||
}
|
||||
var context = this._audioContext;
|
||||
var sampleRate = context.sampleRate;
|
||||
var gain = this._gainNode.gain;
|
||||
this._gainNode = null;
|
||||
|
||||
var ramp =
|
||||
context.createBuffer(1, kReleaseDuration * sampleRate, sampleRate);
|
||||
for (var i = 0; i < ramp.length; i++) {
|
||||
ramp.getChannelData(0)[i] =
|
||||
(ramp.length - i - 1) / ramp.length * kToneVolume * kMasterVolume;
|
||||
}
|
||||
|
||||
var rampNode = context.createBufferSource();
|
||||
rampNode.buffer = ramp;
|
||||
rampNode.start();
|
||||
rampNode.connect(gain);
|
||||
// Change the current base gain from kToneVolume * kMasterVolume to 0,
|
||||
// cancelling the initial change from adding the ramp.
|
||||
gain.setValueAtTime(0.0, 0.0);
|
||||
|
||||
// Stop the oscillators some time after the release ramp reaches 0.
|
||||
// Some extra time is included to allow the release to start.
|
||||
while (this._playingNodes.length) {
|
||||
this._playingNodes.pop().
|
||||
stop(context.currentTime + kReleaseDuration + 0.5);
|
||||
}
|
||||
},
|
||||
|
||||
// Takes an array of steps, a step being:
|
||||
// - frequency for channel 1
|
||||
// - frequency for channel 2
|
||||
// - duration
|
||||
playSequence: function tp_playSequence(sequence) {
|
||||
this._ensureAudio();
|
||||
// AudioContext.currentTime is the last time received on the main thread
|
||||
// from the audio graph. Trying to start a tone at currentTime will not
|
||||
// start playing until this main thread returns to the event loop and
|
||||
// sends a message to the audio graph thread. Allow a little time for
|
||||
// this. The way Gecko processes audio in chunks of 30ms means this
|
||||
// should be at least 60 ms. Experiments on Buri indicate that in
|
||||
// practice this delay is 165 to 190 ms, much of which is the code that
|
||||
// runs before returning to the event loop.
|
||||
var when = this._audioContext.currentTime + 0.2;
|
||||
for (var index = 0; index < sequence.length; ++index) {
|
||||
var step = sequence[index];
|
||||
var frequencies = step.slice(0, 2);
|
||||
var duration = step[2] / 1000;
|
||||
this._startAt(frequencies, when, duration);
|
||||
when += duration;
|
||||
}
|
||||
}
|
||||
};
|
||||
174
gaia/shared/js/dialer/utils.js
Executable file
174
gaia/shared/js/dialer/utils.js
Executable file
|
|
@ -0,0 +1,174 @@
|
|||
'use strict';
|
||||
|
||||
/* exported Utils */
|
||||
|
||||
var Utils = {
|
||||
prettyDate: function ut_prettyDate(time) {
|
||||
var f = Intl.DateTimeFormat(navigator.languages, {
|
||||
hour12: navigator.mozHour12,
|
||||
hour: 'numeric',
|
||||
minute: 'numeric'
|
||||
});
|
||||
return f.format(new Date(time));
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders localized time duration to a given dom node
|
||||
* @param {HTMLElement} node that will hold the time duration content
|
||||
* @param {Number} time duration in ms
|
||||
* @param {String} l10nPrefix prefix used to select the right l10n id.
|
||||
* Default value 'callDuration'.
|
||||
*/
|
||||
prettyDuration: function(node, duration, l10nPrefix) {
|
||||
var elapsed = new Date(duration);
|
||||
var h = elapsed.getUTCHours();
|
||||
var m = elapsed.getUTCMinutes();
|
||||
var s = elapsed.getUTCSeconds();
|
||||
|
||||
var l10nId = l10nPrefix || 'callDuration';
|
||||
var durationL10n = {
|
||||
h: h + '',
|
||||
m: m + '',
|
||||
s: s + ''
|
||||
};
|
||||
|
||||
if (l10nId === 'callDuration') {
|
||||
// Pad the args with a leading 0 if we're displaying them in purely
|
||||
// digital format.
|
||||
durationL10n = {
|
||||
h: (h > 9 ? '' : '0') + h,
|
||||
m: (m > 9 ? '' : '0') + m,
|
||||
s: (s > 9 ? '' : '0') + s
|
||||
};
|
||||
}
|
||||
|
||||
if (l10nPrefix === 'callDurationTextFormat' && h === 0 && m === 0) {
|
||||
// Special case: only display in seconds format (i.e. "5 s") with text
|
||||
// formatting, as digital formatting doesn't support this.
|
||||
l10nId += 'Seconds';
|
||||
} else {
|
||||
l10nId += h > 0 ? 'Hours' : 'Minutes';
|
||||
}
|
||||
|
||||
navigator.mozL10n.setAttributes(node, l10nId, durationL10n);
|
||||
},
|
||||
|
||||
setHeaderDate: function ut_setHeaderDate(elem, time) {
|
||||
var diff = (Date.now() - time) / 1000;
|
||||
var day_diff = Math.floor(diff / 86400);
|
||||
var formattedTime;
|
||||
if (isNaN(day_diff)) {
|
||||
elem.setAttribute('data-l10n-id', 'incorrectDate');
|
||||
return;
|
||||
} else if (day_diff === 0) {
|
||||
elem.setAttribute('data-l10n-id', 'today');
|
||||
return;
|
||||
} else if (day_diff === 1) {
|
||||
elem.setAttribute('data-l10n-id', 'yesterday');
|
||||
return;
|
||||
} else if (day_diff < 6) {
|
||||
formattedTime = (new Date(time)).toLocaleString(navigator.languages, {
|
||||
weekday: 'long',
|
||||
});
|
||||
} else {
|
||||
formattedTime = (new Date(time)).toLocaleString(navigator.languages, {
|
||||
year: '2-digit',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
});
|
||||
}
|
||||
elem.removeAttribute('data-l10n-id');
|
||||
elem.textContent = formattedTime;
|
||||
},
|
||||
|
||||
getPhoneNumberPrimaryInfo: function ut_getPhoneNumberPrimaryInfo(matchingTel,
|
||||
contact) {
|
||||
if (contact) {
|
||||
if (contact.name && contact.name.length && contact.name[0] !== '') {
|
||||
return contact.name;
|
||||
} else if (contact.org && contact.org.length && contact.org[0] !== '') {
|
||||
return contact.org;
|
||||
}
|
||||
}
|
||||
if (matchingTel) {
|
||||
return matchingTel.value;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
toCamelCase: function ut_toCamelCase(str) {
|
||||
return str.replace(/\-(.)/g, function replacer(str, p1) {
|
||||
return p1.toUpperCase();
|
||||
});
|
||||
},
|
||||
|
||||
_phoneTypesL10n: {
|
||||
'mobile': 'phone_type_mobile',
|
||||
'home': 'phone_type_home',
|
||||
'work': 'phone_type_work',
|
||||
'personal': 'phone_type_personal',
|
||||
'faxHome': 'phone_type_fax_home',
|
||||
'faxOffice': 'phone_type_fax_office',
|
||||
'faxOther': 'phone_type_fax_other',
|
||||
'other': 'phone_type_other'
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* In case of a call linked to a contact, the additional information of the
|
||||
* phone number subject of the call consists in the type and carrier
|
||||
* associated with this phone number.
|
||||
*
|
||||
* Each call is associated with an *unique number* and this phone number can
|
||||
* belong to n specific contact(s). We don't care about the contact having
|
||||
* more than one phone number, as we are only interested in the additional
|
||||
* information of the current call that is associated with *one and only one*
|
||||
* phone number.
|
||||
*
|
||||
* The type of the phone number will be localized if we have a matching key.
|
||||
*/
|
||||
getPhoneNumberAdditionalInfo:
|
||||
function ut_getPhoneNumberAdditionalInfo(matchingTel) {
|
||||
// In case that there is no stored type for this number, we default to
|
||||
// "Mobile".
|
||||
var type = matchingTel.type || 'mobile';
|
||||
var carrier = matchingTel.carrier;
|
||||
|
||||
if (Array.isArray(type)) {
|
||||
type = type[0];
|
||||
}
|
||||
|
||||
var id;
|
||||
var args = {};
|
||||
|
||||
|
||||
if (this._phoneTypesL10n.hasOwnProperty(type)) {
|
||||
id = this._phoneTypesL10n[type];
|
||||
} else {
|
||||
id = 'phone_type_custom';
|
||||
args.type = type;
|
||||
}
|
||||
|
||||
if (carrier) {
|
||||
id += '_and_carrier';
|
||||
args.carrier = carrier;
|
||||
}
|
||||
|
||||
return {
|
||||
id: id,
|
||||
args: Object.keys(args).length ? args : null
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the passed string is one of the predefined phone types that we
|
||||
* localize when displayed.
|
||||
*
|
||||
* @param {String} type A string representing the phone type
|
||||
* @return {Boolean} true if the string is one of the predefined phone types,
|
||||
* false otherwise.
|
||||
*/
|
||||
isPhoneType: function ut_isPhoneType(type) {
|
||||
return this._phoneTypesL10n.hasOwnProperty(type);
|
||||
}
|
||||
};
|
||||
73
gaia/shared/js/dialer/voicemail.js
Executable file
73
gaia/shared/js/dialer/voicemail.js
Executable file
|
|
@ -0,0 +1,73 @@
|
|||
'use strict';
|
||||
|
||||
/* global Promise */
|
||||
|
||||
(function(exports) {
|
||||
/**
|
||||
* This Voicemail module provides an easy way to identify if any given number
|
||||
* is voicemail number or not. Voicemail number could be stored in mozSettings
|
||||
* under key of 'ril.iccInfo.mbdn' or in navigator.mozVoicemail. User of this
|
||||
* module don't need to know where voicemail number stores, just query
|
||||
* voicemail number by invoking Voicemail.check()
|
||||
*
|
||||
* @example
|
||||
* Voicemail.check('123', cardIndex).then(function(isVoicemailNumber) {
|
||||
* // do something based on value of isVoicemailNumber
|
||||
* });
|
||||
*
|
||||
*/
|
||||
var Voicemail = {
|
||||
/**
|
||||
* Query if a number is voicemail number or not
|
||||
*
|
||||
* @param {String} number - Number in query
|
||||
* @param {Number} [cardIndex] - SIM card index in query, 0 if not specified
|
||||
* @returns {Promise} - A Promise that resolves as true if number is
|
||||
* voicemail number, false otherwise.
|
||||
*/
|
||||
check: function vm_check(number, cardIndex) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
if (!number) {
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var serviceId = cardIndex;
|
||||
// check the voicemail number if the number is in the sim card
|
||||
var voicemail = navigator.mozVoicemail;
|
||||
if (voicemail) {
|
||||
var voicemailNumber = voicemail.getNumber(serviceId);
|
||||
if (voicemailNumber === number) {
|
||||
resolve(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// check the voicemail number with the mozSetting value
|
||||
// based on /shared/resources/apn.json
|
||||
var settings = navigator.mozSettings;
|
||||
var req = settings.createLock().get('ril.iccInfo.mbdn');
|
||||
req.onsuccess = function getVoicemailNumber() {
|
||||
var isVoicemailNumber = false;
|
||||
var voicemailNumbers = req.result['ril.iccInfo.mbdn'];
|
||||
var voicemailNumber;
|
||||
|
||||
if (typeof voicemailNumbers === 'string') {
|
||||
voicemailNumber = voicemailNumbers;
|
||||
} else {
|
||||
voicemailNumber = voicemailNumbers && voicemailNumbers[serviceId];
|
||||
}
|
||||
if (voicemailNumber === number) {
|
||||
isVoicemailNumber = true;
|
||||
}
|
||||
resolve(isVoicemailNumber);
|
||||
};
|
||||
|
||||
req.onerror = function getVoicemailNumberError() {
|
||||
resolve(false);
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
exports.Voicemail = Voicemail;
|
||||
}(window));
|
||||
Loading…
Reference in a new issue