Bug 989728 - [B2G] [RIL] split test_conference.js into smaller pieces to avoid timeout failure. r=vicamo
authorHsin-Yi Tsai <htsai@mozilla.com>
Tue, 08 Apr 2014 14:10:06 +0800
changeset 196818 7348f3d4ec58529b8987b58720c12ff8374c9dfe
parent 196817 d51c091a7eb3df44ca2a625fa9db9ce146cd4a8d
child 196819 dc921822936584afee5a5876b36cb05693cdbc20
push id3624
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:49:01 +0000
treeherdermozilla-beta@b1a5da15899a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvicamo
bugs989728
milestone31.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 989728 - [B2G] [RIL] split test_conference.js into smaller pieces to avoid timeout failure. r=vicamo * * * split test_conference.js - p4 - split test_conference
dom/telephony/test/marionette/head.js
dom/telephony/test/marionette/manifest.ini
dom/telephony/test/marionette/test_audiomanager_phonestate.js
dom/telephony/test/marionette/test_conference.js
dom/telephony/test/marionette/test_conference_add_error.js
dom/telephony/test/marionette/test_conference_remove_error.js
dom/telephony/test/marionette/test_conference_three_hangup_one.js
dom/telephony/test/marionette/test_conference_three_remove_one.js
dom/telephony/test/marionette/test_conference_two_calls.js
dom/telephony/test/marionette/test_conference_two_hangup_one.js
dom/telephony/test/marionette/test_conference_two_hold_resume.js
dom/telephony/test/marionette/test_conference_two_remove_one.js
dom/telephony/test/marionette/test_dsds_connection_conflict.js
dom/telephony/test/marionette/test_dsds_normal_call.js
dom/telephony/test/marionette/test_outgoing_emergency_in_airplane_mode.js
dom/telephony/test/marionette/test_outgoing_radio_off.js
--- a/dom/telephony/test/marionette/head.js
+++ b/dom/telephony/test/marionette/head.js
@@ -1,13 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 let Promise = SpecialPowers.Cu.import("resource://gre/modules/Promise.jsm").Promise;
 let telephony;
+let conference;
 
 /**
  * Emulator helper.
  */
 let emulator = (function() {
   let pendingCmdCount = 0;
   let originalRunEmulatorCmd = runEmulatorCmd;
 
@@ -46,24 +47,16 @@ let emulator = (function() {
     waitFinish: waitFinish
   };
 }());
 
 /**
  * Telephony related helper functions.
  */
 (function() {
-  function checkInitialState() {
-    log("Verify initial state.");
-    ok(telephony, "telephony");
-    is(telephony.active, null, "telephony.active");
-    ok(telephony.calls, "telephony.calls");
-    is(telephony.calls.length, 0, "telephony.calls.length");
-  }
-
   /**
    * @return Promise
    */
   function clearCalls() {
     let deferred = Promise.defer();
 
     log("Clear existing calls.");
     emulator.run("gsm clear", function(result) {
@@ -77,18 +70,992 @@ let emulator = (function() {
         log("Failed to clear existing calls.");
         deferred.reject();
       }
     });
 
     return deferred.promise;
   }
 
-  this.checkInitialState = checkInitialState;
-  this.clearCalls = clearCalls;
+  /**
+   * Provide a string with format of the emulator call list result.
+   *
+   * @param prefix
+   *        Possible values are "outbound" and "inbound".
+   * @param number
+   *        Call number.
+   * @return A string with format of the emulator call list result.
+   */
+  function callStrPool(prefix, number) {
+    let padding = "           : ";
+    let numberInfo = prefix + number + padding.substr(number.length);
+
+    let info = {};
+    let states = ['ringing', 'incoming', 'active', 'held'];
+    for (let state of states) {
+      info[state] = numberInfo + state;
+    }
+
+    return info;
+  }
+
+  /**
+   * Provide a corresponding string of an outgoing call. The string is with
+   * format of the emulator call list result.
+   *
+   * @param number
+   *        Number of an outgoing call.
+   * @return A string with format of the emulator call list result.
+   */
+  function outCallStrPool(number) {
+    return callStrPool("outbound to  ", number);
+  }
+
+  /**
+   * Provide a corresponding string of an incoming call. The string is with
+   * format of the emulator call list result.
+   *
+   * @param number
+   *        Number of an incoming call.
+   * @return A string with format of the emulator call list result.
+   */
+  function inCallStrPool(number) {
+    return callStrPool("inbound from ", number);
+  }
+
+  /**
+   * Check utility functions.
+   */
+
+  function checkInitialState() {
+    log("Verify initial state.");
+    ok(telephony.calls, 'telephony.call');
+    checkTelephonyActiveAndCalls(null, []);
+    ok(conference.calls, 'conference.calls');
+    checkConferenceStateAndCalls('', []);
+  }
+
+  /**
+   * Convenient helper to compare a TelephonyCall and a received call event.
+   */
+  function checkEventCallState(event, call, state) {
+    is(call, event.call, "event.call");
+    is(call.state, state, "call state");
+  }
+
+  /**
+   * Convenient helper to check mozTelephony.active and mozTelephony.calls.
+   */
+  function checkTelephonyActiveAndCalls(active, calls) {
+    is(telephony.active, active, "telephony.active");
+    is(telephony.calls.length, calls.length, "telephony.calls");
+    for (let i = 0; i < calls.length; ++i) {
+      is(telephony.calls[i], calls[i]);
+    }
+  }
+
+  /**
+   * Convenient helper to check mozTelephony.conferenceGroup.state and
+   * .conferenceGroup.calls.
+   */
+  function checkConferenceStateAndCalls(state, calls) {
+    is(conference.state, state, "conference.state");
+    is(conference.calls.length, calls.length, "conference.calls");
+    for (let i = 0; i < calls.length; i++) {
+      is(conference.calls[i], calls[i]);
+    }
+  }
+
+  /**
+   * Convenient helper to handle *.oncallschanged event.
+   *
+   * @param container
+   *        Representation of "mozTelephony" or "mozTelephony.conferenceGroup."
+   * @param containerName
+   *        Name of container. Could be an arbitrary string, used for debug
+   *        messages only.
+   * @param expectedCalls
+   *        An array of calls.
+   * @param callback
+   *        A callback function.
+   */
+  function check_oncallschanged(container, containerName, expectedCalls,
+                                callback) {
+    container.oncallschanged = function(event) {
+      log("Received 'callschanged' event for the " + containerName);
+      if (event.call) {
+        let index = expectedCalls.indexOf(event.call);
+        ok(index != -1);
+        expectedCalls.splice(index, 1);
+
+        if (expectedCalls.length === 0) {
+          container.oncallschanged = null;
+          callback();
+        }
+      }
+    };
+  }
+
+  /**
+   * Convenient helper to handle *.ongroupchange event.
+   *
+   * @param call
+   *        A TelephonyCall object.
+   * @param callName
+   *        Name of a call. Could be an arbitrary string, used for debug messages
+   *        only.
+   * @param group
+   *        Representation of mozTelephony.conferenceGroup.
+   * @param callback
+   *        A callback function.
+   */
+  function check_ongroupchange(call, callName, group, callback) {
+    call.ongroupchange = function(event) {
+      log("Received 'groupchange' event for the " + callName);
+      call.ongroupchange = null;
+
+      is(call.group, group);
+      callback();
+    };
+  }
+
+  /**
+   * Convenient helper to handle *.onstatechange event.
+   *
+   * @param container
+   *        Representation of a TelephonyCall or mozTelephony.conferenceGroup.
+   * @param containerName
+   *        Name of container. Could be an arbitrary string, used for debug messages
+   *        only.
+   * @param state
+   *        A string.
+   * @param callback
+   *        A callback function.
+   */
+  function check_onstatechange(container, containerName, state, callback) {
+    container.onstatechange = function(event) {
+      log("Received 'statechange' event for the " + containerName);
+      container.onstatechange = null;
+
+      is(container.state, state);
+      callback();
+    };
+  }
+
+  /**
+   * Convenient helper to check the sequence of call state and event handlers.
+   *
+   * @param state
+   *        A string of the expected call state.
+   * @param previousEvent
+   *        A string of the event that should come before the expected state.
+   */
+  function StateEventChecker(state, previousEvent) {
+    let event = 'on' + state;
+
+    return function(call, callName, callback) {
+      call[event] = function() {
+        log("Received '" + state + "' event for the " + callName);
+        call[event] = null;
+
+        if (previousEvent) {
+          // We always clear the event handler when the event is received.
+          // Therefore, if the corresponding handler is not existed, the expected
+          // previous event has been already received.
+          ok(!call[previousEvent]);
+        }
+        is(call.state, state);
+        callback();
+      };
+    };
+  }
+
+  /**
+   * Convenient helper to check the call list existing in the emulator.
+   *
+   * @param expectedCallList
+   *        An array of call info with the format of "callStrPool()[state]".
+   * @return A deferred promise.
+   */
+  function checkEmulatorCallList(expectedCallList) {
+    let deferred = Promise.defer();
+
+    emulator.run("gsm list", function(result) {
+        log("Call list is now: " + result);
+        for (let i = 0; i < expectedCallList.length; ++i) {
+          is(result[i], expectedCallList[i], "emulator calllist");
+        }
+        is(result[expectedCallList.length], "OK", "emulator calllist");
+        deferred.resolve();
+        });
+
+    return deferred.promise;
+  }
+
+  /**
+   * Super convenient helper to check calls and state of mozTelephony and
+   * mozTelephony.conferenceGroup.
+   *
+   * @param active
+   *        A TelephonyCall object. Should be the expected active call.
+   * @param calls
+   *        An array of TelephonyCall objects. Should be the expected list of
+   *        mozTelephony.calls.
+   * @param conferenceState
+   *        A string. Should be the expected conference state.
+   * @param conferenceCalls
+   *        An array of TelephonyCall objects. Should be the expected list of
+   *        mozTelephony.conferenceGroup.calls.
+   */
+  function checkState(active, calls, conferenceState, conferenceCalls) {
+    checkTelephonyActiveAndCalls(active, calls);
+    checkConferenceStateAndCalls(conferenceState, conferenceCalls);
+  }
+
+  /**
+   * Super convenient helper to check calls and state of mozTelephony and
+   * mozTelephony.conferenceGroup as well as the calls existing in the emulator.
+   *
+   * @param active
+   *        A TelephonyCall object. Should be the expected active call.
+   * @param calls
+   *        An array of TelephonyCall objects. Should be the expected list of
+   *        mozTelephony.calls.
+   * @param conferenceState
+   *        A string. Should be the expected conference state.
+   * @param conferenceCalls
+   *        An array of TelephonyCall objects. Should be the expected list of
+   *        mozTelephony.conferenceGroup.calls.
+   * @param callList
+   *        An array of call info with the format of "callStrPool()[state]".
+   * @return A deferred promise.
+   */
+  function checkAll(active, calls, conferenceState, conferenceCalls, callList) {
+    checkState(active, calls, conferenceState, conferenceCalls);
+    return checkEmulatorCallList(callList);
+  }
+
+  /**
+   * Request utility functions.
+   */
+
+  /**
+   * Make sure there's no pending event before we jump to the next action.
+   *
+   * @param received
+   *        A string of the received event.
+   * @param pending
+   *        An array of the pending events.
+   * @param nextAction
+   *        A callback function that is called when there's no pending event.
+   */
+  function receivedPending(received, pending, nextAction) {
+    let index = pending.indexOf(received);
+    if (index != -1) {
+      pending.splice(index, 1);
+    }
+    if (pending.length === 0) {
+      nextAction();
+    }
+  }
+
+  /**
+   * Make an outgoing call.
+   *
+   * @param number
+   *        A string.
+   * @param serviceId [optional]
+   *        Identification of a service. 0 is set as default.
+   * @return A deferred promise.
+   */
+  function dial(number, serviceId) {
+    serviceId = typeof serviceId !== "undefined" ? serviceId : 0;
+    log("Make an outgoing call: " + number + ", serviceId: " + serviceId);
+
+    let deferred = Promise.defer();
+
+    telephony.dial(number, serviceId).then(call => {
+      ok(call);
+      is(call.number, number);
+      is(call.state, "dialing");
+      is(call.serviceId, serviceId);
+
+      call.onalerting = function onalerting(event) {
+        call.onalerting = null;
+        log("Received 'onalerting' call event.");
+        checkEventCallState(event, call, "alerting");
+        deferred.resolve(call);
+      };
+    });
+
+    return deferred.promise;
+  }
+
+  /**
+   * Answer an incoming call.
+   *
+   * @param call
+   *        An incoming TelephonyCall object.
+   * @param conferenceStateChangeCallback [optional]
+   *        A callback function which is called if answering an incoming call
+   *        triggers conference state change.
+   * @return A deferred promise.
+   */
+  function answer(call, conferenceStateChangeCallback) {
+    log("Answering the incoming call.");
+
+    let deferred = Promise.defer();
+    let done = function() {
+      deferred.resolve(call);
+    };
+
+    let pending = ["call.onconnected"];
+    let receive = function(name) {
+      receivedPending(name, pending, done);
+    };
+
+    // When there's already a connected conference call, answering a new incoming
+    // call triggers conference state change. We should wait for
+    // |conference.onstatechange| before checking the state of the conference call.
+    if (conference.state === "connected") {
+      pending.push("conference.onstatechange");
+      check_onstatechange(conference, "conference", "held", function() {
+        if (typeof conferenceStateChangeCallback === "function") {
+          conferenceStateChangeCallback();
+        }
+        receive("conference.onstatechange");
+      });
+    }
+
+    call.onconnecting = function onconnectingIn(event) {
+      log("Received 'connecting' call event for incoming call.");
+      call.onconnecting = null;
+      checkEventCallState(event, call, "connecting");
+    };
+
+    call.onconnected = function onconnectedIn(event) {
+      log("Received 'connected' call event for incoming call.");
+      call.onconnected = null;
+      checkEventCallState(event, call, "connected");
+      ok(!call.onconnecting);
+      receive("call.onconnected");
+    };
+    call.answer();
+
+    return deferred.promise;
+  }
+
+  /**
+   * Simulate an incoming call.
+   *
+   * @param number
+   *        A string.
+   * @return A deferred promise.
+   */
+  function remoteDial(number) {
+    log("Simulating an incoming call.");
+
+    let deferred = Promise.defer();
+
+    telephony.onincoming = function onincoming(event) {
+      log("Received 'incoming' call event.");
+      telephony.onincoming = null;
+
+      let call = event.call;
+
+      ok(call);
+      is(call.number, number);
+      is(call.state, "incoming");
+
+      deferred.resolve(call);
+    };
+    emulator.run("gsm call " + number);
+
+    return deferred.promise;
+  }
+
+  /**
+   * Remote party answers the call.
+   *
+   * @param call
+   *        A TelephonyCall object.
+   * @return A deferred promise.
+   */
+  function remoteAnswer(call) {
+    log("Remote answering the call.");
+
+    let deferred = Promise.defer();
+
+    call.onconnected = function onconnected(event) {
+      log("Received 'connected' call event.");
+      call.onconnected = null;
+      checkEventCallState(event, call, "connected");
+      deferred.resolve(call);
+    };
+    emulator.run("gsm accept " + call.number);
+
+    return deferred.promise;
+  }
+
+  /**
+   * Remote party hangs up the call.
+   *
+   * @param call
+   *        A TelephonyCall object.
+   * @return A deferred promise.
+   */
+  function remoteHangUp(call) {
+    log("Remote hanging up the call.");
+
+    let deferred = Promise.defer();
+
+    call.ondisconnected = function ondisconnected(event) {
+      log("Received 'disconnected' call event.");
+      call.ondisconnected = null;
+      checkEventCallState(event, call, "disconnected");
+      deferred.resolve(call);
+    };
+    emulator.run("gsm cancel " + call.number);
+
+    return deferred.promise;
+  }
+
+  /**
+   * Remote party hangs up all the calls.
+   *
+   * @param calls
+   *        An array of TelephonyCall objects.
+   * @return A deferred promise.
+   */
+  function remoteHangUpCalls(calls) {
+    let promise = Promise.resolve();
+
+    for (let call of calls) {
+      promise = promise.then(remoteHangUp.bind(null, call));
+    }
+
+    return promise;
+  }
+
+  /**
+   * Add calls to conference.
+   *
+   * @param callsToAdd
+   *        An array of TelephonyCall objects to be added into conference. The
+   *        length of the array should be 1 or 2.
+   * @param connectedCallback [optional]
+   *        A callback function which is called when conference state becomes
+   *        connected.
+   * @return A deferred promise.
+   */
+  function addCallsToConference(callsToAdd, connectedCallback) {
+    log("Add " + callsToAdd.length + " calls into conference.");
+
+    let deferred = Promise.defer();
+    let done = function() {
+      deferred.resolve();
+    };
+
+    let pending = ["conference.oncallschanged", "conference.onconnected"];
+    let receive = function(name) {
+      receivedPending(name, pending, done);
+    };
+
+    let check_onconnected  = StateEventChecker('connected', 'onresuming');
+
+    for (let call of callsToAdd) {
+      let callName = "callToAdd (" + call.number + ')';
+
+      let ongroupchange = callName + ".ongroupchange";
+      pending.push(ongroupchange);
+      check_ongroupchange(call, callName, conference,
+                          receive.bind(null, ongroupchange));
+
+      let onstatechange = callName + ".onstatechange";
+      pending.push(onstatechange);
+      check_onstatechange(call, callName, 'connected',
+                          receive.bind(null, onstatechange));
+    }
+
+    check_oncallschanged(conference, 'conference', callsToAdd,
+                         receive.bind(null, "conference.oncallschanged"));
+
+    check_onconnected(conference, "conference", function() {
+      ok(!conference.oncallschanged);
+      if (typeof connectedCallback === 'function') {
+        connectedCallback();
+      }
+      receive("conference.onconnected");
+    });
+
+    // Cannot use apply() through webidl, so just separate the cases to handle.
+    if (callsToAdd.length == 2) {
+      conference.add(callsToAdd[0], callsToAdd[1]);
+    } else {
+      conference.add(callsToAdd[0]);
+    }
+
+    return deferred.promise;
+  }
+
+  /**
+   * Hold the conference.
+   *
+   * @param calls
+   *        An array of TelephonyCall objects existing in conference.
+   * @param heldCallback [optional]
+   *        A callback function which is called when conference state becomes
+   *        held.
+   * @return A deferred promise.
+   */
+  function holdConference(calls, heldCallback) {
+    log("Holding the conference call.");
+
+    let deferred = Promise.defer();
+    let done = function() {
+      deferred.resolve();
+    };
+
+    let pending = ["conference.onholding", "conference.onheld"];
+    let receive = function(name) {
+      receivedPending(name, pending, done);
+    };
+
+    let check_onholding = StateEventChecker('holding', null);
+    let check_onheld = StateEventChecker('held', 'onholding');
+
+    for (let call of calls) {
+      let callName = "call (" + call.number + ')';
+
+      let onholding = callName + ".onholding";
+      pending.push(onholding);
+      check_onholding(call, callName, receive.bind(null, onholding));
+
+      let onheld = callName + ".onheld";
+      pending.push(onheld);
+      check_onheld(call, callName, receive.bind(null, onheld));
+    }
+
+    check_onholding(conference, "conference",
+                    receive.bind(null, "conference.onholding"));
+
+    check_onheld(conference, "conference", function() {
+      if (typeof heldCallback === 'function') {
+        heldCallback();
+      }
+      receive("conference.onheld");
+    });
+
+    conference.hold();
+
+    return deferred.promise;
+  }
+
+  /**
+   * Resume the conference.
+   *
+   * @param calls
+   *        An array of TelephonyCall objects existing in conference.
+   * @param connectedCallback [optional]
+   *        A callback function which is called when conference state becomes
+   *        connected.
+   * @return A deferred promise.
+   */
+  function resumeConference(calls, connectedCallback) {
+    log("Resuming the held conference call.");
+
+    let deferred = Promise.defer();
+    let done = function() {
+      deferred.resolve();
+    };
+
+    let pending = ["conference.onresuming", "conference.onconnected"];
+    let receive = function(name) {
+      receivedPending(name, pending, done);
+    };
+
+    let check_onresuming   = StateEventChecker('resuming', null);
+    let check_onconnected  = StateEventChecker('connected', 'onresuming');
+
+    for (let call of calls) {
+      let callName = "call (" + call.number + ')';
+
+      let onresuming = callName + ".onresuming";
+      pending.push(onresuming);
+      check_onresuming(call, callName, receive.bind(null, onresuming));
+
+      let onconnected = callName + ".onconnected";
+      pending.push(onconnected);
+      check_onconnected(call, callName, receive.bind(null, onconnected));
+    }
+
+    check_onresuming(conference, "conference",
+                     receive.bind(null, "conference.onresuming"));
+
+    check_onconnected(conference, "conference", function() {
+      if (typeof connectedCallback === 'function') {
+        connectedCallback();
+      }
+      receive("conference.onconnected");
+    });
+
+    conference.resume();
+
+    return deferred.promise;
+  }
+
+  /**
+   * Remove a call out of conference.
+   *
+   * @param callToRemove
+   *        A TelephonyCall object existing in conference.
+   * @param autoRemovedCalls
+   *        An array of TelephonyCall objects which is going to be automatically
+   *        removed. The length of the array should be 0 or 1.
+   * @param remainedCalls
+   *        An array of TelephonyCall objects which remain in conference.
+   * @param stateChangeCallback [optional]
+   *        A callback function which is called when conference state changes.
+   * @return A deferred promise.
+   */
+  function removeCallInConference(callToRemove, autoRemovedCalls, remainedCalls,
+                                  statechangeCallback) {
+    log("Removing a participant from the conference call.");
+
+    is(conference.state, 'connected');
+
+    let deferred = Promise.defer();
+    let done = function() {
+      deferred.resolve();
+    };
+
+    let pending = ["callToRemove.ongroupchange", "telephony.oncallschanged",
+                   "conference.oncallschanged", "conference.onstatechange"];
+    let receive = function(name) {
+      receivedPending(name, pending, done);
+    };
+
+    // Remained call in conference will be held.
+    for (let call of remainedCalls) {
+      let callName = "remainedCall (" + call.number + ')';
+
+      let onstatechange = callName + ".onstatechange";
+      pending.push(onstatechange);
+      check_onstatechange(call, callName, 'held',
+                          receive.bind(null, onstatechange));
+    }
+
+    // When a call is removed from conference with 2 calls, another one will be
+    // automatically removed from group and be put on hold.
+    for (let call of autoRemovedCalls) {
+      let callName = "autoRemovedCall (" + call.number + ')';
+
+      let ongroupchange = callName + ".ongroupchange";
+      pending.push(ongroupchange);
+      check_ongroupchange(call, callName, null,
+                          receive.bind(null, ongroupchange));
+
+      let onstatechange = callName + ".onstatechange";
+      pending.push(onstatechange);
+      check_onstatechange(call, callName, 'held',
+                          receive.bind(null, onstatechange));
+    }
+
+    check_ongroupchange(callToRemove, "callToRemove", null, function() {
+      is(callToRemove.state, 'connected');
+      receive("callToRemove.ongroupchange");
+    });
+
+    check_oncallschanged(telephony, 'telephony',
+                         autoRemovedCalls.concat(callToRemove),
+                         receive.bind(null, "telephony.oncallschanged"));
+
+    check_oncallschanged(conference, 'conference',
+                         autoRemovedCalls.concat(callToRemove), function() {
+      is(conference.calls.length, remainedCalls.length);
+      receive("conference.oncallschanged");
+    });
+
+    check_onstatechange(conference, 'conference',
+                        (remainedCalls.length ? 'held' : ''), function() {
+      ok(!conference.oncallschanged);
+      if (typeof statechangeCallback === 'function') {
+        statechangeCallback();
+      }
+      receive("conference.onstatechange");
+    });
+
+    conference.remove(callToRemove);
+
+    return deferred.promise;
+  }
+
+  /**
+   * Hangup a call in conference.
+   *
+   * @param callToHangUp
+   *        A TelephonyCall object existing in conference.
+   * @param autoRemovedCalls
+   *        An array of TelephonyCall objects which is going to be automatically
+   *        removed. The length of the array should be 0 or 1.
+   * @param remainedCalls
+   *        An array of TelephonyCall objects which remain in conference.
+   * @param stateChangeCallback [optional]
+   *        A callback function which is called when conference state changes.
+   * @return A deferred promise.
+   */
+  function hangUpCallInConference(callToHangUp, autoRemovedCalls, remainedCalls,
+                                  statechangeCallback) {
+    log("Release one call in conference.");
+
+    let deferred = Promise.defer();
+    let done = function() {
+      deferred.resolve();
+    };
+
+    let pending = ["conference.oncallschanged", "remoteHangUp"];
+    let receive = function(name) {
+      receivedPending(name, pending, done);
+    };
+
+    // When a call is hang up from conference with 2 calls, another one will be
+    // automatically removed from group.
+    for (let call of autoRemovedCalls) {
+      let callName = "autoRemovedCall (" + call.number + ')';
+
+      let ongroupchange = callName + ".ongroupchange";
+      pending.push(ongroupchange);
+      check_ongroupchange(call, callName, null,
+                          receive.bind(null, ongroupchange));
+    }
+
+    if (autoRemovedCalls.length) {
+      pending.push("telephony.oncallschanged");
+      check_oncallschanged(telephony, 'telephony',
+                           autoRemovedCalls,
+                           receive.bind(null, "telephony.oncallschanged"));
+    }
+
+    check_oncallschanged(conference, 'conference',
+                         autoRemovedCalls.concat(callToHangUp), function() {
+      is(conference.calls.length, remainedCalls.length);
+      receive("conference.oncallschanged");
+    });
+
+    if (remainedCalls.length === 0) {
+      pending.push("conference.onstatechange");
+      check_onstatechange(conference, 'conference', '', function() {
+        ok(!conference.oncallschanged);
+        if (typeof statechangeCallback === 'function') {
+          statechangeCallback();
+        }
+        receive("conference.onstatechange");
+      });
+    }
+
+    remoteHangUp(callToHangUp)
+      .then(receive.bind(null, "remoteHangUp"));
+
+    return deferred.promise;
+  }
+
+  /**
+   * Setup 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 setupConferenceTwoCalls(outNumber, inNumber) {
+    log('Create conference with two calls.');
+
+    let outCall;
+    let inCall;
+    let outInfo = outCallStrPool(outNumber);
+    let inInfo = inCallStrPool(inNumber);
+
+    return Promise.resolve()
+      .then(checkInitialState)
+      .then(() => dial(outNumber))
+      .then(call => { outCall = call; })
+      .then(() => checkAll(outCall, [outCall], '', [], [outInfo.ringing]))
+      .then(() => remoteAnswer(outCall))
+      .then(() => checkAll(outCall, [outCall], '', [], [outInfo.active]))
+      .then(() => remoteDial(inNumber))
+      .then(call => { inCall = call; })
+      .then(() => checkAll(outCall, [outCall, inCall], '', [],
+                           [outInfo.active, inInfo.incoming]))
+      .then(() => answer(inCall))
+      .then(() => checkAll(inCall, [outCall, inCall], '', [],
+                           [outInfo.held, inInfo.active]))
+      .then(() => addCallsToConference([outCall, inCall], function() {
+        checkState(conference, [], 'connected', [outCall, inCall]);
+      }))
+      .then(() => checkAll(conference, [], 'connected', [outCall, inCall],
+                           [outInfo.active, inInfo.active]))
+      .then(() => {
+        return [outCall, inCall];
+      });
+  }
+
+  /**
+   * Setup a conference with an outgoing call and two incoming calls.
+   *
+   * @param outNumber
+   *        Number of an outgoing call.
+   * @param inNumber
+   *        Number of an incoming call.
+   * @param inNumber2
+   *        Number of an incoming call.
+   * @return Promise<[outCall, inCall, inCall2]>
+   */
+  function setupConferenceThreeCalls(outNumber, inNumber, inNumber2) {
+    log('Create conference with three calls.');
+
+    let outCall;
+    let inCall;
+    let inCall2;
+    let outInfo = outCallStrPool(outNumber);
+    let inInfo = inCallStrPool(inNumber);
+    let inInfo2 = inCallStrPool(inNumber2);
+
+    return Promise.resolve()
+      .then(() => setupConferenceTwoCalls(outNumber, inNumber))
+      .then(calls => {
+          outCall = calls[0];
+          inCall = calls[1];
+      })
+      .then(() => remoteDial(inNumber2))
+      .then(call => { inCall2 = call; })
+      .then(() => checkAll(conference, [inCall2], 'connected', [outCall, inCall],
+                           [outInfo.active, inInfo.active, inInfo2.incoming]))
+      .then(() => answer(inCall2, function() {
+        checkState(inCall2, [inCall2], 'held', [outCall, inCall]);
+      }))
+      .then(() => checkAll(inCall2, [inCall2], 'held', [outCall, inCall],
+                           [outInfo.held, inInfo.held, inInfo2.active]))
+      .then(() => addCallsToConference([inCall2], function() {
+        checkState(conference, [], 'connected', [outCall, inCall, inCall2]);
+      }))
+      .then(() => checkAll(conference, [],
+                           'connected', [outCall, inCall, inCall2],
+                           [outInfo.active, inInfo.active, inInfo2.active]))
+      .then(() => {
+        return [outCall, inCall, inCall2];
+      });
+  }
+
+  /**
+   * Setup a conference with an outgoing call and four incoming calls.
+   *
+   * @param outNumber
+   *        Number of an outgoing call.
+   * @param inNumber
+   *        Number of an incoming call.
+   * @param inNumber2
+   *        Number of an incoming call.
+   * @param inNumber3
+   *        Number of an incoming call.
+   * @param inNumber4
+   *        Number of an incoming call.
+   * @return Promise<[outCall, inCall, inCall2, inCall3, inCall4]>
+   */
+  function setupConferenceFiveCalls(outNumber, inNumber, inNumber2, inNumber3,
+                                    inNumber4) {
+    log('Create conference with five calls.');
+
+    let outCall;
+    let inCall;
+    let inCall2;
+    let inCall3;
+    let inCall4;
+    let outInfo = outCallStrPool(outNumber);
+    let inInfo = inCallStrPool(inNumber);
+    let inInfo2 = inCallStrPool(inNumber2);
+    let inInfo3 = inCallStrPool(inNumber3);
+    let inInfo4 = inCallStrPool(inNumber4);
+
+    return Promise.resolve()
+      .then(() => setupConferenceThreeCalls(outNumber, inNumber, inNumber2))
+      .then(calls => {
+        [outCall, inCall, inCall2] = calls;
+      })
+      .then(() => remoteDial(inNumber3))
+      .then(call => {inCall3 = call;})
+      .then(() => checkAll(conference, [inCall3], 'connected',
+                           [outCall, inCall, inCall2],
+                           [outInfo.active, inInfo.active, inInfo2.active,
+                           inInfo3.incoming]))
+      .then(() => answer(inCall3, function() {
+        checkState(inCall3, [inCall3], 'held', [outCall, inCall, inCall2]);
+      }))
+      .then(() => checkAll(inCall3, [inCall3], 'held',
+                           [outCall, inCall, inCall2],
+                           [outInfo.held, inInfo.held, inInfo2.held,
+                            inInfo3.active]))
+      .then(() => addCallsToConference([inCall3], function() {
+        checkState(conference, [], 'connected', [outCall, inCall, inCall2, inCall3]);
+      }))
+      .then(() => checkAll(conference, [], 'connected',
+                           [outCall, inCall, inCall2, inCall3],
+                           [outInfo.active, inInfo.active, inInfo2.active,
+                            inInfo3.active]))
+      .then(() => remoteDial(inNumber4))
+      .then(call => {inCall4 = call;})
+      .then(() => checkAll(conference, [inCall4], 'connected',
+                           [outCall, inCall, inCall2, inCall3],
+                           [outInfo.active, inInfo.active, inInfo2.active,
+                            inInfo3.active, inInfo4.incoming]))
+      .then(() => answer(inCall4, function() {
+        checkState(inCall4, [inCall4], 'held', [outCall, inCall, inCall2, inCall3]);
+      }))
+      .then(() => checkAll(inCall4, [inCall4], 'held',
+                           [outCall, inCall, inCall2, inCall3],
+                           [outInfo.held, inInfo.held, inInfo2.held,
+                            inInfo3.held, inInfo4.active]))
+      .then(() => addCallsToConference([inCall4], function() {
+        checkState(conference, [], 'connected', [outCall, inCall, inCall2,
+                                                 inCall3, inCall4]);
+      }))
+      .then(() => checkAll(conference, [], 'connected',
+                           [outCall, inCall, inCall2, inCall3, inCall4],
+                           [outInfo.active, inInfo.active, inInfo2.active,
+                            inInfo3.active, inInfo4.active]))
+      .then(() => {
+        return [outCall, inCall, inCall2, inCall3, inCall4];
+      });
+  }
+
+  /**
+   * Public members.
+   */
+
+  this.gCheckInitialState = checkInitialState;
+  this.gClearCalls = clearCalls;
+  this.gOutCallStrPool = outCallStrPool;
+  this.gInCallStrPool = inCallStrPool;
+  this.gCheckState = checkState;
+  this.gCheckAll = checkAll;
+  this.gDial = dial;
+  this.gAnswer = answer;
+  this.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.gSetupConferenceTwoCalls = setupConferenceTwoCalls;
+  this.gSetupConferenceThreeCalls = setupConferenceThreeCalls;
+  this.gSetupConferenceFiveCalls = setupConferenceFiveCalls;
+  this.gReceivedPending = receivedPending;
 }());
 
 function _startTest(permissions, test) {
   function permissionSetUp() {
     SpecialPowers.setBoolPref("dom.mozSettings.enabled", true);
     for (let per of permissions) {
       SpecialPowers.addPermission(per, true, document);
     }
@@ -102,17 +1069,19 @@ function _startTest(permissions, test) {
   }
 
   function setUp() {
     log("== Test SetUp ==");
     permissionSetUp();
     // Make sure that we get the telephony after adding permission.
     telephony = window.navigator.mozTelephony;
     ok(telephony);
-    return clearCalls().then(checkInitialState);
+    conference = telephony.conferenceGroup;
+    ok(conference);
+    return gClearCalls().then(gCheckInitialState);
   }
 
   // Extend finish() with tear down.
   finish = (function() {
     let originalFinish = finish;
 
     function tearDown() {
       log("== Test TearDown ==");
--- a/dom/telephony/test/marionette/manifest.ini
+++ b/dom/telephony/test/marionette/manifest.ini
@@ -37,15 +37,22 @@ disabled = Bug 820802
 [test_outgoing_onstatechange.js]
 disabled = Bug 821966
 [test_redundant_operations.js]
 disabled = Bug 821927
 [test_multiple_hold.js]
 disabled = Bug 821958
 [test_outgoing_emergency_in_airplane_mode.js]
 [test_emergency_label.js]
-[test_conference.js]
 [test_dsds_default_service_id.js]
 [test_call_mute.js]
 [test_dsds_normal_call.js]
 [test_dsds_connection_conflict.js]
 [test_audiomanager_phonestate.js]
 [test_outgoing_answer_radio_off.js]
+[test_conference_two_calls.js]
+[test_conference_add_error.js]
+[test_conference_remove_error.js]
+[test_conference_two_hangup_one.js]
+[test_conference_two_hold_resume.js]
+[test_conference_two_remove_one.js]
+[test_conference_three_hangup_one.js]
+[test_conference_three_remove_one.js]
--- a/dom/telephony/test/marionette/test_audiomanager_phonestate.js
+++ b/dom/telephony/test/marionette/test_audiomanager_phonestate.js
@@ -9,170 +9,16 @@ const AUDIO_MANAGER_CONTRACT_ID = "@mozi
 // See nsIAudioManager
 const PHONE_STATE_INVALID          = -2;
 const PHONE_STATE_CURRENT          = -1;
 const PHONE_STATE_NORMAL           = 0;
 const PHONE_STATE_RINGTONE         = 1;
 const PHONE_STATE_IN_CALL          = 2;
 const PHONE_STATE_IN_COMMUNICATION = 3;
 
-let conference;
-
-function checkTelephonyActiveAndCalls(active, calls) {
-  is(telephony.active, active, "telephony.active");
-  is(telephony.calls.length, calls.length, "telephony.calls");
-  for (let i = 0; i < calls.length; ++i) {
-    is(telephony.calls[i], calls[i]);
-  }
-}
-
-function checkConferenceStateAndCalls(state, calls) {
-  is(conference.state, state, "conference.state");
-  is(conference.calls.length, calls.length, "conference.calls");
-  for (let i = 0; i < calls.length; i++) {
-    is(conference.calls[i], calls[i]);
-  }
-}
-
-function checkInitialState() {
-  log("Verify initial state.");
-  ok(telephony.calls, 'telephony.call');
-  checkTelephonyActiveAndCalls(null, []);
-  ok(conference.calls, 'conference.calls');
-  checkConferenceStateAndCalls('', []);
-}
-
-function checkEventCallState(event, call, state) {
-  is(call, event.call, "event.call");
-  is(call.state, state, "call state");
-}
-
-function dial(number) {
-  log("Make an outgoing call: " + number);
-
-  let deferred = Promise.defer();
-
-  telephony.dial(number).then(call => {
-    ok(call);
-    is(call.number, number);
-    is(call.state, "dialing");
-
-    call.onalerting = function onalerting(event) {
-      call.onalerting = null;
-      log("Received 'onalerting' call event.");
-      checkEventCallState(event, call, "alerting");
-      deferred.resolve(call);
-    };
-  });
-
-  return deferred.promise;
-}
-
-function answer(call) {
-  log("Answering the incoming call.");
-
-  let deferred = Promise.defer();
-
-  call.onconnecting = function onconnectingIn(event) {
-    log("Received 'connecting' call event for incoming call.");
-    call.onconnecting = null;
-    checkEventCallState(event, call, "connecting");
-  };
-
-  call.onconnected = function onconnectedIn(event) {
-    log("Received 'connected' call event for incoming call.");
-    call.onconnected = null;
-    checkEventCallState(event, call, "connected");
-    ok(!call.onconnecting);
-    deferred.resolve(call);
-  };
-  call.answer();
-
-  return deferred.promise;
-}
-
-function remoteDial(number) {
-  log("Simulating an incoming call.");
-
-  let deferred = Promise.defer();
-
-  telephony.onincoming = function onincoming(event) {
-    log("Received 'incoming' call event.");
-    telephony.onimcoming = null;
-
-    let call = event.call;
-
-    ok(call);
-    is(call.number, number);
-    is(call.state, "incoming");
-
-    deferred.resolve(call);
-  };
-  emulator.run("gsm call " + number);
-
-  return deferred.promise;
-}
-
-function remoteAnswer(call) {
-  log("Remote answering the call.");
-
-  let deferred = Promise.defer();
-
-  call.onconnected = function onconnected(event) {
-    log("Received 'connected' call event.");
-    call.onconnected = null;
-    checkEventCallState(event, call, "connected");
-    deferred.resolve(call);
-  };
-  emulator.run("gsm accept " + call.number);
-
-  return deferred.promise;
-}
-
-function remoteHangUp(call) {
-  log("Remote hanging up the call.");
-
-  let deferred = Promise.defer();
-
-  call.ondisconnected = function ondisconnected(event) {
-    log("Received 'disconnected' call event.");
-    call.ondisconnected = null;
-    checkEventCallState(event, call, "disconnected");
-    deferred.resolve(call);
-  };
-  emulator.run("gsm cancel " + call.number);
-
-  return deferred.promise;
-}
-
-function remoteHangUpCalls(calls) {
-  let promise = Promise.resolve();
-
-  for (let call of calls) {
-    promise = promise.then(remoteHangUp.bind(null, call));
-  }
-
-  return promise;
-}
-
-// The length of callsToAdd should be 2.
-function addCallsToConference(callsToAdd) {
-  log("Add " + callsToAdd.length + " calls into conference.");
-
-  let deferred = Promise.defer();
-
-  conference.onconnected = function() {
-    deferred.resolve();
-  };
-
-  conference.add(callsToAdd[0], callsToAdd[1]);
-
-  return deferred.promise;
-}
-
 let audioManager;
 function checkStates(speakerEnabled, phoneState) {
   if (!audioManager) {
     audioManager = SpecialPowers.Cc[AUDIO_MANAGER_CONTRACT_ID]
                                 .getService(SpecialPowers.Ci.nsIAudioManager);
     ok(audioManager, "nsIAudioManager instance");
   }
 
@@ -198,69 +44,65 @@ function check(phoneStateOrig, phoneStat
   }
 
   telephony.speakerEnabled = false;
   checkStates(false, arguments.length > 2 ? phoneStateDisabled : phoneStateOrig);
 }
 
 // Start the test
 startTest(function() {
-  conference = telephony.conferenceGroup;
-  ok(conference);
-
   let outNumber = "5555550101";
   let inNumber  = "5555550201";
   let outCall;
   let inCall;
 
   Promise.resolve()
-    .then(checkInitialState)
     .then(() => check(PHONE_STATE_CURRENT, PHONE_STATE_NORMAL, PHONE_STATE_NORMAL))
 
     // Dial in
-    .then(() => remoteDial(inNumber))
+    .then(() => gRemoteDial(inNumber))
     .then(call => { inCall = call; })
     // TODO - Bug 948860: should this be {RINGTONE, RINGTONE, RINGTONE}?
     // From current UX spec, there is no chance that an user may enable speaker
     // during alerting, so basically this 'set speaker enable' action can't
     // happen in B2G.
     .then(() => check(PHONE_STATE_RINGTONE, PHONE_STATE_NORMAL, PHONE_STATE_NORMAL))
-    .then(() => answer(inCall))
+    .then(() => gAnswer(inCall))
     .then(() => check(PHONE_STATE_IN_CALL, PHONE_STATE_IN_CALL))
     // Hang up all
-    .then(() => remoteHangUp(inCall))
+    .then(() => gRemoteHangUp(inCall))
     .then(() => check(PHONE_STATE_NORMAL, PHONE_STATE_NORMAL))
 
     // Dial out
-    .then(() => dial(outNumber))
+    .then(() => gDial(outNumber))
     .then(call => { outCall = call; })
     .then(() => check(PHONE_STATE_IN_CALL, PHONE_STATE_IN_CALL))
-    .then(() => remoteAnswer(outCall))
+    .then(() => gRemoteAnswer(outCall))
     .then(() => check(PHONE_STATE_IN_CALL, PHONE_STATE_IN_CALL))
     // Hang up all
-    .then(() => remoteHangUp(outCall))
+    .then(() => gRemoteHangUp(outCall))
     .then(() => check(PHONE_STATE_NORMAL, PHONE_STATE_NORMAL))
 
     // Dial out
-    .then(() => dial(outNumber))
+    .then(() => gDial(outNumber))
     .then(call => { outCall = call; })
     .then(() => check(PHONE_STATE_IN_CALL, PHONE_STATE_IN_CALL))
-    .then(() => remoteAnswer(outCall))
+    .then(() => gRemoteAnswer(outCall))
     .then(() => check(PHONE_STATE_IN_CALL, PHONE_STATE_IN_CALL))
     // Dial out and dial in
-    .then(() => remoteDial(inNumber))
+    .then(() => gRemoteDial(inNumber))
     .then(call => { inCall = call; })
     .then(() => check(PHONE_STATE_IN_CALL, PHONE_STATE_IN_CALL))
-    .then(() => answer(inCall))
+    .then(() => gAnswer(inCall))
     .then(() => check(PHONE_STATE_IN_CALL, PHONE_STATE_IN_CALL))
     // Conference
-    .then(() => addCallsToConference([outCall, inCall]))
+    .then(() => gAddCallsToConference([outCall, inCall]))
     .then(() => check(PHONE_STATE_IN_CALL, PHONE_STATE_IN_CALL))
     // Hang up all
-    .then(() => remoteHangUpCalls([outCall, inCall]))
+    .then(() => gRemoteHangUpCalls([outCall, inCall]))
     .then(() => check(PHONE_STATE_NORMAL, PHONE_STATE_NORMAL))
 
     // End
     .then(null, error => {
       ok(false, 'promise rejects during test.');
     })
     .then(finish);
 });
deleted file mode 100644
--- a/dom/telephony/test/marionette/test_conference.js
+++ /dev/null
@@ -1,967 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-MARIONETTE_TIMEOUT = 60000;
-MARIONETTE_HEAD_JS = 'head.js';
-
-let conference;
-
-/**
- * The functions are created to provide the string format of the emulator call
- * list results.
- *
- * Usage:
- *   let outInfo = outCallStrPool('911');
- *   outInfo.ringing == "outbound to 911        : ringing"
- *   outInfo.active  == "outbound to 911        : active"
- */
-function callStrPool(prefix, number) {
-  let padding = "           : ";
-  let numberInfo = prefix + number + padding.substr(number.length);
-
-  let info = {};
-  let states = ['ringing', 'incoming', 'active', 'held'];
-  for (let state of states) {
-    info[state] = numberInfo + state;
-  }
-
-  return info;
-}
-
-function outCallStrPool(number) {
-  return callStrPool("outbound to  ", number);
-}
-
-function inCallStrPool(number) {
-  return callStrPool("inbound from ", number);
-}
-
-function checkInitialState() {
-  log("Verify initial state.");
-  ok(telephony.calls, 'telephony.call');
-  checkTelephonyActiveAndCalls(null, []);
-  ok(conference.calls, 'conference.calls');
-  checkConferenceStateAndCalls('', []);
-}
-
-function checkEventCallState(event, call, state) {
-  is(call, event.call, "event.call");
-  is(call.state, state, "call state");
-}
-
-function checkTelephonyActiveAndCalls(active, calls) {
-  is(telephony.active, active, "telephony.active");
-  is(telephony.calls.length, calls.length, "telephony.calls");
-  for (let i = 0; i < calls.length; ++i) {
-    is(telephony.calls[i], calls[i]);
-  }
-}
-
-function checkConferenceStateAndCalls(state, calls) {
-  is(conference.state, state, "conference.state");
-  is(conference.calls.length, calls.length, "conference.calls");
-  for (let i = 0; i < calls.length; i++) {
-    is(conference.calls[i], calls[i]);
-  }
-}
-
-function checkState(active, calls, conferenceState, conferenceCalls) {
-  checkTelephonyActiveAndCalls(active, calls);
-  checkConferenceStateAndCalls(conferenceState, conferenceCalls);
-}
-
-function checkEmulatorCallList(expectedCallList) {
-  let deferred = Promise.defer();
-
-  emulator.run("gsm list", function(result) {
-    log("Call list is now: " + result);
-    for (let i = 0; i < expectedCallList.length; ++i) {
-      is(result[i], expectedCallList[i], "emulator calllist");
-    }
-    is(result[expectedCallList.length], "OK", "emulator calllist");
-    deferred.resolve();
-  });
-
-  return deferred.promise;
-}
-
-// Promise.
-function checkAll(active, calls, conferenceState, conferenceCalls, callList) {
-  checkState(active, calls, conferenceState, conferenceCalls);
-  return checkEmulatorCallList(callList);
-}
-
-// Make sure there's no pending event before we jump to the next case.
-function receivedPending(received, pending, nextAction) {
-  let index = pending.indexOf(received);
-  if (index != -1) {
-    pending.splice(index, 1);
-  }
-  if (pending.length === 0) {
-    nextAction();
-  }
-}
-
-function dial(number) {
-  log("Make an outgoing call: " + number);
-
-  let deferred = Promise.defer();
-
-  telephony.dial(number).then(call => {
-    ok(call);
-    is(call.number, number);
-    is(call.state, "dialing");
-
-    call.onalerting = function onalerting(event) {
-      call.onalerting = null;
-      log("Received 'onalerting' call event.");
-      checkEventCallState(event, call, "alerting");
-      deferred.resolve(call);
-    };
-  });
-
-  return deferred.promise;
-}
-
-// Answering an incoming call could trigger conference state change.
-function answer(call, conferenceStateChangeCallback) {
-  log("Answering the incoming call.");
-
-  let deferred = Promise.defer();
-  let done = function() {
-    deferred.resolve(call);
-  };
-
-  let pending = ["call.onconnected"];
-  let receive = function(name) {
-    receivedPending(name, pending, done);
-  };
-
-  // When there's already a connected conference call, answering a new incoming
-  // call triggers conference state change. We should wait for
-  // |conference.onstatechange| before checking the state of the conference call.
-  if (conference.state === "connected") {
-    pending.push("conference.onstatechange");
-    check_onstatechange(conference, "conference", "held", function() {
-      if (typeof conferenceStateChangeCallback === "function") {
-        conferenceStateChangeCallback();
-      }
-      receive("conference.onstatechange");
-    });
-  }
-
-  call.onconnecting = function onconnectingIn(event) {
-    log("Received 'connecting' call event for incoming call.");
-    call.onconnecting = null;
-    checkEventCallState(event, call, "connecting");
-  };
-
-  call.onconnected = function onconnectedIn(event) {
-    log("Received 'connected' call event for incoming call.");
-    call.onconnected = null;
-    checkEventCallState(event, call, "connected");
-    ok(!call.onconnecting);
-    receive("call.onconnected");
-  };
-  call.answer();
-
-  return deferred.promise;
-}
-
-function remoteDial(number) {
-  log("Simulating an incoming call.");
-
-  let deferred = Promise.defer();
-
-  telephony.onincoming = function onincoming(event) {
-    log("Received 'incoming' call event.");
-    telephony.onincoming = null;
-
-    let call = event.call;
-
-    ok(call);
-    is(call.number, number);
-    is(call.state, "incoming");
-
-    deferred.resolve(call);
-  };
-  emulator.run("gsm call " + number);
-
-  return deferred.promise;
-}
-
-function remoteAnswer(call) {
-  log("Remote answering the call.");
-
-  let deferred = Promise.defer();
-
-  call.onconnected = function onconnected(event) {
-    log("Received 'connected' call event.");
-    call.onconnected = null;
-    checkEventCallState(event, call, "connected");
-    deferred.resolve(call);
-  };
-  emulator.run("gsm accept " + call.number);
-
-  return deferred.promise;
-}
-
-function remoteHangUp(call) {
-  log("Remote hanging up the call.");
-
-  let deferred = Promise.defer();
-
-  call.ondisconnected = function ondisconnected(event) {
-    log("Received 'disconnected' call event.");
-    call.ondisconnected = null;
-    checkEventCallState(event, call, "disconnected");
-    deferred.resolve(call);
-  };
-  emulator.run("gsm cancel " + call.number);
-
-  return deferred.promise;
-}
-
-function remoteHangUpCalls(calls) {
-  let promise = Promise.resolve();
-
-  for (let call of calls) {
-    promise = promise.then(remoteHangUp.bind(null, call));
-  }
-
-  return promise;
-}
-
-// container might be telephony or conference.
-function check_oncallschanged(container, containerName, expectedCalls,
-                              callback) {
-  container.oncallschanged = function(event) {
-    log("Received 'callschanged' event for the " + containerName);
-    if (event.call) {
-      let index = expectedCalls.indexOf(event.call);
-      ok(index != -1);
-      expectedCalls.splice(index, 1);
-
-      if (expectedCalls.length === 0) {
-        container.oncallschanged = null;
-        callback();
-      }
-    }
-  };
-}
-
-function check_ongroupchange(call, callName, group, callback) {
-  call.ongroupchange = function(event) {
-    log("Received 'groupchange' event for the " + callName);
-    call.ongroupchange = null;
-
-    is(call.group, group);
-    callback();
-  };
-}
-
-function check_onstatechange(call, callName, state, callback) {
-  call.onstatechange = function(event) {
-    log("Received 'statechange' event for the " + callName);
-    call.onstatechange = null;
-
-    is(call.state, state);
-    callback();
-  };
-}
-
-function StateEventChecker(state, previousEvent) {
-  let event = 'on' + state;
-
-  return function(call, callName, callback) {
-    call[event] = function() {
-      log("Received '" + state + "' event for the " + callName);
-      call[event] = null;
-
-      if (previousEvent) {
-        // We always clear the event handler when the event is received.
-        // Therefore, if the corresponding handler is not existed, the expected
-        // previous event has been already received.
-        ok(!call[previousEvent]);
-      }
-      is(call.state, state);
-      callback();
-    };
-  };
-}
-
-let check_onholding    = StateEventChecker('holding', null);
-let check_onheld       = StateEventChecker('held', 'onholding');
-let check_onresuming   = StateEventChecker('resuming', null);
-let check_onconnected  = StateEventChecker('connected', 'onresuming');
-
-// The length of callsToAdd should be 1 or 2.
-function addCallsToConference(callsToAdd, connectedCallback) {
-  log("Add " + callsToAdd.length + " calls into conference.");
-
-  let deferred = Promise.defer();
-  let done = function() {
-    deferred.resolve();
-  };
-
-  let pending = ["conference.oncallschanged", "conference.onconnected"];
-  let receive = function(name) {
-    receivedPending(name, pending, done);
-  };
-
-  for (let call of callsToAdd) {
-    let callName = "callToAdd (" + call.number + ')';
-
-    let ongroupchange = callName + ".ongroupchange";
-    pending.push(ongroupchange);
-    check_ongroupchange(call, callName, conference,
-                        receive.bind(null, ongroupchange));
-
-    let onstatechange = callName + ".onstatechange";
-    pending.push(onstatechange);
-    check_onstatechange(call, callName, 'connected',
-                        receive.bind(null, onstatechange));
-  }
-
-  check_oncallschanged(conference, 'conference', callsToAdd,
-                       receive.bind(null, "conference.oncallschanged"));
-
-  check_onconnected(conference, "conference", function() {
-    ok(!conference.oncallschanged);
-    if (typeof connectedCallback === 'function') {
-      connectedCallback();
-    }
-    receive("conference.onconnected");
-  });
-
-  // Cannot use apply() through webidl, so just separate the cases to handle.
-  if (callsToAdd.length == 2) {
-    conference.add(callsToAdd[0], callsToAdd[1]);
-  } else {
-    conference.add(callsToAdd[0]);
-  }
-
-  return deferred.promise;
-}
-
-function holdConference(calls, heldCallback) {
-  log("Holding the conference call.");
-
-  let deferred = Promise.defer();
-  let done = function() {
-    deferred.resolve();
-  };
-
-  let pending = ["conference.onholding", "conference.onheld"];
-  let receive = function(name) {
-    receivedPending(name, pending, done);
-  };
-
-  for (let call of calls) {
-    let callName = "call (" + call.number + ')';
-
-    let onholding = callName + ".onholding";
-    pending.push(onholding);
-    check_onholding(call, callName, receive.bind(null, onholding));
-
-    let onheld = callName + ".onheld";
-    pending.push(onheld);
-    check_onheld(call, callName, receive.bind(null, onheld));
-  }
-
-  check_onholding(conference, "conference",
-                  receive.bind(null, "conference.onholding"));
-
-  check_onheld(conference, "conference", function() {
-    if (typeof heldCallback === 'function') {
-      heldCallback();
-    }
-    receive("conference.onheld");
-  });
-
-  conference.hold();
-
-  return deferred.promise;
-}
-
-function resumeConference(calls, connectedCallback) {
-  log("Resuming the held conference call.");
-
-  let deferred = Promise.defer();
-  let done = function() {
-    deferred.resolve();
-  };
-
-  let pending = ["conference.onresuming", "conference.onconnected"];
-  let receive = function(name) {
-    receivedPending(name, pending, done);
-  };
-
-  for (let call of calls) {
-    let callName = "call (" + call.number + ')';
-
-    let onresuming = callName + ".onresuming";
-    pending.push(onresuming);
-    check_onresuming(call, callName, receive.bind(null, onresuming));
-
-    let onconnected = callName + ".onconnected";
-    pending.push(onconnected);
-    check_onconnected(call, callName, receive.bind(null, onconnected));
-  }
-
-  check_onresuming(conference, "conference",
-                   receive.bind(null, "conference.onresuming"));
-
-  check_onconnected(conference, "conference", function() {
-    if (typeof connectedCallback === 'function') {
-      connectedCallback();
-    }
-    receive("conference.onconnected");
-  });
-
-  conference.resume();
-
-  return deferred.promise;
-}
-
-// The length of autoRemovedCalls should be 0 or 1.
-function removeCallInConference(callToRemove, autoRemovedCalls, remainedCalls,
-                                statechangeCallback) {
-  log("Removing a participant from the conference call.");
-
-  is(conference.state, 'connected');
-
-  let deferred = Promise.defer();
-  let done = function() {
-    deferred.resolve();
-  };
-
-  let pending = ["callToRemove.ongroupchange", "telephony.oncallschanged",
-                 "conference.oncallschanged", "conference.onstatechange"];
-  let receive = function(name) {
-    receivedPending(name, pending, done);
-  };
-
-  // Remained call in conference will be held.
-  for (let call of remainedCalls) {
-    let callName = "remainedCall (" + call.number + ')';
-
-    let onstatechange = callName + ".onstatechange";
-    pending.push(onstatechange);
-    check_onstatechange(call, callName, 'held',
-                        receive.bind(null, onstatechange));
-  }
-
-  // When a call is removed from conference with 2 calls, another one will be
-  // automatically removed from group and be put on hold.
-  for (let call of autoRemovedCalls) {
-    let callName = "autoRemovedCall (" + call.number + ')';
-
-    let ongroupchange = callName + ".ongroupchange";
-    pending.push(ongroupchange);
-    check_ongroupchange(call, callName, null,
-                        receive.bind(null, ongroupchange));
-
-    let onstatechange = callName + ".onstatechange";
-    pending.push(onstatechange);
-    check_onstatechange(call, callName, 'held',
-                        receive.bind(null, onstatechange));
-  }
-
-  check_ongroupchange(callToRemove, "callToRemove", null, function() {
-    is(callToRemove.state, 'connected');
-    receive("callToRemove.ongroupchange");
-  });
-
-  check_oncallschanged(telephony, 'telephony',
-                       autoRemovedCalls.concat(callToRemove),
-                       receive.bind(null, "telephony.oncallschanged"));
-
-  check_oncallschanged(conference, 'conference',
-                       autoRemovedCalls.concat(callToRemove), function() {
-    is(conference.calls.length, remainedCalls.length);
-    receive("conference.oncallschanged");
-  });
-
-  check_onstatechange(conference, 'conference',
-                      (remainedCalls.length ? 'held' : ''), function() {
-    ok(!conference.oncallschanged);
-    if (typeof statechangeCallback === 'function') {
-      statechangeCallback();
-    }
-    receive("conference.onstatechange");
-  });
-
-  conference.remove(callToRemove);
-
-  return deferred.promise;
-}
-
-// The length of autoRemovedCalls should be 0 or 1.
-function hangUpCallInConference(callToHangUp, autoRemovedCalls, remainedCalls,
-                                statechangeCallback) {
-  log("Release one call in conference.");
-
-  let deferred = Promise.defer();
-  let done = function() {
-    deferred.resolve();
-  };
-
-  let pending = ["conference.oncallschanged", "remoteHangUp"];
-  let receive = function(name) {
-    receivedPending(name, pending, done);
-  };
-
-  // When a call is hang up from conference with 2 calls, another one will be
-  // automatically removed from group.
-  for (let call of autoRemovedCalls) {
-    let callName = "autoRemovedCall (" + call.number + ')';
-
-    let ongroupchange = callName + ".ongroupchange";
-    pending.push(ongroupchange);
-    check_ongroupchange(call, callName, null,
-                        receive.bind(null, ongroupchange));
-  }
-
-  if (autoRemovedCalls.length) {
-    pending.push("telephony.oncallschanged");
-    check_oncallschanged(telephony, 'telephony',
-                         autoRemovedCalls,
-                         receive.bind(null, "telephony.oncallschanged"));
-  }
-
-  check_oncallschanged(conference, 'conference',
-                       autoRemovedCalls.concat(callToHangUp), function() {
-    is(conference.calls.length, remainedCalls.length);
-    receive("conference.oncallschanged");
-  });
-
-  if (remainedCalls.length === 0) {
-    pending.push("conference.onstatechange");
-    check_onstatechange(conference, 'conference', '', function() {
-      ok(!conference.oncallschanged);
-      if (typeof statechangeCallback === 'function') {
-        statechangeCallback();
-      }
-      receive("conference.onstatechange");
-    });
-  }
-
-  remoteHangUp(callToHangUp)
-    .then(receive.bind(null, "remoteHangUp"));
-
-  return deferred.promise;
-}
-
-function handleConferenceRemoveError(callToRemove) {
-  log('Handle conference remove error.');
-
-  let deferred = Promise.defer();
-
-  conference.onerror = function(evt) {
-    log('Receiving a conference error event.');
-    is(evt.name, 'removeError', 'conference removeError');
-
-    deferred.resolve();
-  }
-  conference.remove(callToRemove);
-
-  return deferred.promise;
-}
-
-function handleConferenceAddError(callToAdd) {
-  log('Handle conference add error.');
-
-  let deferred = Promise.defer();
-
-  conference.onerror = function(evt) {
-    log('Receiving a conference error event.');
-    is(evt.name, 'addError', 'conference addError');
-
-    deferred.resolve();
-  }
-  conference.add(callToAdd);
-
-  return deferred.promise;
-}
-
-/**
- * Setup a conference with an outgoing call and an incoming call.
- *
- * @return Promise<[outCall, inCall]>
- */
-function setupConferenceTwoCalls(outNumber, inNumber) {
-  log('Create conference with two calls.');
-
-  let outCall;
-  let inCall;
-  let outInfo = outCallStrPool(outNumber);
-  let inInfo = inCallStrPool(inNumber);
-
-  return Promise.resolve()
-    .then(checkInitialState)
-    .then(() => dial(outNumber))
-    .then(call => { outCall = call; })
-    .then(() => checkAll(outCall, [outCall], '', [], [outInfo.ringing]))
-    .then(() => remoteAnswer(outCall))
-    .then(() => checkAll(outCall, [outCall], '', [], [outInfo.active]))
-    .then(() => remoteDial(inNumber))
-    .then(call => { inCall = call; })
-    .then(() => checkAll(outCall, [outCall, inCall], '', [],
-                         [outInfo.active, inInfo.incoming]))
-    .then(() => answer(inCall))
-    .then(() => checkAll(inCall, [outCall, inCall], '', [],
-                         [outInfo.held, inInfo.active]))
-    .then(() => addCallsToConference([outCall, inCall], function() {
-      checkState(conference, [], 'connected', [outCall, inCall]);
-    }))
-    .then(() => checkAll(conference, [], 'connected', [outCall, inCall],
-                         [outInfo.active, inInfo.active]))
-    .then(() => {
-      return [outCall, inCall];
-    });
-}
-
-/**
- * Setup a conference with an outgoing call and two incoming calls.
- *
- * @return Promise<[outCall, inCall, inCall2]>
- */
-function setupConferenceThreeCalls(outNumber, inNumber, inNumber2) {
-  log('Create conference with three calls.');
-
-  let outCall;
-  let inCall;
-  let inCall2;
-  let outInfo = outCallStrPool(outNumber);
-  let inInfo = inCallStrPool(inNumber);
-  let inInfo2 = inCallStrPool(inNumber2);
-
-  return Promise.resolve()
-    .then(() => setupConferenceTwoCalls(outNumber, inNumber))
-    .then(calls => {
-      outCall = calls[0];
-      inCall = calls[1];
-    })
-    .then(() => remoteDial(inNumber2))
-    .then(call => { inCall2 = call; })
-    .then(() => checkAll(conference, [inCall2], 'connected', [outCall, inCall],
-                         [outInfo.active, inInfo.active, inInfo2.incoming]))
-    .then(() => answer(inCall2, function() {
-      checkState(inCall2, [inCall2], 'held', [outCall, inCall]);
-    }))
-    .then(() => checkAll(inCall2, [inCall2], 'held', [outCall, inCall],
-                         [outInfo.held, inInfo.held, inInfo2.active]))
-    .then(() => addCallsToConference([inCall2], function() {
-      checkState(conference, [], 'connected', [outCall, inCall, inCall2]);
-    }))
-    .then(() => checkAll(conference, [],
-                         'connected', [outCall, inCall, inCall2],
-                         [outInfo.active, inInfo.active, inInfo2.active]))
-    .then(() => {
-      return [outCall, inCall, inCall2];
-    });
-}
-
-/**
- * Setup a conference with an outgoing call and four incoming calls.
- *
- * @return Promise<[outCall, inCall, inCall2, inCall3, inCall4]>
- */
-function setupConferenceFiveCalls(outNumber, inNumber, inNumber2, inNumber3,
-                                  inNumber4) {
-  log('Create conference with five calls.');
-
-  let outCall;
-  let inCall;
-  let inCall2;
-  let inCall3;
-  let inCall4;
-  let outInfo = outCallStrPool(outNumber);
-  let inInfo = inCallStrPool(inNumber);
-  let inInfo2 = inCallStrPool(inNumber2);
-  let inInfo3 = inCallStrPool(inNumber3);
-  let inInfo4 = inCallStrPool(inNumber4);
-
-  return Promise.resolve()
-    .then(() => setupConferenceThreeCalls(outNumber, inNumber, inNumber2))
-    .then(calls => {
-      [outCall, inCall, inCall2] = calls;
-    })
-    .then(() => remoteDial(inNumber3))
-    .then(call => {inCall3 = call;})
-    .then(() => checkAll(conference, [inCall3], 'connected',
-                         [outCall, inCall, inCall2],
-                         [outInfo.active, inInfo.active, inInfo2.active,
-                          inInfo3.incoming]))
-    .then(() => answer(inCall3, function() {
-      checkState(inCall3, [inCall3], 'held', [outCall, inCall, inCall2]);
-    }))
-    .then(() => checkAll(inCall3, [inCall3], 'held',
-                         [outCall, inCall, inCall2],
-                         [outInfo.held, inInfo.held, inInfo2.held,
-                          inInfo3.active]))
-    .then(() => addCallsToConference([inCall3], function() {
-      checkState(conference, [], 'connected', [outCall, inCall, inCall2, inCall3]);
-    }))
-    .then(() => checkAll(conference, [], 'connected',
-                         [outCall, inCall, inCall2, inCall3],
-                         [outInfo.active, inInfo.active, inInfo2.active,
-                          inInfo3.active]))
-    .then(() => remoteDial(inNumber4))
-    .then(call => {inCall4 = call;})
-    .then(() => checkAll(conference, [inCall4], 'connected',
-                         [outCall, inCall, inCall2, inCall3],
-                         [outInfo.active, inInfo.active, inInfo2.active,
-                          inInfo3.active, inInfo4.incoming]))
-    .then(() => answer(inCall4, function() {
-      checkState(inCall4, [inCall4], 'held', [outCall, inCall, inCall2, inCall3]);
-    }))
-    .then(() => checkAll(inCall4, [inCall4], 'held',
-                         [outCall, inCall, inCall2, inCall3],
-                         [outInfo.held, inInfo.held, inInfo2.held,
-                          inInfo3.held, inInfo4.active]))
-    .then(() => addCallsToConference([inCall4], function() {
-      checkState(conference, [], 'connected', [outCall, inCall, inCall2,
-                                               inCall3, inCall4]);
-    }))
-    .then(() => checkAll(conference, [], 'connected',
-                         [outCall, inCall, inCall2, inCall3, inCall4],
-                         [outInfo.active, inInfo.active, inInfo2.active,
-                          inInfo3.active, inInfo4.active]))
-    .then(() => {
-      return [outCall, inCall, inCall2, inCall3, inCall4];
-    });
-}
-
-function testConferenceTwoCalls() {
-  log('= testConferenceTwoCalls =');
-
-  let outCall;
-  let inCall;
-  let outNumber = "5555550101";
-  let inNumber  = "5555550201";
-
-  return Promise.resolve()
-    .then(() => setupConferenceTwoCalls(outNumber, inNumber))
-    .then(calls => {
-      [outCall, inCall] = calls;
-    })
-    .then(() => remoteHangUpCalls([outCall, inCall]));
-}
-
-function testConferenceHoldAndResume() {
-  log('= testConferenceHoldAndResume =');
-
-  let outCall;
-  let inCall;
-  let outNumber = "5555550101";
-  let inNumber  = "5555550201";
-  let outInfo = outCallStrPool(outNumber);
-  let inInfo = inCallStrPool(inNumber);
-
-  return Promise.resolve()
-    .then(() => setupConferenceTwoCalls(outNumber, inNumber))
-    .then(calls => {
-      [outCall, inCall] = calls;
-    })
-    .then(() => holdConference([outCall, inCall], function() {
-      checkState(null, [], 'held', [outCall, inCall]);
-    }))
-    .then(() => checkAll(null, [], 'held', [outCall, inCall],
-                         [outInfo.held, inInfo.held]))
-    .then(() => resumeConference([outCall, inCall], function() {
-      checkState(conference, [], 'connected', [outCall, inCall]);
-    }))
-    .then(() => checkAll(conference, [], 'connected', [outCall, inCall],
-                         [outInfo.active, inInfo.active]))
-    .then(() => remoteHangUpCalls([outCall, inCall]));
-}
-
-function testConferenceThreeAndRemoveOne() {
-  log('= testConferenceThreeAndRemoveOne =');
-
-  let outCall;
-  let inCall;
-  let inCall2;
-  let outNumber = "5555550101";
-  let inNumber  = "5555550201";
-  let inNumber2 = "5555550202";
-  let outInfo = outCallStrPool(outNumber);
-  let inInfo = inCallStrPool(inNumber);
-  let inInfo2 = inCallStrPool(inNumber2);
-
-  return Promise.resolve()
-    .then(() => setupConferenceThreeCalls(outNumber, inNumber, inNumber2))
-    .then(calls => {
-      [outCall, inCall, inCall2] = calls;
-    })
-    .then(() => removeCallInConference(outCall, [], [inCall, inCall2],
-                                       function() {
-        checkState(outCall, [outCall], 'held', [inCall, inCall2]);
-    }))
-    .then(() => checkAll(outCall, [outCall], 'held', [inCall, inCall2],
-                         [outInfo.active, inInfo.held, inInfo2.held]))
-    .then(() => remoteHangUpCalls([outCall, inCall, inCall2]));
-}
-
-function testConferenceThreeAndHangupOne() {
-  log('= testConferenceThreeAndHangupOne =');
-
-  let outCall;
-  let inCall;
-  let inCall2;
-  let outNumber = "5555550101";
-  let inNumber  = "5555550201";
-  let inNumber2 = "5555550202";
-  let inInfo = inCallStrPool(inNumber);
-  let inInfo2 = inCallStrPool(inNumber2);
-
-  return Promise.resolve()
-    .then(() => setupConferenceThreeCalls(outNumber, inNumber, inNumber2))
-    .then(calls => {
-      [outCall, inCall, inCall2] = calls;
-    })
-    .then(() => hangUpCallInConference(outCall, [], [inCall, inCall2]))
-    .then(() => checkAll(conference, [], 'connected', [inCall, inCall2],
-                         [inInfo.active, inInfo2.active]))
-    .then(() => remoteHangUpCalls([inCall, inCall2]));
-}
-
-function testConferenceTwoAndRemoveOne() {
-  log('= testConferenceTwoAndRemoveOne =');
-
-  let outCall;
-  let inCall;
-  let outNumber = "5555550101";
-  let inNumber  = "5555550201";
-  let outInfo = outCallStrPool(outNumber);
-  let inInfo = inCallStrPool(inNumber);
-
-  return Promise.resolve()
-    .then(() => setupConferenceTwoCalls(outNumber, inNumber))
-    .then(calls => {
-      [outCall, inCall] = calls;
-    })
-    .then(() => removeCallInConference(outCall, [inCall], [], function() {
-      checkState(outCall, [outCall, inCall], '', []);
-    }))
-    .then(() => checkAll(outCall, [outCall, inCall], '', [],
-                         [outInfo.active, inInfo.held]))
-    .then(() => remoteHangUpCalls([outCall, inCall]));
-}
-
-function testConferenceTwoAndHangupOne() {
-  log('= testConferenceTwoAndHangupOne =');
-
-  let outCall;
-  let inCall;
-  let outNumber = "5555550101";
-  let inNumber  = "5555550201";
-  let inInfo = inCallStrPool(inNumber);
-
-  return Promise.resolve()
-    .then(() => setupConferenceTwoCalls(outNumber, inNumber))
-    .then(calls => {
-      [outCall, inCall] = calls;
-    })
-    .then(() => hangUpCallInConference(outCall, [inCall], [], function() {
-      checkState(inCall, [inCall], '', []);
-    }))
-    .then(() => checkAll(inCall, [inCall], '', [], [inInfo.active]))
-    .then(() => remoteHangUpCalls([inCall]));
-}
-
-function testConferenceRemoveError() {
-  log('= testConferenceRemoveError =');
-
-  let outCall;
-  let inCall;
-  let inCall2;
-  let outNumber = "5555550101";
-  let inNumber  = "5555550201";
-  let inNumber2 = "5555550202";
-  let outInfo = outCallStrPool(outNumber);
-  let inInfo = inCallStrPool(inNumber);
-  let inInfo2 = inCallStrPool(inNumber2);
-
-  return Promise.resolve()
-    .then(() => setupConferenceTwoCalls(outNumber, inNumber))
-    .then(calls => {
-      [outCall, inCall] = calls;
-    })
-    .then(() => remoteDial(inNumber2))
-    .then(call => {inCall2 = call;})
-    .then(() => checkAll(conference, [inCall2], 'connected', [outCall, inCall],
-                         [outInfo.active, inInfo.active, inInfo2.incoming]))
-    .then(() => answer(inCall2, function() {
-      checkState(inCall2, [inCall2], 'held', [outCall, inCall]);
-    }))
-    .then(() => checkAll(inCall2, [inCall2], 'held', [outCall, inCall],
-                         [outInfo.held, inInfo.held, inInfo2.active]))
-    .then(() => resumeConference([outCall, inCall], function() {
-      checkState(conference, [inCall2], 'connected', [outCall, inCall]);
-    }))
-    // Not allowed to remove a call when there are one connected and one held
-    // calls.
-    .then(() => handleConferenceRemoveError(outCall))
-    .then(() => remoteHangUpCalls([outCall, inCall, inCall2]));
-}
-
-function testConferenceAddError() {
-  log('= testConferenceAddError =');
-
-  let outCall, inCall, inCall2, inCall3, inCall4, inCall5;
-  let outNumber = "5555550101";
-  let inNumber  = "5555550201";
-  let inNumber2 = "5555550202";
-  let inNumber3 = "5555550203";
-  let inNumber4 = "5555550204";
-  let inNumber5 = "5555550205";
-  let outInfo = outCallStrPool(outNumber);
-  let inInfo = inCallStrPool(inNumber);
-  let inInfo2 = inCallStrPool(inNumber2);
-  let inInfo3 = inCallStrPool(inNumber3);
-  let inInfo4 = inCallStrPool(inNumber4);
-  let inInfo5 = inCallStrPool(inNumber5);
-
-  return Promise.resolve()
-    .then(() => setupConferenceFiveCalls(outNumber, inNumber, inNumber2,
-                                         inNumber3, inNumber4))
-    .then(calls => {
-      [outCall, inCall, inCall2, inCall3, inCall4] = calls;
-    })
-    .then(() => remoteDial(inNumber5))
-    .then(call => {inCall5 = call;})
-    .then(() => answer(inCall5, function() {
-      checkState(inCall5, [inCall5], 'held',
-                 [outCall, inCall, inCall2, inCall3, inCall4]);
-    }))
-    .then(() => checkAll(inCall5, [inCall5], 'held',
-                         [outCall, inCall, inCall2, inCall3, inCall4],
-                         [outInfo.held, inInfo.held, inInfo2.held,
-                          inInfo3.held, inInfo4.held, inInfo5.active]))
-    // Maximum number of conference participants is 5.
-    .then(() => handleConferenceAddError(inCall5))
-    .then(() => remoteHangUpCalls([outCall, inCall, inCall2, inCall3, inCall4,
-                                   inCall5]));
-}
-
-// Start the test
-startTest(function() {
-  conference = telephony.conferenceGroup;
-  ok(conference);
-
-  testConferenceTwoCalls()
-    .then(testConferenceHoldAndResume)
-    .then(testConferenceThreeAndRemoveOne)
-    .then(testConferenceThreeAndHangupOne)
-    .then(testConferenceTwoAndRemoveOne)
-    .then(testConferenceTwoAndHangupOne)
-    .then(testConferenceRemoveError)
-    .then(testConferenceAddError)
-    .then(null, error => {
-      ok(false, 'promise rejects during test.');
-    })
-    .then(finish);
-});
new file mode 100644
--- /dev/null
+++ b/dom/telephony/test/marionette/test_conference_add_error.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+function handleConferenceAddError(callToAdd) {
+  log('Handle conference add error.');
+
+  let deferred = Promise.defer();
+
+  conference.onerror = function(evt) {
+    log('Receiving a conference error event.');
+    conference.onerror = null;
+
+    is(evt.name, 'addError', 'conference addError');
+
+    deferred.resolve();
+  }
+  conference.add(callToAdd);
+
+  return deferred.promise;
+}
+
+function testConferenceAddError() {
+  log('= testConferenceAddError =');
+
+  let outCall, inCall, inCall2, inCall3, inCall4, inCall5;
+  let outNumber = "5555550101";
+  let inNumber  = "5555550201";
+  let inNumber2 = "5555550202";
+  let inNumber3 = "5555550203";
+  let inNumber4 = "5555550204";
+  let inNumber5 = "5555550205";
+  let outInfo = gOutCallStrPool(outNumber);
+  let inInfo = gInCallStrPool(inNumber);
+  let inInfo2 = gInCallStrPool(inNumber2);
+  let inInfo3 = gInCallStrPool(inNumber3);
+  let inInfo4 = gInCallStrPool(inNumber4);
+  let inInfo5 = gInCallStrPool(inNumber5);
+
+  return Promise.resolve()
+    .then(() => gSetupConferenceFiveCalls(outNumber, inNumber, inNumber2,
+                                          inNumber3, inNumber4))
+    .then(calls => {
+      [outCall, inCall, inCall2, inCall3, inCall4] = calls;
+    })
+    .then(() => gRemoteDial(inNumber5))
+    .then(call => {inCall5 = call;})
+    .then(() => gAnswer(inCall5, function() {
+      gCheckState(inCall5, [inCall5], 'held',
+                  [outCall, inCall, inCall2, inCall3, inCall4]);
+    }))
+    .then(() => gCheckAll(inCall5, [inCall5], 'held',
+                          [outCall, inCall, inCall2, inCall3, inCall4],
+                          [outInfo.held, inInfo.held, inInfo2.held,
+                           inInfo3.held, inInfo4.held, inInfo5.active]))
+    // Maximum number of conference participants is 5.
+    .then(() => handleConferenceAddError(inCall5))
+    .then(() => gRemoteHangUpCalls([outCall, inCall, inCall2, inCall3, inCall4,
+                                    inCall5]));
+}
+
+// Start the test
+startTest(function() {
+  testConferenceAddError()
+    .then(null, error => {
+      ok(false, 'promise rejects during test.');
+    })
+    .then(finish);
+});
new file mode 100644
--- /dev/null
+++ b/dom/telephony/test/marionette/test_conference_remove_error.js
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+function handleConferenceRemoveError(callToRemove) {
+  log('Handle conference remove error.');
+
+  let deferred = Promise.defer();
+
+  conference.onerror = function(evt) {
+    log('Receiving a conference error event.');
+    conference.onerror = null;
+
+    is(evt.name, 'removeError', 'conference removeError');
+
+    deferred.resolve();
+  }
+  conference.remove(callToRemove);
+
+  return deferred.promise;
+}
+
+function testConferenceRemoveError() {
+  log('= testConferenceRemoveError =');
+
+  let outCall;
+  let inCall;
+  let inCall2;
+  let outNumber = "5555550101";
+  let inNumber  = "5555550201";
+  let inNumber2 = "5555550202";
+  let outInfo = gOutCallStrPool(outNumber);
+  let inInfo = gInCallStrPool(inNumber);
+  let inInfo2 = gInCallStrPool(inNumber2);
+
+  return Promise.resolve()
+    .then(() => gSetupConferenceTwoCalls(outNumber, inNumber))
+    .then(calls => {
+      [outCall, inCall] = calls;
+    })
+    .then(() => gRemoteDial(inNumber2))
+    .then(call => {inCall2 = call;})
+    .then(() => gCheckAll(conference, [inCall2], 'connected', [outCall, inCall],
+                          [outInfo.active, inInfo.active, inInfo2.incoming]))
+    .then(() => gAnswer(inCall2, function() {
+      gCheckState(inCall2, [inCall2], 'held', [outCall, inCall]);
+    }))
+    .then(() => gCheckAll(inCall2, [inCall2], 'held', [outCall, inCall],
+                          [outInfo.held, inInfo.held, inInfo2.active]))
+    .then(() => gResumeConference([outCall, inCall], function() {
+      gCheckState(conference, [inCall2], 'connected', [outCall, inCall]);
+    }))
+    // Not allowed to remove a call when there are one connected and one held
+    // calls.
+    .then(() => handleConferenceRemoveError(outCall))
+    .then(() => gRemoteHangUpCalls([outCall, inCall, inCall2]));
+}
+
+// Start the test
+startTest(function() {
+  testConferenceRemoveError()
+    .then(null, error => {
+      ok(false, 'promise rejects during test.');
+    })
+    .then(finish);
+});
new file mode 100644
--- /dev/null
+++ b/dom/telephony/test/marionette/test_conference_three_hangup_one.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+function testConferenceThreeAndHangupOne() {
+  log('= testConferenceThreeAndHangupOne =');
+
+  let outCall;
+  let inCall;
+  let inCall2;
+  let outNumber = "5555550101";
+  let inNumber  = "5555550201";
+  let inNumber2 = "5555550202";
+  let inInfo = gInCallStrPool(inNumber);
+  let inInfo2 = gInCallStrPool(inNumber2);
+
+  return Promise.resolve()
+    .then(() => gSetupConferenceThreeCalls(outNumber, inNumber, inNumber2))
+    .then(calls => {
+      [outCall, inCall, inCall2] = calls;
+    })
+    .then(() => gHangUpCallInConference(outCall, [], [inCall, inCall2]))
+    .then(() => gCheckAll(conference, [], 'connected', [inCall, inCall2],
+                          [inInfo.active, inInfo2.active]))
+    .then(() => gRemoteHangUpCalls([inCall, inCall2]));
+}
+
+// Start the test
+startTest(function() {
+  testConferenceThreeAndHangupOne()
+    .then(null, error => {
+      ok(false, 'promise rejects during test.');
+    })
+    .then(finish);
+});
new file mode 100644
--- /dev/null
+++ b/dom/telephony/test/marionette/test_conference_three_remove_one.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+function testConferenceThreeAndRemoveOne() {
+  log('= testConferenceThreeAndRemoveOne =');
+
+  let outCall;
+  let inCall;
+  let inCall2;
+  let outNumber = "5555550101";
+  let inNumber  = "5555550201";
+  let inNumber2 = "5555550202";
+  let outInfo = gOutCallStrPool(outNumber);
+  let inInfo = gInCallStrPool(inNumber);
+  let inInfo2 = gInCallStrPool(inNumber2);
+
+  return Promise.resolve()
+    .then(() => gSetupConferenceThreeCalls(outNumber, inNumber, inNumber2))
+    .then(calls => {
+      [outCall, inCall, inCall2] = calls;
+    })
+    .then(() => gRemoveCallInConference(outCall, [], [inCall, inCall2],
+                                        function() {
+        gCheckState(outCall, [outCall], 'held', [inCall, inCall2]);
+    }))
+    .then(() => gCheckAll(outCall, [outCall], 'held', [inCall, inCall2],
+                          [outInfo.active, inInfo.held, inInfo2.held]))
+    .then(() => gRemoteHangUpCalls([outCall, inCall, inCall2]));
+}
+
+// Start the test
+startTest(function() {
+  testConferenceThreeAndRemoveOne()
+    .then(null, error => {
+      ok(false, 'promise rejects during test.');
+    })
+    .then(finish);
+});
new file mode 100644
--- /dev/null
+++ b/dom/telephony/test/marionette/test_conference_two_calls.js
@@ -0,0 +1,30 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+function testConferenceTwoCalls() {
+  log('= testConferenceTwoCalls =');
+
+  let outCall;
+  let inCall;
+  let outNumber = "5555550101";
+  let inNumber  = "5555550201";
+
+  return Promise.resolve()
+    .then(() => gSetupConferenceTwoCalls(outNumber, inNumber))
+    .then(calls => {
+      [outCall, inCall] = calls;
+    })
+    .then(() => gRemoteHangUpCalls([outCall, inCall]));
+}
+
+// Start the test
+startTest(function() {
+  testConferenceTwoCalls()
+    .then(null, error => {
+      ok(false, 'promise rejects during test.');
+    })
+    .then(finish);
+});
new file mode 100644
--- /dev/null
+++ b/dom/telephony/test/marionette/test_conference_two_hangup_one.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+function testConferenceTwoAndHangupOne() {
+  log('= testConferenceTwoAndHangupOne =');
+
+  let outCall;
+  let inCall;
+  let outNumber = "5555550101";
+  let inNumber  = "5555550201";
+  let inInfo = gInCallStrPool(inNumber);
+
+  return Promise.resolve()
+    .then(() => gSetupConferenceTwoCalls(outNumber, inNumber))
+    .then(calls => {
+      [outCall, inCall] = calls;
+    })
+    .then(() => gHangUpCallInConference(outCall, [inCall], [], function() {
+      gCheckState(inCall, [inCall], '', []);
+    }))
+    .then(() => gCheckAll(inCall, [inCall], '', [], [inInfo.active]))
+    .then(() => gRemoteHangUpCalls([inCall]));
+}
+
+// Start the test
+startTest(function() {
+  testConferenceTwoAndHangupOne()
+    .then(null, error => {
+      ok(false, 'promise rejects during test.');
+    })
+    .then(finish);
+});
new file mode 100644
--- /dev/null
+++ b/dom/telephony/test/marionette/test_conference_two_hold_resume.js
@@ -0,0 +1,42 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+function testConferenceHoldAndResume() {
+  log('= testConferenceHoldAndResume =');
+
+  let outCall;
+  let inCall;
+  let outNumber = "5555550101";
+  let inNumber  = "5555550201";
+  let outInfo = gOutCallStrPool(outNumber);
+  let inInfo = gInCallStrPool(inNumber);
+
+  return Promise.resolve()
+    .then(() => gSetupConferenceTwoCalls(outNumber, inNumber))
+    .then(calls => {
+      [outCall, inCall] = calls;
+    })
+    .then(() => gHoldConference([outCall, inCall], function() {
+      gCheckState(null, [], 'held', [outCall, inCall]);
+    }))
+    .then(() => gCheckAll(null, [], 'held', [outCall, inCall],
+                          [outInfo.held, inInfo.held]))
+    .then(() => gResumeConference([outCall, inCall], function() {
+      gCheckState(conference, [], 'connected', [outCall, inCall]);
+    }))
+    .then(() => gCheckAll(conference, [], 'connected', [outCall, inCall],
+                          [outInfo.active, inInfo.active]))
+    .then(() => gRemoteHangUpCalls([outCall, inCall]));
+}
+
+// Start the test
+startTest(function() {
+  testConferenceHoldAndResume()
+    .then(null, error => {
+      ok(false, 'promise rejects during test.');
+    })
+    .then(finish);
+});
new file mode 100644
--- /dev/null
+++ b/dom/telephony/test/marionette/test_conference_two_remove_one.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+function testConferenceTwoAndRemoveOne() {
+  log('= testConferenceTwoAndRemoveOne =');
+
+  let outCall;
+  let inCall;
+  let outNumber = "5555550101";
+  let inNumber  = "5555550201";
+  let outInfo = gOutCallStrPool(outNumber);
+  let inInfo = gInCallStrPool(inNumber);
+
+  return Promise.resolve()
+    .then(() => gSetupConferenceTwoCalls(outNumber, inNumber))
+    .then(calls => {
+      [outCall, inCall] = calls;
+    })
+    .then(() => gRemoveCallInConference(outCall, [inCall], [], function() {
+      gCheckState(outCall, [outCall, inCall], '', []);
+    }))
+    .then(() => gCheckAll(outCall, [outCall, inCall], '', [],
+                          [outInfo.active, inInfo.held]))
+    .then(() => gRemoteHangUpCalls([outCall, inCall]));
+}
+
+// Start the test
+startTest(function() {
+  testConferenceTwoAndRemoveOne()
+    .then(null, error => {
+      ok(false, 'promise rejects during test.');
+    })
+    .then(finish);
+});
--- a/dom/telephony/test/marionette/test_dsds_connection_conflict.js
+++ b/dom/telephony/test/marionette/test_dsds_connection_conflict.js
@@ -1,44 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_TIMEOUT = 60000;
 MARIONETTE_HEAD_JS = 'head.js';
 
-function remoteAnswer(call) {
-  log("Remote answering the call.");
-
-  let deferred = Promise.defer();
-
-  call.onconnected = function onconnected(event) {
-    log("Received 'connected' call event.");
-    call.onconnected = null;
-    deferred.resolve(call);
-  };
-  emulator.run("gsm accept " + call.number);
-
-  return deferred.promise;
-}
-
-function remoteHangUp(call) {
-  log("Remote hanging up the call.");
-
-  let deferred = Promise.defer();
-
-  call.ondisconnected = function ondisconnected(event) {
-    log("Received 'disconnected' call event.");
-    call.ondisconnected = null;
-    deferred.resolve(call);
-  };
-  emulator.run("gsm cancel " + call.number);
-
-  return deferred.promise;
-}
-
 function muxModem(id) {
   let deferred = Promise.defer();
 
   emulator.run("mux modem " + id, function() {
     deferred.resolve();
   });
 
   return deferred.promise;
@@ -54,27 +24,27 @@ function testNewCallWhenOtherConnectionI
     .then(() => muxModem(firstServiceId))
     .then(() => {
       return telephony.dial("0912345000", firstServiceId);
     })
     .then(call => {
       outCall = call;
       is(outCall.serviceId, firstServiceId);
     })
-    .then(() => remoteAnswer(outCall))
+    .then(() => gRemoteAnswer(outCall))
     .then(() => {
       return telephony.dial("0912345001", secondServiceId);
     })
     .then(() => {
       log("The promise should not be resolved");
       ok(false);
     }, cause => {
       is(cause, "OtherConnectionInUse");
     })
-    .then(() => remoteHangUp(outCall));
+    .then(() => gRemoteHangUp(outCall));
 }
 
 startDSDSTest(function() {
   testNewCallWhenOtherConnectionInUse(0, 1)
     .then(() => testNewCallWhenOtherConnectionInUse(1, 0))
     .then(() => muxModem(0))
     .then(null, () => {
       ok(false, "promise rejects during test.");
--- a/dom/telephony/test/marionette/test_dsds_normal_call.js
+++ b/dom/telephony/test/marionette/test_dsds_normal_call.js
@@ -1,220 +1,56 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_TIMEOUT = 60000;
 MARIONETTE_HEAD_JS = 'head.js';
 
-/**
- * The functions are created to provide the string format of the emulator call
- * list results.
- *
- * Usage:
- *   let outInfo = OutCallStrPool("911");
- *   outInfo.ringing == "outbound to 911        : ringing"
- *   outInfo.active  == "outbound to 911        : active"
- */
-function CallStrPool(prefix, number) {
-  let padding = "           : ";
-  let numberInfo = prefix + number + padding.substr(number.length);
-
-  let info = {};
-  let states = ["ringing", "incoming", "active", "held"];
-  for (let state of states) {
-    info[state] = numberInfo + state;
-  }
-
-  return info;
-}
-
-function OutCallStrPool(number) {
-  return CallStrPool("outbound to  ", number);
-}
-
-function InCallStrPool(number) {
-  return CallStrPool("inbound from ", number);
-}
-
-function checkEventCallState(event, call, state) {
-  is(call, event.call, "event.call");
-  is(call.state, state, "call state");
-}
-
-function checkTelephonyActiveAndCalls(active, calls) {
-  is(telephony.active, active, "telephony.active");
-  is(telephony.calls.length, calls.length, "telephony.calls");
-  for (let i = 0; i < calls.length; ++i) {
-    is(telephony.calls[i], calls[i]);
-  }
-}
-
-function checkEmulatorCallList(expectedCallList) {
-  let deferred = Promise.defer();
-
-  emulator.run("gsm list", function(result) {
-    log("Call list is now: " + result);
-    for (let i = 0; i < expectedCallList.length; ++i) {
-      is(result[i], expectedCallList[i], "emulator calllist");
-    }
-    is(result[expectedCallList.length], "OK", "emulator calllist");
-    deferred.resolve();
-  });
-
-  return deferred.promise;
-}
-
-// Promise.
-function checkAll(active, calls, callList) {
-  checkTelephonyActiveAndCalls(active, calls);
-  return checkEmulatorCallList(callList);
-}
-
-function dial(number, serviceId) {
-  serviceId = typeof serviceId !== "undefined" ? serviceId : 0;
-  log("Make an outgoing call: " + number + ", serviceId: " + serviceId);
-
-  let deferred = Promise.defer();
-
-  telephony.dial(number).then(call => {
-    ok(call);
-    is(call.number, number);
-    is(call.state, "dialing");
-
-    call.onalerting = function onalerting(event) {
-      call.onalerting = null;
-      log("Received 'onalerting' call event.");
-      is(call.serviceId, serviceId);
-      checkEventCallState(event, call, "alerting");
-      deferred.resolve(call);
-    };
-  });
-
-  return deferred.promise;
-}
-
-function remoteDial(number) {
-  log("Simulating an incoming call.");
-
-  let deferred = Promise.defer();
-
-  telephony.onincoming = function onincoming(event) {
-    log("Received 'incoming' call event.");
-    telephony.onincoming = null;
-
-    let call = event.call;
-
-    ok(call);
-    is(call.number, number);
-    is(call.state, "incoming");
-
-    deferred.resolve(call);
-  };
-  emulator.run("gsm call " + number);
-
-  return deferred.promise;
-}
-
-function answer(call) {
-  log("Answering the incoming call.");
-
-  let deferred = Promise.defer();
-
-  call.onconnecting = function onconnectingIn(event) {
-    log("Received 'connecting' call event for incoming call.");
-    call.onconnecting = null;
-    checkEventCallState(event, call, "connecting");
-  };
-
-  call.onconnected = function onconnectedIn(event) {
-    log("Received 'connected' call event for incoming call.");
-    call.onconnected = null;
-    checkEventCallState(event, call, "connected");
-    ok(!call.onconnecting);
-    deferred.resolve(call);
-  };
-  call.answer();
-
-  return deferred.promise;
-}
-
-function remoteAnswer(call) {
-  log("Remote answering the call.");
-
-  let deferred = Promise.defer();
-
-  call.onconnected = function onconnected(event) {
-    log("Received 'connected' call event.");
-    call.onconnected = null;
-    checkEventCallState(event, call, "connected");
-    deferred.resolve(call);
-  };
-  emulator.run("gsm accept " + call.number);
-
-  return deferred.promise;
-}
-
-function remoteHangUp(call) {
-  log("Remote hanging up the call.");
-
-  let deferred = Promise.defer();
-
-  call.ondisconnected = function ondisconnected(event) {
-    log("Received 'disconnected' call event.");
-    call.ondisconnected = null;
-    checkEventCallState(event, call, "disconnected");
-    deferred.resolve(call);
-  };
-  emulator.run("gsm cancel " + call.number);
-
-  return deferred.promise;
-}
-
 function muxModem(id) {
   let deferred = Promise.defer();
 
   emulator.run("mux modem " + id, function() {
     deferred.resolve();
   });
 
   return deferred.promise;
 }
 
 function testOutgoingCallForServiceId(number, serviceId) {
   let outCall;
-  let outInfo = OutCallStrPool(number);
+  let outInfo = gOutCallStrPool(number);
 
   return Promise.resolve()
-    .then(() => dial(number, serviceId))
+    .then(() => gDial(number, serviceId))
     .then(call => {
       outCall = call;
       is(outCall.serviceId, serviceId);
     })
-    .then(() => checkAll(outCall, [outCall], [outInfo.ringing]))
-    .then(() => remoteAnswer(outCall))
-    .then(() => checkAll(outCall, [outCall], [outInfo.active]))
-    .then(() => remoteHangUp(outCall))
-    .then(() => checkAll(null, [], []));
+    .then(() => gCheckAll(outCall, [outCall], '', [], [outInfo.ringing]))
+    .then(() => gRemoteAnswer(outCall))
+    .then(() => gCheckAll(outCall, [outCall], '', [], [outInfo.active]))
+    .then(() => gRemoteHangUp(outCall))
+    .then(() => gCheckAll(null, [], '', [], []));
 }
 
 function testIncomingCallForServiceId(number, serviceId) {
   let inCall;
-  let inInfo = InCallStrPool(number);
+  let inInfo = gInCallStrPool(number);
 
   return Promise.resolve()
-    .then(() => remoteDial(number))
+    .then(() => gRemoteDial(number))
     .then(call => {
       inCall = call;
       is(inCall.serviceId, serviceId);
     })
-    .then(() => checkAll(null, [inCall], [inInfo.incoming]))
-    .then(() => answer(inCall))
-    .then(() => checkAll(inCall, [inCall], [inInfo.active]))
-    .then(() => remoteHangUp(inCall))
-    .then(() => checkAll(null, [], []));
+    .then(() => gCheckAll(null, [inCall], '', [], [inInfo.incoming]))
+    .then(() => gAnswer(inCall))
+    .then(() => gCheckAll(inCall, [inCall], '', [], [inInfo.active]))
+    .then(() => gRemoteHangUp(inCall))
+    .then(() => gCheckAll(null, [], '', [], []));
 }
 
 function testOutgoingCall() {
   log("= testOutgoingCall =");
 
   return Promise.resolve()
     .then(() => muxModem(0))
     .then(() => testOutgoingCallForServiceId("0912345000", 0))
--- a/dom/telephony/test/marionette/test_outgoing_emergency_in_airplane_mode.js
+++ b/dom/telephony/test/marionette/test_outgoing_emergency_in_airplane_mode.js
@@ -3,44 +3,34 @@
 
 MARIONETTE_TIMEOUT = 60000;
 MARIONETTE_HEAD_JS = 'head.js';
 
 let connection;
 let number = "112";
 let outgoing;
 
-function receivedPending(received, pending, nextAction) {
-  let index = pending.indexOf(received);
-  if (index != -1) {
-    pending.splice(index, 1);
-  }
-  if (pending.length === 0) {
-    nextAction();
-  }
-}
-
 function setRadioEnabled(enabled, callback) {
   let request  = connection.setRadioEnabled(enabled);
   let desiredRadioState = enabled ? 'enabled' : 'disabled';
 
   let pending = ['onradiostatechange', 'onsuccess'];
   let done = callback;
 
   connection.onradiostatechange = function() {
     let state = connection.radioState;
     log("Received 'radiostatechange' event, radioState: " + state);
 
     if (state == desiredRadioState) {
-      receivedPending('onradiostatechange', pending, done);
+      gReceivedPending('onradiostatechange', pending, done);
     }
   };
 
   request.onsuccess = function onsuccess() {
-    receivedPending('onsuccess', pending, done);
+    gReceivedPending('onsuccess', pending, done);
   };
 
   request.onerror = function onerror() {
     ok(false, "setRadioEnabled should be ok");
   };
 }
 
 function dial() {
--- a/dom/telephony/test/marionette/test_outgoing_radio_off.js
+++ b/dom/telephony/test/marionette/test_outgoing_radio_off.js
@@ -1,44 +1,34 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_TIMEOUT = 60000;
 MARIONETTE_HEAD_JS = 'head.js';
 
 let connection;
 
-function receivedPending(received, pending, nextAction) {
-  let index = pending.indexOf(received);
-  if (index != -1) {
-    pending.splice(index, 1);
-  }
-  if (pending.length === 0) {
-    nextAction();
-  }
-}
-
 function setRadioEnabled(enabled, callback) {
   let request  = connection.setRadioEnabled(enabled);
   let desiredRadioState = enabled ? 'enabled' : 'disabled';
 
   let pending = ['onradiostatechange', 'onsuccess'];
   let done = callback;
 
   connection.onradiostatechange = function() {
     let state = connection.radioState;
     log("Received 'radiostatechange' event, radioState: " + state);
 
     if (state == desiredRadioState) {
-      receivedPending('onradiostatechange', pending, done);
+      gReceivedPending('onradiostatechange', pending, done);
     }
   };
 
   request.onsuccess = function onsuccess() {
-    receivedPending('onsuccess', pending, done);
+    gReceivedPending('onsuccess', pending, done);
   };
 
   request.onerror = function onerror() {
     ok(false, "setRadioEnabled should be ok");
   };
 }
 
 function dial(number) {