mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-31 22:25:30 +00:00
1154 lines
34 KiB
JavaScript
1154 lines
34 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
let Promise = SpecialPowers.Cu.import("resource://gre/modules/Promise.jsm").Promise;
|
|
let telephony;
|
|
let conference;
|
|
|
|
const kPrefRilDebuggingEnabled = "ril.debugging.enabled";
|
|
|
|
/**
|
|
* Emulator helper.
|
|
*/
|
|
let emulator = (function() {
|
|
let pendingCmdCount = 0;
|
|
let originalRunEmulatorCmd = runEmulatorCmd;
|
|
|
|
// Overwritten it so people could not call this function directly.
|
|
runEmulatorCmd = function() {
|
|
throw "Use emulator.run(cmd, callback) instead of runEmulatorCmd";
|
|
};
|
|
|
|
function run(cmd, callback) {
|
|
pendingCmdCount++;
|
|
originalRunEmulatorCmd(cmd, function(result) {
|
|
is(result[result.length - 1], "OK", "emulator command should be OK.");
|
|
pendingCmdCount--;
|
|
if (callback && typeof callback === "function") {
|
|
callback(result);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @return Promise
|
|
*/
|
|
function waitFinish() {
|
|
let deferred = Promise.defer();
|
|
|
|
waitFor(function() {
|
|
deferred.resolve();
|
|
}, function() {
|
|
return pendingCmdCount === 0;
|
|
});
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
return {
|
|
run: run,
|
|
waitFinish: waitFinish
|
|
};
|
|
}());
|
|
|
|
/**
|
|
* Telephony related helper functions.
|
|
*/
|
|
(function() {
|
|
/**
|
|
* @return Promise
|
|
*/
|
|
function delay(ms) {
|
|
let deferred = Promise.defer();
|
|
|
|
let startTime = Date.now();
|
|
waitFor(function() {
|
|
deferred.resolve();
|
|
},function() {
|
|
let duration = Date.now() - startTime;
|
|
return (duration >= ms);
|
|
});
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
/**
|
|
* @return Promise
|
|
*/
|
|
function clearCalls() {
|
|
let deferred = Promise.defer();
|
|
|
|
log("Clear existing calls.");
|
|
emulator.run("gsm clear", function(result) {
|
|
if (result[0] == "OK") {
|
|
waitFor(function() {
|
|
deferred.resolve();
|
|
}, function() {
|
|
return telephony.calls.length === 0;
|
|
});
|
|
} else {
|
|
log("Failed to clear existing calls.");
|
|
deferred.reject();
|
|
}
|
|
});
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
/**
|
|
* Provide a string with format of the emulator call list result.
|
|
*
|
|
* @param prefix
|
|
* Possible values are "outbound" and "inbound".
|
|
* @param number
|
|
* Call number.
|
|
* @return A string with format of the emulator call list result.
|
|
*/
|
|
function callStrPool(prefix, number) {
|
|
let padding = " : ";
|
|
let numberInfo = prefix + number + padding.substr(number.length);
|
|
|
|
let info = {};
|
|
let states = ['ringing', 'incoming', 'active', 'held'];
|
|
for (let state of states) {
|
|
info[state] = numberInfo + state;
|
|
}
|
|
|
|
return info;
|
|
}
|
|
|
|
/**
|
|
* Provide a corresponding string of an outgoing call. The string is with
|
|
* format of the emulator call list result.
|
|
*
|
|
* @param number
|
|
* Number of an outgoing call.
|
|
* @return A string with format of the emulator call list result.
|
|
*/
|
|
function outCallStrPool(number) {
|
|
return callStrPool("outbound to ", number);
|
|
}
|
|
|
|
/**
|
|
* Provide a corresponding string of an incoming call. The string is with
|
|
* format of the emulator call list result.
|
|
*
|
|
* @param number
|
|
* Number of an incoming call.
|
|
* @return A string with format of the emulator call list result.
|
|
*/
|
|
function inCallStrPool(number) {
|
|
return callStrPool("inbound from ", number);
|
|
}
|
|
|
|
/**
|
|
* Check utility functions.
|
|
*/
|
|
|
|
function checkInitialState() {
|
|
log("Verify initial state.");
|
|
ok(telephony.calls, 'telephony.call');
|
|
checkTelephonyActiveAndCalls(null, []);
|
|
ok(conference.calls, 'conference.calls');
|
|
checkConferenceStateAndCalls('', []);
|
|
}
|
|
|
|
/**
|
|
* Convenient helper to compare a TelephonyCall and a received call event.
|
|
*/
|
|
function checkEventCallState(event, call, state) {
|
|
is(call, event.call, "event.call");
|
|
is(call.state, state, "call state");
|
|
}
|
|
|
|
/**
|
|
* Convenient helper to check mozTelephony.active and mozTelephony.calls.
|
|
*/
|
|
function checkTelephonyActiveAndCalls(active, calls) {
|
|
is(telephony.active, active, "telephony.active");
|
|
is(telephony.calls.length, calls.length, "telephony.calls");
|
|
for (let i = 0; i < calls.length; ++i) {
|
|
is(telephony.calls[i], calls[i]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convenient helper to check mozTelephony.conferenceGroup.state and
|
|
* .conferenceGroup.calls.
|
|
*/
|
|
function checkConferenceStateAndCalls(state, calls) {
|
|
is(conference.state, state, "conference.state");
|
|
is(conference.calls.length, calls.length, "conference.calls");
|
|
for (let i = 0; i < calls.length; i++) {
|
|
is(conference.calls[i], calls[i]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convenient helper to handle *.oncallschanged event.
|
|
*
|
|
* @param container
|
|
* Representation of "mozTelephony" or "mozTelephony.conferenceGroup."
|
|
* @param containerName
|
|
* Name of container. Could be an arbitrary string, used for debug
|
|
* messages only.
|
|
* @param expectedCalls
|
|
* An array of calls.
|
|
* @param callback
|
|
* A callback function.
|
|
*/
|
|
function check_oncallschanged(container, containerName, expectedCalls,
|
|
callback) {
|
|
container.oncallschanged = function(event) {
|
|
log("Received 'callschanged' event for the " + containerName);
|
|
if (event.call) {
|
|
let index = expectedCalls.indexOf(event.call);
|
|
ok(index != -1);
|
|
expectedCalls.splice(index, 1);
|
|
|
|
if (expectedCalls.length === 0) {
|
|
container.oncallschanged = null;
|
|
callback();
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Convenient helper to handle *.ongroupchange event.
|
|
*
|
|
* @param call
|
|
* A TelephonyCall object.
|
|
* @param callName
|
|
* Name of a call. Could be an arbitrary string, used for debug messages
|
|
* only.
|
|
* @param group
|
|
* Representation of mozTelephony.conferenceGroup.
|
|
* @param callback
|
|
* A callback function.
|
|
*/
|
|
function check_ongroupchange(call, callName, group, callback) {
|
|
call.ongroupchange = function(event) {
|
|
log("Received 'groupchange' event for the " + callName);
|
|
call.ongroupchange = null;
|
|
|
|
is(call.group, group);
|
|
callback();
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Convenient helper to handle *.onstatechange event.
|
|
*
|
|
* @param container
|
|
* Representation of a TelephonyCall or mozTelephony.conferenceGroup.
|
|
* @param containerName
|
|
* Name of container. Could be an arbitrary string, used for debug messages
|
|
* only.
|
|
* @param state
|
|
* A string.
|
|
* @param callback
|
|
* A callback function.
|
|
*/
|
|
function check_onstatechange(container, containerName, state, callback) {
|
|
container.onstatechange = function(event) {
|
|
log("Received 'statechange' event for the " + containerName);
|
|
container.onstatechange = null;
|
|
|
|
is(container.state, state);
|
|
callback();
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Convenient helper to check the sequence of call state and event handlers.
|
|
*
|
|
* @param state
|
|
* A string of the expected call state.
|
|
* @param previousEvent
|
|
* A string of the event that should come before the expected state.
|
|
*/
|
|
function StateEventChecker(state, previousEvent) {
|
|
let event = 'on' + state;
|
|
|
|
return function(call, callName, callback) {
|
|
call[event] = function() {
|
|
log("Received '" + state + "' event for the " + callName);
|
|
call[event] = null;
|
|
|
|
if (previousEvent) {
|
|
// We always clear the event handler when the event is received.
|
|
// Therefore, if the corresponding handler is not existed, the expected
|
|
// previous event has been already received.
|
|
ok(!call[previousEvent]);
|
|
}
|
|
is(call.state, state);
|
|
callback();
|
|
};
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Convenient helper to check the call list existing in the emulator.
|
|
*
|
|
* @param expectedCallList
|
|
* An array of call info with the format of "callStrPool()[state]".
|
|
* @return A deferred promise.
|
|
*/
|
|
function checkEmulatorCallList(expectedCallList) {
|
|
let deferred = Promise.defer();
|
|
|
|
emulator.run("gsm list", function(result) {
|
|
log("Call list is now: " + result);
|
|
for (let i = 0; i < expectedCallList.length; ++i) {
|
|
is(result[i], expectedCallList[i], "emulator calllist");
|
|
}
|
|
is(result[expectedCallList.length], "OK", "emulator calllist");
|
|
deferred.resolve();
|
|
});
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
/**
|
|
* Super convenient helper to check calls and state of mozTelephony and
|
|
* mozTelephony.conferenceGroup.
|
|
*
|
|
* @param active
|
|
* A TelephonyCall object. Should be the expected active call.
|
|
* @param calls
|
|
* An array of TelephonyCall objects. Should be the expected list of
|
|
* mozTelephony.calls.
|
|
* @param conferenceState
|
|
* A string. Should be the expected conference state.
|
|
* @param conferenceCalls
|
|
* An array of TelephonyCall objects. Should be the expected list of
|
|
* mozTelephony.conferenceGroup.calls.
|
|
*/
|
|
function checkState(active, calls, conferenceState, conferenceCalls) {
|
|
checkTelephonyActiveAndCalls(active, calls);
|
|
checkConferenceStateAndCalls(conferenceState, conferenceCalls);
|
|
}
|
|
|
|
/**
|
|
* Super convenient helper to check calls and state of mozTelephony and
|
|
* mozTelephony.conferenceGroup as well as the calls existing in the emulator.
|
|
*
|
|
* @param active
|
|
* A TelephonyCall object. Should be the expected active call.
|
|
* @param calls
|
|
* An array of TelephonyCall objects. Should be the expected list of
|
|
* mozTelephony.calls.
|
|
* @param conferenceState
|
|
* A string. Should be the expected conference state.
|
|
* @param conferenceCalls
|
|
* An array of TelephonyCall objects. Should be the expected list of
|
|
* mozTelephony.conferenceGroup.calls.
|
|
* @param callList
|
|
* An array of call info with the format of "callStrPool()[state]".
|
|
* @return A deferred promise.
|
|
*/
|
|
function checkAll(active, calls, conferenceState, conferenceCalls, callList) {
|
|
checkState(active, calls, conferenceState, conferenceCalls);
|
|
return checkEmulatorCallList(callList);
|
|
}
|
|
|
|
/**
|
|
* Request utility functions.
|
|
*/
|
|
|
|
/**
|
|
* Make sure there's no pending event before we jump to the next action.
|
|
*
|
|
* @param received
|
|
* A string of the received event.
|
|
* @param pending
|
|
* An array of the pending events.
|
|
* @param nextAction
|
|
* A callback function that is called when there's no pending event.
|
|
*/
|
|
function receivedPending(received, pending, nextAction) {
|
|
let index = pending.indexOf(received);
|
|
if (index != -1) {
|
|
pending.splice(index, 1);
|
|
}
|
|
if (pending.length === 0) {
|
|
nextAction();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Make an outgoing call.
|
|
*
|
|
* @param number
|
|
* A string.
|
|
* @param serviceId [optional]
|
|
* Identification of a service. 0 is set as default.
|
|
* @return A deferred promise.
|
|
*/
|
|
function dial(number, serviceId) {
|
|
serviceId = typeof serviceId !== "undefined" ? serviceId : 0;
|
|
log("Make an outgoing call: " + number + ", serviceId: " + serviceId);
|
|
|
|
let deferred = Promise.defer();
|
|
|
|
telephony.dial(number, serviceId).then(call => {
|
|
ok(call);
|
|
is(call.number, number);
|
|
is(call.state, "dialing");
|
|
is(call.serviceId, serviceId);
|
|
|
|
call.onalerting = function onalerting(event) {
|
|
call.onalerting = null;
|
|
log("Received 'onalerting' call event.");
|
|
checkEventCallState(event, call, "alerting");
|
|
deferred.resolve(call);
|
|
};
|
|
}, cause => {
|
|
deferred.reject(cause);
|
|
});
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
/**
|
|
* Answer an incoming call.
|
|
*
|
|
* @param call
|
|
* An incoming TelephonyCall object.
|
|
* @param conferenceStateChangeCallback [optional]
|
|
* A callback function which is called if answering an incoming call
|
|
* triggers conference state change.
|
|
* @return A deferred promise.
|
|
*/
|
|
function answer(call, conferenceStateChangeCallback) {
|
|
log("Answering the incoming call.");
|
|
|
|
let deferred = Promise.defer();
|
|
let done = function() {
|
|
deferred.resolve(call);
|
|
};
|
|
|
|
let pending = ["call.onconnected"];
|
|
let receive = function(name) {
|
|
receivedPending(name, pending, done);
|
|
};
|
|
|
|
// When there's already a connected conference call, answering a new incoming
|
|
// call triggers conference state change. We should wait for
|
|
// |conference.onstatechange| before checking the state of the conference call.
|
|
if (conference.state === "connected") {
|
|
pending.push("conference.onstatechange");
|
|
check_onstatechange(conference, "conference", "held", function() {
|
|
if (typeof conferenceStateChangeCallback === "function") {
|
|
conferenceStateChangeCallback();
|
|
}
|
|
receive("conference.onstatechange");
|
|
});
|
|
}
|
|
|
|
call.onconnecting = function onconnectingIn(event) {
|
|
log("Received 'connecting' call event for incoming call.");
|
|
call.onconnecting = null;
|
|
checkEventCallState(event, call, "connecting");
|
|
};
|
|
|
|
call.onconnected = function onconnectedIn(event) {
|
|
log("Received 'connected' call event for incoming call.");
|
|
call.onconnected = null;
|
|
checkEventCallState(event, call, "connected");
|
|
ok(!call.onconnecting);
|
|
receive("call.onconnected");
|
|
};
|
|
call.answer();
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
/**
|
|
* Hold a call.
|
|
*
|
|
* @param call
|
|
* A TelephonyCall object.
|
|
* @return A deferred promise.
|
|
*/
|
|
function hold(call) {
|
|
log("Putting the call on hold.");
|
|
|
|
let deferred = Promise.defer();
|
|
|
|
let gotHolding = false;
|
|
call.onholding = function onholding(event) {
|
|
log("Received 'holding' call event");
|
|
call.onholding = null;
|
|
checkEventCallState(event, call, "holding");
|
|
gotHolding = true;
|
|
};
|
|
|
|
call.onheld = function onheld(event) {
|
|
log("Received 'held' call event");
|
|
call.onheld = null;
|
|
checkEventCallState(event, call, "held");
|
|
ok(gotHolding);
|
|
deferred.resolve(call);
|
|
};
|
|
call.hold();
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
/**
|
|
* Simulate an incoming call.
|
|
*
|
|
* @param number
|
|
* A string.
|
|
* @return A deferred promise.
|
|
*/
|
|
function remoteDial(number) {
|
|
log("Simulating an incoming call.");
|
|
|
|
let deferred = Promise.defer();
|
|
|
|
telephony.onincoming = function onincoming(event) {
|
|
log("Received 'incoming' call event.");
|
|
telephony.onincoming = null;
|
|
|
|
let call = event.call;
|
|
|
|
ok(call);
|
|
is(call.number, number);
|
|
is(call.state, "incoming");
|
|
|
|
deferred.resolve(call);
|
|
};
|
|
emulator.run("gsm call " + number);
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
/**
|
|
* Remote party answers the call.
|
|
*
|
|
* @param call
|
|
* A TelephonyCall object.
|
|
* @return A deferred promise.
|
|
*/
|
|
function remoteAnswer(call) {
|
|
log("Remote answering the call.");
|
|
|
|
let deferred = Promise.defer();
|
|
|
|
call.onconnected = function onconnected(event) {
|
|
log("Received 'connected' call event.");
|
|
call.onconnected = null;
|
|
checkEventCallState(event, call, "connected");
|
|
deferred.resolve(call);
|
|
};
|
|
emulator.run("gsm accept " + call.number);
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
/**
|
|
* Remote party hangs up the call.
|
|
*
|
|
* @param call
|
|
* A TelephonyCall object.
|
|
* @return A deferred promise.
|
|
*/
|
|
function remoteHangUp(call) {
|
|
log("Remote hanging up the call.");
|
|
|
|
let deferred = Promise.defer();
|
|
|
|
call.ondisconnected = function ondisconnected(event) {
|
|
log("Received 'disconnected' call event.");
|
|
call.ondisconnected = null;
|
|
checkEventCallState(event, call, "disconnected");
|
|
deferred.resolve(call);
|
|
};
|
|
emulator.run("gsm cancel " + call.number);
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
/**
|
|
* Remote party hangs up all the calls.
|
|
*
|
|
* @param calls
|
|
* An array of TelephonyCall objects.
|
|
* @return A deferred promise.
|
|
*/
|
|
function remoteHangUpCalls(calls) {
|
|
let promise = Promise.resolve();
|
|
|
|
for (let call of calls) {
|
|
promise = promise.then(remoteHangUp.bind(null, call));
|
|
}
|
|
|
|
return promise;
|
|
}
|
|
|
|
/**
|
|
* Add calls to conference.
|
|
*
|
|
* @param callsToAdd
|
|
* An array of TelephonyCall objects to be added into conference. The
|
|
* length of the array should be 1 or 2.
|
|
* @param connectedCallback [optional]
|
|
* A callback function which is called when conference state becomes
|
|
* connected.
|
|
* @return A deferred promise.
|
|
*/
|
|
function addCallsToConference(callsToAdd, connectedCallback) {
|
|
log("Add " + callsToAdd.length + " calls into conference.");
|
|
|
|
let deferred = Promise.defer();
|
|
let done = function() {
|
|
deferred.resolve();
|
|
};
|
|
|
|
let pending = ["conference.oncallschanged", "conference.onconnected"];
|
|
let receive = function(name) {
|
|
receivedPending(name, pending, done);
|
|
};
|
|
|
|
let check_onconnected = StateEventChecker('connected', 'onresuming');
|
|
|
|
for (let call of callsToAdd) {
|
|
let callName = "callToAdd (" + call.number + ')';
|
|
|
|
let ongroupchange = callName + ".ongroupchange";
|
|
pending.push(ongroupchange);
|
|
check_ongroupchange(call, callName, conference,
|
|
receive.bind(null, ongroupchange));
|
|
|
|
let onstatechange = callName + ".onstatechange";
|
|
pending.push(onstatechange);
|
|
check_onstatechange(call, callName, 'connected',
|
|
receive.bind(null, onstatechange));
|
|
}
|
|
|
|
check_oncallschanged(conference, 'conference', callsToAdd,
|
|
receive.bind(null, "conference.oncallschanged"));
|
|
|
|
check_onconnected(conference, "conference", function() {
|
|
ok(!conference.oncallschanged);
|
|
if (typeof connectedCallback === 'function') {
|
|
connectedCallback();
|
|
}
|
|
receive("conference.onconnected");
|
|
});
|
|
|
|
// Cannot use apply() through webidl, so just separate the cases to handle.
|
|
if (callsToAdd.length == 2) {
|
|
conference.add(callsToAdd[0], callsToAdd[1]);
|
|
} else {
|
|
conference.add(callsToAdd[0]);
|
|
}
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
/**
|
|
* Hold the conference.
|
|
*
|
|
* @param calls
|
|
* An array of TelephonyCall objects existing in conference.
|
|
* @param heldCallback [optional]
|
|
* A callback function which is called when conference state becomes
|
|
* held.
|
|
* @return A deferred promise.
|
|
*/
|
|
function holdConference(calls, heldCallback) {
|
|
log("Holding the conference call.");
|
|
|
|
let deferred = Promise.defer();
|
|
let done = function() {
|
|
deferred.resolve();
|
|
};
|
|
|
|
let pending = ["conference.onholding", "conference.onheld"];
|
|
let receive = function(name) {
|
|
receivedPending(name, pending, done);
|
|
};
|
|
|
|
let check_onholding = StateEventChecker('holding', null);
|
|
let check_onheld = StateEventChecker('held', 'onholding');
|
|
|
|
for (let call of calls) {
|
|
let callName = "call (" + call.number + ')';
|
|
|
|
let onholding = callName + ".onholding";
|
|
pending.push(onholding);
|
|
check_onholding(call, callName, receive.bind(null, onholding));
|
|
|
|
let onheld = callName + ".onheld";
|
|
pending.push(onheld);
|
|
check_onheld(call, callName, receive.bind(null, onheld));
|
|
}
|
|
|
|
check_onholding(conference, "conference",
|
|
receive.bind(null, "conference.onholding"));
|
|
|
|
check_onheld(conference, "conference", function() {
|
|
if (typeof heldCallback === 'function') {
|
|
heldCallback();
|
|
}
|
|
receive("conference.onheld");
|
|
});
|
|
|
|
conference.hold();
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
/**
|
|
* Resume the conference.
|
|
*
|
|
* @param calls
|
|
* An array of TelephonyCall objects existing in conference.
|
|
* @param connectedCallback [optional]
|
|
* A callback function which is called when conference state becomes
|
|
* connected.
|
|
* @return A deferred promise.
|
|
*/
|
|
function resumeConference(calls, connectedCallback) {
|
|
log("Resuming the held conference call.");
|
|
|
|
let deferred = Promise.defer();
|
|
let done = function() {
|
|
deferred.resolve();
|
|
};
|
|
|
|
let pending = ["conference.onresuming", "conference.onconnected"];
|
|
let receive = function(name) {
|
|
receivedPending(name, pending, done);
|
|
};
|
|
|
|
let check_onresuming = StateEventChecker('resuming', null);
|
|
let check_onconnected = StateEventChecker('connected', 'onresuming');
|
|
|
|
for (let call of calls) {
|
|
let callName = "call (" + call.number + ')';
|
|
|
|
let onresuming = callName + ".onresuming";
|
|
pending.push(onresuming);
|
|
check_onresuming(call, callName, receive.bind(null, onresuming));
|
|
|
|
let onconnected = callName + ".onconnected";
|
|
pending.push(onconnected);
|
|
check_onconnected(call, callName, receive.bind(null, onconnected));
|
|
}
|
|
|
|
check_onresuming(conference, "conference",
|
|
receive.bind(null, "conference.onresuming"));
|
|
|
|
check_onconnected(conference, "conference", function() {
|
|
if (typeof connectedCallback === 'function') {
|
|
connectedCallback();
|
|
}
|
|
receive("conference.onconnected");
|
|
});
|
|
|
|
conference.resume();
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
/**
|
|
* Remove a call out of conference.
|
|
*
|
|
* @param callToRemove
|
|
* A TelephonyCall object existing in conference.
|
|
* @param autoRemovedCalls
|
|
* An array of TelephonyCall objects which is going to be automatically
|
|
* removed. The length of the array should be 0 or 1.
|
|
* @param remainedCalls
|
|
* An array of TelephonyCall objects which remain in conference.
|
|
* @param stateChangeCallback [optional]
|
|
* A callback function which is called when conference state changes.
|
|
* @return A deferred promise.
|
|
*/
|
|
function removeCallInConference(callToRemove, autoRemovedCalls, remainedCalls,
|
|
statechangeCallback) {
|
|
log("Removing a participant from the conference call.");
|
|
|
|
is(conference.state, 'connected');
|
|
|
|
let deferred = Promise.defer();
|
|
let done = function() {
|
|
deferred.resolve();
|
|
};
|
|
|
|
let pending = ["callToRemove.ongroupchange", "telephony.oncallschanged",
|
|
"conference.oncallschanged", "conference.onstatechange"];
|
|
let receive = function(name) {
|
|
receivedPending(name, pending, done);
|
|
};
|
|
|
|
// Remained call in conference will be held.
|
|
for (let call of remainedCalls) {
|
|
let callName = "remainedCall (" + call.number + ')';
|
|
|
|
let onstatechange = callName + ".onstatechange";
|
|
pending.push(onstatechange);
|
|
check_onstatechange(call, callName, 'held',
|
|
receive.bind(null, onstatechange));
|
|
}
|
|
|
|
// When a call is removed from conference with 2 calls, another one will be
|
|
// automatically removed from group and be put on hold.
|
|
for (let call of autoRemovedCalls) {
|
|
let callName = "autoRemovedCall (" + call.number + ')';
|
|
|
|
let ongroupchange = callName + ".ongroupchange";
|
|
pending.push(ongroupchange);
|
|
check_ongroupchange(call, callName, null,
|
|
receive.bind(null, ongroupchange));
|
|
|
|
let onstatechange = callName + ".onstatechange";
|
|
pending.push(onstatechange);
|
|
check_onstatechange(call, callName, 'held',
|
|
receive.bind(null, onstatechange));
|
|
}
|
|
|
|
check_ongroupchange(callToRemove, "callToRemove", null, function() {
|
|
is(callToRemove.state, 'connected');
|
|
receive("callToRemove.ongroupchange");
|
|
});
|
|
|
|
check_oncallschanged(telephony, 'telephony',
|
|
autoRemovedCalls.concat(callToRemove),
|
|
receive.bind(null, "telephony.oncallschanged"));
|
|
|
|
check_oncallschanged(conference, 'conference',
|
|
autoRemovedCalls.concat(callToRemove), function() {
|
|
is(conference.calls.length, remainedCalls.length);
|
|
receive("conference.oncallschanged");
|
|
});
|
|
|
|
check_onstatechange(conference, 'conference',
|
|
(remainedCalls.length ? 'held' : ''), function() {
|
|
ok(!conference.oncallschanged);
|
|
if (typeof statechangeCallback === 'function') {
|
|
statechangeCallback();
|
|
}
|
|
receive("conference.onstatechange");
|
|
});
|
|
|
|
conference.remove(callToRemove);
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
/**
|
|
* Hangup a call in conference.
|
|
*
|
|
* @param callToHangUp
|
|
* A TelephonyCall object existing in conference.
|
|
* @param autoRemovedCalls
|
|
* An array of TelephonyCall objects which is going to be automatically
|
|
* removed. The length of the array should be 0 or 1.
|
|
* @param remainedCalls
|
|
* An array of TelephonyCall objects which remain in conference.
|
|
* @param stateChangeCallback [optional]
|
|
* A callback function which is called when conference state changes.
|
|
* @return A deferred promise.
|
|
*/
|
|
function hangUpCallInConference(callToHangUp, autoRemovedCalls, remainedCalls,
|
|
statechangeCallback) {
|
|
log("Release one call in conference.");
|
|
|
|
let deferred = Promise.defer();
|
|
let done = function() {
|
|
deferred.resolve();
|
|
};
|
|
|
|
let pending = ["conference.oncallschanged", "remoteHangUp"];
|
|
let receive = function(name) {
|
|
receivedPending(name, pending, done);
|
|
};
|
|
|
|
// When a call is hang up from conference with 2 calls, another one will be
|
|
// automatically removed from group.
|
|
for (let call of autoRemovedCalls) {
|
|
let callName = "autoRemovedCall (" + call.number + ')';
|
|
|
|
let ongroupchange = callName + ".ongroupchange";
|
|
pending.push(ongroupchange);
|
|
check_ongroupchange(call, callName, null,
|
|
receive.bind(null, ongroupchange));
|
|
}
|
|
|
|
if (autoRemovedCalls.length) {
|
|
pending.push("telephony.oncallschanged");
|
|
check_oncallschanged(telephony, 'telephony',
|
|
autoRemovedCalls,
|
|
receive.bind(null, "telephony.oncallschanged"));
|
|
}
|
|
|
|
check_oncallschanged(conference, 'conference',
|
|
autoRemovedCalls.concat(callToHangUp), function() {
|
|
is(conference.calls.length, remainedCalls.length);
|
|
receive("conference.oncallschanged");
|
|
});
|
|
|
|
if (remainedCalls.length === 0) {
|
|
pending.push("conference.onstatechange");
|
|
check_onstatechange(conference, 'conference', '', function() {
|
|
ok(!conference.oncallschanged);
|
|
if (typeof statechangeCallback === 'function') {
|
|
statechangeCallback();
|
|
}
|
|
receive("conference.onstatechange");
|
|
});
|
|
}
|
|
|
|
remoteHangUp(callToHangUp)
|
|
.then(receive.bind(null, "remoteHangUp"));
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
/**
|
|
* Create a conference with an outgoing call and an incoming call.
|
|
*
|
|
* @param outNumber
|
|
* Number of an outgoing call.
|
|
* @param inNumber
|
|
* Number of an incoming call.
|
|
* @return Promise<[outCall, inCall]>
|
|
*/
|
|
function createConferenceWithTwoCalls(outNumber, inNumber) {
|
|
let outCall;
|
|
let inCall;
|
|
let outInfo = outCallStrPool(outNumber);
|
|
let inInfo = inCallStrPool(inNumber);
|
|
|
|
return Promise.resolve()
|
|
.then(checkInitialState)
|
|
.then(() => dial(outNumber))
|
|
.then(call => { outCall = call; })
|
|
.then(() => checkAll(outCall, [outCall], '', [], [outInfo.ringing]))
|
|
.then(() => remoteAnswer(outCall))
|
|
.then(() => checkAll(outCall, [outCall], '', [], [outInfo.active]))
|
|
.then(() => remoteDial(inNumber))
|
|
.then(call => { inCall = call; })
|
|
.then(() => checkAll(outCall, [outCall, inCall], '', [],
|
|
[outInfo.active, inInfo.incoming]))
|
|
.then(() => answer(inCall))
|
|
.then(() => checkAll(inCall, [outCall, inCall], '', [],
|
|
[outInfo.held, inInfo.active]))
|
|
.then(() => addCallsToConference([outCall, inCall], function() {
|
|
checkState(conference, [], 'connected', [outCall, inCall]);
|
|
}))
|
|
.then(() => checkAll(conference, [], 'connected', [outCall, inCall],
|
|
[outInfo.active, inInfo.active]))
|
|
.then(() => {
|
|
return [outCall, inCall];
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Create a new incoming call and add it into the conference.
|
|
*
|
|
* @param inNumber
|
|
* Number of an incoming call.
|
|
* @param conferenceCalls
|
|
* Calls already in conference.
|
|
* @return Promise<[calls in the conference]>
|
|
*/
|
|
function createCallAndAddToConference(inNumber, conferenceCalls) {
|
|
// Create an info array. allInfo = [info1, info2, ...].
|
|
let allInfo = conferenceCalls.map(function(call, i) {
|
|
return (i === 0) ? outCallStrPool(call.number)
|
|
: inCallStrPool(call.number);
|
|
});
|
|
|
|
// Define state property of the info array.
|
|
// Ex: allInfo.active = [info1.active, info2.active, ...].
|
|
function addInfoState(allInfo, state) {
|
|
Object.defineProperty(allInfo, state, {
|
|
get: function() {
|
|
return allInfo.map(function(info) { return info[state]; });
|
|
}
|
|
});
|
|
}
|
|
|
|
for (let state of ['ringing', 'incoming', 'active', 'held']) {
|
|
addInfoState(allInfo, state);
|
|
}
|
|
|
|
let newCall;
|
|
let newInfo = inCallStrPool(inNumber);
|
|
|
|
return remoteDial(inNumber)
|
|
.then(call => { newCall = call; })
|
|
.then(() => checkAll(conference, [newCall], 'connected', conferenceCalls,
|
|
allInfo.active.concat(newInfo.incoming)))
|
|
.then(() => answer(newCall, function() {
|
|
checkState(newCall, [newCall], 'held', conferenceCalls);
|
|
}))
|
|
.then(() => checkAll(newCall, [newCall], 'held', conferenceCalls,
|
|
allInfo.held.concat(newInfo.active)))
|
|
.then(() => {
|
|
// We are going to add the new call into the conference.
|
|
conferenceCalls.push(newCall);
|
|
allInfo.push(newInfo);
|
|
})
|
|
.then(() => addCallsToConference([newCall], function() {
|
|
checkState(conference, [], 'connected', conferenceCalls);
|
|
}))
|
|
.then(() => checkAll(conference, [], 'connected', conferenceCalls,
|
|
allInfo.active))
|
|
.then(() => {
|
|
return conferenceCalls;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Setup a conference with an outgoing call and N incoming calls.
|
|
*
|
|
* @param callNumbers
|
|
* Array of numbers, the first number is for outgoing call and the
|
|
* remaining numbers are for incoming calls.
|
|
* @return Promise<[calls in the conference]>
|
|
*/
|
|
function setupConference(callNumbers) {
|
|
log("Create a conference with " + callNumbers.length + " calls.");
|
|
|
|
let promise = createConferenceWithTwoCalls(callNumbers[0], callNumbers[1]);
|
|
|
|
callNumbers.shift();
|
|
callNumbers.shift();
|
|
for (let number of callNumbers) {
|
|
promise = promise.then(createCallAndAddToConference.bind(null, number));
|
|
}
|
|
|
|
return promise;
|
|
}
|
|
|
|
/**
|
|
* Public members.
|
|
*/
|
|
|
|
this.gDelay = delay;
|
|
this.gCheckInitialState = checkInitialState;
|
|
this.gClearCalls = clearCalls;
|
|
this.gOutCallStrPool = outCallStrPool;
|
|
this.gInCallStrPool = inCallStrPool;
|
|
this.gCheckState = checkState;
|
|
this.gCheckAll = checkAll;
|
|
this.gDial = dial;
|
|
this.gAnswer = answer;
|
|
this.gHold = hold;
|
|
this.gRemoteDial = remoteDial;
|
|
this.gRemoteAnswer = remoteAnswer;
|
|
this.gRemoteHangUp = remoteHangUp;
|
|
this.gRemoteHangUpCalls = remoteHangUpCalls;
|
|
this.gAddCallsToConference = addCallsToConference;
|
|
this.gHoldConference = holdConference;
|
|
this.gResumeConference = resumeConference;
|
|
this.gRemoveCallInConference = removeCallInConference;
|
|
this.gHangUpCallInConference = hangUpCallInConference;
|
|
this.gSetupConference = setupConference;
|
|
this.gReceivedPending = receivedPending;
|
|
}());
|
|
|
|
function _startTest(permissions, test) {
|
|
function permissionSetUp() {
|
|
SpecialPowers.setBoolPref("dom.mozSettings.enabled", true);
|
|
for (let per of permissions) {
|
|
SpecialPowers.addPermission(per, true, document);
|
|
}
|
|
}
|
|
|
|
function permissionTearDown() {
|
|
SpecialPowers.clearUserPref("dom.mozSettings.enabled");
|
|
for (let per of permissions) {
|
|
SpecialPowers.removePermission(per, document);
|
|
}
|
|
}
|
|
|
|
let debugPref;
|
|
|
|
function setUp() {
|
|
log("== Test SetUp ==");
|
|
|
|
// Turn on debugging pref.
|
|
debugPref = SpecialPowers.getBoolPref(kPrefRilDebuggingEnabled);
|
|
SpecialPowers.setBoolPref(kPrefRilDebuggingEnabled, true);
|
|
log("Set debugging pref: " + debugPref + " => true");
|
|
|
|
permissionSetUp();
|
|
|
|
// Make sure that we get the telephony after adding permission.
|
|
telephony = window.navigator.mozTelephony;
|
|
ok(telephony);
|
|
conference = telephony.conferenceGroup;
|
|
ok(conference);
|
|
return gClearCalls().then(gCheckInitialState);
|
|
}
|
|
|
|
// Extend finish() with tear down.
|
|
finish = (function() {
|
|
let originalFinish = finish;
|
|
|
|
function tearDown() {
|
|
log("== Test TearDown ==");
|
|
emulator.waitFinish()
|
|
.then(() => {
|
|
permissionTearDown();
|
|
|
|
// Restore debugging pref.
|
|
SpecialPowers.setBoolPref(kPrefRilDebuggingEnabled, debugPref);
|
|
log("Set debugging pref: true => " + debugPref);
|
|
})
|
|
.then(function() {
|
|
originalFinish.apply(this, arguments);
|
|
});
|
|
}
|
|
|
|
return tearDown.bind(this);
|
|
}());
|
|
|
|
function mainTest() {
|
|
setUp()
|
|
.then(function onSuccess() {
|
|
log("== Test Start ==");
|
|
test();
|
|
}, function onError(error) {
|
|
SpecialPowers.Cu.reportError(error);
|
|
ok(false, "SetUp error");
|
|
});
|
|
}
|
|
|
|
mainTest();
|
|
}
|
|
|
|
function startTest(test) {
|
|
_startTest(["telephony"], test);
|
|
}
|
|
|
|
function startTestWithPermissions(permissions, test) {
|
|
_startTest(permissions.concat("telephony"), test);
|
|
}
|
|
|
|
function startDSDSTest(test) {
|
|
let numRIL;
|
|
try {
|
|
numRIL = SpecialPowers.getIntPref("ril.numRadioInterfaces");
|
|
} catch (ex) {
|
|
numRIL = 1; // Pref not set.
|
|
}
|
|
|
|
if (numRIL > 1) {
|
|
startTest(test);
|
|
} else {
|
|
log("Not a DSDS environment. Test is skipped.");
|
|
ok(true); // We should run at least one test.
|
|
finish();
|
|
}
|
|
}
|