|
1 /* Any copyright is dedicated to the Public Domain. |
|
2 * http://creativecommons.org/publicdomain/zero/1.0/ */ |
|
3 |
|
4 let Promise = SpecialPowers.Cu.import("resource://gre/modules/Promise.jsm").Promise; |
|
5 let telephony; |
|
6 let conference; |
|
7 |
|
8 /** |
|
9 * Emulator helper. |
|
10 */ |
|
11 let emulator = (function() { |
|
12 let pendingCmdCount = 0; |
|
13 let originalRunEmulatorCmd = runEmulatorCmd; |
|
14 |
|
15 // Overwritten it so people could not call this function directly. |
|
16 runEmulatorCmd = function() { |
|
17 throw "Use emulator.run(cmd, callback) instead of runEmulatorCmd"; |
|
18 }; |
|
19 |
|
20 function run(cmd, callback) { |
|
21 pendingCmdCount++; |
|
22 originalRunEmulatorCmd(cmd, function(result) { |
|
23 pendingCmdCount--; |
|
24 if (callback && typeof callback === "function") { |
|
25 callback(result); |
|
26 } |
|
27 }); |
|
28 } |
|
29 |
|
30 /** |
|
31 * @return Promise |
|
32 */ |
|
33 function waitFinish() { |
|
34 let deferred = Promise.defer(); |
|
35 |
|
36 waitFor(function() { |
|
37 deferred.resolve(); |
|
38 }, function() { |
|
39 return pendingCmdCount === 0; |
|
40 }); |
|
41 |
|
42 return deferred.promise; |
|
43 } |
|
44 |
|
45 return { |
|
46 run: run, |
|
47 waitFinish: waitFinish |
|
48 }; |
|
49 }()); |
|
50 |
|
51 // Delay 1s before each telephony.dial() |
|
52 // The workaround here should be removed after bug 1005816. |
|
53 |
|
54 let originalDial; |
|
55 |
|
56 function delayTelephonyDial() { |
|
57 originalDial = telephony.dial; |
|
58 telephony.dial = function(number, serviceId) { |
|
59 let deferred = Promise.defer(); |
|
60 |
|
61 let startTime = Date.now(); |
|
62 waitFor(function() { |
|
63 originalDial.call(telephony, number, serviceId).then(call => { |
|
64 deferred.resolve(call); |
|
65 }, cause => { |
|
66 deferred.reject(cause); |
|
67 }); |
|
68 }, function() { |
|
69 duration = Date.now() - startTime; |
|
70 return (duration >= 1000); |
|
71 }); |
|
72 |
|
73 return deferred.promise; |
|
74 }; |
|
75 } |
|
76 |
|
77 function restoreTelephonyDial() { |
|
78 telephony.dial = originalDial; |
|
79 } |
|
80 |
|
81 /** |
|
82 * Telephony related helper functions. |
|
83 */ |
|
84 (function() { |
|
85 /** |
|
86 * @return Promise |
|
87 */ |
|
88 function clearCalls() { |
|
89 let deferred = Promise.defer(); |
|
90 |
|
91 log("Clear existing calls."); |
|
92 emulator.run("gsm clear", function(result) { |
|
93 if (result[0] == "OK") { |
|
94 waitFor(function() { |
|
95 deferred.resolve(); |
|
96 }, function() { |
|
97 return telephony.calls.length === 0; |
|
98 }); |
|
99 } else { |
|
100 log("Failed to clear existing calls."); |
|
101 deferred.reject(); |
|
102 } |
|
103 }); |
|
104 |
|
105 return deferred.promise; |
|
106 } |
|
107 |
|
108 /** |
|
109 * Provide a string with format of the emulator call list result. |
|
110 * |
|
111 * @param prefix |
|
112 * Possible values are "outbound" and "inbound". |
|
113 * @param number |
|
114 * Call number. |
|
115 * @return A string with format of the emulator call list result. |
|
116 */ |
|
117 function callStrPool(prefix, number) { |
|
118 let padding = " : "; |
|
119 let numberInfo = prefix + number + padding.substr(number.length); |
|
120 |
|
121 let info = {}; |
|
122 let states = ['ringing', 'incoming', 'active', 'held']; |
|
123 for (let state of states) { |
|
124 info[state] = numberInfo + state; |
|
125 } |
|
126 |
|
127 return info; |
|
128 } |
|
129 |
|
130 /** |
|
131 * Provide a corresponding string of an outgoing call. The string is with |
|
132 * format of the emulator call list result. |
|
133 * |
|
134 * @param number |
|
135 * Number of an outgoing call. |
|
136 * @return A string with format of the emulator call list result. |
|
137 */ |
|
138 function outCallStrPool(number) { |
|
139 return callStrPool("outbound to ", number); |
|
140 } |
|
141 |
|
142 /** |
|
143 * Provide a corresponding string of an incoming call. The string is with |
|
144 * format of the emulator call list result. |
|
145 * |
|
146 * @param number |
|
147 * Number of an incoming call. |
|
148 * @return A string with format of the emulator call list result. |
|
149 */ |
|
150 function inCallStrPool(number) { |
|
151 return callStrPool("inbound from ", number); |
|
152 } |
|
153 |
|
154 /** |
|
155 * Check utility functions. |
|
156 */ |
|
157 |
|
158 function checkInitialState() { |
|
159 log("Verify initial state."); |
|
160 ok(telephony.calls, 'telephony.call'); |
|
161 checkTelephonyActiveAndCalls(null, []); |
|
162 ok(conference.calls, 'conference.calls'); |
|
163 checkConferenceStateAndCalls('', []); |
|
164 } |
|
165 |
|
166 /** |
|
167 * Convenient helper to compare a TelephonyCall and a received call event. |
|
168 */ |
|
169 function checkEventCallState(event, call, state) { |
|
170 is(call, event.call, "event.call"); |
|
171 is(call.state, state, "call state"); |
|
172 } |
|
173 |
|
174 /** |
|
175 * Convenient helper to check mozTelephony.active and mozTelephony.calls. |
|
176 */ |
|
177 function checkTelephonyActiveAndCalls(active, calls) { |
|
178 is(telephony.active, active, "telephony.active"); |
|
179 is(telephony.calls.length, calls.length, "telephony.calls"); |
|
180 for (let i = 0; i < calls.length; ++i) { |
|
181 is(telephony.calls[i], calls[i]); |
|
182 } |
|
183 } |
|
184 |
|
185 /** |
|
186 * Convenient helper to check mozTelephony.conferenceGroup.state and |
|
187 * .conferenceGroup.calls. |
|
188 */ |
|
189 function checkConferenceStateAndCalls(state, calls) { |
|
190 is(conference.state, state, "conference.state"); |
|
191 is(conference.calls.length, calls.length, "conference.calls"); |
|
192 for (let i = 0; i < calls.length; i++) { |
|
193 is(conference.calls[i], calls[i]); |
|
194 } |
|
195 } |
|
196 |
|
197 /** |
|
198 * Convenient helper to handle *.oncallschanged event. |
|
199 * |
|
200 * @param container |
|
201 * Representation of "mozTelephony" or "mozTelephony.conferenceGroup." |
|
202 * @param containerName |
|
203 * Name of container. Could be an arbitrary string, used for debug |
|
204 * messages only. |
|
205 * @param expectedCalls |
|
206 * An array of calls. |
|
207 * @param callback |
|
208 * A callback function. |
|
209 */ |
|
210 function check_oncallschanged(container, containerName, expectedCalls, |
|
211 callback) { |
|
212 container.oncallschanged = function(event) { |
|
213 log("Received 'callschanged' event for the " + containerName); |
|
214 if (event.call) { |
|
215 let index = expectedCalls.indexOf(event.call); |
|
216 ok(index != -1); |
|
217 expectedCalls.splice(index, 1); |
|
218 |
|
219 if (expectedCalls.length === 0) { |
|
220 container.oncallschanged = null; |
|
221 callback(); |
|
222 } |
|
223 } |
|
224 }; |
|
225 } |
|
226 |
|
227 /** |
|
228 * Convenient helper to handle *.ongroupchange event. |
|
229 * |
|
230 * @param call |
|
231 * A TelephonyCall object. |
|
232 * @param callName |
|
233 * Name of a call. Could be an arbitrary string, used for debug messages |
|
234 * only. |
|
235 * @param group |
|
236 * Representation of mozTelephony.conferenceGroup. |
|
237 * @param callback |
|
238 * A callback function. |
|
239 */ |
|
240 function check_ongroupchange(call, callName, group, callback) { |
|
241 call.ongroupchange = function(event) { |
|
242 log("Received 'groupchange' event for the " + callName); |
|
243 call.ongroupchange = null; |
|
244 |
|
245 is(call.group, group); |
|
246 callback(); |
|
247 }; |
|
248 } |
|
249 |
|
250 /** |
|
251 * Convenient helper to handle *.onstatechange event. |
|
252 * |
|
253 * @param container |
|
254 * Representation of a TelephonyCall or mozTelephony.conferenceGroup. |
|
255 * @param containerName |
|
256 * Name of container. Could be an arbitrary string, used for debug messages |
|
257 * only. |
|
258 * @param state |
|
259 * A string. |
|
260 * @param callback |
|
261 * A callback function. |
|
262 */ |
|
263 function check_onstatechange(container, containerName, state, callback) { |
|
264 container.onstatechange = function(event) { |
|
265 log("Received 'statechange' event for the " + containerName); |
|
266 container.onstatechange = null; |
|
267 |
|
268 is(container.state, state); |
|
269 callback(); |
|
270 }; |
|
271 } |
|
272 |
|
273 /** |
|
274 * Convenient helper to check the sequence of call state and event handlers. |
|
275 * |
|
276 * @param state |
|
277 * A string of the expected call state. |
|
278 * @param previousEvent |
|
279 * A string of the event that should come before the expected state. |
|
280 */ |
|
281 function StateEventChecker(state, previousEvent) { |
|
282 let event = 'on' + state; |
|
283 |
|
284 return function(call, callName, callback) { |
|
285 call[event] = function() { |
|
286 log("Received '" + state + "' event for the " + callName); |
|
287 call[event] = null; |
|
288 |
|
289 if (previousEvent) { |
|
290 // We always clear the event handler when the event is received. |
|
291 // Therefore, if the corresponding handler is not existed, the expected |
|
292 // previous event has been already received. |
|
293 ok(!call[previousEvent]); |
|
294 } |
|
295 is(call.state, state); |
|
296 callback(); |
|
297 }; |
|
298 }; |
|
299 } |
|
300 |
|
301 /** |
|
302 * Convenient helper to check the call list existing in the emulator. |
|
303 * |
|
304 * @param expectedCallList |
|
305 * An array of call info with the format of "callStrPool()[state]". |
|
306 * @return A deferred promise. |
|
307 */ |
|
308 function checkEmulatorCallList(expectedCallList) { |
|
309 let deferred = Promise.defer(); |
|
310 |
|
311 emulator.run("gsm list", function(result) { |
|
312 log("Call list is now: " + result); |
|
313 for (let i = 0; i < expectedCallList.length; ++i) { |
|
314 is(result[i], expectedCallList[i], "emulator calllist"); |
|
315 } |
|
316 is(result[expectedCallList.length], "OK", "emulator calllist"); |
|
317 deferred.resolve(); |
|
318 }); |
|
319 |
|
320 return deferred.promise; |
|
321 } |
|
322 |
|
323 /** |
|
324 * Super convenient helper to check calls and state of mozTelephony and |
|
325 * mozTelephony.conferenceGroup. |
|
326 * |
|
327 * @param active |
|
328 * A TelephonyCall object. Should be the expected active call. |
|
329 * @param calls |
|
330 * An array of TelephonyCall objects. Should be the expected list of |
|
331 * mozTelephony.calls. |
|
332 * @param conferenceState |
|
333 * A string. Should be the expected conference state. |
|
334 * @param conferenceCalls |
|
335 * An array of TelephonyCall objects. Should be the expected list of |
|
336 * mozTelephony.conferenceGroup.calls. |
|
337 */ |
|
338 function checkState(active, calls, conferenceState, conferenceCalls) { |
|
339 checkTelephonyActiveAndCalls(active, calls); |
|
340 checkConferenceStateAndCalls(conferenceState, conferenceCalls); |
|
341 } |
|
342 |
|
343 /** |
|
344 * Super convenient helper to check calls and state of mozTelephony and |
|
345 * mozTelephony.conferenceGroup as well as the calls existing in the emulator. |
|
346 * |
|
347 * @param active |
|
348 * A TelephonyCall object. Should be the expected active call. |
|
349 * @param calls |
|
350 * An array of TelephonyCall objects. Should be the expected list of |
|
351 * mozTelephony.calls. |
|
352 * @param conferenceState |
|
353 * A string. Should be the expected conference state. |
|
354 * @param conferenceCalls |
|
355 * An array of TelephonyCall objects. Should be the expected list of |
|
356 * mozTelephony.conferenceGroup.calls. |
|
357 * @param callList |
|
358 * An array of call info with the format of "callStrPool()[state]". |
|
359 * @return A deferred promise. |
|
360 */ |
|
361 function checkAll(active, calls, conferenceState, conferenceCalls, callList) { |
|
362 checkState(active, calls, conferenceState, conferenceCalls); |
|
363 return checkEmulatorCallList(callList); |
|
364 } |
|
365 |
|
366 /** |
|
367 * Request utility functions. |
|
368 */ |
|
369 |
|
370 /** |
|
371 * Make sure there's no pending event before we jump to the next action. |
|
372 * |
|
373 * @param received |
|
374 * A string of the received event. |
|
375 * @param pending |
|
376 * An array of the pending events. |
|
377 * @param nextAction |
|
378 * A callback function that is called when there's no pending event. |
|
379 */ |
|
380 function receivedPending(received, pending, nextAction) { |
|
381 let index = pending.indexOf(received); |
|
382 if (index != -1) { |
|
383 pending.splice(index, 1); |
|
384 } |
|
385 if (pending.length === 0) { |
|
386 nextAction(); |
|
387 } |
|
388 } |
|
389 |
|
390 /** |
|
391 * Make an outgoing call. |
|
392 * |
|
393 * @param number |
|
394 * A string. |
|
395 * @param serviceId [optional] |
|
396 * Identification of a service. 0 is set as default. |
|
397 * @return A deferred promise. |
|
398 */ |
|
399 function dial(number, serviceId) { |
|
400 serviceId = typeof serviceId !== "undefined" ? serviceId : 0; |
|
401 log("Make an outgoing call: " + number + ", serviceId: " + serviceId); |
|
402 |
|
403 let deferred = Promise.defer(); |
|
404 |
|
405 telephony.dial(number, serviceId).then(call => { |
|
406 ok(call); |
|
407 is(call.number, number); |
|
408 is(call.state, "dialing"); |
|
409 is(call.serviceId, serviceId); |
|
410 |
|
411 call.onalerting = function onalerting(event) { |
|
412 call.onalerting = null; |
|
413 log("Received 'onalerting' call event."); |
|
414 checkEventCallState(event, call, "alerting"); |
|
415 deferred.resolve(call); |
|
416 }; |
|
417 }, cause => { |
|
418 deferred.reject(cause); |
|
419 }); |
|
420 |
|
421 return deferred.promise; |
|
422 } |
|
423 |
|
424 /** |
|
425 * Answer an incoming call. |
|
426 * |
|
427 * @param call |
|
428 * An incoming TelephonyCall object. |
|
429 * @param conferenceStateChangeCallback [optional] |
|
430 * A callback function which is called if answering an incoming call |
|
431 * triggers conference state change. |
|
432 * @return A deferred promise. |
|
433 */ |
|
434 function answer(call, conferenceStateChangeCallback) { |
|
435 log("Answering the incoming call."); |
|
436 |
|
437 let deferred = Promise.defer(); |
|
438 let done = function() { |
|
439 deferred.resolve(call); |
|
440 }; |
|
441 |
|
442 let pending = ["call.onconnected"]; |
|
443 let receive = function(name) { |
|
444 receivedPending(name, pending, done); |
|
445 }; |
|
446 |
|
447 // When there's already a connected conference call, answering a new incoming |
|
448 // call triggers conference state change. We should wait for |
|
449 // |conference.onstatechange| before checking the state of the conference call. |
|
450 if (conference.state === "connected") { |
|
451 pending.push("conference.onstatechange"); |
|
452 check_onstatechange(conference, "conference", "held", function() { |
|
453 if (typeof conferenceStateChangeCallback === "function") { |
|
454 conferenceStateChangeCallback(); |
|
455 } |
|
456 receive("conference.onstatechange"); |
|
457 }); |
|
458 } |
|
459 |
|
460 call.onconnecting = function onconnectingIn(event) { |
|
461 log("Received 'connecting' call event for incoming call."); |
|
462 call.onconnecting = null; |
|
463 checkEventCallState(event, call, "connecting"); |
|
464 }; |
|
465 |
|
466 call.onconnected = function onconnectedIn(event) { |
|
467 log("Received 'connected' call event for incoming call."); |
|
468 call.onconnected = null; |
|
469 checkEventCallState(event, call, "connected"); |
|
470 ok(!call.onconnecting); |
|
471 receive("call.onconnected"); |
|
472 }; |
|
473 call.answer(); |
|
474 |
|
475 return deferred.promise; |
|
476 } |
|
477 |
|
478 /** |
|
479 * Hold a call. |
|
480 * |
|
481 * @param call |
|
482 * A TelephonyCall object. |
|
483 * @return A deferred promise. |
|
484 */ |
|
485 function hold(call) { |
|
486 log("Putting the call on hold."); |
|
487 |
|
488 let deferred = Promise.defer(); |
|
489 |
|
490 let gotHolding = false; |
|
491 call.onholding = function onholding(event) { |
|
492 log("Received 'holding' call event"); |
|
493 call.onholding = null; |
|
494 checkEventCallState(event, call, "holding"); |
|
495 gotHolding = true; |
|
496 }; |
|
497 |
|
498 call.onheld = function onheld(event) { |
|
499 log("Received 'held' call event"); |
|
500 call.onheld = null; |
|
501 checkEventCallState(event, call, "held"); |
|
502 ok(gotHolding); |
|
503 deferred.resolve(call); |
|
504 }; |
|
505 call.hold(); |
|
506 |
|
507 return deferred.promise; |
|
508 } |
|
509 |
|
510 /** |
|
511 * Simulate an incoming call. |
|
512 * |
|
513 * @param number |
|
514 * A string. |
|
515 * @return A deferred promise. |
|
516 */ |
|
517 function remoteDial(number) { |
|
518 log("Simulating an incoming call."); |
|
519 |
|
520 let deferred = Promise.defer(); |
|
521 |
|
522 telephony.onincoming = function onincoming(event) { |
|
523 log("Received 'incoming' call event."); |
|
524 telephony.onincoming = null; |
|
525 |
|
526 let call = event.call; |
|
527 |
|
528 ok(call); |
|
529 is(call.number, number); |
|
530 is(call.state, "incoming"); |
|
531 |
|
532 deferred.resolve(call); |
|
533 }; |
|
534 emulator.run("gsm call " + number); |
|
535 |
|
536 return deferred.promise; |
|
537 } |
|
538 |
|
539 /** |
|
540 * Remote party answers the call. |
|
541 * |
|
542 * @param call |
|
543 * A TelephonyCall object. |
|
544 * @return A deferred promise. |
|
545 */ |
|
546 function remoteAnswer(call) { |
|
547 log("Remote answering the call."); |
|
548 |
|
549 let deferred = Promise.defer(); |
|
550 |
|
551 call.onconnected = function onconnected(event) { |
|
552 log("Received 'connected' call event."); |
|
553 call.onconnected = null; |
|
554 checkEventCallState(event, call, "connected"); |
|
555 deferred.resolve(call); |
|
556 }; |
|
557 emulator.run("gsm accept " + call.number); |
|
558 |
|
559 return deferred.promise; |
|
560 } |
|
561 |
|
562 /** |
|
563 * Remote party hangs up the call. |
|
564 * |
|
565 * @param call |
|
566 * A TelephonyCall object. |
|
567 * @return A deferred promise. |
|
568 */ |
|
569 function remoteHangUp(call) { |
|
570 log("Remote hanging up the call."); |
|
571 |
|
572 let deferred = Promise.defer(); |
|
573 |
|
574 call.ondisconnected = function ondisconnected(event) { |
|
575 log("Received 'disconnected' call event."); |
|
576 call.ondisconnected = null; |
|
577 checkEventCallState(event, call, "disconnected"); |
|
578 deferred.resolve(call); |
|
579 }; |
|
580 emulator.run("gsm cancel " + call.number); |
|
581 |
|
582 return deferred.promise; |
|
583 } |
|
584 |
|
585 /** |
|
586 * Remote party hangs up all the calls. |
|
587 * |
|
588 * @param calls |
|
589 * An array of TelephonyCall objects. |
|
590 * @return A deferred promise. |
|
591 */ |
|
592 function remoteHangUpCalls(calls) { |
|
593 let promise = Promise.resolve(); |
|
594 |
|
595 for (let call of calls) { |
|
596 promise = promise.then(remoteHangUp.bind(null, call)); |
|
597 } |
|
598 |
|
599 return promise; |
|
600 } |
|
601 |
|
602 /** |
|
603 * Add calls to conference. |
|
604 * |
|
605 * @param callsToAdd |
|
606 * An array of TelephonyCall objects to be added into conference. The |
|
607 * length of the array should be 1 or 2. |
|
608 * @param connectedCallback [optional] |
|
609 * A callback function which is called when conference state becomes |
|
610 * connected. |
|
611 * @return A deferred promise. |
|
612 */ |
|
613 function addCallsToConference(callsToAdd, connectedCallback) { |
|
614 log("Add " + callsToAdd.length + " calls into conference."); |
|
615 |
|
616 let deferred = Promise.defer(); |
|
617 let done = function() { |
|
618 deferred.resolve(); |
|
619 }; |
|
620 |
|
621 let pending = ["conference.oncallschanged", "conference.onconnected"]; |
|
622 let receive = function(name) { |
|
623 receivedPending(name, pending, done); |
|
624 }; |
|
625 |
|
626 let check_onconnected = StateEventChecker('connected', 'onresuming'); |
|
627 |
|
628 for (let call of callsToAdd) { |
|
629 let callName = "callToAdd (" + call.number + ')'; |
|
630 |
|
631 let ongroupchange = callName + ".ongroupchange"; |
|
632 pending.push(ongroupchange); |
|
633 check_ongroupchange(call, callName, conference, |
|
634 receive.bind(null, ongroupchange)); |
|
635 |
|
636 let onstatechange = callName + ".onstatechange"; |
|
637 pending.push(onstatechange); |
|
638 check_onstatechange(call, callName, 'connected', |
|
639 receive.bind(null, onstatechange)); |
|
640 } |
|
641 |
|
642 check_oncallschanged(conference, 'conference', callsToAdd, |
|
643 receive.bind(null, "conference.oncallschanged")); |
|
644 |
|
645 check_onconnected(conference, "conference", function() { |
|
646 ok(!conference.oncallschanged); |
|
647 if (typeof connectedCallback === 'function') { |
|
648 connectedCallback(); |
|
649 } |
|
650 receive("conference.onconnected"); |
|
651 }); |
|
652 |
|
653 // Cannot use apply() through webidl, so just separate the cases to handle. |
|
654 if (callsToAdd.length == 2) { |
|
655 conference.add(callsToAdd[0], callsToAdd[1]); |
|
656 } else { |
|
657 conference.add(callsToAdd[0]); |
|
658 } |
|
659 |
|
660 return deferred.promise; |
|
661 } |
|
662 |
|
663 /** |
|
664 * Hold the conference. |
|
665 * |
|
666 * @param calls |
|
667 * An array of TelephonyCall objects existing in conference. |
|
668 * @param heldCallback [optional] |
|
669 * A callback function which is called when conference state becomes |
|
670 * held. |
|
671 * @return A deferred promise. |
|
672 */ |
|
673 function holdConference(calls, heldCallback) { |
|
674 log("Holding the conference call."); |
|
675 |
|
676 let deferred = Promise.defer(); |
|
677 let done = function() { |
|
678 deferred.resolve(); |
|
679 }; |
|
680 |
|
681 let pending = ["conference.onholding", "conference.onheld"]; |
|
682 let receive = function(name) { |
|
683 receivedPending(name, pending, done); |
|
684 }; |
|
685 |
|
686 let check_onholding = StateEventChecker('holding', null); |
|
687 let check_onheld = StateEventChecker('held', 'onholding'); |
|
688 |
|
689 for (let call of calls) { |
|
690 let callName = "call (" + call.number + ')'; |
|
691 |
|
692 let onholding = callName + ".onholding"; |
|
693 pending.push(onholding); |
|
694 check_onholding(call, callName, receive.bind(null, onholding)); |
|
695 |
|
696 let onheld = callName + ".onheld"; |
|
697 pending.push(onheld); |
|
698 check_onheld(call, callName, receive.bind(null, onheld)); |
|
699 } |
|
700 |
|
701 check_onholding(conference, "conference", |
|
702 receive.bind(null, "conference.onholding")); |
|
703 |
|
704 check_onheld(conference, "conference", function() { |
|
705 if (typeof heldCallback === 'function') { |
|
706 heldCallback(); |
|
707 } |
|
708 receive("conference.onheld"); |
|
709 }); |
|
710 |
|
711 conference.hold(); |
|
712 |
|
713 return deferred.promise; |
|
714 } |
|
715 |
|
716 /** |
|
717 * Resume the conference. |
|
718 * |
|
719 * @param calls |
|
720 * An array of TelephonyCall objects existing in conference. |
|
721 * @param connectedCallback [optional] |
|
722 * A callback function which is called when conference state becomes |
|
723 * connected. |
|
724 * @return A deferred promise. |
|
725 */ |
|
726 function resumeConference(calls, connectedCallback) { |
|
727 log("Resuming the held conference call."); |
|
728 |
|
729 let deferred = Promise.defer(); |
|
730 let done = function() { |
|
731 deferred.resolve(); |
|
732 }; |
|
733 |
|
734 let pending = ["conference.onresuming", "conference.onconnected"]; |
|
735 let receive = function(name) { |
|
736 receivedPending(name, pending, done); |
|
737 }; |
|
738 |
|
739 let check_onresuming = StateEventChecker('resuming', null); |
|
740 let check_onconnected = StateEventChecker('connected', 'onresuming'); |
|
741 |
|
742 for (let call of calls) { |
|
743 let callName = "call (" + call.number + ')'; |
|
744 |
|
745 let onresuming = callName + ".onresuming"; |
|
746 pending.push(onresuming); |
|
747 check_onresuming(call, callName, receive.bind(null, onresuming)); |
|
748 |
|
749 let onconnected = callName + ".onconnected"; |
|
750 pending.push(onconnected); |
|
751 check_onconnected(call, callName, receive.bind(null, onconnected)); |
|
752 } |
|
753 |
|
754 check_onresuming(conference, "conference", |
|
755 receive.bind(null, "conference.onresuming")); |
|
756 |
|
757 check_onconnected(conference, "conference", function() { |
|
758 if (typeof connectedCallback === 'function') { |
|
759 connectedCallback(); |
|
760 } |
|
761 receive("conference.onconnected"); |
|
762 }); |
|
763 |
|
764 conference.resume(); |
|
765 |
|
766 return deferred.promise; |
|
767 } |
|
768 |
|
769 /** |
|
770 * Remove a call out of conference. |
|
771 * |
|
772 * @param callToRemove |
|
773 * A TelephonyCall object existing in conference. |
|
774 * @param autoRemovedCalls |
|
775 * An array of TelephonyCall objects which is going to be automatically |
|
776 * removed. The length of the array should be 0 or 1. |
|
777 * @param remainedCalls |
|
778 * An array of TelephonyCall objects which remain in conference. |
|
779 * @param stateChangeCallback [optional] |
|
780 * A callback function which is called when conference state changes. |
|
781 * @return A deferred promise. |
|
782 */ |
|
783 function removeCallInConference(callToRemove, autoRemovedCalls, remainedCalls, |
|
784 statechangeCallback) { |
|
785 log("Removing a participant from the conference call."); |
|
786 |
|
787 is(conference.state, 'connected'); |
|
788 |
|
789 let deferred = Promise.defer(); |
|
790 let done = function() { |
|
791 deferred.resolve(); |
|
792 }; |
|
793 |
|
794 let pending = ["callToRemove.ongroupchange", "telephony.oncallschanged", |
|
795 "conference.oncallschanged", "conference.onstatechange"]; |
|
796 let receive = function(name) { |
|
797 receivedPending(name, pending, done); |
|
798 }; |
|
799 |
|
800 // Remained call in conference will be held. |
|
801 for (let call of remainedCalls) { |
|
802 let callName = "remainedCall (" + call.number + ')'; |
|
803 |
|
804 let onstatechange = callName + ".onstatechange"; |
|
805 pending.push(onstatechange); |
|
806 check_onstatechange(call, callName, 'held', |
|
807 receive.bind(null, onstatechange)); |
|
808 } |
|
809 |
|
810 // When a call is removed from conference with 2 calls, another one will be |
|
811 // automatically removed from group and be put on hold. |
|
812 for (let call of autoRemovedCalls) { |
|
813 let callName = "autoRemovedCall (" + call.number + ')'; |
|
814 |
|
815 let ongroupchange = callName + ".ongroupchange"; |
|
816 pending.push(ongroupchange); |
|
817 check_ongroupchange(call, callName, null, |
|
818 receive.bind(null, ongroupchange)); |
|
819 |
|
820 let onstatechange = callName + ".onstatechange"; |
|
821 pending.push(onstatechange); |
|
822 check_onstatechange(call, callName, 'held', |
|
823 receive.bind(null, onstatechange)); |
|
824 } |
|
825 |
|
826 check_ongroupchange(callToRemove, "callToRemove", null, function() { |
|
827 is(callToRemove.state, 'connected'); |
|
828 receive("callToRemove.ongroupchange"); |
|
829 }); |
|
830 |
|
831 check_oncallschanged(telephony, 'telephony', |
|
832 autoRemovedCalls.concat(callToRemove), |
|
833 receive.bind(null, "telephony.oncallschanged")); |
|
834 |
|
835 check_oncallschanged(conference, 'conference', |
|
836 autoRemovedCalls.concat(callToRemove), function() { |
|
837 is(conference.calls.length, remainedCalls.length); |
|
838 receive("conference.oncallschanged"); |
|
839 }); |
|
840 |
|
841 check_onstatechange(conference, 'conference', |
|
842 (remainedCalls.length ? 'held' : ''), function() { |
|
843 ok(!conference.oncallschanged); |
|
844 if (typeof statechangeCallback === 'function') { |
|
845 statechangeCallback(); |
|
846 } |
|
847 receive("conference.onstatechange"); |
|
848 }); |
|
849 |
|
850 conference.remove(callToRemove); |
|
851 |
|
852 return deferred.promise; |
|
853 } |
|
854 |
|
855 /** |
|
856 * Hangup a call in conference. |
|
857 * |
|
858 * @param callToHangUp |
|
859 * A TelephonyCall object existing in conference. |
|
860 * @param autoRemovedCalls |
|
861 * An array of TelephonyCall objects which is going to be automatically |
|
862 * removed. The length of the array should be 0 or 1. |
|
863 * @param remainedCalls |
|
864 * An array of TelephonyCall objects which remain in conference. |
|
865 * @param stateChangeCallback [optional] |
|
866 * A callback function which is called when conference state changes. |
|
867 * @return A deferred promise. |
|
868 */ |
|
869 function hangUpCallInConference(callToHangUp, autoRemovedCalls, remainedCalls, |
|
870 statechangeCallback) { |
|
871 log("Release one call in conference."); |
|
872 |
|
873 let deferred = Promise.defer(); |
|
874 let done = function() { |
|
875 deferred.resolve(); |
|
876 }; |
|
877 |
|
878 let pending = ["conference.oncallschanged", "remoteHangUp"]; |
|
879 let receive = function(name) { |
|
880 receivedPending(name, pending, done); |
|
881 }; |
|
882 |
|
883 // When a call is hang up from conference with 2 calls, another one will be |
|
884 // automatically removed from group. |
|
885 for (let call of autoRemovedCalls) { |
|
886 let callName = "autoRemovedCall (" + call.number + ')'; |
|
887 |
|
888 let ongroupchange = callName + ".ongroupchange"; |
|
889 pending.push(ongroupchange); |
|
890 check_ongroupchange(call, callName, null, |
|
891 receive.bind(null, ongroupchange)); |
|
892 } |
|
893 |
|
894 if (autoRemovedCalls.length) { |
|
895 pending.push("telephony.oncallschanged"); |
|
896 check_oncallschanged(telephony, 'telephony', |
|
897 autoRemovedCalls, |
|
898 receive.bind(null, "telephony.oncallschanged")); |
|
899 } |
|
900 |
|
901 check_oncallschanged(conference, 'conference', |
|
902 autoRemovedCalls.concat(callToHangUp), function() { |
|
903 is(conference.calls.length, remainedCalls.length); |
|
904 receive("conference.oncallschanged"); |
|
905 }); |
|
906 |
|
907 if (remainedCalls.length === 0) { |
|
908 pending.push("conference.onstatechange"); |
|
909 check_onstatechange(conference, 'conference', '', function() { |
|
910 ok(!conference.oncallschanged); |
|
911 if (typeof statechangeCallback === 'function') { |
|
912 statechangeCallback(); |
|
913 } |
|
914 receive("conference.onstatechange"); |
|
915 }); |
|
916 } |
|
917 |
|
918 remoteHangUp(callToHangUp) |
|
919 .then(receive.bind(null, "remoteHangUp")); |
|
920 |
|
921 return deferred.promise; |
|
922 } |
|
923 |
|
924 /** |
|
925 * Setup a conference with an outgoing call and an incoming call. |
|
926 * |
|
927 * @param outNumber |
|
928 * Number of an outgoing call. |
|
929 * @param inNumber |
|
930 * Number of an incoming call. |
|
931 * @return Promise<[outCall, inCall]> |
|
932 */ |
|
933 function setupConferenceTwoCalls(outNumber, inNumber) { |
|
934 log('Create conference with two calls.'); |
|
935 |
|
936 let outCall; |
|
937 let inCall; |
|
938 let outInfo = outCallStrPool(outNumber); |
|
939 let inInfo = inCallStrPool(inNumber); |
|
940 |
|
941 return Promise.resolve() |
|
942 .then(checkInitialState) |
|
943 .then(() => dial(outNumber)) |
|
944 .then(call => { outCall = call; }) |
|
945 .then(() => checkAll(outCall, [outCall], '', [], [outInfo.ringing])) |
|
946 .then(() => remoteAnswer(outCall)) |
|
947 .then(() => checkAll(outCall, [outCall], '', [], [outInfo.active])) |
|
948 .then(() => remoteDial(inNumber)) |
|
949 .then(call => { inCall = call; }) |
|
950 .then(() => checkAll(outCall, [outCall, inCall], '', [], |
|
951 [outInfo.active, inInfo.incoming])) |
|
952 .then(() => answer(inCall)) |
|
953 .then(() => checkAll(inCall, [outCall, inCall], '', [], |
|
954 [outInfo.held, inInfo.active])) |
|
955 .then(() => addCallsToConference([outCall, inCall], function() { |
|
956 checkState(conference, [], 'connected', [outCall, inCall]); |
|
957 })) |
|
958 .then(() => checkAll(conference, [], 'connected', [outCall, inCall], |
|
959 [outInfo.active, inInfo.active])) |
|
960 .then(() => { |
|
961 return [outCall, inCall]; |
|
962 }); |
|
963 } |
|
964 |
|
965 /** |
|
966 * Setup a conference with an outgoing call and two incoming calls. |
|
967 * |
|
968 * @param outNumber |
|
969 * Number of an outgoing call. |
|
970 * @param inNumber |
|
971 * Number of an incoming call. |
|
972 * @param inNumber2 |
|
973 * Number of an incoming call. |
|
974 * @return Promise<[outCall, inCall, inCall2]> |
|
975 */ |
|
976 function setupConferenceThreeCalls(outNumber, inNumber, inNumber2) { |
|
977 log('Create conference with three calls.'); |
|
978 |
|
979 let outCall; |
|
980 let inCall; |
|
981 let inCall2; |
|
982 let outInfo = outCallStrPool(outNumber); |
|
983 let inInfo = inCallStrPool(inNumber); |
|
984 let inInfo2 = inCallStrPool(inNumber2); |
|
985 |
|
986 return Promise.resolve() |
|
987 .then(() => setupConferenceTwoCalls(outNumber, inNumber)) |
|
988 .then(calls => { |
|
989 outCall = calls[0]; |
|
990 inCall = calls[1]; |
|
991 }) |
|
992 .then(() => remoteDial(inNumber2)) |
|
993 .then(call => { inCall2 = call; }) |
|
994 .then(() => checkAll(conference, [inCall2], 'connected', [outCall, inCall], |
|
995 [outInfo.active, inInfo.active, inInfo2.incoming])) |
|
996 .then(() => answer(inCall2, function() { |
|
997 checkState(inCall2, [inCall2], 'held', [outCall, inCall]); |
|
998 })) |
|
999 .then(() => checkAll(inCall2, [inCall2], 'held', [outCall, inCall], |
|
1000 [outInfo.held, inInfo.held, inInfo2.active])) |
|
1001 .then(() => addCallsToConference([inCall2], function() { |
|
1002 checkState(conference, [], 'connected', [outCall, inCall, inCall2]); |
|
1003 })) |
|
1004 .then(() => checkAll(conference, [], |
|
1005 'connected', [outCall, inCall, inCall2], |
|
1006 [outInfo.active, inInfo.active, inInfo2.active])) |
|
1007 .then(() => { |
|
1008 return [outCall, inCall, inCall2]; |
|
1009 }); |
|
1010 } |
|
1011 |
|
1012 /** |
|
1013 * Setup a conference with an outgoing call and four incoming calls. |
|
1014 * |
|
1015 * @param outNumber |
|
1016 * Number of an outgoing call. |
|
1017 * @param inNumber |
|
1018 * Number of an incoming call. |
|
1019 * @param inNumber2 |
|
1020 * Number of an incoming call. |
|
1021 * @param inNumber3 |
|
1022 * Number of an incoming call. |
|
1023 * @param inNumber4 |
|
1024 * Number of an incoming call. |
|
1025 * @return Promise<[outCall, inCall, inCall2, inCall3, inCall4]> |
|
1026 */ |
|
1027 function setupConferenceFiveCalls(outNumber, inNumber, inNumber2, inNumber3, |
|
1028 inNumber4) { |
|
1029 log('Create conference with five calls.'); |
|
1030 |
|
1031 let outCall; |
|
1032 let inCall; |
|
1033 let inCall2; |
|
1034 let inCall3; |
|
1035 let inCall4; |
|
1036 let outInfo = outCallStrPool(outNumber); |
|
1037 let inInfo = inCallStrPool(inNumber); |
|
1038 let inInfo2 = inCallStrPool(inNumber2); |
|
1039 let inInfo3 = inCallStrPool(inNumber3); |
|
1040 let inInfo4 = inCallStrPool(inNumber4); |
|
1041 |
|
1042 return Promise.resolve() |
|
1043 .then(() => setupConferenceThreeCalls(outNumber, inNumber, inNumber2)) |
|
1044 .then(calls => { |
|
1045 [outCall, inCall, inCall2] = calls; |
|
1046 }) |
|
1047 .then(() => remoteDial(inNumber3)) |
|
1048 .then(call => {inCall3 = call;}) |
|
1049 .then(() => checkAll(conference, [inCall3], 'connected', |
|
1050 [outCall, inCall, inCall2], |
|
1051 [outInfo.active, inInfo.active, inInfo2.active, |
|
1052 inInfo3.incoming])) |
|
1053 .then(() => answer(inCall3, function() { |
|
1054 checkState(inCall3, [inCall3], 'held', [outCall, inCall, inCall2]); |
|
1055 })) |
|
1056 .then(() => checkAll(inCall3, [inCall3], 'held', |
|
1057 [outCall, inCall, inCall2], |
|
1058 [outInfo.held, inInfo.held, inInfo2.held, |
|
1059 inInfo3.active])) |
|
1060 .then(() => addCallsToConference([inCall3], function() { |
|
1061 checkState(conference, [], 'connected', [outCall, inCall, inCall2, inCall3]); |
|
1062 })) |
|
1063 .then(() => checkAll(conference, [], 'connected', |
|
1064 [outCall, inCall, inCall2, inCall3], |
|
1065 [outInfo.active, inInfo.active, inInfo2.active, |
|
1066 inInfo3.active])) |
|
1067 .then(() => remoteDial(inNumber4)) |
|
1068 .then(call => {inCall4 = call;}) |
|
1069 .then(() => checkAll(conference, [inCall4], 'connected', |
|
1070 [outCall, inCall, inCall2, inCall3], |
|
1071 [outInfo.active, inInfo.active, inInfo2.active, |
|
1072 inInfo3.active, inInfo4.incoming])) |
|
1073 .then(() => answer(inCall4, function() { |
|
1074 checkState(inCall4, [inCall4], 'held', [outCall, inCall, inCall2, inCall3]); |
|
1075 })) |
|
1076 .then(() => checkAll(inCall4, [inCall4], 'held', |
|
1077 [outCall, inCall, inCall2, inCall3], |
|
1078 [outInfo.held, inInfo.held, inInfo2.held, |
|
1079 inInfo3.held, inInfo4.active])) |
|
1080 .then(() => addCallsToConference([inCall4], function() { |
|
1081 checkState(conference, [], 'connected', [outCall, inCall, inCall2, |
|
1082 inCall3, inCall4]); |
|
1083 })) |
|
1084 .then(() => checkAll(conference, [], 'connected', |
|
1085 [outCall, inCall, inCall2, inCall3, inCall4], |
|
1086 [outInfo.active, inInfo.active, inInfo2.active, |
|
1087 inInfo3.active, inInfo4.active])) |
|
1088 .then(() => { |
|
1089 return [outCall, inCall, inCall2, inCall3, inCall4]; |
|
1090 }); |
|
1091 } |
|
1092 |
|
1093 /** |
|
1094 * Public members. |
|
1095 */ |
|
1096 |
|
1097 this.gCheckInitialState = checkInitialState; |
|
1098 this.gClearCalls = clearCalls; |
|
1099 this.gOutCallStrPool = outCallStrPool; |
|
1100 this.gInCallStrPool = inCallStrPool; |
|
1101 this.gCheckState = checkState; |
|
1102 this.gCheckAll = checkAll; |
|
1103 this.gDial = dial; |
|
1104 this.gAnswer = answer; |
|
1105 this.gHold = hold; |
|
1106 this.gRemoteDial = remoteDial; |
|
1107 this.gRemoteAnswer = remoteAnswer; |
|
1108 this.gRemoteHangUp = remoteHangUp; |
|
1109 this.gRemoteHangUpCalls = remoteHangUpCalls; |
|
1110 this.gAddCallsToConference = addCallsToConference; |
|
1111 this.gHoldConference = holdConference; |
|
1112 this.gResumeConference = resumeConference; |
|
1113 this.gRemoveCallInConference = removeCallInConference; |
|
1114 this.gHangUpCallInConference = hangUpCallInConference; |
|
1115 this.gSetupConferenceTwoCalls = setupConferenceTwoCalls; |
|
1116 this.gSetupConferenceThreeCalls = setupConferenceThreeCalls; |
|
1117 this.gSetupConferenceFiveCalls = setupConferenceFiveCalls; |
|
1118 this.gReceivedPending = receivedPending; |
|
1119 }()); |
|
1120 |
|
1121 function _startTest(permissions, test) { |
|
1122 function permissionSetUp() { |
|
1123 SpecialPowers.setBoolPref("dom.mozSettings.enabled", true); |
|
1124 for (let per of permissions) { |
|
1125 SpecialPowers.addPermission(per, true, document); |
|
1126 } |
|
1127 } |
|
1128 |
|
1129 function permissionTearDown() { |
|
1130 SpecialPowers.clearUserPref("dom.mozSettings.enabled"); |
|
1131 for (let per of permissions) { |
|
1132 SpecialPowers.removePermission(per, document); |
|
1133 } |
|
1134 } |
|
1135 |
|
1136 function setUp() { |
|
1137 log("== Test SetUp =="); |
|
1138 permissionSetUp(); |
|
1139 // Make sure that we get the telephony after adding permission. |
|
1140 telephony = window.navigator.mozTelephony; |
|
1141 ok(telephony); |
|
1142 delayTelephonyDial(); |
|
1143 conference = telephony.conferenceGroup; |
|
1144 ok(conference); |
|
1145 return gClearCalls().then(gCheckInitialState); |
|
1146 } |
|
1147 |
|
1148 // Extend finish() with tear down. |
|
1149 finish = (function() { |
|
1150 let originalFinish = finish; |
|
1151 |
|
1152 function tearDown() { |
|
1153 log("== Test TearDown =="); |
|
1154 restoreTelephonyDial(); |
|
1155 emulator.waitFinish() |
|
1156 .then(permissionTearDown) |
|
1157 .then(function() { |
|
1158 originalFinish.apply(this, arguments); |
|
1159 }); |
|
1160 } |
|
1161 |
|
1162 return tearDown.bind(this); |
|
1163 }()); |
|
1164 |
|
1165 function mainTest() { |
|
1166 setUp() |
|
1167 .then(function onSuccess() { |
|
1168 log("== Test Start =="); |
|
1169 test(); |
|
1170 }, function onError(error) { |
|
1171 SpecialPowers.Cu.reportError(error); |
|
1172 ok(false, "SetUp error"); |
|
1173 }); |
|
1174 } |
|
1175 |
|
1176 mainTest(); |
|
1177 } |
|
1178 |
|
1179 function startTest(test) { |
|
1180 _startTest(["telephony"], test); |
|
1181 } |
|
1182 |
|
1183 function startTestWithPermissions(permissions, test) { |
|
1184 _startTest(permissions.concat("telephony"), test); |
|
1185 } |
|
1186 |
|
1187 function startDSDSTest(test) { |
|
1188 let numRIL; |
|
1189 try { |
|
1190 numRIL = SpecialPowers.getIntPref("ril.numRadioInterfaces"); |
|
1191 } catch (ex) { |
|
1192 numRIL = 1; // Pref not set. |
|
1193 } |
|
1194 |
|
1195 if (numRIL > 1) { |
|
1196 startTest(test); |
|
1197 } else { |
|
1198 log("Not a DSDS environment. Test is skipped."); |
|
1199 ok(true); // We should run at least one test. |
|
1200 finish(); |
|
1201 } |
|
1202 } |