diff options
Diffstat (limited to 'apps/system/emergency-call/js/keypad.js')
-rw-r--r-- | apps/system/emergency-call/js/keypad.js | 461 |
1 files changed, 461 insertions, 0 deletions
diff --git a/apps/system/emergency-call/js/keypad.js b/apps/system/emergency-call/js/keypad.js new file mode 100644 index 0000000..6163ecf --- /dev/null +++ b/apps/system/emergency-call/js/keypad.js @@ -0,0 +1,461 @@ +/* + * The code is being shared between system/emergency-call/js/keypad.js + * and dialer/js/keypad.js. Be sure to update both file when you commit! + * + */ + +'use strict'; + +var kFontStep = 4; +var minFontSize = 12; + +// Frequencies comming 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 keypadSoundIsEnabled = true; +SettingsListener.observe('phone.ring.keypad', true, function(value) { + keypadSoundIsEnabled = !!value; +}); + +var TonePlayer = { + _sampleRate: 4000, + + init: function tp_init() { + document.addEventListener('mozvisibilitychange', + this.visibilityChange.bind(this)); + this.ensureAudio(); + }, + + ensureAudio: function tp_ensureAudio() { + if (this._audio) + return; + + this._audio = new Audio(); + this._audio.volume = 0.5; + this._audio.mozSetup(2, this._sampleRate); + }, + + generateFrames: function tp_generateFrames(soundData, freqRow, freqCol) { + var currentSoundSample = 0; + var kr = 2 * Math.PI * freqRow / this._sampleRate; + var kc = 2 * Math.PI * freqCol / this._sampleRate; + for (var i = 0; i < soundData.length; i += 2) { + var smoother = 0.5 + (Math.sin((i * Math.PI) / soundData.length)) / 2; + + soundData[i] = Math.sin(kr * currentSoundSample) * smoother; + soundData[i + 1] = Math.sin(kc * currentSoundSample) * smoother; + + currentSoundSample++; + } + }, + + play: function tp_play(frequencies) { + var soundDataSize = this._sampleRate / 4; + var soundData = new Float32Array(soundDataSize); + this.generateFrames(soundData, frequencies[0], frequencies[1]); + this._audio.mozWriteAudio(soundData); + }, + + // If the app loses focus, close the audio stream. This works around an + // issue in Gecko where the Audio Data API causes gfx performance problems, + // in particular when scrolling the homescreen. + // See: https://bugzilla.mozilla.org/show_bug.cgi?id=779914 + visibilityChange: function tp_visibilityChange(e) { + if (!document.mozHidden) { + this.ensureAudio(); + } else { + // Reset the audio stream. This ensures that the stream is shutdown + // *immediately*. + this._audio.src = ''; + delete this._audio; + } + } + +}; + +var KeypadManager = { + _phoneNumber: '', + _onCall: false, + + get phoneNumberView() { + delete this.phoneNumberView; + return this.phoneNumberView = document.getElementById('phone-number-view'); + }, + + get fakePhoneNumberView() { + delete this.fakePhoneNumberView; + return this.fakePhoneNumberView = + document.getElementById('fake-phone-number-view'); + }, + + get phoneNumberViewContainer() { + delete this.phoneNumberViewContainer; + return this.phoneNumberViewContainer = + document.getElementById('phone-number-view-container'); + }, + + get keypad() { + delete this.keypad; + return this.keypad = document.getElementById('keypad'); + }, + + get callBar() { + delete this.callBar; + return this.callBar = + document.getElementById('keypad-callbar'); + }, + + get hideBar() { + delete this.hideBar; + return this.hideBar = document.getElementById('keypad-hidebar'); + }, + + get callBarAddContact() { + delete this.callBarAddContact; + return this.callBarAddContact = + document.getElementById('keypad-callbar-add-contact'); + }, + + get callBarCallAction() { + delete this.callBarCallAction; + return this.callBarCallAction = + document.getElementById('keypad-callbar-call-action'); + }, + + get callBarCancelAction() { + delete this.callBarCancelAction; + return this.callBarCancelAction = + document.getElementById('keypad-callbar-cancel'); + }, + + get deleteButton() { + delete this.deleteButton; + return this.deleteButton = document.getElementById('keypad-delete'); + }, + + get hideBarHangUpAction() { + delete this.hideBarHangUpAction; + return this.hideBarHangUpAction = + document.getElementById('keypad-hidebar-hang-up-action-wrapper'); + }, + + get hideBarHideAction() { + delete this.hideBarHideAction; + return this.hideBarHideAction = + document.getElementById('keypad-hidebar-hide-keypad-action'); + }, + + init: function kh_init() { + // Update the minimum phone number phone size. + // The UX team states that the minimum font size should be + // 10pt. First off, we convert it to px multiplying it 0.226 times, + // then we convert it to rem multiplying it a number of times equal + // to the font-size property of the body element. + var defaultFontSize = window.getComputedStyle(document.body, null) + .getPropertyValue('font-size'); + minFontSize = parseInt(parseInt(defaultFontSize) * 10 * 0.226); + + this.phoneNumberView.value = ''; + this._phoneNumber = ''; + + var keyHandler = this.keyHandler.bind(this); + this.keypad.addEventListener('mousedown', keyHandler, true); + this.keypad.addEventListener('mouseup', keyHandler, true); + this.deleteButton.addEventListener('mousedown', keyHandler); + this.deleteButton.addEventListener('mouseup', keyHandler); + + // The keypad add contact bar is only included in the normal version of + // the keypad. + if (this.callBarAddContact) { + this.callBarAddContact.addEventListener('mouseup', + this.addContact.bind(this)); + } + + // The keypad add contact bar is only included in the normal version and + // the emergency call version of the keypad. + if (this.callBarCallAction) { + this.callBarCallAction.addEventListener('mouseup', + this.makeCall.bind(this)); + } + + // The keypad cancel bar is only the emergency call version of the keypad. + if (this.callBarCancelAction) { + this.callBarCancelAction.addEventListener('mouseup', 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('mouseup', + this.callbarBackAction); + } + + if (this.hideBarHangUpAction) { + this.hideBarHangUpAction.addEventListener('mouseup', + this.hangUpCallFromKeypad); + } + + TonePlayer.init(); + + this.render(); + }, + + moveCaretToEnd: function hk_util_moveCaretToEnd(el) { + if (typeof el.selectionStart == 'number') { + el.selectionStart = el.selectionEnd = el.value.length; + } else if (typeof el.createTextRange != 'undefined') { + el.focus(); + var range = el.createTextRange(); + range.collapse(false); + range.select(); + } + }, + + render: function hk_render(layoutType) { + if (layoutType == 'oncall') { + this._onCall = true; + var numberNode = CallScreen.activeCall.querySelector('.number'); + this._phoneNumber = numberNode.textContent; + 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'); + } + }, + + makeCall: function hk_makeCall(event) { + event.stopPropagation(); + + if (this._phoneNumber != '') { + CallHandler.call(KeypadManager._phoneNumber); + } + }, + + addContact: function hk_addContact(event) { + var number = this._phoneNumber; + if (!number) + return; + + try { + var activity = new MozActivity({ + name: 'new', + data: { + type: 'webcontacts/contact', + params: { + 'tel': number + } + } + }); + } catch (e) { + console.log('WebActivities unavailable? : ' + e); + } + }, + + callbarBackAction: function hk_callbarBackAction(event) { + CallScreen.hideKeypad(); + }, + + hangUpCallFromKeypad: function hk_hangUpCallFromKeypad(event) { + CallScreen.views.classList.remove('show'); + OnCallHandler.end(); + }, + + formatPhoneNumber: function kh_formatPhoneNumber(mode) { + switch (mode) { + case 'dialpad': + var fakeView = this.fakePhoneNumberView; + var view = this.phoneNumberView; + + // We consider the case where the delete button may have + // been used to delete the whole phone number. + if (view.value == '') { + view.style.fontSize = view.dataset.size; + return; + } + break; + + case 'on-call': + var fakeView = CallScreen.activeCall.querySelector('.fake-number'); + var view = CallScreen.activeCall.querySelector('.number'); + break; + } + + var computedStyle = window.getComputedStyle(view, null); + var currentFontSize = computedStyle.getPropertyValue('font-size'); + if (!('size' in view.dataset)) { + view.dataset.size = currentFontSize; + } + + var newFontSize = this.getNextFontSize(view, fakeView, + parseInt(view.dataset.size), + parseInt(currentFontSize)); + view.style.fontSize = newFontSize + 'px'; + this.addEllipsis(view, fakeView, newFontSize); + }, + + addEllipsis: function kh_addEllipsis(view, fakeView, currentFontSize) { + var viewWidth = view.getBoundingClientRect().width; + fakeView.style.fontSize = currentFontSize + 'px'; + fakeView.innerHTML = view.value; + + var counter = 1; + var value = view.value; + + var newPhoneNumber; + while (fakeView.getBoundingClientRect().width > viewWidth) { + newPhoneNumber = '\u2026' + value.substr(-value.length + counter); + fakeView.innerHTML = newPhoneNumber; + counter++; + } + + if (newPhoneNumber) { + view.value = newPhoneNumber; + } + }, + + getNextFontSize: function kh_getNextFontSize(view, fakeView, + fontSize, initialFontSize) { + var viewWidth = view.getBoundingClientRect().width; + fakeView.style.fontSize = fontSize + 'px'; + fakeView.innerHTML = view.value; + + var rect = fakeView.getBoundingClientRect(); + while ((rect.width > viewWidth) && (fontSize > minFontSize)) { + fontSize = Math.max(fontSize - kFontStep, minFontSize); + fakeView.style.fontSize = fontSize + 'px'; + rect = fakeView.getBoundingClientRect(); + } + + if ((rect.width < viewWidth) && (fontSize < initialFontSize)) { + fakeView.style.fontSize = (fontSize + kFontStep) + 'px'; + rect = fakeView.getBoundingClientRect(); + if (rect.width <= viewWidth) { + fontSize += kFontStep; + } + } + return fontSize; + }, + + keyHandler: function kh_keyHandler(event) { + var key = event.target.dataset.value; + + if (!key) + return; + + event.stopPropagation(); + if (event.type == 'mousedown') { + this._longPress = false; + + if (key != 'delete') { + if (keypadSoundIsEnabled) { + TonePlayer.play(gTonesFrequencies[key]); + } + + // Sending the DTMF tone if on a call + var telephony = navigator.mozTelephony; + if (telephony && telephony.active && + telephony.active.state == 'connected') { + + telephony.startTone(key); + window.setTimeout(function ch_stopTone() { + telephony.stopTone(); + }, 100); + + } + } + + // Manage long press + if (key == '0' || key == 'delete') { + this._holdTimer = setTimeout(function(self) { + if (key == 'delete') { + self._phoneNumber = ''; + } else { + self._phoneNumber += '+'; + } + + self._longPress = true; + self._updatePhoneNumberView(); + }, 400, this); + } + + // Voicemail long press (needs to be longer since it actually dials) + if (event.target.dataset.voicemail) { + this._holdTimer = setTimeout(function vm_call(self) { + self._longPress = true; + self._callVoicemail(); + }, 3000, this); + } + } else if (event.type == 'mouseup') { + // If it was a long press our work is already done + if (this._longPress) { + this._longPress = false; + this._holdTimer = null; + return; + } + if (key == 'delete') { + this._phoneNumber = this._phoneNumber.slice(0, -1); + } else { + this._phoneNumber += key; + } + + if (this._holdTimer) + clearTimeout(this._holdTimer); + + this._updatePhoneNumberView(); + } + }, + + updatePhoneNumber: function kh_updatePhoneNumber(number) { + this._phoneNumber = number; + this._updatePhoneNumberView(); + }, + + _updatePhoneNumberView: function kh_updatePhoneNumberview() { + var phoneNumber = this._phoneNumber; + + // If there are digits in the phone number, show the delete button. + var visibility = (phoneNumber.length > 0) ? 'visible' : 'hidden'; + this.deleteButton.style.visibility = visibility; + + if (this._onCall) { + var view = CallScreen.activeCall.querySelector('.number'); + view.textContent = phoneNumber; + this.formatPhoneNumber('on-call'); + } else { + this.phoneNumberView.value = phoneNumber; + this.moveCaretToEnd(this.phoneNumberView); + this.formatPhoneNumber('dialpad'); + } + }, + + _callVoicemail: function kh_callVoicemail() { + var voicemail = navigator.mozVoicemail; + if (voicemail && voicemail.number) { + CallHandler.call(voicemail.number); + } + } +}; |