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
974
gaia/apps/callscreen/js/calls_handler.js
Executable file
974
gaia/apps/callscreen/js/calls_handler.js
Executable file
|
|
@ -0,0 +1,974 @@
|
|||
/* globals BluetoothHelper, CallScreen,
|
||||
ConferenceGroupHandler, Contacts, HandledCall, KeypadManager,
|
||||
SimplePhoneMatcher, TonePlayer, Utils */
|
||||
|
||||
'use strict';
|
||||
|
||||
/* globals BluetoothHelper, CallScreen, Contacts, FontSizeManager, HandledCall,
|
||||
KeypadManager, SimplePhoneMatcher, TonePlayer, Utils */
|
||||
|
||||
var CallsHandler = (function callsHandler() {
|
||||
// Changing this will probably require markup changes
|
||||
var CALLS_LIMIT = 2;
|
||||
var CDMA_CALLS_LIMIT = 2;
|
||||
|
||||
var handledCalls = [];
|
||||
var exitCallScreenTimeout = null;
|
||||
|
||||
var toneInterval = null; // Timer used to play the waiting tone
|
||||
var telephony = window.navigator.mozTelephony;
|
||||
telephony.oncallschanged = onCallsChanged;
|
||||
|
||||
// Setting up the SimplePhoneMatcher
|
||||
var conn = window.navigator.mozMobileConnections &&
|
||||
window.navigator.mozMobileConnections[0];
|
||||
|
||||
if (conn && conn.voice && conn.voice.network && conn.voice.network.mcc) {
|
||||
SimplePhoneMatcher.mcc = conn.voice.network.mcc;
|
||||
}
|
||||
|
||||
var btHelper = new BluetoothHelper();
|
||||
|
||||
var screenLock;
|
||||
|
||||
/* === Setup === */
|
||||
function setup() {
|
||||
if (telephony) {
|
||||
// Somehow the muted property appears to true after initialization.
|
||||
// Set it to false.
|
||||
telephony.muted = false;
|
||||
// Once bug 1129882 and bug 1113086 are landed, the new audio channel
|
||||
// service will manage all the channels from System app, and to call
|
||||
// this api is let the audio channel service know the Callscreen app
|
||||
// is using the telephony audio channel.
|
||||
if (telephony.ownAudioChannel) {
|
||||
telephony.ownAudioChannel();
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: Use BTManager.isConnected() through btHelper
|
||||
// once bug 929376 is finished.
|
||||
btHelper.getConnectedDevicesByProfile(btHelper.profiles.HFP,
|
||||
function(result) {
|
||||
CallScreen.setBTReceiverIcon(!!(result && result.length));
|
||||
});
|
||||
|
||||
btHelper.onhfpstatuschanged = function(evt) {
|
||||
CallScreen.setBTReceiverIcon(evt.status);
|
||||
};
|
||||
|
||||
var acm = navigator.mozAudioChannelManager;
|
||||
if (acm) {
|
||||
acm.addEventListener('headphoneschange', function onheadphoneschange() {
|
||||
if (acm.headphones) {
|
||||
CallScreen.switchToDefaultOut();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
btHelper.onscostatuschanged = function onscostatuschanged(evt) {
|
||||
if (evt.status) {
|
||||
CallScreen.switchToDefaultOut();
|
||||
}
|
||||
};
|
||||
|
||||
navigator.mozSetMessageHandler('headset-button', handleHSCommand);
|
||||
navigator.mozSetMessageHandler('bluetooth-dialer-command', handleBTCommand);
|
||||
}
|
||||
|
||||
/* === Handled calls === */
|
||||
var highPriorityWakeLock = null;
|
||||
function onCallsChanged() {
|
||||
// Acquire or release the high-priority wake lock, as necessary. This
|
||||
// (mostly) prevents this process from being killed while we're on a call.
|
||||
if (!highPriorityWakeLock && telephony.calls.length > 0) {
|
||||
highPriorityWakeLock = navigator.requestWakeLock('high-priority');
|
||||
}
|
||||
if (highPriorityWakeLock && telephony.calls.length === 0) {
|
||||
highPriorityWakeLock.unlock();
|
||||
highPriorityWakeLock = null;
|
||||
}
|
||||
|
||||
// Make sure we play the busy tone when appropriate
|
||||
if (telephony.active) {
|
||||
telephony.active.addEventListener(
|
||||
'disconnected', handleDisconnectAndPlayBusyTone
|
||||
);
|
||||
}
|
||||
|
||||
// Adding any new calls to handledCalls
|
||||
telephony.calls.forEach(function callIterator(call) {
|
||||
var alreadyAdded = handledCalls.some(function hcIterator(hc) {
|
||||
return (hc.call === call);
|
||||
});
|
||||
|
||||
if (!alreadyAdded) {
|
||||
addCall(call);
|
||||
}
|
||||
});
|
||||
|
||||
// Removing any ended calls to handledCalls
|
||||
function hcIterator(call) {
|
||||
return (call === hc.call);
|
||||
}
|
||||
|
||||
for (var index = (handledCalls.length - 1); index >= 0; index--) {
|
||||
var hc = handledCalls[index];
|
||||
|
||||
var stillHere = telephony.calls.some(hcIterator) ||
|
||||
telephony.conferenceGroup.calls.some(hcIterator);
|
||||
|
||||
if (!stillHere) {
|
||||
removeCall(index);
|
||||
}
|
||||
}
|
||||
|
||||
if (cdmaCallWaiting()) {
|
||||
handleCallWaiting(telephony.calls[0]);
|
||||
} else {
|
||||
if (isCdma3WayCall()) {
|
||||
CallScreen.hidePlaceNewCallButton();
|
||||
} else if (handledCalls.length !== 0) {
|
||||
CallScreen.showPlaceNewCallButton();
|
||||
}
|
||||
}
|
||||
|
||||
// Update the state of the hold/merge button depending on the calls' state
|
||||
updateMergeAndOnHoldStatus();
|
||||
|
||||
// Update mute and speaker buttons.
|
||||
updateMuteAndSpeakerStatus();
|
||||
|
||||
CallScreen.setCallerContactImage();
|
||||
exitCallScreenIfNoCalls(CallScreen.callEndPromptTime);
|
||||
}
|
||||
|
||||
function addCall(call) {
|
||||
// Once we already have 1 call, we need to care about incoming
|
||||
// calls and insert new dialing calls.
|
||||
if (handledCalls.length &&
|
||||
(call.state != 'incoming') && (call.state != 'dialing')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// No more room
|
||||
if (telephony.calls.length > CALLS_LIMIT) {
|
||||
HandledCall(call);
|
||||
call.hangUp();
|
||||
return;
|
||||
}
|
||||
|
||||
// First incoming or outgoing call, reset mute and speaker.
|
||||
if (handledCalls.length === 0) {
|
||||
CallScreen.unmute();
|
||||
CallScreen.enableKeypadButton();
|
||||
|
||||
/**
|
||||
* Do not connect bluetooth SCO for first incoming/outgoing call.
|
||||
*
|
||||
* Bluetooth certification test requires SCO be connected after
|
||||
* user answers incoming call. Gecko bluetooth would connect SCO
|
||||
* automatically once 1) user answers incoming call or
|
||||
* 2) user dials outgoing call.
|
||||
*/
|
||||
CallScreen.switchToDefaultOut(true /* do not connect */);
|
||||
}
|
||||
|
||||
// Find an available node for displaying the call
|
||||
var hc = new HandledCall(call);
|
||||
handledCalls.push(hc);
|
||||
CallScreen.insertCall(hc.node);
|
||||
|
||||
if (call.state === 'incoming') {
|
||||
turnScreenOn(call);
|
||||
}
|
||||
|
||||
if (handledCalls.length > 1) {
|
||||
// New incoming call, signaling the user.
|
||||
if (call.state === 'incoming') {
|
||||
hc.hide();
|
||||
handleCallWaiting(call);
|
||||
|
||||
// User performed another outgoing call. show its status.
|
||||
} else {
|
||||
updatePlaceNewCall();
|
||||
hc.show();
|
||||
}
|
||||
} else {
|
||||
if (window.location.hash.startsWith('#locked') &&
|
||||
(call.state === 'incoming')) {
|
||||
CallScreen.initLockScreenLayout();
|
||||
CallScreen.render('incoming-locked');
|
||||
} else {
|
||||
CallScreen.render(call.state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function removeCall(index) {
|
||||
handledCalls.splice(index, 1);
|
||||
|
||||
if (handledCalls.length === 0) {
|
||||
CallScreen.disableKeypadButton();
|
||||
return;
|
||||
}
|
||||
|
||||
// Only hiding the incoming bar if we have another one to display.
|
||||
// Let handledCall catches disconnect event itself.
|
||||
CallScreen.hideIncoming();
|
||||
|
||||
var remainingCall = handledCalls[0];
|
||||
if (remainingCall.call.state === 'incoming') {
|
||||
// The active call ended, showing the incoming call
|
||||
remainingCall.show();
|
||||
|
||||
// This is the difference between an endAndAnswer() and
|
||||
// the active call being disconnected while a call is waiting
|
||||
setTimeout(function nextTick() {
|
||||
if (remainingCall.call.state === 'incoming') {
|
||||
CallScreen.render('incoming');
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function turnScreenOn(call) {
|
||||
screenLock = navigator.requestWakeLock('screen');
|
||||
|
||||
call.addEventListener('statechange', function callStateChange() {
|
||||
call.removeEventListener('statechange', callStateChange);
|
||||
|
||||
if (screenLock) {
|
||||
screenLock.unlock();
|
||||
screenLock = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Play the busy tone in response to a disconnected event with the
|
||||
* appropriate reason set. Once the tone has finished this will also
|
||||
* automatically close the callscreen if no more calls are present.
|
||||
*
|
||||
* @param evt {Object} The event delivered in the
|
||||
* TelephonyCall.ondisconnected event-handler.
|
||||
*/
|
||||
function handleDisconnectAndPlayBusyTone(evt) {
|
||||
if (evt.call.disconnectedReason === 'Busy') {
|
||||
// ANSI call waiting tone for a 3 seconds window.
|
||||
var sequence = [[480, 620, 500], [0, 0, 500],
|
||||
[480, 620, 500], [0, 0, 500],
|
||||
[480, 620, 500], [0, 0, 500]];
|
||||
var sequenceDuration = sequence.reduce(function(prev, curr) {
|
||||
return prev + curr[2];
|
||||
}, 0);
|
||||
|
||||
TonePlayer.playSequence(sequence);
|
||||
exitCallScreenIfNoCalls(sequenceDuration);
|
||||
}
|
||||
}
|
||||
|
||||
function handleCallWaiting(call) {
|
||||
var number = call.secondId ? call.secondId.number : call.id.number;
|
||||
|
||||
if (!number) {
|
||||
navigator.mozL10n.setAttributes(CallScreen.incomingNumber,
|
||||
'withheld-number');
|
||||
// Force a synchronous translation so we can resize the string
|
||||
navigator.mozL10n.translateFragment(CallScreen.incomingNumber);
|
||||
FontSizeManager.adaptToSpace(FontSizeManager.SECOND_INCOMING_CALL,
|
||||
CallScreen.incomingNumber, false, 'end');
|
||||
return;
|
||||
}
|
||||
|
||||
if (navigator.mozIccManager.iccIds.length > 1) {
|
||||
navigator.mozL10n.setAttributes(
|
||||
CallScreen.incomingSim,
|
||||
'sim-number',
|
||||
{ n: call.serviceId + 1 }
|
||||
);
|
||||
} else {
|
||||
CallScreen.incomingSim.hidden = true;
|
||||
}
|
||||
|
||||
Contacts.findByNumber(number,
|
||||
function lookupContact(contact, matchingTel) {
|
||||
if (contact && contact.name) {
|
||||
CallScreen.incomingInfo.classList.add('additionalInfo');
|
||||
CallScreen.incomingNumber.textContent = contact.name;
|
||||
|
||||
var additionalL10n =
|
||||
Utils.getPhoneNumberAdditionalInfo(matchingTel);
|
||||
navigator.mozL10n.setAttributes(
|
||||
CallScreen.incomingNumberAdditionalTelType,
|
||||
additionalL10n.id,
|
||||
additionalL10n.args);
|
||||
CallScreen.incomingNumberAdditionalTel.textContent = number;
|
||||
} else {
|
||||
CallScreen.incomingInfo.classList.remove('additionalInfo');
|
||||
CallScreen.incomingNumber.textContent = number;
|
||||
CallScreen.incomingNumberAdditionalTelType.removeAttribute(
|
||||
'data-l10n-id');
|
||||
CallScreen.incomingNumberAdditionalTelType.textContent = '';
|
||||
CallScreen.incomingNumberAdditionalTel.textContent = '';
|
||||
}
|
||||
|
||||
FontSizeManager.adaptToSpace(
|
||||
FontSizeManager.SECOND_INCOMING_CALL, CallScreen.incomingNumber,
|
||||
false, 'end');
|
||||
if (contact && contact.name) {
|
||||
FontSizeManager.ensureFixedBaseline(
|
||||
FontSizeManager.SECOND_INCOMING_CALL, CallScreen.incomingNumber);
|
||||
}
|
||||
});
|
||||
|
||||
if (cdmaCallWaiting()) {
|
||||
CallScreen.holdAndAnswerOnly = true;
|
||||
}
|
||||
|
||||
CallScreen.showIncoming();
|
||||
playWaitingTone(call);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks now and also in CallScreen.callEndPromptTime seconds if there
|
||||
* are no currently handled calls, and if not, exits the app. Resets
|
||||
* this timer on each successive invocation.
|
||||
*
|
||||
* @param timeout {Integer} A duration in ms after which the callscreen
|
||||
* should be closed.
|
||||
*/
|
||||
function exitCallScreenIfNoCalls(timeout) {
|
||||
if (handledCalls.length === 0) {
|
||||
// Prevent the user from doing anything while we're waiting for the exit
|
||||
// timer to fire. This prevents them from taking any actions that would
|
||||
// require there to be a handled call.
|
||||
document.body.classList.toggle('no-handled-calls', true);
|
||||
|
||||
if (exitCallScreenTimeout !== null) {
|
||||
clearTimeout(exitCallScreenTimeout);
|
||||
exitCallScreenTimeout = null;
|
||||
}
|
||||
exitCallScreenTimeout = setTimeout(function(evt) {
|
||||
exitCallScreenTimeout = null;
|
||||
if (handledCalls.length === 0) {
|
||||
window.close();
|
||||
} else {
|
||||
document.body.classList.toggle('no-handled-calls', false);
|
||||
}
|
||||
}, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
function updateAllPhoneNumberDisplays() {
|
||||
handledCalls.forEach(function(call) {
|
||||
if (!call._leftGroup) {
|
||||
call.restorePhoneNumber();
|
||||
}
|
||||
});
|
||||
}
|
||||
window.addEventListener('resize', updateAllPhoneNumberDisplays);
|
||||
|
||||
/**
|
||||
* Return the number of calls currently present in one state or another.
|
||||
* This includes all regular calls irrespective of their state plus a
|
||||
* conference group call if one is present.
|
||||
*
|
||||
* @returns {Integer} The number of calls currently present.
|
||||
*/
|
||||
function numOpenLines() {
|
||||
return telephony.calls.length +
|
||||
(telephony.conferenceGroup.calls.length ? 1 : 0);
|
||||
}
|
||||
|
||||
/* === Bluetooth Headset support ===*/
|
||||
function handleBTCommand(message) {
|
||||
var command = message.command;
|
||||
switch (command) {
|
||||
case 'CHUP':
|
||||
end();
|
||||
break;
|
||||
case 'ATA':
|
||||
answer();
|
||||
break;
|
||||
case 'CHLD=0':
|
||||
hangupWaitingCalls();
|
||||
break;
|
||||
case 'CHLD=1':
|
||||
// End the active call and answer the other one
|
||||
if ((handledCalls.length === 1) && !cdmaCallWaiting()) {
|
||||
end();
|
||||
} else {
|
||||
endAndAnswer();
|
||||
}
|
||||
break;
|
||||
case 'CHLD=2':
|
||||
// Hold the active call and answer the other one
|
||||
if ((handledCalls.length === 1) && !cdmaCallWaiting()) {
|
||||
holdOrResumeSingleCall();
|
||||
} else {
|
||||
holdAndAnswer();
|
||||
}
|
||||
break;
|
||||
case 'CHLD=3':
|
||||
mergeCalls();
|
||||
break;
|
||||
default:
|
||||
var partialCommand = command.substring(0, 3);
|
||||
if (partialCommand === 'VTS') {
|
||||
KeypadManager.press(command.substring(4));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Headset command support */
|
||||
var lastHeadsetPress = 0;
|
||||
|
||||
function handleHSCommand(message) {
|
||||
/**
|
||||
* See bug 853132: plugging / unplugging some headphones might send a
|
||||
* 'headset-button-press' / 'headset-button-release' message
|
||||
* => if these two events happen in the same second, it's a click;
|
||||
* => if these two events are too distant, ignore them.
|
||||
*/
|
||||
switch (message) {
|
||||
case 'headset-button-press':
|
||||
lastHeadsetPress = Date.now();
|
||||
return;
|
||||
case 'headset-button-release':
|
||||
if ((Date.now() - lastHeadsetPress) > 1000) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (telephony.active) {
|
||||
end();
|
||||
} else if ((handledCalls.length > 1) || cdmaCallWaiting()) {
|
||||
holdAndAnswer();
|
||||
} else {
|
||||
answer();
|
||||
}
|
||||
}
|
||||
|
||||
/* === User Actions === */
|
||||
function answer() {
|
||||
// We should always have only 1 call here
|
||||
if (!handledCalls.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
handledCalls[0].call.answer();
|
||||
}
|
||||
|
||||
function holdAndAnswer() {
|
||||
if ((handledCalls.length < 2) && !cdmaCallWaiting()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (telephony.active) {
|
||||
// connected, incoming
|
||||
telephony.active.hold(); // the incoming call is answered by gecko
|
||||
|
||||
// Check for CDMA mode before calling bluetooth CDMA-specific functions
|
||||
if (cdmaCallWaiting()) {
|
||||
btHelper.answerWaitingCall();
|
||||
}
|
||||
} else if (handledCalls.length >= 2) {
|
||||
// held, incoming
|
||||
var lastCall = handledCalls[handledCalls.length - 1].call;
|
||||
lastCall.answer(); // the previous call is held by gecko
|
||||
} else {
|
||||
// Held call in CDMA mode, hold to answer to the second call
|
||||
handledCalls[0].call.hold();
|
||||
}
|
||||
|
||||
CallScreen.hideIncoming();
|
||||
|
||||
if (cdmaCallWaiting()) {
|
||||
/* In CDMA mode we need to update the displayed call to reflect the fact
|
||||
* that we're not aware anymore to which number we're connected. We also
|
||||
* need to stop the call waiting tone as the call state doesn't change
|
||||
* after answering and thus doesn't trigger the stop callback. */
|
||||
handledCalls[0].updateCallNumber();
|
||||
CallScreen.cdmaCallWaiting = true;
|
||||
stopWaitingTone();
|
||||
}
|
||||
}
|
||||
|
||||
function endAndAnswer() {
|
||||
if ((handledCalls.length < 2) && !cdmaCallWaiting()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (telephony.active === telephony.conferenceGroup) {
|
||||
endConferenceCall().then(function() {
|
||||
CallScreen.hideIncoming();
|
||||
}, function() {});
|
||||
return;
|
||||
}
|
||||
|
||||
if (cdmaCallWaiting()) {
|
||||
/* We're in CDMA mode, there's no way to hang the existing call nor to
|
||||
* know if we're connected to the second call hence we treat this as an
|
||||
* hold-and-answer scenario. */
|
||||
handledCalls[0].call.hold();
|
||||
stopWaitingTone();
|
||||
btHelper.answerWaitingCall();
|
||||
} else {
|
||||
var callToEnd = telephony.active || // connected, incoming
|
||||
handledCalls[handledCalls.length - 2].call; // held, incoming
|
||||
|
||||
var callToAnswer;
|
||||
handledCalls.some(function(handledCall) {
|
||||
if (handledCall.call !== callToEnd) {
|
||||
callToAnswer = handledCall.call;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (callToEnd && callToAnswer) {
|
||||
callToEnd.addEventListener('disconnected', function ondisconnected() {
|
||||
callToEnd.removeEventListener('disconnected', ondisconnected);
|
||||
// Answer the incoming call after hanging up the active call
|
||||
callToAnswer.answer();
|
||||
});
|
||||
|
||||
callToEnd.hangUp(); // hangup the active call
|
||||
} else if (callToEnd) {
|
||||
callToEnd.hangUp(); // hangup the active call
|
||||
} else if (callToAnswer) {
|
||||
callToAnswer.answer(); // answer the incoming call
|
||||
}
|
||||
}
|
||||
|
||||
CallScreen.hideIncoming();
|
||||
|
||||
if (cdmaCallWaiting()) {
|
||||
/* In CDMA mode we need to update the displayed call to reflect the fact
|
||||
* that we're not aware anymore to which number we're connected. We also
|
||||
* need to stop the call waiting tone as the call state doesn't change
|
||||
* after answering and thus doesn't trigger the stop callback. */
|
||||
handledCalls[0].updateCallNumber();
|
||||
CallScreen.cdmaCallWaiting = true;
|
||||
}
|
||||
}
|
||||
|
||||
function toggleCalls() {
|
||||
if (CallScreen.incomingContainer.classList.contains('displayed') &&
|
||||
!cdmaCallWaiting()) {
|
||||
/* In CDMA call waiting mode only one call is displayed but we can still
|
||||
* switch between calls if a second one is present. */
|
||||
return;
|
||||
}
|
||||
|
||||
if (numOpenLines() < 2 && !cdmaCallWaiting()) {
|
||||
// Putting a call on Hold when there are no other
|
||||
// calls in progress has been disabled until a less
|
||||
// accidental user-interface is implemented.
|
||||
// See bug 894232 and bug 882056 for more background.
|
||||
// We can now hold a call only from BT devices.
|
||||
if (!telephony.active) {
|
||||
holdOrResumeSingleCall();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
telephony.active.hold();
|
||||
btHelper.toggleCalls();
|
||||
}
|
||||
|
||||
function holdOrResumeSingleCall() {
|
||||
if (numOpenLines() !== 1 ||
|
||||
(telephony.calls.length &&
|
||||
(telephony.calls[0].state === 'incoming' ||
|
||||
!telephony.calls[0].switchable))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (telephony.active) {
|
||||
telephony.active.hold();
|
||||
} else {
|
||||
var line = telephony.calls.length ?
|
||||
telephony.calls[0] : telephony.conferenceGroup;
|
||||
|
||||
line.resume();
|
||||
}
|
||||
}
|
||||
|
||||
// Hang up the held call or the second incoming call
|
||||
function hangupWaitingCalls() {
|
||||
handledCalls.forEach(function(handledCall) {
|
||||
var callState = handledCall.call.state;
|
||||
if (callState === 'held' ||
|
||||
(callState === 'incoming' && handledCalls.length > 1)) {
|
||||
handledCall.call.hangUp();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function ignore() {
|
||||
/* On CDMA there's no way to hangup an incoming call so we just skip this
|
||||
* step and hide the incoming call. */
|
||||
if (cdmaCallWaiting()) {
|
||||
stopWaitingTone();
|
||||
btHelper.ignoreWaitingCall();
|
||||
} else {
|
||||
var ignoreIndex = handledCalls.length - 1;
|
||||
handledCalls[ignoreIndex].call.hangUp();
|
||||
}
|
||||
|
||||
CallScreen.hideIncoming();
|
||||
}
|
||||
|
||||
function endConferenceCall() {
|
||||
return telephony.conferenceGroup.hangUp().then(function() {
|
||||
ConferenceGroupHandler.signalConferenceEnded();
|
||||
}, function() {
|
||||
console.error('Failed to hangup Conference Call');
|
||||
});
|
||||
}
|
||||
|
||||
function end() {
|
||||
var callToEnd;
|
||||
|
||||
// If there is an active call we end this one
|
||||
if (telephony.active) {
|
||||
callToEnd = telephony.active;
|
||||
} else if (numOpenLines() === 1) {
|
||||
// If there's a single call we end it
|
||||
if (telephony.conferenceGroup.calls.length) {
|
||||
callToEnd = telephony.conferenceGroup;
|
||||
} else {
|
||||
callToEnd = telephony.calls[0];
|
||||
}
|
||||
} else {
|
||||
// If not we're rejecting the last incoming call
|
||||
if (!handledCalls.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var lastCallIndex = handledCalls.length - 1;
|
||||
callToEnd = handledCalls[lastCallIndex].call;
|
||||
}
|
||||
|
||||
// If this is a conference call end all the calls in it
|
||||
if (callToEnd.calls) {
|
||||
endConferenceCall();
|
||||
} else {
|
||||
callToEnd.hangUp();
|
||||
}
|
||||
}
|
||||
|
||||
function unmute() {
|
||||
telephony.muted = false;
|
||||
}
|
||||
|
||||
function switchToSpeaker() {
|
||||
// add a btHelper.isConnected() check before calling disconnectSco
|
||||
// once bug 929376 lands.
|
||||
btHelper.disconnectSco();
|
||||
if (!telephony.speakerEnabled) {
|
||||
telephony.speakerEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
function switchToDefaultOut(doNotConnect) {
|
||||
if (telephony.speakerEnabled) {
|
||||
telephony.speakerEnabled = false;
|
||||
}
|
||||
|
||||
if (!doNotConnect && telephony.active && !document.hidden) {
|
||||
// add a btHelper.isConnected() check before calling disconnectSco
|
||||
// once bug 929376 lands.
|
||||
btHelper.connectSco();
|
||||
}
|
||||
}
|
||||
|
||||
function switchToReceiver() {
|
||||
// add a btHelper.isConnected() check before calling disconnectSco
|
||||
// once bug 929376 lands.
|
||||
btHelper.disconnectSco();
|
||||
if (telephony.speakerEnabled) {
|
||||
telephony.speakerEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function toggleMute() {
|
||||
telephony.muted = !telephony.muted;
|
||||
}
|
||||
|
||||
function toggleSpeaker() {
|
||||
if (telephony.speakerEnabled) {
|
||||
CallsHandler.switchToDefaultOut();
|
||||
} else {
|
||||
CallsHandler.switchToSpeaker();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays the ANSI call waiting tone for a 10 seconds window
|
||||
*
|
||||
* @param {Object} call The call object to which the wait tone is referred to.
|
||||
*/
|
||||
function playWaitingTone(call) {
|
||||
// ANSI call waiting tone for a 10 sec window
|
||||
var sequence = [[440, 440, 100],
|
||||
[0, 0, 100],
|
||||
[440, 440, 100]];
|
||||
|
||||
toneInterval = window.setInterval(function playTone() {
|
||||
TonePlayer.playSequence(sequence);
|
||||
}, 10000);
|
||||
TonePlayer.playSequence(sequence);
|
||||
|
||||
call.addEventListener('statechange', function callStateChange() {
|
||||
call.removeEventListener('statechange', callStateChange);
|
||||
window.clearInterval(toneInterval);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops playing the waiting tone
|
||||
*/
|
||||
function stopWaitingTone() {
|
||||
window.clearInterval(toneInterval);
|
||||
}
|
||||
|
||||
function activeCall() {
|
||||
var telephonyActiveCall = telephony.active;
|
||||
var active = null;
|
||||
for (var i = 0; i < handledCalls.length; i++) {
|
||||
var handledCall = handledCalls[i];
|
||||
if (telephonyActiveCall === handledCall.call) {
|
||||
active = handledCall;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return active;
|
||||
}
|
||||
|
||||
function activeCallForContactImage() {
|
||||
if (handledCalls.length === 1) {
|
||||
return handledCalls[0];
|
||||
}
|
||||
|
||||
// The active call can be null. We're concatenating the active call with the
|
||||
// list of all handled calls. The active call will appear twice in this
|
||||
// array if it's not null.
|
||||
return [activeCall()].concat(handledCalls).find(function(elem) {
|
||||
return !elem || !elem.call.group;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects if we're in CDMA call waiting mode
|
||||
*
|
||||
* @return {Boolean} Returns true if we're in CDMA call waiting mode.
|
||||
*/
|
||||
function cdmaCallWaiting() {
|
||||
return ((telephony.calls.length === 1) &&
|
||||
(telephony.calls[0].state === 'connected') &&
|
||||
(telephony.calls[0].secondId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects if we're first call on CDMA network
|
||||
*
|
||||
* @return {Boolean} Return true if we're first call on CDMA network.
|
||||
*/
|
||||
function isFirstCallOnCdmaNetwork() {
|
||||
var cdmaTypes = ['evdo0', 'evdoa', 'evdob', '1xrtt', 'is95a', 'is95b'];
|
||||
if (handledCalls.length !== 0) {
|
||||
var ci = handledCalls[0].call.serviceId;
|
||||
var type = window.navigator.mozMobileConnections[ci].voice.type;
|
||||
|
||||
return (cdmaTypes.indexOf(type) !== -1);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isCdma3WayCall() {
|
||||
return isFirstCallOnCdmaNetwork() &&
|
||||
((telephony.calls.length === CDMA_CALLS_LIMIT) ||
|
||||
(telephony.conferenceGroup.calls.length > 0));
|
||||
}
|
||||
|
||||
function mergeCalls() {
|
||||
if (telephony.conferenceGroup.calls.length === 0 &&
|
||||
telephony.calls.length === 2) {
|
||||
telephony.conferenceGroup.add(telephony.calls[0], telephony.calls[1])
|
||||
.catch(function() {
|
||||
CallScreen.showStatusMessage('conferenceAddError');
|
||||
});
|
||||
} else if (telephony.conferenceGroup.calls.length > 0 &&
|
||||
telephony.calls.length === 1) {
|
||||
telephony.conferenceGroup.add(telephony.calls[0])
|
||||
.catch(function() {
|
||||
CallScreen.showStatusMessage('conferenceAddError');
|
||||
});
|
||||
} else {
|
||||
console.warn('Cannot join conference call.');
|
||||
}
|
||||
}
|
||||
|
||||
/* === Telephony audio channel competing functions ===*/
|
||||
|
||||
/**
|
||||
* Check if a call is being established.
|
||||
*
|
||||
* @returns true if a call is being established, false otherwise
|
||||
*/
|
||||
function isEstablishingCall() {
|
||||
return telephony.calls.some(function(call) {
|
||||
return call.state === 'dialing' || call.state === 'alerting';
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if any of the calls is currently on hold.
|
||||
*
|
||||
* @returns true if a call is on hold, false otherwise
|
||||
*/
|
||||
function isAnyCallOnHold() {
|
||||
return telephony.calls.some(call => call.state === 'held') ||
|
||||
(telephony.conferenceGroup && telephony.conferenceGroup.state === 'held');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if any of the calls can be put on hold or resumed.
|
||||
*
|
||||
* @returns true if a call can be put on hold or resumed, false otherwise
|
||||
*/
|
||||
function isAnyCallSwitchable() {
|
||||
return telephony.calls.some(call => call.switchable) ||
|
||||
((telephony.conferenceGroup.calls.length > 0) &&
|
||||
telephony.conferenceGroup.calls.every(call => call.switchable));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if all non-conference calls are mergeable.
|
||||
*
|
||||
* @returns true if all non-confernece calls can be merged, false otherwise
|
||||
*/
|
||||
function isEveryCallMergeable() {
|
||||
return telephony.calls.every(call => call.mergeable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow placing a new call only when we've not already placed one that isn't
|
||||
* connected yet.
|
||||
*/
|
||||
function updatePlaceNewCall() {
|
||||
if (isEstablishingCall() || (numOpenLines() === 0)) {
|
||||
CallScreen.disablePlaceNewCallButton();
|
||||
} else {
|
||||
CallScreen.enablePlaceNewCallButton();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the state of the hold/merge button to reflect the current calls'
|
||||
* state. If only one call is available the hold button alone will be
|
||||
* displayed if the call's switchable. The state of the button will depend on
|
||||
* the call being on hold or not. If two calls are being handled at the same
|
||||
* time we'll display the merge button if the second call's mergeable. If not
|
||||
* no button will be displayed at all. We don't support cases where more than
|
||||
* two calls are being handled at the same time; this code will need to be
|
||||
* revisited if CALLS_LIMIT is increased above 2.
|
||||
*/
|
||||
function updateMergeAndOnHoldStatus() {
|
||||
var isEstablishing = isEstablishingCall();
|
||||
|
||||
if (numOpenLines() === 0) {
|
||||
/* If no calls are currently present just disable the button hold button
|
||||
* but do not hide it.*/
|
||||
CallScreen.disableOnHoldButton();
|
||||
} else if (numOpenLines() > 1 && !isEstablishing) {
|
||||
/* If more than one call has been established show only the merge
|
||||
* button or no button at all if the calls are not mergeable. */
|
||||
CallScreen.hideOnHoldButton();
|
||||
|
||||
if (isEveryCallMergeable()) {
|
||||
CallScreen.showOnHoldAndMergeContainer();
|
||||
CallScreen.showMergeButton();
|
||||
} else {
|
||||
CallScreen.hideOnHoldAndMergeContainer();
|
||||
}
|
||||
} else {
|
||||
/* If only one call has been established show only the hold button or
|
||||
* no button at all if the calls are not switchable. */
|
||||
CallScreen.hideMergeButton();
|
||||
CallScreen.setShowIsHeld(!telephony.active && isAnyCallOnHold());
|
||||
|
||||
if (isEstablishing) {
|
||||
CallScreen.disableOnHoldButton();
|
||||
} else {
|
||||
CallScreen.enableOnHoldButton();
|
||||
}
|
||||
|
||||
if (isAnyCallSwitchable()) {
|
||||
CallScreen.showOnHoldAndMergeContainer();
|
||||
CallScreen.showOnHoldButton();
|
||||
} else {
|
||||
CallScreen.hideOnHoldAndMergeContainer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the state of the speaker and mute buttons. Both buttons are active
|
||||
* only if there's an active call.
|
||||
*/
|
||||
function updateMuteAndSpeakerStatus() {
|
||||
if (telephony.active) {
|
||||
CallScreen.enableMuteButton();
|
||||
CallScreen.enableSpeakerButton();
|
||||
} else {
|
||||
CallScreen.disableMuteButton();
|
||||
CallScreen.disableSpeakerButton();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
setup: setup,
|
||||
|
||||
answer: answer,
|
||||
holdAndAnswer: holdAndAnswer,
|
||||
endAndAnswer: endAndAnswer,
|
||||
toggleCalls: toggleCalls,
|
||||
ignore: ignore,
|
||||
end: end,
|
||||
toggleMute: toggleMute,
|
||||
toggleSpeaker: toggleSpeaker,
|
||||
unmute: unmute,
|
||||
switchToReceiver: switchToReceiver,
|
||||
switchToSpeaker: switchToSpeaker,
|
||||
switchToDefaultOut: switchToDefaultOut,
|
||||
holdOrResumeSingleCall: holdOrResumeSingleCall,
|
||||
|
||||
checkCalls: onCallsChanged,
|
||||
mergeCalls: mergeCalls,
|
||||
updateAllPhoneNumberDisplays: updateAllPhoneNumberDisplays,
|
||||
updatePlaceNewCall: updatePlaceNewCall,
|
||||
exitCallScreenIfNoCalls: exitCallScreenIfNoCalls,
|
||||
updateMergeAndOnHoldStatus: updateMergeAndOnHoldStatus,
|
||||
updateMuteAndSpeakerStatus: updateMuteAndSpeakerStatus,
|
||||
|
||||
get activeCall() {
|
||||
return activeCall();
|
||||
},
|
||||
|
||||
get activeCallForContactImage() {
|
||||
return activeCallForContactImage();
|
||||
},
|
||||
|
||||
isFirstCallOnCdmaNetwork: isFirstCallOnCdmaNetwork
|
||||
};
|
||||
})();
|
||||
Loading…
Reference in a new issue