зеркало из https://github.com/mozilla/gecko-dev.git
1678 строки
50 KiB
JavaScript
1678 строки
50 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
// Emulate Promise.jsm semantics.
|
|
Promise.defer = function() { return new Deferred(); };
|
|
function Deferred() {
|
|
this.promise = new Promise(function(resolve, reject) {
|
|
this.resolve = resolve;
|
|
this.reject = reject;
|
|
}.bind(this));
|
|
Object.freeze(this);
|
|
}
|
|
|
|
var telephony;
|
|
var conference;
|
|
|
|
const kPrefRilDebuggingEnabled = "ril.debugging.enabled";
|
|
|
|
/**
|
|
* Emulator helper.
|
|
*/
|
|
var emulator = (function() {
|
|
let pendingCmdCount = 0;
|
|
let originalRunEmulatorCmd = runEmulatorCmd;
|
|
|
|
let pendingShellCount = 0;
|
|
let originalRunEmulatorShell = runEmulatorShell;
|
|
|
|
// Overwritten it so people could not call this function directly.
|
|
runEmulatorCmd = function() {
|
|
throw "Use emulator.runCmdWithCallback(cmd, callback) instead of runEmulatorCmd";
|
|
};
|
|
|
|
// Overwritten it so people could not call this function directly.
|
|
runEmulatorShell = function() {
|
|
throw "Use emulator.runShellCmd(cmd) instead of runEmulatorShell";
|
|
};
|
|
|
|
/**
|
|
* @return Promise
|
|
*/
|
|
function runCmd(cmd) {
|
|
return new Promise(function(resolve, reject) {
|
|
pendingCmdCount++;
|
|
originalRunEmulatorCmd(cmd, function(result) {
|
|
pendingCmdCount--;
|
|
if (result[result.length - 1] === "OK") {
|
|
resolve(result);
|
|
} else {
|
|
is(result[result.length - 1], "OK", "emulator command result.");
|
|
reject();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @return Promise
|
|
*/
|
|
function runCmdWithCallback(cmd, callback) {
|
|
return runCmd(cmd).then(result => {
|
|
if (callback && typeof callback === "function") {
|
|
callback(result);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @return Promise
|
|
*/
|
|
function runShellCmd(aCommands) {
|
|
return new Promise(function(resolve, reject) {
|
|
++pendingShellCount;
|
|
originalRunEmulatorShell(aCommands, function(aResult) {
|
|
--pendingShellCount;
|
|
resolve(aResult);
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @return Promise
|
|
*/
|
|
function waitFinish() {
|
|
return new Promise(function(resolve, reject) {
|
|
waitFor(function() {
|
|
resolve();
|
|
}, function() {
|
|
return pendingCmdCount === 0 && pendingShellCount === 0;
|
|
});
|
|
});
|
|
}
|
|
|
|
return {
|
|
runCmd: runCmd,
|
|
runCmdWithCallback: runCmdWithCallback,
|
|
runShellCmd: runShellCmd,
|
|
waitFinish: waitFinish
|
|
};
|
|
}());
|
|
|
|
/**
|
|
* Modem Helper
|
|
*
|
|
* TODO: Should select which modem here to support multi-SIM
|
|
*/
|
|
|
|
function modemHelperGenerator() {
|
|
function Modem(aClientID) {
|
|
this.clientID = aClientID;
|
|
}
|
|
Modem.prototype = {
|
|
clientID: 0,
|
|
|
|
voiceTypeToTech: function(aVoiceType) {
|
|
switch(aVoiceType) {
|
|
case "gsm":
|
|
case "gprs":
|
|
case "edge":
|
|
return "gsm";
|
|
|
|
case "umts":
|
|
case "hsdpa":
|
|
case "hsupa":
|
|
case "hspa":
|
|
case "hspa+":
|
|
return "wcdma";
|
|
|
|
case "is95a":
|
|
case "is95b":
|
|
case "1xrtt":
|
|
return "cdma";
|
|
|
|
case "evdo0":
|
|
case "evdoa":
|
|
case "evdob":
|
|
return "evdo";
|
|
|
|
case "ehrpd":
|
|
return "ehrpd";
|
|
|
|
case "lte":
|
|
return "lte";
|
|
|
|
default:
|
|
return null;
|
|
}
|
|
},
|
|
|
|
isCDMA: function() {
|
|
var mobileConn = navigator.mozMobileConnections[this.clientID];
|
|
var tech = mobileConn && this.voiceTypeToTech(mobileConn.voice.type);
|
|
return tech === "cdma" || tech === "evdo" || tech == "ehrpd";
|
|
},
|
|
|
|
isGSM: function() {
|
|
var mobileConn = navigator.mozMobileConnections[this.clientID];
|
|
var tech = mobileConn && this.voiceTypeToTech(mobileConn.voice.type);
|
|
return tech === "gsm" || tech === "wcdma" || tech === "lte";
|
|
},
|
|
|
|
/**
|
|
* @return Promise:
|
|
*/
|
|
changeTech: function(aTech, aMask) {
|
|
let target = navigator.mozMobileConnections[this.clientID];
|
|
|
|
let mask = aMask || {
|
|
gsm: "gsm",
|
|
wcdma: "gsm/wcdma",
|
|
cdma: "cdma",
|
|
evdo: "evdo0",
|
|
ehrpd: "ehrpd",
|
|
lte: "lte"
|
|
}[aTech];
|
|
|
|
let waitForExpectedTech = () => {
|
|
return new Promise((aResolve, aReject) => {
|
|
let listener = aEvent => {
|
|
log("MobileConnection[" + this.clientID + "] " +
|
|
"received event 'voicechange'");
|
|
if (aTech === this.voiceTypeToTech(target.voice.type)) {
|
|
target.removeEventListener("voicechange", listener);
|
|
aResolve();
|
|
}
|
|
};
|
|
|
|
target.addEventListener("voicechange", listener);
|
|
});
|
|
};
|
|
|
|
// TODO: Should select a modem here to support multi-SIM
|
|
let changeToExpectedTech = () => {
|
|
return Promise.resolve()
|
|
.then(() => emulator.runCmd("modem tech " + aTech + " " + mask))
|
|
.then(() => emulator.runCmd("modem tech"))
|
|
.then(result => is(result[0], aTech + " " + mask,
|
|
"Check modem 'tech/preferred mask'"));
|
|
}
|
|
|
|
return aTech === this.voiceTypeToTech(target.voice.type)
|
|
? Promise.resolve()
|
|
: Promise.all([waitForExpectedTech(), changeToExpectedTech()]);
|
|
}
|
|
};
|
|
|
|
let modems = [];
|
|
for (let i = 0; i < navigator.mozMobileConnections.length; ++i) {
|
|
modems.push(new Modem(i));
|
|
}
|
|
return modems;
|
|
}
|
|
|
|
var Modems = modemHelperGenerator();
|
|
var Modem = Modems[0];
|
|
|
|
/**
|
|
* Telephony related helper functions.
|
|
*/
|
|
(function() {
|
|
/**
|
|
* @return Promise
|
|
*/
|
|
function delay(ms) {
|
|
return new Promise(function(resolve, reject) {
|
|
let startTime = Date.now();
|
|
waitFor(function() {
|
|
resolve();
|
|
},function() {
|
|
let duration = Date.now() - startTime;
|
|
return (duration >= ms);
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Wait for one named system message.
|
|
*
|
|
* Resolve if that named message is received. Never reject.
|
|
*
|
|
* Fulfill params: the message passed.
|
|
*
|
|
* @param aEventName
|
|
* A string message name.
|
|
* @param aMatchFun [optional]
|
|
* A matching function returns true or false to filter the message. If no
|
|
* matching function passed the promise is resolved after receiving the
|
|
* first message.
|
|
*
|
|
* @return Promise<Message>
|
|
*/
|
|
function waitForSystemMessage(aMessageName, aMatchFun = null) {
|
|
// Current page may not register to receiving the message. We should
|
|
// register it first.
|
|
let systemMessenger = SpecialPowers.Cc["@mozilla.org/system-message-internal;1"]
|
|
.getService(SpecialPowers.Ci.nsISystemMessagesInternal);
|
|
|
|
// TODO: Find a better way to get current pageURI and manifestURI.
|
|
systemMessenger.registerPage(aMessageName,
|
|
SpecialPowers.Services.io.newURI("app://system.gaiamobile.org/index.html", null, null),
|
|
SpecialPowers.Services.io.newURI("app://system.gaiamobile.org/manifest.webapp", null, null));
|
|
|
|
return new Promise(function(aResolve, aReject) {
|
|
window.navigator.mozSetMessageHandler(aMessageName, function(aMessage) {
|
|
if (!aMatchFun || aMatchFun(aMessage)) {
|
|
log("System message '" + aMessageName + "' got.");
|
|
window.navigator.mozSetMessageHandler(aMessageName, null);
|
|
aResolve(aMessage);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Wait for one named event.
|
|
*
|
|
* @param aTarget
|
|
* A event target.
|
|
* @param aEventName
|
|
* A string event name.
|
|
* @param aPredicate [optional]
|
|
* A predicate function, resolve the promise if aPredicate(event)
|
|
* return true
|
|
* @return Promise<DOMEvent>
|
|
*/
|
|
function waitForEvent(aTarget, aEventName, aPredicate) {
|
|
return new Promise(function(resolve, reject) {
|
|
aTarget.addEventListener(aEventName, function onevent(aEvent) {
|
|
if (aPredicate === undefined || aPredicate(aEvent)) {
|
|
aTarget.removeEventListener(aEventName, onevent);
|
|
|
|
let label = "X";
|
|
if (aTarget instanceof TelephonyCall) {
|
|
label = "Call (" + aTarget.id.number + ")";
|
|
} else if (aTarget instanceof TelephonyCallGroup) {
|
|
label = "Conference";
|
|
} else if (aTarget instanceof Telephony) {
|
|
label = "Telephony";
|
|
}
|
|
|
|
log(label + " received event '" + aEventName + "'");
|
|
resolve(aEvent);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Wait for callschanged event with event.call == aExpectedCall
|
|
*
|
|
* @param aTarget
|
|
* A event target.
|
|
* @param aExpectedCall [optional]
|
|
* Expected call for event.call
|
|
* @return Promise<TelephonyCall>
|
|
*/
|
|
function waitForCallsChangedEvent(aTarget, aExpectedCall) {
|
|
if (aExpectedCall === undefined) {
|
|
return waitForEvent(aTarget, "callschanged").then(event => event.call);
|
|
} else {
|
|
return waitForEvent(aTarget, "callschanged",
|
|
event => event.call == aExpectedCall)
|
|
.then(event => event.call);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wait for call state event, e.g., "connected", "disconnected", ...
|
|
*
|
|
* @param aTarget
|
|
* A event target.
|
|
* @param aState
|
|
* State name
|
|
* @return Promise<TelephonyCall>
|
|
*/
|
|
function waitForNamedStateEvent(aTarget, aState) {
|
|
return waitForEvent(aTarget, aState)
|
|
.then(event => {
|
|
if (aTarget instanceof TelephonyCall) {
|
|
is(aTarget, event.call, "event.call");
|
|
}
|
|
is(aTarget.state, aState, "check state");
|
|
return aTarget;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Wait for groupchange event.
|
|
*
|
|
* @param aCall
|
|
* A TelephonyCall object.
|
|
* @param aGroup
|
|
* The new group
|
|
* @return Promise<TelephonyCall>
|
|
*/
|
|
function waitForGroupChangeEvent(aCall, aGroup) {
|
|
return waitForEvent(aCall, "groupchange")
|
|
.then(() => {
|
|
is(aCall.group, aGroup, "call group");
|
|
return aCall;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Wait for statechange event.
|
|
*
|
|
* @param aTarget
|
|
* A event target.
|
|
* @param aState
|
|
* The desired new state. Check it.
|
|
* @return Promise<DOMEvent>
|
|
*/
|
|
function waitForStateChangeEvent(aTarget, aState) {
|
|
return waitForEvent(aTarget, "statechange")
|
|
.then(() => {
|
|
is(aTarget.state, aState);
|
|
return aTarget;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @return Promise
|
|
*/
|
|
function waitForNoCall() {
|
|
return new Promise(function(resolve, reject) {
|
|
waitFor(function() {
|
|
resolve();
|
|
}, function() {
|
|
return telephony.calls.length === 0;
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @return Promise
|
|
*/
|
|
function clearCalls() {
|
|
log("Clear existing calls.");
|
|
|
|
// Hang up all calls.
|
|
let hangUpPromises = [];
|
|
|
|
for (let call of telephony.calls) {
|
|
log(".. hangUp " + call.id.number);
|
|
hangUpPromises.push(hangUp(call));
|
|
}
|
|
|
|
for (let call of conference.calls) {
|
|
log(".. hangUp " + call.id.number);
|
|
hangUpPromises.push(hangUp(call));
|
|
}
|
|
|
|
return Promise.all(hangUpPromises)
|
|
.then(() => {
|
|
return emulator.runCmd("telephony clear").then(waitForNoCall);
|
|
})
|
|
.then(waitForNoCall);
|
|
}
|
|
|
|
/**
|
|
* 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", "waiting", "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');
|
|
ok(conference.calls, 'conference.calls');
|
|
checkState(null, [], "", []);
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
**** Check Functions ****
|
|
****************************************************************************/
|
|
|
|
/**
|
|
* Convenient helper to compare two call lists (order is not important).
|
|
*/
|
|
function checkCalls(actualCalls, expectedCalls) {
|
|
if (actualCalls.length != expectedCalls.length) {
|
|
ok(false, "check calls.length");
|
|
return;
|
|
}
|
|
|
|
let expectedSet = new Set(expectedCalls);
|
|
for (let i = 0; i < actualCalls.length; ++i) {
|
|
ok(expectedSet.has(actualCalls[i]), "should contain the call");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convenient helper to check the expected call number and name.
|
|
*
|
|
* @param number
|
|
* A string sent to modem.
|
|
* @param numberPresentation
|
|
* An unsigned short integer sent to modem.
|
|
* @param name
|
|
* A string sent to modem.
|
|
* @param namePresentation
|
|
* An unsigned short integer sent to modem.
|
|
* @param receivedNumber
|
|
* A string exposed by Telephony API.
|
|
* @param receivedName
|
|
* A string exposed by Telephony API.
|
|
*/
|
|
function checkCallId(number, numberPresentation, name, namePresentation,
|
|
receivedNumber, receivedName) {
|
|
let expectedNum = !numberPresentation ? number : "";
|
|
is(receivedNumber, expectedNum, "check number per numberPresentation");
|
|
|
|
let expectedName;
|
|
if (numberPresentation) {
|
|
expectedName = "";
|
|
} else if (!namePresentation) {
|
|
expectedName = name ? name : "";
|
|
} else {
|
|
expectedName = "";
|
|
}
|
|
is(receivedName, expectedName, "check name per number/namePresentation");
|
|
}
|
|
|
|
/**
|
|
* 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 Promise
|
|
*/
|
|
function checkEmulatorCallList(expectedCallList) {
|
|
return emulator.runCmd("telephony list").then(result => {
|
|
log("Call list is now: " + result);
|
|
// The last element of the result is "OK"
|
|
is(result.length - 1,
|
|
expectedCallList.length,
|
|
"Emulator call list length");
|
|
|
|
for (let i = 0; i < expectedCallList.length; ++i) {
|
|
is(result[i], expectedCallList[i], "emulator calllist");
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
is(telephony.active, active, "telephony.active");
|
|
checkCalls(telephony.calls, calls);
|
|
is(conference.state, conferenceState, "conference.state");
|
|
checkCalls(conference.calls, 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 Promise
|
|
*/
|
|
function checkAll(active, calls, conferenceState, conferenceCalls, callList) {
|
|
checkState(active, calls, conferenceState, conferenceCalls);
|
|
return checkEmulatorCallList(callList);
|
|
}
|
|
|
|
/**
|
|
* The factory function for creating an expected call.
|
|
*
|
|
* @param aReference
|
|
* The reference of the telephonyCall object.
|
|
* @param aNumber
|
|
* The call number.
|
|
* @param aConference
|
|
* Shows whether the call belongs to the conference.
|
|
* @param aDirection
|
|
* The direction of the call, "in" for inbound, and "out" for outbound.
|
|
* @param aState
|
|
* The expected state of the call.
|
|
* @param aEmulatorState
|
|
* The state logged in emulator now, may be different from aState.
|
|
* @param aDisconnectedReason
|
|
* The disconnected reason if the call becomed disconnected.
|
|
*/
|
|
function createExptectedCall(aReference, aNumber, aConference, aDirection,
|
|
aState, aEmulatorState, aDisconnectedReason) {
|
|
return {
|
|
reference: aReference,
|
|
number: aNumber,
|
|
conference: aConference,
|
|
direction: aDirection,
|
|
state: aState,
|
|
emulatorState: aEmulatorState,
|
|
disconnectedReason: aDisconnectedReason
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Check telephony.active
|
|
*
|
|
* @param aExpectedCalls
|
|
* An array of expected calls.
|
|
* @param aExpectedCallsInConference
|
|
* An array of expected calls in the conference.
|
|
*/
|
|
function checkActive(aExpectedCalls, aExpectedCallsInConference) {
|
|
// Get the active call
|
|
let activeCalls = aExpectedCalls && aExpectedCalls.map(aExpectedCall => {
|
|
let isActive = aExpectedCall.state === "connected" ||
|
|
aExpectedCall.state === "alerting" ||
|
|
aExpectedCall.state === "dialing";
|
|
return isActive ? aExpectedCall.reference : null;
|
|
});
|
|
|
|
// Since a CDMA active call and a CDMA waiting call refer to the same
|
|
// telephony call object, we remove duplicated elememts in activeCalls
|
|
activeCalls = new Set(activeCalls);
|
|
activeCalls.delete(null);
|
|
activeCalls = [...activeCalls];
|
|
|
|
ok(activeCalls.length < 2, "Too many active calls in telephony.calls");
|
|
let activeCall = activeCalls.length ? activeCalls[0] : null;
|
|
|
|
// Get the active conference
|
|
let callsInConference = aExpectedCallsInConference || [];
|
|
let activeConference = callsInConference.length &&
|
|
callsInConference[0].state === "connected"
|
|
? navigator.mozTelephony.conferenceGroup
|
|
: null;
|
|
|
|
// Check telephony.active
|
|
ok(!(activeCall && activeConference),
|
|
"An active call cannot coexist with an active conference call.");
|
|
is(telephony.active, activeCall || activeConference, "check Active");
|
|
}
|
|
|
|
/**
|
|
* Check whether the data in telephony and emulator meets our expectation.
|
|
*
|
|
* NOTE: Conference call is not supported in this function yet, so related
|
|
* checks are skipped.
|
|
*
|
|
* Fulfill params:
|
|
* {
|
|
* reference, -- the reference of the call object instance.
|
|
* number, -- the call number.
|
|
* conference, -- shows whether it belongs to the conference.
|
|
* direction, -- "in" for inbound, and "out" for outbound.
|
|
* state, -- the call state.
|
|
* emulatorState, -- the call state logged in emulator now.
|
|
* disconnectedReason, -- the disconnected reason of a disconnected call.
|
|
* }
|
|
*
|
|
* @param aExpectedCalls
|
|
* An array of call records.
|
|
* @return Promise
|
|
*/
|
|
function equals(aExpectedCalls) {
|
|
// Classify calls
|
|
let callsInTelephony = [];
|
|
let CallsInConference = [];
|
|
|
|
aExpectedCalls.forEach(function(aCall) {
|
|
if (aCall.state === "disconnected") {
|
|
is(aCall.disconnectedReason,
|
|
aCall.reference.disconnectedReason,
|
|
"Check disconnectedReason");
|
|
return;
|
|
}
|
|
|
|
if (aCall.conference) {
|
|
CallsInConference.push(aCall);
|
|
return;
|
|
}
|
|
|
|
callsInTelephony.push(aCall);
|
|
});
|
|
|
|
// Check the active connection
|
|
checkActive(callsInTelephony, CallsInConference);
|
|
|
|
// Check telephony.calls
|
|
let references = callsInTelephony.map(aCall => aCall.reference);
|
|
references = new Set(references);
|
|
is(telephony.calls.length, references.size, "Check telephony.calls.length");
|
|
|
|
callsInTelephony.forEach(aExpectedCall => {
|
|
let number = aExpectedCall.number;
|
|
let call = telephony.calls.find(aCall => {
|
|
return (aCall.id.number === number) ||
|
|
(Modem.isCDMA() ? (aCall.secondId && aCall.secondId.number === number) : false);
|
|
});
|
|
if (!call) {
|
|
ok(false, "telephony.calls lost the call(number: " + number + ")");
|
|
return;
|
|
}
|
|
|
|
is(call, aExpectedCall.reference,
|
|
"Check the object reference of number:" + number);
|
|
|
|
is(call.state, aExpectedCall.state,
|
|
"Check call.state of number:" + number);
|
|
});
|
|
|
|
// Check conference.calls
|
|
// NOTE: This function doesn't support conference call now, so the length of
|
|
// |CallsInConference| should be 0, and the conference state shoul be "".
|
|
is(conference.state, "", "Conference call is not supported yet.");
|
|
is(CallsInConference.length, 0, "Conference call is not supported yet.");
|
|
|
|
// Check the emulator call list
|
|
// NOTE: Conference is not supported yet, so |CallsInConference| is ignored.
|
|
let strings = callsInTelephony.map(aCall => {
|
|
// The emulator doesn't have records for disconnected calls.
|
|
if (aCall.emulatorState === "disconnected") {
|
|
return null;
|
|
}
|
|
|
|
let state = {
|
|
alerting: "ringing",
|
|
connected: "active",
|
|
held: "held",
|
|
incoming: "incoming"
|
|
}[aCall.state];
|
|
|
|
state = aCall.emulatorState || state;
|
|
let prefix = (aCall.direction === "in") ? "inbound from "
|
|
: "outbound to ";
|
|
|
|
return state ? (prefix + aCall.number + " : " + state) : null;
|
|
});
|
|
|
|
return checkEmulatorCallList(strings.filter(aString => aString));
|
|
}
|
|
|
|
/****************************************************************************
|
|
**** Request utility functions ****
|
|
****************************************************************************/
|
|
|
|
/**
|
|
* Make an outgoing call.
|
|
*
|
|
* @param number
|
|
* A string.
|
|
* @param serviceId [optional]
|
|
* Identification of a service. 0 is set as default.
|
|
* @return Promise<TelephonyCall>
|
|
*/
|
|
function dial(number, serviceId) {
|
|
serviceId = typeof serviceId !== "undefined" ? serviceId : 0;
|
|
log("Make an outgoing call: " + number + ", serviceId: " + serviceId);
|
|
|
|
let outCall;
|
|
|
|
return telephony.dial(number, serviceId)
|
|
.then(call => outCall = call)
|
|
.then(() => {
|
|
ok(outCall instanceof TelephonyCall, "check instance");
|
|
is(outCall.id.number, number);
|
|
is(outCall.serviceId, serviceId);
|
|
|
|
// A CDMA call goes to connected state directly when the operator find
|
|
// its callee, which makes the "connected" state in CDMA calls behaves
|
|
// like the "alerting" state in GSM calls.
|
|
let state = Modems[serviceId].isGSM() ? "alerting" : "connected";
|
|
|
|
// Sometimes the dialing state is missing, see Bug 1220548.
|
|
if (outCall.state === state) {
|
|
log("got " + state + " state, dialing is missing");
|
|
return;
|
|
}
|
|
|
|
is(outCall.state, "dialing", "check state");
|
|
|
|
return waitForNamedStateEvent(outCall, state);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Make an outgoing emergency call.
|
|
*
|
|
* @param number
|
|
* A string.
|
|
* @return Promise<TelephonyCall>
|
|
*/
|
|
function dialEmergency(number) {
|
|
log("Make an outgoing emergency call: " + number);
|
|
|
|
let outCall;
|
|
|
|
return telephony.dialEmergency(number)
|
|
.then(call => outCall = call)
|
|
.then(() => {
|
|
ok(outCall instanceof TelephonyCall, "check instance");
|
|
ok(outCall);
|
|
is(outCall.id.number, number);
|
|
|
|
// Similar to function |dial|, a CDMA call directly goes to connected
|
|
// state when the operator find its callee.
|
|
let state = Modems[outCall.serviceId].isGSM() ? "alerting"
|
|
: "connected";
|
|
|
|
// Sometimes the dialing state is missing, see Bug 1220548.
|
|
if (outCall.state === state) {
|
|
log("got " + state + " state, dialing is missing");
|
|
return;
|
|
}
|
|
|
|
is(outCall.state, "dialing", "check state");
|
|
|
|
return waitForNamedStateEvent(outCall, state);
|
|
})
|
|
.then(() => {
|
|
is(outCall.emergency, true, "check emergency");
|
|
return outCall;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Simulate a call dialed out by STK directly.
|
|
*
|
|
* @param number
|
|
* A string.
|
|
* @return Promise<TelephonyCall>
|
|
*/
|
|
function dialSTK(number) {
|
|
log("STK makes an outgoing call: " + number);
|
|
|
|
let p1 = waitForCallsChangedEvent(telephony);
|
|
let p2 = emulator.runCmd("stk setupcall " + number);
|
|
|
|
return Promise.all([p1, p2])
|
|
.then(result => {
|
|
let call = result[0];
|
|
|
|
ok(call instanceof TelephonyCall, "check instance");
|
|
is(call.id.number, number, "check number");
|
|
|
|
// Sometimes the dialing state is missing, see Bug 1220548.
|
|
if (call.state === "alerting") {
|
|
log("got alerting state, dialing is missing");
|
|
return;
|
|
}
|
|
|
|
is(call.state, "dialing", "check call state");
|
|
|
|
return waitForNamedStateEvent(call, "alerting");
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 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 Promise<TelephonyCall>
|
|
*/
|
|
function answer(call, conferenceStateChangeCallback) {
|
|
log("Answering the incoming call.");
|
|
|
|
let promises = [];
|
|
|
|
// incoming call triggers conference state change. We should wait for
|
|
// |conference.onstatechange| before checking the state of the conference
|
|
// call.
|
|
if (conference.state === "connected") {
|
|
let promise = waitForStateChangeEvent(conference, "held")
|
|
.then(() => {
|
|
if (typeof conferenceStateChangeCallback === "function") {
|
|
conferenceStateChangeCallback();
|
|
}
|
|
});
|
|
|
|
promises.push(promise);
|
|
}
|
|
|
|
promises.push(waitForNamedStateEvent(call, "connected"));
|
|
promises.push(call.answer());
|
|
|
|
return Promise.all(promises).then(() => call);
|
|
}
|
|
|
|
/**
|
|
* Hold a call.
|
|
*
|
|
* @param aCall
|
|
* A TelephonyCall object.
|
|
* @param aWaitForEvent
|
|
* Decide whether to wait for the state event.
|
|
* @return Promise<TelephonyCall>
|
|
*/
|
|
function hold(aCall, aWaitForEvent = true) {
|
|
log("Putting the call on hold.");
|
|
|
|
let promises = [];
|
|
|
|
if (aWaitForEvent) {
|
|
promises.push(waitForNamedStateEvent(aCall, "held"));
|
|
}
|
|
promises.push(aCall.hold());
|
|
|
|
return Promise.all(promises).then(() => aCall);
|
|
}
|
|
|
|
/**
|
|
* Resume a call.
|
|
*
|
|
* @param call
|
|
* A TelephonyCall object.
|
|
* @return Promise<TelephonyCall>
|
|
*/
|
|
function resume(call, aWaitForEvent = true) {
|
|
log("Resuming the held call.");
|
|
|
|
let promises = [];
|
|
|
|
promises.push(waitForNamedStateEvent(call, "connected"));
|
|
promises.push(call.resume());
|
|
|
|
return Promise.all(promises).then(() => call);
|
|
}
|
|
|
|
/**
|
|
* Locally hang up a call.
|
|
*
|
|
* @param call
|
|
* A TelephonyCall object.
|
|
* @return Promise<TelephonyCall>
|
|
*/
|
|
function hangUp(call) {
|
|
log("Local hanging up the call: " + call.id.number);
|
|
|
|
let promises = [];
|
|
|
|
promises.push(waitForNamedStateEvent(call, "disconnected"));
|
|
promises.push(call.hangUp());
|
|
|
|
return Promise.all(promises).then(() => call);
|
|
}
|
|
|
|
/**
|
|
* Simulate an incoming call.
|
|
*
|
|
* @param number
|
|
* A string.
|
|
* @param numberPresentation [optional]
|
|
* An unsigned short integer.
|
|
* @param name [optional]
|
|
* A string.
|
|
* @param namePresentation [optional]
|
|
* An unsigned short integer.
|
|
* @return Promise<TelephonyCall>
|
|
*/
|
|
function remoteDial(number, numberPresentation, name, namePresentation) {
|
|
log("Simulating an incoming call.");
|
|
|
|
// Register listeners
|
|
let promises = [waitForEvent(telephony, "callschanged")];
|
|
if (Modem.isGSM() ||
|
|
Modem.isCDMA() && telephony.calls.length == 0) {
|
|
promises.push(waitForEvent(telephony, "incoming"));
|
|
}
|
|
|
|
// Make an incoming call
|
|
numberPresentation = numberPresentation || "";
|
|
name = name || "";
|
|
namePresentation = namePresentation || "";
|
|
promises.push(emulator.runCmd("telephony call " + number +
|
|
"," + numberPresentation +
|
|
"," + name +
|
|
"," + namePresentation));
|
|
|
|
// Return the promise and check
|
|
return Promise.all(promises)
|
|
.then(aResult => {
|
|
let call = aResult[0].call;
|
|
ok(call);
|
|
|
|
let isCdmaSecondCall = Modem.isCDMA() &&
|
|
navigator.mozTelephony.calls[0].secondId;
|
|
|
|
is(call.state, isCdmaSecondCall ? "connected" : "incoming");
|
|
checkCallId(number, numberPresentation,
|
|
name, namePresentation,
|
|
isCdmaSecondCall ? call.secondId.number : call.id.number,
|
|
isCdmaSecondCall ? call.secondId.name : call.id.name);
|
|
|
|
return call;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Remote party answers the call.
|
|
*
|
|
* @param call
|
|
* A TelephonyCall object.
|
|
* @return Promise<TelephonyCall>
|
|
*/
|
|
function remoteAnswer(call) {
|
|
log("Remote answering the call: " + call.id.number);
|
|
|
|
emulator.runCmd("telephony accept " + call.id.number);
|
|
|
|
// A CDMA call goes to connected state directly when the operator find its
|
|
// callee, which makes the "connected" state in CDMA calls behaves like the
|
|
// "alerting" state in GSM calls, so we don't have to wait for the call to
|
|
// change to "connected" state here for CDMA calls.
|
|
return Modem.isCDMA() ? Promise.resolve()
|
|
: waitForNamedStateEvent(call, "connected");
|
|
}
|
|
|
|
/**
|
|
* Remote party hangs up the call.
|
|
*
|
|
* @param aIdentifier
|
|
* <A TelephonyCall object or a number string>
|
|
* @return Promise<TelephonyCall>
|
|
*/
|
|
function remoteHangUp(aIdentifier, aWaitForEvent = true) {
|
|
let call;
|
|
let number;
|
|
if (typeof aIdentifier === "string") { // A number is passed in.
|
|
number = aIdentifier;
|
|
call = navigator.mozTelephony.calls.find(aCall => {
|
|
return aCall.id.number === number ||
|
|
aCall.secondId && aCall.secondId.number === number;
|
|
});
|
|
} else { // A Telephony call object is passed in.
|
|
call = aIdentifier;
|
|
number = aIdentifier.id.number;
|
|
}
|
|
|
|
log("Remote hanging up the call: " + number);
|
|
|
|
let promises = [];
|
|
if (aWaitForEvent) {
|
|
promises.push(waitForNamedStateEvent(call, "disconnected"));
|
|
}
|
|
|
|
promises.push(emulator.runCmd("telephony cancel " + number));
|
|
return Promise.all(promises);
|
|
}
|
|
|
|
/**
|
|
* Remote party hangs up all the calls.
|
|
*
|
|
* @param calls
|
|
* An array of TelephonyCall objects.
|
|
* @return Promise
|
|
*/
|
|
function remoteHangUpCalls(calls) {
|
|
let promises = calls.map(remoteHangUp);
|
|
return Promise.all(promises);
|
|
}
|
|
|
|
/**
|
|
* 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 Promise<[TelephonyCall ...]>
|
|
*/
|
|
function addCallsToConference(callsToAdd, connectedCallback) {
|
|
log("Add " + callsToAdd.length + " calls into conference.");
|
|
|
|
let promises = [];
|
|
|
|
for (let call of callsToAdd) {
|
|
promises.push(waitForCallsChangedEvent(conference, call));
|
|
promises.push(waitForGroupChangeEvent(call, conference));
|
|
promises.push(waitForNamedStateEvent(call, "connected"));
|
|
promises.push(waitForStateChangeEvent(call, "connected"));
|
|
}
|
|
|
|
let promise = waitForNamedStateEvent(conference, "connected")
|
|
.then(() => {
|
|
if (typeof connectedCallback === "function") {
|
|
connectedCallback();
|
|
}
|
|
});
|
|
promises.push(promise);
|
|
|
|
// Cannot use apply() through webidl, so just separate the cases to handle.
|
|
if (callsToAdd.length == 2) {
|
|
promise = conference.add(callsToAdd[0], callsToAdd[1]);
|
|
promises.push(promise);
|
|
} else {
|
|
promise = conference.add(callsToAdd[0]);
|
|
promises.push(promise);
|
|
}
|
|
|
|
return Promise.all(promises).then(() => conference.calls);
|
|
}
|
|
|
|
/**
|
|
* Hold the conference.
|
|
*
|
|
* @param callsInConference
|
|
* An array of TelephonyCall objects existing in conference.
|
|
* @param heldCallback [optional]
|
|
* A callback function which is called when conference state becomes
|
|
* held.
|
|
* @return Promise<[TelephonyCall ...]>
|
|
*/
|
|
function holdConference(callsInConference, heldCallback) {
|
|
log("Holding the conference call.");
|
|
|
|
let promises = [];
|
|
|
|
for (let call of callsInConference) {
|
|
promises.push(waitForNamedStateEvent(call, "held"));
|
|
}
|
|
|
|
let promise = waitForNamedStateEvent(conference, "held")
|
|
.then(() => {
|
|
if (typeof heldCallback === "function") {
|
|
heldCallback();
|
|
}
|
|
});
|
|
promises.push(promise);
|
|
|
|
promises.push(conference.hold());
|
|
|
|
return Promise.all(promises).then(() => conference.calls);
|
|
}
|
|
|
|
/**
|
|
* Resume the conference.
|
|
*
|
|
* @param callsInConference
|
|
* An array of TelephonyCall objects existing in conference.
|
|
* @param connectedCallback [optional]
|
|
* A callback function which is called when conference state becomes
|
|
* connected.
|
|
* @return Promise<[TelephonyCall ...]>
|
|
*/
|
|
function resumeConference(callsInConference, connectedCallback) {
|
|
log("Resuming the held conference call.");
|
|
|
|
let promises = [];
|
|
|
|
for (let call of callsInConference) {
|
|
promises.push(waitForNamedStateEvent(call, "connected"));
|
|
}
|
|
|
|
let promise = waitForNamedStateEvent(conference, "connected")
|
|
.then(() => {
|
|
if (typeof connectedCallback === "function") {
|
|
connectedCallback();
|
|
}
|
|
});
|
|
promises.push(promise);
|
|
|
|
promises.push(conference.resume());
|
|
|
|
return Promise.all(promises).then(() => conference.calls);
|
|
}
|
|
|
|
/**
|
|
* 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 Promise<[TelephonyCall ...]>
|
|
*/
|
|
function removeCallInConference(callToRemove, autoRemovedCalls, remainedCalls,
|
|
statechangeCallback) {
|
|
log("Removing a participant from the conference call.");
|
|
|
|
is(conference.state, 'connected');
|
|
|
|
let promises = [];
|
|
|
|
// callToRemove.
|
|
promises.push(waitForCallsChangedEvent(telephony, callToRemove));
|
|
promises.push(waitForCallsChangedEvent(conference, callToRemove));
|
|
promises.push(waitForGroupChangeEvent(callToRemove, null).then(() => {
|
|
is(callToRemove.state, 'connected');
|
|
}));
|
|
|
|
// 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) {
|
|
promises.push(waitForCallsChangedEvent(telephony, call));
|
|
promises.push(waitForCallsChangedEvent(conference, call));
|
|
promises.push(waitForGroupChangeEvent(call, null));
|
|
promises.push(waitForStateChangeEvent(call, "held"));
|
|
}
|
|
|
|
// Remained call in conference will be held.
|
|
for (let call of remainedCalls) {
|
|
promises.push(waitForStateChangeEvent(call, "held"));
|
|
}
|
|
|
|
let finalConferenceState = remainedCalls.length ? "held" : "";
|
|
let promise = waitForStateChangeEvent(conference, finalConferenceState)
|
|
.then(() => {
|
|
if (typeof statechangeCallback === 'function') {
|
|
statechangeCallback();
|
|
}
|
|
});
|
|
promises.push(promise);
|
|
|
|
promises.push(conference.remove(callToRemove));
|
|
|
|
return Promise.all(promises)
|
|
.then(() => checkCalls(conference.calls, remainedCalls))
|
|
.then(() => conference.calls);
|
|
}
|
|
|
|
/**
|
|
* 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 Promise<[TelephonyCall ...]>
|
|
*/
|
|
function hangUpCallInConference(callToHangUp, autoRemovedCalls, remainedCalls,
|
|
statechangeCallback) {
|
|
log("Release one call in conference.");
|
|
|
|
let promises = [];
|
|
|
|
// callToHangUp.
|
|
promises.push(waitForCallsChangedEvent(conference, callToHangUp));
|
|
|
|
// When a call is removed from conference with 2 calls, another one will be
|
|
// automatically removed from group.
|
|
for (let call of autoRemovedCalls) {
|
|
promises.push(waitForCallsChangedEvent(telephony, call));
|
|
promises.push(waitForCallsChangedEvent(conference, call));
|
|
promises.push(waitForGroupChangeEvent(call, null));
|
|
}
|
|
|
|
if (remainedCalls.length === 0) {
|
|
let promise = waitForStateChangeEvent(conference, "")
|
|
.then(() => {
|
|
if (typeof statechangeCallback === 'function') {
|
|
statechangeCallback();
|
|
}
|
|
});
|
|
promises.push(promise);
|
|
}
|
|
|
|
promises.push(remoteHangUp(callToHangUp));
|
|
|
|
return Promise.all(promises)
|
|
.then(() => checkCalls(conference.calls, remainedCalls))
|
|
.then(() => conference.calls);
|
|
}
|
|
|
|
/**
|
|
* Hangup conference.
|
|
*
|
|
* @return Promise
|
|
*/
|
|
function hangUpConference() {
|
|
log("Hangup conference.");
|
|
|
|
let promises = [];
|
|
|
|
promises.push(waitForStateChangeEvent(conference, ""));
|
|
|
|
for (let call of conference.calls) {
|
|
promises.push(waitForNamedStateEvent(call, "disconnected"));
|
|
}
|
|
|
|
return conference.hangUp().then(() => {
|
|
return Promise.all(promises);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 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.waiting]))
|
|
.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.id.number)
|
|
: inCallStrPool(call.id.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", "waiting", "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.waiting)))
|
|
.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;
|
|
}
|
|
|
|
/**
|
|
* Send out the MMI code.
|
|
*
|
|
* @param mmi
|
|
* String of MMI code
|
|
* @return Promise<MozMMIResult>
|
|
*/
|
|
function sendMMI(mmi) {
|
|
return telephony.dial(mmi).then(mmiCall => {
|
|
ok(mmiCall instanceof MMICall, "mmiCall is instance of MMICall");
|
|
ok(mmiCall.result instanceof Promise, "result is Promise");
|
|
return mmiCall.result;
|
|
});
|
|
}
|
|
|
|
function sendTone(tone, pause, serviceId) {
|
|
log("Send DTMF " + tone + " serviceId " + serviceId);
|
|
return telephony.sendTones(tone, pause, null, serviceId);
|
|
}
|
|
|
|
/**
|
|
* Config radio.
|
|
*
|
|
* @param connection
|
|
* MobileConnection object.
|
|
* @param enabled
|
|
* True to enable the radio.
|
|
* @return Promise
|
|
*/
|
|
function setRadioEnabled(connection, enabled) {
|
|
let desiredRadioState = enabled ? 'enabled' : 'disabled';
|
|
log("Set radio: " + desiredRadioState);
|
|
|
|
if (connection.radioState === desiredRadioState) {
|
|
return Promise.resolve();
|
|
}
|
|
|
|
let promises = [];
|
|
|
|
promises.push(gWaitForEvent(connection, "radiostatechange", event => {
|
|
let state = connection.radioState;
|
|
log("current radioState: " + state);
|
|
return state == desiredRadioState;
|
|
}));
|
|
|
|
// Wait for icc status to finish updating. Please see bug 1169504 for the
|
|
// reason why we need this.
|
|
promises.push(gWaitForEvent(connection, "iccchange", event => {
|
|
let iccId = connection.iccId;
|
|
log("current iccId: " + iccId);
|
|
return !!iccId === enabled;
|
|
}));
|
|
|
|
promises.push(connection.setRadioEnabled(enabled));
|
|
|
|
return Promise.all(promises);
|
|
}
|
|
|
|
function setRadioEnabledAll(enabled) {
|
|
let promises = [];
|
|
let numOfSim = navigator.mozMobileConnections.length;
|
|
|
|
for (let i = 0; i < numOfSim; i++) {
|
|
let connection = navigator.mozMobileConnections[i];
|
|
ok(connection instanceof MozMobileConnection,
|
|
"connection[" + i + "] is instanceof " + connection.constructor);
|
|
|
|
promises.push(setRadioEnabled(connection, enabled));
|
|
}
|
|
|
|
return Promise.all(promises);
|
|
}
|
|
|
|
/**
|
|
* Public members.
|
|
*/
|
|
|
|
this.gDelay = delay;
|
|
this.gWaitForSystemMessage = waitForSystemMessage;
|
|
this.gWaitForEvent = waitForEvent;
|
|
this.gWaitForCallsChangedEvent = waitForCallsChangedEvent;
|
|
this.gWaitForNamedStateEvent = waitForNamedStateEvent;
|
|
this.gWaitForStateChangeEvent = waitForStateChangeEvent;
|
|
this.gCheckInitialState = checkInitialState;
|
|
this.gClearCalls = clearCalls;
|
|
this.gOutCallStrPool = outCallStrPool;
|
|
this.gInCallStrPool = inCallStrPool;
|
|
this.gCheckState = checkState;
|
|
this.gCheckAll = checkAll;
|
|
this.gSendMMI = sendMMI;
|
|
this.gDial = dial;
|
|
this.gDialEmergency = dialEmergency;
|
|
this.gDialSTK = dialSTK;
|
|
this.gAnswer = answer;
|
|
this.gHangUp = hangUp;
|
|
this.gHold = hold;
|
|
this.gResume = resume;
|
|
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.gHangUpConference = hangUpConference;
|
|
this.gSendTone = sendTone;
|
|
this.gSetupConference = setupConference;
|
|
this.gSetRadioEnabled = setRadioEnabled;
|
|
this.gSetRadioEnabledAll = setRadioEnabledAll;
|
|
|
|
// Telephony helper
|
|
this.TelephonyHelper = {
|
|
dial: dial,
|
|
answer: answer,
|
|
hangUp: hangUp,
|
|
hold: hold,
|
|
resume: resume,
|
|
equals: equals,
|
|
createExptectedCall: createExptectedCall
|
|
};
|
|
|
|
// Remote Utils, TODO: This should be an array for multi-SIM scenarios
|
|
this.Remotes = [{
|
|
dial: remoteDial,
|
|
answer: remoteAnswer,
|
|
hangUp: remoteHangUp
|
|
}];
|
|
this.Remote = this.Remotes[0];
|
|
}());
|
|
|
|
function _startTest(permissions, test) {
|
|
function typesToPermissions(types) {
|
|
return types.map(type => {
|
|
return {
|
|
"type": type,
|
|
"allow": 1,
|
|
"context": document
|
|
};
|
|
});
|
|
}
|
|
|
|
function ensureRadio() {
|
|
log("== Ensure Radio ==");
|
|
return new Promise(function(resolve, reject) {
|
|
SpecialPowers.pushPermissions(typesToPermissions(["mobileconnection"]), () => {
|
|
gSetRadioEnabledAll(true).then(() => {
|
|
SpecialPowers.popPermissions(() => {
|
|
resolve();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function permissionSetUp() {
|
|
log("== Permission SetUp ==");
|
|
return new Promise(function(resolve, reject) {
|
|
SpecialPowers.pushPermissions(typesToPermissions(permissions), resolve);
|
|
});
|
|
}
|
|
|
|
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");
|
|
|
|
return Promise.resolve()
|
|
.then(ensureRadio)
|
|
.then(permissionSetUp)
|
|
.then(() => {
|
|
// Make sure that we get the telephony after adding permission.
|
|
telephony = window.navigator.mozTelephony;
|
|
ok(telephony);
|
|
conference = telephony.conferenceGroup;
|
|
ok(conference);
|
|
})
|
|
.then(gClearCalls)
|
|
.then(gCheckInitialState);
|
|
}
|
|
|
|
// Extend finish() with tear down.
|
|
finish = (function() {
|
|
let originalFinish = finish;
|
|
|
|
function tearDown() {
|
|
log("== Test TearDown ==");
|
|
emulator.waitFinish()
|
|
.then(() => {
|
|
// Restore debugging pref.
|
|
SpecialPowers.setBoolPref(kPrefRilDebuggingEnabled, debugPref);
|
|
log("Set debugging pref: true => " + debugPref);
|
|
})
|
|
.then(function() {
|
|
originalFinish.apply(this, arguments);
|
|
});
|
|
}
|
|
|
|
return tearDown.bind(this);
|
|
}());
|
|
|
|
setUp().then(() => {
|
|
log("== Test Start ==");
|
|
test();
|
|
})
|
|
.catch(error => ok(false, error));
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|