dom/telephony/test/marionette/head.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* Any copyright is dedicated to the Public Domain.
     2  * http://creativecommons.org/publicdomain/zero/1.0/ */
     4 let Promise = SpecialPowers.Cu.import("resource://gre/modules/Promise.jsm").Promise;
     5 let telephony;
     6 let conference;
     8 /**
     9  * Emulator helper.
    10  */
    11 let emulator = (function() {
    12   let pendingCmdCount = 0;
    13   let originalRunEmulatorCmd = runEmulatorCmd;
    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   };
    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   }
    30   /**
    31    * @return Promise
    32    */
    33   function waitFinish() {
    34     let deferred = Promise.defer();
    36     waitFor(function() {
    37       deferred.resolve();
    38     }, function() {
    39       return pendingCmdCount === 0;
    40     });
    42     return deferred.promise;
    43   }
    45   return {
    46     run: run,
    47     waitFinish: waitFinish
    48   };
    49 }());
    51 // Delay 1s before each telephony.dial()
    52 // The workaround here should be removed after bug 1005816.
    54 let originalDial;
    56 function delayTelephonyDial() {
    57   originalDial = telephony.dial;
    58   telephony.dial = function(number, serviceId) {
    59     let deferred = Promise.defer();
    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     });
    73     return deferred.promise;
    74   };
    75 }
    77 function restoreTelephonyDial() {
    78   telephony.dial = originalDial;
    79 }
    81 /**
    82  * Telephony related helper functions.
    83  */
    84 (function() {
    85   /**
    86    * @return Promise
    87    */
    88   function clearCalls() {
    89     let deferred = Promise.defer();
    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     });
   105     return deferred.promise;
   106   }
   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);
   121     let info = {};
   122     let states = ['ringing', 'incoming', 'active', 'held'];
   123     for (let state of states) {
   124       info[state] = numberInfo + state;
   125     }
   127     return info;
   128   }
   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   }
   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   }
   154   /**
   155    * Check utility functions.
   156    */
   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   }
   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   }
   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   }
   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   }
   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);
   219         if (expectedCalls.length === 0) {
   220           container.oncallschanged = null;
   221           callback();
   222         }
   223       }
   224     };
   225   }
   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;
   245       is(call.group, group);
   246       callback();
   247     };
   248   }
   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;
   268       is(container.state, state);
   269       callback();
   270     };
   271   }
   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;
   284     return function(call, callName, callback) {
   285       call[event] = function() {
   286         log("Received '" + state + "' event for the " + callName);
   287         call[event] = null;
   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   }
   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();
   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         });
   320     return deferred.promise;
   321   }
   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   }
   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   }
   366   /**
   367    * Request utility functions.
   368    */
   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   }
   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);
   403     let deferred = Promise.defer();
   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);
   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     });
   421     return deferred.promise;
   422   }
   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.");
   437     let deferred = Promise.defer();
   438     let done = function() {
   439       deferred.resolve(call);
   440     };
   442     let pending = ["call.onconnected"];
   443     let receive = function(name) {
   444       receivedPending(name, pending, done);
   445     };
   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     }
   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     };
   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();
   475     return deferred.promise;
   476   }
   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.");
   488     let deferred = Promise.defer();
   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     };
   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();
   507     return deferred.promise;
   508   }
   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.");
   520     let deferred = Promise.defer();
   522     telephony.onincoming = function onincoming(event) {
   523       log("Received 'incoming' call event.");
   524       telephony.onincoming = null;
   526       let call = event.call;
   528       ok(call);
   529       is(call.number, number);
   530       is(call.state, "incoming");
   532       deferred.resolve(call);
   533     };
   534     emulator.run("gsm call " + number);
   536     return deferred.promise;
   537   }
   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.");
   549     let deferred = Promise.defer();
   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);
   559     return deferred.promise;
   560   }
   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.");
   572     let deferred = Promise.defer();
   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);
   582     return deferred.promise;
   583   }
   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();
   595     for (let call of calls) {
   596       promise = promise.then(remoteHangUp.bind(null, call));
   597     }
   599     return promise;
   600   }
   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.");
   616     let deferred = Promise.defer();
   617     let done = function() {
   618       deferred.resolve();
   619     };
   621     let pending = ["conference.oncallschanged", "conference.onconnected"];
   622     let receive = function(name) {
   623       receivedPending(name, pending, done);
   624     };
   626     let check_onconnected  = StateEventChecker('connected', 'onresuming');
   628     for (let call of callsToAdd) {
   629       let callName = "callToAdd (" + call.number + ')';
   631       let ongroupchange = callName + ".ongroupchange";
   632       pending.push(ongroupchange);
   633       check_ongroupchange(call, callName, conference,
   634                           receive.bind(null, ongroupchange));
   636       let onstatechange = callName + ".onstatechange";
   637       pending.push(onstatechange);
   638       check_onstatechange(call, callName, 'connected',
   639                           receive.bind(null, onstatechange));
   640     }
   642     check_oncallschanged(conference, 'conference', callsToAdd,
   643                          receive.bind(null, "conference.oncallschanged"));
   645     check_onconnected(conference, "conference", function() {
   646       ok(!conference.oncallschanged);
   647       if (typeof connectedCallback === 'function') {
   648         connectedCallback();
   649       }
   650       receive("conference.onconnected");
   651     });
   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     }
   660     return deferred.promise;
   661   }
   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.");
   676     let deferred = Promise.defer();
   677     let done = function() {
   678       deferred.resolve();
   679     };
   681     let pending = ["conference.onholding", "conference.onheld"];
   682     let receive = function(name) {
   683       receivedPending(name, pending, done);
   684     };
   686     let check_onholding = StateEventChecker('holding', null);
   687     let check_onheld = StateEventChecker('held', 'onholding');
   689     for (let call of calls) {
   690       let callName = "call (" + call.number + ')';
   692       let onholding = callName + ".onholding";
   693       pending.push(onholding);
   694       check_onholding(call, callName, receive.bind(null, onholding));
   696       let onheld = callName + ".onheld";
   697       pending.push(onheld);
   698       check_onheld(call, callName, receive.bind(null, onheld));
   699     }
   701     check_onholding(conference, "conference",
   702                     receive.bind(null, "conference.onholding"));
   704     check_onheld(conference, "conference", function() {
   705       if (typeof heldCallback === 'function') {
   706         heldCallback();
   707       }
   708       receive("conference.onheld");
   709     });
   711     conference.hold();
   713     return deferred.promise;
   714   }
   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.");
   729     let deferred = Promise.defer();
   730     let done = function() {
   731       deferred.resolve();
   732     };
   734     let pending = ["conference.onresuming", "conference.onconnected"];
   735     let receive = function(name) {
   736       receivedPending(name, pending, done);
   737     };
   739     let check_onresuming   = StateEventChecker('resuming', null);
   740     let check_onconnected  = StateEventChecker('connected', 'onresuming');
   742     for (let call of calls) {
   743       let callName = "call (" + call.number + ')';
   745       let onresuming = callName + ".onresuming";
   746       pending.push(onresuming);
   747       check_onresuming(call, callName, receive.bind(null, onresuming));
   749       let onconnected = callName + ".onconnected";
   750       pending.push(onconnected);
   751       check_onconnected(call, callName, receive.bind(null, onconnected));
   752     }
   754     check_onresuming(conference, "conference",
   755                      receive.bind(null, "conference.onresuming"));
   757     check_onconnected(conference, "conference", function() {
   758       if (typeof connectedCallback === 'function') {
   759         connectedCallback();
   760       }
   761       receive("conference.onconnected");
   762     });
   764     conference.resume();
   766     return deferred.promise;
   767   }
   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.");
   787     is(conference.state, 'connected');
   789     let deferred = Promise.defer();
   790     let done = function() {
   791       deferred.resolve();
   792     };
   794     let pending = ["callToRemove.ongroupchange", "telephony.oncallschanged",
   795                    "conference.oncallschanged", "conference.onstatechange"];
   796     let receive = function(name) {
   797       receivedPending(name, pending, done);
   798     };
   800     // Remained call in conference will be held.
   801     for (let call of remainedCalls) {
   802       let callName = "remainedCall (" + call.number + ')';
   804       let onstatechange = callName + ".onstatechange";
   805       pending.push(onstatechange);
   806       check_onstatechange(call, callName, 'held',
   807                           receive.bind(null, onstatechange));
   808     }
   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 + ')';
   815       let ongroupchange = callName + ".ongroupchange";
   816       pending.push(ongroupchange);
   817       check_ongroupchange(call, callName, null,
   818                           receive.bind(null, ongroupchange));
   820       let onstatechange = callName + ".onstatechange";
   821       pending.push(onstatechange);
   822       check_onstatechange(call, callName, 'held',
   823                           receive.bind(null, onstatechange));
   824     }
   826     check_ongroupchange(callToRemove, "callToRemove", null, function() {
   827       is(callToRemove.state, 'connected');
   828       receive("callToRemove.ongroupchange");
   829     });
   831     check_oncallschanged(telephony, 'telephony',
   832                          autoRemovedCalls.concat(callToRemove),
   833                          receive.bind(null, "telephony.oncallschanged"));
   835     check_oncallschanged(conference, 'conference',
   836                          autoRemovedCalls.concat(callToRemove), function() {
   837       is(conference.calls.length, remainedCalls.length);
   838       receive("conference.oncallschanged");
   839     });
   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     });
   850     conference.remove(callToRemove);
   852     return deferred.promise;
   853   }
   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.");
   873     let deferred = Promise.defer();
   874     let done = function() {
   875       deferred.resolve();
   876     };
   878     let pending = ["conference.oncallschanged", "remoteHangUp"];
   879     let receive = function(name) {
   880       receivedPending(name, pending, done);
   881     };
   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 + ')';
   888       let ongroupchange = callName + ".ongroupchange";
   889       pending.push(ongroupchange);
   890       check_ongroupchange(call, callName, null,
   891                           receive.bind(null, ongroupchange));
   892     }
   894     if (autoRemovedCalls.length) {
   895       pending.push("telephony.oncallschanged");
   896       check_oncallschanged(telephony, 'telephony',
   897                            autoRemovedCalls,
   898                            receive.bind(null, "telephony.oncallschanged"));
   899     }
   901     check_oncallschanged(conference, 'conference',
   902                          autoRemovedCalls.concat(callToHangUp), function() {
   903       is(conference.calls.length, remainedCalls.length);
   904       receive("conference.oncallschanged");
   905     });
   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     }
   918     remoteHangUp(callToHangUp)
   919       .then(receive.bind(null, "remoteHangUp"));
   921     return deferred.promise;
   922   }
   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.');
   936     let outCall;
   937     let inCall;
   938     let outInfo = outCallStrPool(outNumber);
   939     let inInfo = inCallStrPool(inNumber);
   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   }
   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.');
   979     let outCall;
   980     let inCall;
   981     let inCall2;
   982     let outInfo = outCallStrPool(outNumber);
   983     let inInfo = inCallStrPool(inNumber);
   984     let inInfo2 = inCallStrPool(inNumber2);
   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       });
  1012   /**
  1013    * Setup a conference with an outgoing call and four incoming calls.
  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.');
  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);
  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       });
  1093   /**
  1094    * Public members.
  1095    */
  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 }());
  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);
  1129   function permissionTearDown() {
  1130     SpecialPowers.clearUserPref("dom.mozSettings.enabled");
  1131     for (let per of permissions) {
  1132       SpecialPowers.removePermission(per, document);
  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);
  1148   // Extend finish() with tear down.
  1149   finish = (function() {
  1150     let originalFinish = finish;
  1152     function tearDown() {
  1153       log("== Test TearDown ==");
  1154       restoreTelephonyDial();
  1155       emulator.waitFinish()
  1156         .then(permissionTearDown)
  1157         .then(function() {
  1158           originalFinish.apply(this, arguments);
  1159         });
  1162     return tearDown.bind(this);
  1163   }());
  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       });
  1176   mainTest();
  1179 function startTest(test) {
  1180   _startTest(["telephony"], test);
  1183 function startTestWithPermissions(permissions, test) {
  1184   _startTest(permissions.concat("telephony"), test);
  1187 function startDSDSTest(test) {
  1188   let numRIL;
  1189   try {
  1190     numRIL = SpecialPowers.getIntPref("ril.numRadioInterfaces");
  1191   } catch (ex) {
  1192     numRIL = 1;  // Pref not set.
  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();

mercurial