Bug 772765 - part2 - Telephony DOM. r=bent
authorHsin-Yi Tsai <htsai@mozilla.com>
Sat, 06 Jul 2013 18:24:55 +0800
changeset 157490 9cf19ed96e2aae67c3f5aec3b8a58a99336a0152
parent 157489 dce012abf2fc063c5540df44b2413a9606f0ef1a
child 157491 aa452f603515c88a9fafeadf41ce80ef6ae887a4
push id407
push userlsblakk@mozilla.com
push dateTue, 03 Dec 2013 03:32:50 +0000
treeherdermozilla-release@babf8c9ebc52 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbent
bugs772765
milestone26.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 772765 - part2 - Telephony DOM. r=bent
content/base/src/nsGkAtomList.h
dom/telephony/CallsList.cpp
dom/telephony/CallsList.h
dom/telephony/Telephony.cpp
dom/telephony/Telephony.h
dom/telephony/TelephonyCall.cpp
dom/telephony/TelephonyCall.h
dom/telephony/TelephonyCallGroup.cpp
dom/telephony/TelephonyCallGroup.h
dom/telephony/TelephonyCommon.h
dom/telephony/moz.build
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -706,16 +706,17 @@ GK_ATOM(ondragover, "ondragover")
 GK_ATOM(ondragstart, "ondragstart")
 GK_ATOM(ondrop, "ondrop")
 GK_ATOM(onenabled, "onenabled")
 GK_ATOM(onemergencycbmodechange, "onemergencycbmodechange")
 GK_ATOM(onerror, "onerror")
 GK_ATOM(onfailed, "onfailed")
 GK_ATOM(onfocus, "onfocus")
 GK_ATOM(onget, "onget")
+GK_ATOM(ongroupchange, "ongroupchange")
 GK_ATOM(onhashchange, "onhashchange")
 GK_ATOM(onheadphoneschange, "onheadphoneschange")
 GK_ATOM(onheld, "onheld")
 GK_ATOM(onhfpstatuschanged, "onhfpstatuschanged")
 GK_ATOM(onholding, "onholding")
 GK_ATOM(oniccinfochange, "oniccinfochange")
 GK_ATOM(onicccardlockerror, "onicccardlockerror")
 GK_ATOM(onincoming, "onincoming")
--- a/dom/telephony/CallsList.cpp
+++ b/dom/telephony/CallsList.cpp
@@ -4,32 +4,35 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "CallsList.h"
 #include "mozilla/dom/CallsListBinding.h"
 
 #include "Telephony.h"
 #include "TelephonyCall.h"
+#include "TelephonyCallGroup.h"
 
 USING_TELEPHONY_NAMESPACE
 using namespace mozilla::dom;
 
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_1(CallsList, mTelephony)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_2(CallsList,
+                                        mTelephony,
+                                        mGroup)
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(CallsList)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(CallsList)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CallsList)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
-CallsList::CallsList(Telephony* aTelephony)
-: mTelephony(aTelephony)
+CallsList::CallsList(Telephony* aTelephony, TelephonyCallGroup* aGroup)
+: mTelephony(aTelephony), mGroup(aGroup)
 {
   MOZ_ASSERT(mTelephony);
 
   SetIsDOMBinding();
 }
 
 CallsList::~CallsList()
 {
@@ -45,25 +48,32 @@ JSObject*
 CallsList::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
 {
   return CallsListBinding::Wrap(aCx, aScope, this);
 }
 
 already_AddRefed<TelephonyCall>
 CallsList::Item(uint32_t aIndex) const
 {
-  nsRefPtr<TelephonyCall> call = mTelephony->CallsArray().SafeElementAt(aIndex);
+  nsRefPtr<TelephonyCall> call;
+  call = mGroup ? mGroup->CallsArray().SafeElementAt(aIndex) :
+                  mTelephony->CallsArray().SafeElementAt(aIndex);
+
   return call.forget();
 }
 
 uint32_t
 CallsList::Length() const
 {
-  return mTelephony->CallsArray().Length();
+  return mGroup ? mGroup->CallsArray().Length() :
+                  mTelephony->CallsArray().Length();
 }
 
 already_AddRefed<TelephonyCall>
 CallsList::IndexedGetter(uint32_t aIndex, bool& aFound) const
 {
-  nsRefPtr<TelephonyCall> call = mTelephony->CallsArray().SafeElementAt(aIndex);
+  nsRefPtr<TelephonyCall> call;
+  call = mGroup ? mGroup->CallsArray().SafeElementAt(aIndex) :
+                  mTelephony->CallsArray().SafeElementAt(aIndex);
   aFound = call ? true : false;
+
   return call.forget();
 }
--- a/dom/telephony/CallsList.h
+++ b/dom/telephony/CallsList.h
@@ -12,22 +12,23 @@
 #include "nsWrapperCache.h"
 
 BEGIN_TELEPHONY_NAMESPACE
 
 class CallsList MOZ_FINAL : public nsISupports,
                             public nsWrapperCache
 {
   nsRefPtr<Telephony> mTelephony;
+  nsRefPtr<TelephonyCallGroup> mGroup;
 
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CallsList)
 
-  CallsList(Telephony* aTelephony);
+  CallsList(Telephony* aTelephony, TelephonyCallGroup* aGroup = nullptr);
 
   nsPIDOMWindow*
   GetParentObject() const;
 
   // WrapperCache
   virtual JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
 
--- a/dom/telephony/Telephony.cpp
+++ b/dom/telephony/Telephony.cpp
@@ -6,26 +6,28 @@
 
 #include "Telephony.h"
 #include "mozilla/dom/TelephonyBinding.h"
 
 #include "nsIURI.h"
 #include "nsPIDOMWindow.h"
 #include "nsIPermissionManager.h"
 
+#include "mozilla/dom/UnionTypes.h"
 #include "nsCharSeparatedTokenizer.h"
 #include "nsContentUtils.h"
 #include "nsCxPusher.h"
 #include "nsNetUtil.h"
 #include "nsServiceManagerUtils.h"
 #include "nsThreadUtils.h"
 
 #include "CallEvent.h"
 #include "CallsList.h"
 #include "TelephonyCall.h"
+#include "TelephonyCallGroup.h"
 
 #define NS_RILCONTENTHELPER_CONTRACTID "@mozilla.org/ril/content-helper;1"
 
 USING_TELEPHONY_NAMESPACE
 using namespace mozilla::dom;
 
 namespace {
 
@@ -142,16 +144,17 @@ Telephony::Create(nsPIDOMWindow* aOwner,
 
   nsRefPtr<Telephony> telephony = new Telephony();
 
   telephony->BindToOwner(aOwner);
 
   telephony->mProvider = ril;
   telephony->mListener = new Listener(telephony);
   telephony->mCallsList = new CallsList(telephony);
+  telephony->mGroup = TelephonyCallGroup::Create(telephony);
 
   nsresult rv = ril->EnumerateCalls(telephony->mListener);
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return nullptr;
   }
 
   rv = ril->RegisterTelephonyMsg(telephony->mListener);
@@ -181,27 +184,16 @@ Telephony::NoteDialedCallFromOtherInstan
 {
   // We don't need to hang on to this call object, it is held alive by mCalls.
   nsRefPtr<TelephonyCall> call = CreateNewDialingCall(aNumber);
 }
 
 nsresult
 Telephony::NotifyCallsChanged(TelephonyCall* aCall)
 {
-  if (aCall) {
-    if (aCall->CallState() == nsITelephonyProvider::CALL_STATE_DIALING ||
-        aCall->CallState() == nsITelephonyProvider::CALL_STATE_ALERTING ||
-        aCall->CallState() == nsITelephonyProvider::CALL_STATE_CONNECTED) {
-      NS_ASSERTION(!mActiveCall, "Already have an active call!");
-      mActiveCall = aCall;
-    } else if (mActiveCall && mActiveCall->CallIndex() == aCall->CallIndex()) {
-      mActiveCall = nullptr;
-    }
-  }
-
   return DispatchCallEvent(NS_LITERAL_STRING("callschanged"), aCall);
 }
 
 already_AddRefed<TelephonyCall>
 Telephony::DialInternal(bool isEmergency,
                         const nsAString& aNumber,
                         ErrorResult& aRv)
 {
@@ -242,29 +234,90 @@ Telephony::DialInternal(bool isEmergency
       nsRefPtr<Telephony> kungFuDeathGrip = telephony;
       telephony->NoteDialedCallFromOtherInstance(aNumber);
     }
   }
 
   return call.forget();
 }
 
+void
+Telephony::UpdateActiveCall(TelephonyCall* aCall, bool aIsAdding)
+{
+  if (aIsAdding) {
+    if (aCall->CallState() == nsITelephonyProvider::CALL_STATE_DIALING ||
+        aCall->CallState() == nsITelephonyProvider::CALL_STATE_ALERTING ||
+        aCall->CallState() == nsITelephonyProvider::CALL_STATE_CONNECTED) {
+      NS_ASSERTION(!mActiveCall, "Already have an active call!");
+      mActiveCall = aCall;
+    }
+  } else if (mActiveCall && mActiveCall->CallIndex() == aCall->CallIndex()) {
+    mActiveCall = nullptr;
+  }
+}
+
+already_AddRefed<TelephonyCall>
+Telephony::GetCall(uint32_t aCallIndex)
+{
+  nsRefPtr<TelephonyCall> call;
+
+  for (uint32_t index = 0; index < mCalls.Length(); index++) {
+    nsRefPtr<TelephonyCall>& tempCall = mCalls[index];
+    if (tempCall->CallIndex() == aCallIndex) {
+      call = tempCall;
+      break;
+    }
+  }
+
+  return call.forget();
+}
+
+bool
+Telephony::MoveCall(uint32_t aCallIndex, bool aIsConference)
+{
+  nsRefPtr<TelephonyCall> call;
+
+  // Move a call to mGroup.
+  if (aIsConference) {
+    call = GetCall(aCallIndex);
+    if (call) {
+      RemoveCall(call);
+      mGroup->AddCall(call);
+      return true;
+    }
+
+    return false;
+  }
+
+  // Remove a call from mGroup.
+  call = mGroup->GetCall(aCallIndex);
+  if (call) {
+    mGroup->RemoveCall(call);
+    AddCall(call);
+    return true;
+  }
+
+  return false;
+}
+
 NS_IMPL_CYCLE_COLLECTION_CLASS(Telephony)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Telephony,
                                                   nsDOMEventTargetHelper)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCalls)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallsList)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGroup)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Telephony,
                                                 nsDOMEventTargetHelper)
   tmp->mActiveCall = nullptr;
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mCalls)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallsList)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mGroup)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(Telephony)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(Telephony, nsDOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(Telephony, nsDOMEventTargetHelper)
 
@@ -311,30 +364,42 @@ Telephony::GetSpeakerEnabled(ErrorResult
 }
 
 void
 Telephony::SetSpeakerEnabled(bool aEnabled, ErrorResult& aRv)
 {
   aRv = mProvider->SetSpeakerEnabled(aEnabled);
 }
 
-already_AddRefed<TelephonyCall>
-Telephony::GetActive() const
+void
+Telephony::GetActive(Nullable<TelephonyCallOrTelephonyCallGroupReturnValue>& aValue)
 {
-  nsCOMPtr<TelephonyCall> activeCall = mActiveCall;
-  return activeCall.forget();
+  if (mActiveCall) {
+    aValue.SetValue().SetAsTelephonyCall() = mActiveCall;
+  } else if (mGroup->CallState() == nsITelephonyProvider::CALL_STATE_CONNECTED) {
+    aValue.SetValue().SetAsTelephonyCallGroup() = mGroup;
+  } else {
+    aValue.SetNull();
+  }
 }
 
 already_AddRefed<CallsList>
 Telephony::Calls() const
 {
   nsRefPtr<CallsList> list = mCallsList;
   return list.forget();
 }
 
+already_AddRefed<TelephonyCallGroup>
+Telephony::ConferenceGroup() const
+{
+  nsRefPtr<TelephonyCallGroup> group = mGroup;
+  return group.forget();
+}
+
 void
 Telephony::StartTone(const nsAString& aDTMFChar, ErrorResult& aRv)
 {
   if (aDTMFChar.IsEmpty()) {
     NS_WARNING("Empty tone string will be ignored");
     return;
   }
 
@@ -363,24 +428,76 @@ Telephony::EventListenerAdded(nsIAtom* a
   }
 }
 
 // nsITelephonyListener
 
 NS_IMETHODIMP
 Telephony::CallStateChanged(uint32_t aCallIndex, uint16_t aCallState,
                             const nsAString& aNumber, bool aIsActive,
-                            bool aIsOutgoing, bool aIsEmergency)
+                            bool aIsOutgoing, bool aIsEmergency,
+                            bool aIsConference)
 {
   NS_ASSERTION(aCallIndex != kOutgoingPlaceholderCallIndex,
                "This should never happen!");
 
   nsRefPtr<TelephonyCall> modifiedCall;
   nsRefPtr<TelephonyCall> outgoingCall;
 
+  // Update calls array first then state of a call in mCalls.
+
+  if (aIsConference) {
+    // Add the call into mGroup if it hasn't been there, otherwise we simply
+    // update its call state. We don't fire the statechange event on a call in
+    // conference here. Instead, the event will be fired later in
+    // TelephonyCallGroup::ChangeState(). Thus the sequence of firing the
+    // statechange events is guaranteed: first on TelephonyCallGroup then on
+    // individual TelephonyCall objects.
+
+    modifiedCall = mGroup->GetCall(aCallIndex);
+    if (modifiedCall) {
+      modifiedCall->ChangeStateInternal(aCallState, false);
+      return NS_OK;
+    }
+
+    // The call becomes a conference call. Remove it from Telephony::mCalls and
+    // add it to mGroup.
+    modifiedCall = GetCall(aCallIndex);
+    if (modifiedCall) {
+      modifiedCall->ChangeStateInternal(aCallState, false);
+      mGroup->AddCall(modifiedCall);
+      RemoveCall(modifiedCall);
+      return NS_OK;
+    }
+
+    // Didn't find this call in mCalls or mGroup. Create a new call.
+    nsRefPtr<TelephonyCall> call =
+      TelephonyCall::Create(this, aNumber, aCallState, aCallIndex,
+                            aIsEmergency, aIsConference);
+    NS_ASSERTION(call, "This should never fail!");
+
+    return NS_OK;
+  }
+
+  // Not a conference call. Remove the call from mGroup if it has been there.
+  modifiedCall = mGroup->GetCall(aCallIndex);
+  if (modifiedCall) {
+    if (aCallState != nsITelephonyProvider::CALL_STATE_DISCONNECTED) {
+      if (modifiedCall->CallState() != aCallState) {
+        modifiedCall->ChangeState(aCallState);
+      }
+      mGroup->RemoveCall(modifiedCall);
+      AddCall(modifiedCall);
+    } else {
+      modifiedCall->ChangeState(aCallState);
+    }
+    return NS_OK;
+  }
+
+  // Update calls in mCalls.
   for (uint32_t index = 0; index < mCalls.Length(); index++) {
     nsRefPtr<TelephonyCall>& tempCall = mCalls[index];
     if (tempCall->CallIndex() == kOutgoingPlaceholderCallIndex) {
       NS_ASSERTION(!outgoingCall, "More than one outgoing call not supported!");
       NS_ASSERTION(tempCall->CallState() ==
                    nsITelephonyProvider::CALL_STATE_DIALING,
                    "Something really wrong here!");
       // Stash this for later, we may need it if aCallIndex doesn't match one of
@@ -437,67 +554,81 @@ Telephony::CallStateChanged(uint32_t aCa
     nsresult rv = DispatchCallEvent(NS_LITERAL_STRING("incoming"), call);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+Telephony::ConferenceCallStateChanged(uint16_t aCallState)
+{
+  mGroup->ChangeState(aCallState);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 Telephony::EnumerateCallStateComplete()
 {
   MOZ_ASSERT(!mEnumerated);
 
   mEnumerated = true;
 
   if (NS_FAILED(NotifyCallsChanged(nullptr))) {
     NS_WARNING("Failed to notify calls changed!");
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Telephony::EnumerateCallState(uint32_t aCallIndex, uint16_t aCallState,
                               const nsAString& aNumber, bool aIsActive,
                               bool aIsOutgoing, bool aIsEmergency,
-                              bool* aContinue)
+                              bool aIsConference, bool* aContinue)
 {
-  // Make sure we don't somehow add duplicates.
-  for (uint32_t index = 0; index < mCalls.Length(); index++) {
-    nsRefPtr<TelephonyCall>& tempCall = mCalls[index];
-    if (tempCall->CallIndex() == aCallIndex) {
-      // We have the call already. Skip it.
-      *aContinue = true;
-      return NS_OK;
-    }
+  nsRefPtr<TelephonyCall> call;
+
+  // We request calls enumeration in constructor, and the asynchronous result
+  // will be sent back through the callback function EnumerateCallState().
+  // However, it is likely to have call state changes, i.e. CallStateChanged()
+  // being called, before the enumeration result comes back. We'd make sure
+  // we don't somehow add duplicates due to the race condition.
+  call = aIsConference ? mGroup->GetCall(aCallIndex) : GetCall(aCallIndex);
+  if (call) {
+    // We have the call either in mCalls or in mGroup. Skip it.
+    *aContinue = true;
+    return NS_OK;
   }
 
-  nsRefPtr<TelephonyCall> call =
-    TelephonyCall::Create(this, aNumber, aCallState, aCallIndex, aIsEmergency);
+  if (MoveCall(aCallIndex, aIsConference)) {
+    *aContinue = true;
+    return NS_OK;
+  }
+
+  // Didn't know anything about this call before now.
+
+  call = TelephonyCall::Create(this, aNumber, aCallState, aCallIndex,
+                               aIsEmergency, aIsConference);
   NS_ASSERTION(call, "This should never fail!");
 
-  NS_ASSERTION(mCalls.Contains(call), "Should have auto-added new call!");
+  NS_ASSERTION(aIsConference ? mGroup->CallsArray().Contains(call) :
+                               mCalls.Contains(call),
+               "Should have auto-added new call!");
 
   *aContinue = true;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Telephony::SupplementaryServiceNotification(int32_t aCallIndex,
                                             uint16_t aNotification)
 {
   nsRefPtr<TelephonyCall> associatedCall;
   if (!mCalls.IsEmpty() && aCallIndex != -1) {
-    for (uint32_t index = 0; index < mCalls.Length(); index++) {
-      nsRefPtr<TelephonyCall>& call = mCalls[index];
-      if (call->CallIndex() == uint32_t(aCallIndex)) {
-        associatedCall = call;
-        break;
-      }
-    }
+    associatedCall = GetCall(aCallIndex);
   }
 
   nsresult rv;
   switch (aNotification) {
     case nsITelephonyProvider::NOTIFICATION_REMOTE_HELD:
       rv = DispatchCallEvent(NS_LITERAL_STRING("remoteheld"), associatedCall);
       break;
     case nsITelephonyProvider::NOTIFICATION_REMOTE_RESUMED:
@@ -521,23 +652,17 @@ Telephony::NotifyError(int32_t aCallInde
     // The connection is not established yet. Get the latest dialing call object.
     if (aCallIndex == -1) {
       nsRefPtr<TelephonyCall>& lastCall = mCalls[mCalls.Length() - 1];
       if (lastCall->CallIndex() == kOutgoingPlaceholderCallIndex) {
         callToNotify = lastCall;
       }
     } else {
       // The connection has been established. Get the failed call.
-      for (uint32_t index = 0; index < mCalls.Length(); index++) {
-        nsRefPtr<TelephonyCall>& call = mCalls[index];
-        if (call->CallIndex() == aCallIndex) {
-          callToNotify = call;
-          break;
-        }
-      }
+      callToNotify = GetCall(aCallIndex);
     }
   }
 
   if (!callToNotify) {
     NS_ERROR("Don't call me with a bad call index!");
     return NS_ERROR_UNEXPECTED;
   }
 
--- a/dom/telephony/Telephony.h
+++ b/dom/telephony/Telephony.h
@@ -3,23 +3,30 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_telephony_telephony_h__
 #define mozilla_dom_telephony_telephony_h__
 
 #include "TelephonyCommon.h"
+
+#include "nsITelephonyProvider.h"
+
 // Need to include TelephonyCall.h because we have inline methods that
 // assume they see the definition of TelephonyCall.
 #include "TelephonyCall.h"
 
-#include "nsITelephonyProvider.h"
+class nsPIDOMWindow;
 
-class nsPIDOMWindow;
+namespace mozilla {
+namespace dom {
+class TelephonyCallOrTelephonyCallGroupReturnValue;
+}
+}
 
 BEGIN_TELEPHONY_NAMESPACE
 
 class Telephony MOZ_FINAL : public nsDOMEventTargetHelper
 {
   /**
    * Class Telephony doesn't actually inherit nsITelephonyListener.
    * Instead, it owns an nsITelephonyListener derived instance mListener
@@ -34,16 +41,18 @@ class Telephony MOZ_FINAL : public nsDOM
 
   nsCOMPtr<nsITelephonyProvider> mProvider;
   nsRefPtr<Listener> mListener;
 
   TelephonyCall* mActiveCall;
   nsTArray<nsRefPtr<TelephonyCall> > mCalls;
   nsRefPtr<CallsList> mCallsList;
 
+  nsRefPtr<TelephonyCallGroup> mGroup;
+
   bool mEnumerated;
 
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSITELEPHONYLISTENER
   NS_REALLY_FORWARD_NSIDOMEVENTTARGET(nsDOMEventTargetHelper)
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Telephony,
                                            nsDOMEventTargetHelper)
@@ -72,22 +81,25 @@ public:
   SetMuted(bool aMuted, ErrorResult& aRv);
 
   bool
   GetSpeakerEnabled(ErrorResult& aRv) const;
 
   void
   SetSpeakerEnabled(bool aEnabled, ErrorResult& aRv);
 
-  already_AddRefed<TelephonyCall>
-  GetActive() const;
+  void
+  GetActive(Nullable<TelephonyCallOrTelephonyCallGroupReturnValue>& aValue);
 
   already_AddRefed<CallsList>
   Calls() const;
 
+  already_AddRefed<TelephonyCallGroup>
+  ConferenceGroup() const;
+
   void
   StartTone(const nsAString& aDTMF, ErrorResult& aRv);
 
   void
   StopTone(ErrorResult& aRv);
 
   IMPL_EVENT_HANDLER(incoming)
   IMPL_EVENT_HANDLER(callschanged)
@@ -99,24 +111,26 @@ public:
 
   static bool CheckPermission(nsPIDOMWindow* aOwner);
 
   void
   AddCall(TelephonyCall* aCall)
   {
     NS_ASSERTION(!mCalls.Contains(aCall), "Already know about this one!");
     mCalls.AppendElement(aCall);
+    UpdateActiveCall(aCall, true);
     NotifyCallsChanged(aCall);
   }
 
   void
   RemoveCall(TelephonyCall* aCall)
   {
     NS_ASSERTION(mCalls.Contains(aCall), "Didn't know about this one!");
     mCalls.RemoveElement(aCall);
+    UpdateActiveCall(aCall, false);
     NotifyCallsChanged(aCall);
   }
 
   nsITelephonyProvider*
   Provider() const
   {
     return mProvider;
   }
@@ -148,13 +162,22 @@ private:
                ErrorResult& aRv);
 
   nsresult
   DispatchCallEvent(const nsAString& aType,
                     TelephonyCall* aCall);
 
   void
   EnqueueEnumerationAck();
+
+  void
+  UpdateActiveCall(TelephonyCall* aCall, bool aIsAdding);
+
+  already_AddRefed<TelephonyCall>
+  GetCall(uint32_t aCallIndex);
+
+  bool
+  MoveCall(uint32_t aCallIndex, bool aIsConference);
 };
 
 END_TELEPHONY_NAMESPACE
 
 #endif // mozilla_dom_telephony_telephony_h__
--- a/dom/telephony/TelephonyCall.cpp
+++ b/dom/telephony/TelephonyCall.cpp
@@ -6,38 +6,41 @@
 
 #include "TelephonyCall.h"
 #include "mozilla/dom/TelephonyCallBinding.h"
 
 #include "mozilla/dom/DOMError.h"
 
 #include "CallEvent.h"
 #include "Telephony.h"
+#include "TelephonyCallGroup.h"
 
 USING_TELEPHONY_NAMESPACE
 using namespace mozilla::dom;
 
 // static
 already_AddRefed<TelephonyCall>
 TelephonyCall::Create(Telephony* aTelephony, const nsAString& aNumber,
-                      uint16_t aCallState, uint32_t aCallIndex, bool aEmergency)
+                      uint16_t aCallState, uint32_t aCallIndex,
+                      bool aEmergency, bool aIsConference)
 {
   NS_ASSERTION(aTelephony, "Null pointer!");
   NS_ASSERTION(!aNumber.IsEmpty(), "Empty number!");
   NS_ASSERTION(aCallIndex >= 1, "Invalid call index!");
 
   nsRefPtr<TelephonyCall> call = new TelephonyCall();
 
   call->BindToOwner(aTelephony->GetOwner());
 
   call->mTelephony = aTelephony;
   call->mNumber = aNumber;
   call->mCallIndex = aCallIndex;
   call->mError = nullptr;
   call->mEmergency = aEmergency;
+  call->mGroup = aIsConference ? aTelephony->ConferenceGroup() : nullptr;
 
   call->ChangeStateInternal(aCallState, false);
 
   return call.forget();
 }
 
 TelephonyCall::TelephonyCall()
   : mCallIndex(kOutgoingPlaceholderCallIndex),
@@ -103,20 +106,28 @@ TelephonyCall::ChangeStateInternal(uint1
   mCallState = aCallState;
 
   if (aCallState == nsITelephonyProvider::CALL_STATE_DIALING) {
     mOutgoing = true;
   }
 
   if (aCallState == nsITelephonyProvider::CALL_STATE_DISCONNECTED) {
     NS_ASSERTION(mLive, "Should be live!");
-    mTelephony->RemoveCall(this);
+    if (mGroup) {
+      mGroup->RemoveCall(this);
+    } else {
+      mTelephony->RemoveCall(this);
+    }
     mLive = false;
   } else if (!mLive) {
-    mTelephony->AddCall(this);
+    if (mGroup) {
+      mGroup->AddCall(this);
+    } else {
+      mTelephony->AddCall(this);
+    }
     mLive = true;
   }
 
   if (aFireEvents) {
     nsresult rv = DispatchCallEvent(NS_LITERAL_STRING("statechange"), this);
     if (NS_FAILED(rv)) {
       NS_WARNING("Failed to dispatch specific event!");
     }
@@ -155,36 +166,55 @@ TelephonyCall::NotifyError(const nsAStri
   ChangeStateInternal(nsITelephonyProvider::CALL_STATE_DISCONNECTED, true);
 
   nsresult rv = DispatchCallEvent(NS_LITERAL_STRING("error"), this);
   if (NS_FAILED(rv)) {
     NS_WARNING("Failed to dispatch error event!");
   }
 }
 
-NS_IMPL_CYCLE_COLLECTION_INHERITED_2(TelephonyCall,
+void
+TelephonyCall::ChangeGroup(TelephonyCallGroup* aGroup)
+{
+  mGroup = aGroup;
+
+  nsresult rv = DispatchCallEvent(NS_LITERAL_STRING("groupchange"), this);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Failed to dispatch error event!");
+  }
+}
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED_3(TelephonyCall,
                                      nsDOMEventTargetHelper,
                                      mTelephony,
-                                     mError);
+                                     mError,
+                                     mGroup);
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(TelephonyCall)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(TelephonyCall, nsDOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(TelephonyCall, nsDOMEventTargetHelper)
 
 // TelephonyCall WebIDL
 
 already_AddRefed<DOMError>
 TelephonyCall::GetError() const
 {
   nsRefPtr<DOMError> error = mError;
   return error.forget();
 }
 
+already_AddRefed<TelephonyCallGroup>
+TelephonyCall::GetGroup() const
+{
+  nsRefPtr<TelephonyCallGroup> group = mGroup;
+  return group.forget();
+}
+
 void
 TelephonyCall::Answer(ErrorResult& aRv)
 {
   if (mCallState != nsITelephonyProvider::CALL_STATE_INCOMING) {
     NS_WARNING("Answer on non-incoming call ignored!");
     return;
   }
 
@@ -220,16 +250,21 @@ TelephonyCall::HangUp(ErrorResult& aRv)
 void
 TelephonyCall::Hold(ErrorResult& aRv)
 {
   if (mCallState != nsITelephonyProvider::CALL_STATE_CONNECTED) {
     NS_WARNING("Hold non-connected call ignored!");
     return;
   }
 
+  if (mGroup) {
+    NS_WARNING("Hold a call in conference ignored!");
+    return;
+  }
+
   nsresult rv = mTelephony->Provider()->HoldCall(mCallIndex);
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return;
   }
 
   ChangeStateInternal(nsITelephonyProvider::CALL_STATE_HOLDING, true);
 }
@@ -237,16 +272,21 @@ TelephonyCall::Hold(ErrorResult& aRv)
 void
 TelephonyCall::Resume(ErrorResult& aRv)
 {
   if (mCallState != nsITelephonyProvider::CALL_STATE_HELD) {
     NS_WARNING("Resume non-held call ignored!");
     return;
   }
 
+  if (mGroup) {
+    NS_WARNING("Resume a call in conference ignored!");
+    return;
+  }
+
   nsresult rv = mTelephony->Provider()->ResumeCall(mCallIndex);
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return;
   }
 
   ChangeStateInternal(nsITelephonyProvider::CALL_STATE_RESUMING, true);
 }
--- a/dom/telephony/TelephonyCall.h
+++ b/dom/telephony/TelephonyCall.h
@@ -13,16 +13,17 @@
 
 class nsPIDOMWindow;
 
 BEGIN_TELEPHONY_NAMESPACE
 
 class TelephonyCall MOZ_FINAL : public nsDOMEventTargetHelper
 {
   nsRefPtr<Telephony> mTelephony;
+  nsRefPtr<TelephonyCallGroup> mGroup;
 
   nsString mNumber;
   nsString mSecondNumber;
   nsString mState;
   bool mEmergency;
   nsRefPtr<mozilla::dom::DOMError> mError;
 
   uint32_t mCallIndex;
@@ -31,16 +32,18 @@ class TelephonyCall MOZ_FINAL : public n
   bool mOutgoing;
 
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_REALLY_FORWARD_NSIDOMEVENTTARGET(nsDOMEventTargetHelper)
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(TelephonyCall,
                                            nsDOMEventTargetHelper)
 
+  friend class Telephony;
+
   nsPIDOMWindow*
   GetParentObject() const
   {
     return GetOwner();
   }
 
   // WrapperCache
   virtual JSObject*
@@ -69,16 +72,19 @@ public:
   Emergency() const
   {
     return mEmergency;
   }
 
   already_AddRefed<DOMError>
   GetError() const;
 
+  already_AddRefed<TelephonyCallGroup>
+  GetGroup() const;
+
   void
   Answer(ErrorResult& aRv);
 
   void
   HangUp(ErrorResult& aRv);
 
   void
   Hold(ErrorResult& aRv);
@@ -92,21 +98,22 @@ public:
   IMPL_EVENT_HANDLER(connecting)
   IMPL_EVENT_HANDLER(connected)
   IMPL_EVENT_HANDLER(disconnecting)
   IMPL_EVENT_HANDLER(disconnected)
   IMPL_EVENT_HANDLER(holding)
   IMPL_EVENT_HANDLER(held)
   IMPL_EVENT_HANDLER(resuming)
   IMPL_EVENT_HANDLER(error)
+  IMPL_EVENT_HANDLER(groupchange)
 
   static already_AddRefed<TelephonyCall>
   Create(Telephony* aTelephony, const nsAString& aNumber, uint16_t aCallState,
          uint32_t aCallIndex = kOutgoingPlaceholderCallIndex,
-         bool aEmergency = false);
+         bool aEmergency = false, bool aIsConference = false);
 
   void
   ChangeState(uint16_t aCallState)
   {
     ChangeStateInternal(aCallState, true);
   }
 
   uint32_t
@@ -145,16 +152,19 @@ public:
   IsOutgoing() const
   {
     return mOutgoing;
   }
 
   void
   NotifyError(const nsAString& aError);
 
+  void
+  ChangeGroup(TelephonyCallGroup* aGroup);
+
 private:
   TelephonyCall();
 
   ~TelephonyCall();
 
   void
   ChangeStateInternal(uint16_t aCallState, bool aFireEvents);
 
new file mode 100644
--- /dev/null
+++ b/dom/telephony/TelephonyCallGroup.cpp
@@ -0,0 +1,283 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TelephonyCallGroup.h"
+#include "mozilla/dom/TelephonyCallGroupBinding.h"
+
+#include "CallEvent.h"
+#include "CallsList.h"
+#include "Telephony.h"
+
+USING_TELEPHONY_NAMESPACE
+using namespace mozilla::dom;
+
+TelephonyCallGroup::TelephonyCallGroup()
+: mCallState(nsITelephonyProvider::CALL_STATE_UNKNOWN)
+{
+  SetIsDOMBinding();
+}
+
+TelephonyCallGroup::~TelephonyCallGroup()
+{
+}
+
+// static
+already_AddRefed<TelephonyCallGroup>
+TelephonyCallGroup::Create(Telephony* aTelephony)
+{
+  NS_ASSERTION(aTelephony, "Null telephony!");
+
+  nsRefPtr<TelephonyCallGroup> group = new TelephonyCallGroup();
+
+  group->BindToOwner(aTelephony->GetOwner());
+
+  group->mTelephony = aTelephony;
+  group->mCallsList = new CallsList(aTelephony, group);
+
+  return group.forget();
+}
+
+JSObject*
+TelephonyCallGroup::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
+{
+  return TelephonyCallGroupBinding::Wrap(aCx, aScope, this);
+}
+
+void
+TelephonyCallGroup::AddCall(TelephonyCall* aCall)
+{
+  NS_ASSERTION(!mCalls.Contains(aCall), "Already know about this one!");
+  mCalls.AppendElement(aCall);
+  aCall->ChangeGroup(this);
+  NotifyCallsChanged(aCall);
+}
+
+void
+TelephonyCallGroup::RemoveCall(TelephonyCall* aCall)
+{
+  NS_ASSERTION(mCalls.Contains(aCall), "Didn't know about this one!");
+  mCalls.RemoveElement(aCall);
+  aCall->ChangeGroup(nullptr);
+  NotifyCallsChanged(aCall);
+}
+
+void
+TelephonyCallGroup::ChangeState(uint16_t aCallState)
+{
+  if (mCallState == aCallState) {
+    return;
+  }
+
+  nsString stateString;
+  switch (aCallState) {
+    case nsITelephonyProvider::CALL_STATE_UNKNOWN:
+      break;
+    case nsITelephonyProvider::CALL_STATE_CONNECTED:
+      stateString.AssignLiteral("connected");
+      break;
+    case nsITelephonyProvider::CALL_STATE_HOLDING:
+      stateString.AssignLiteral("holding");
+      break;
+    case nsITelephonyProvider::CALL_STATE_HELD:
+      stateString.AssignLiteral("held");
+      break;
+    case nsITelephonyProvider::CALL_STATE_RESUMING:
+      stateString.AssignLiteral("resuming");
+      break;
+    default:
+      NS_NOTREACHED("Unknown state!");
+  }
+
+  mState = stateString;
+  mCallState = aCallState;
+
+  nsresult rv = DispatchCallEvent(NS_LITERAL_STRING("statechange"), nullptr);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Failed to dispatch specific event!");
+  }
+  if (!stateString.IsEmpty()) {
+    // This can change if the statechange handler called back here... Need to
+    // figure out something smarter.
+    if (mCallState == aCallState) {
+      rv = DispatchCallEvent(stateString, nullptr);
+      if (NS_FAILED(rv)) {
+        NS_WARNING("Failed to dispatch specific event!");
+      }
+    }
+  }
+
+  for (uint32_t index = 0; index < mCalls.Length(); index++) {
+    nsRefPtr<TelephonyCall> call = mCalls[index];
+    call->ChangeState(aCallState);
+
+    MOZ_ASSERT(call->CallState() == aCallState);
+  }
+}
+
+nsresult
+TelephonyCallGroup::NotifyCallsChanged(TelephonyCall* aCall)
+{
+  return DispatchCallEvent(NS_LITERAL_STRING("callschanged"), aCall);
+}
+
+nsresult
+TelephonyCallGroup::DispatchCallEvent(const nsAString& aType,
+                                      TelephonyCall* aCall)
+{
+  nsRefPtr<CallEvent> event = CallEvent::Create(this, aType, aCall, false, false);
+  return DispatchTrustedEvent(event);
+}
+
+bool
+TelephonyCallGroup::CanConference(const TelephonyCall& aCall,
+                                  TelephonyCall* aSecondCall)
+{
+  if (!aSecondCall) {
+    MOZ_ASSERT(!mCalls.IsEmpty());
+
+    return (mCallState == nsITelephonyProvider::CALL_STATE_CONNECTED &&
+            aCall.CallState() == nsITelephonyProvider::CALL_STATE_HELD) ||
+           (mCallState == nsITelephonyProvider::CALL_STATE_HELD &&
+            aCall.CallState() == nsITelephonyProvider::CALL_STATE_CONNECTED);
+  }
+
+  MOZ_ASSERT(mCallState == nsITelephonyProvider::CALL_STATE_UNKNOWN);
+
+  return (aCall.CallState() == nsITelephonyProvider::CALL_STATE_CONNECTED &&
+          aSecondCall->CallState() == nsITelephonyProvider::CALL_STATE_HELD) ||
+         (aCall.CallState() == nsITelephonyProvider::CALL_STATE_HELD &&
+          aSecondCall->CallState() == nsITelephonyProvider::CALL_STATE_CONNECTED);
+}
+
+already_AddRefed<TelephonyCall>
+TelephonyCallGroup::GetCall(uint32_t aCallIndex)
+{
+  nsRefPtr<TelephonyCall> call;
+
+  for (uint32_t index = 0; index < mCalls.Length(); index++) {
+    nsRefPtr<TelephonyCall>& tempCall = mCalls[index];
+    if (tempCall->CallIndex() == aCallIndex) {
+      call = tempCall;
+      break;
+    }
+  }
+
+  return call.forget();
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(TelephonyCallGroup)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(TelephonyCallGroup,
+                                                  nsDOMEventTargetHelper)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCalls)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallsList)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTelephony)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(TelephonyCallGroup,
+                                                nsDOMEventTargetHelper)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mCalls)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallsList)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mTelephony)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(TelephonyCallGroup)
+NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(TelephonyCallGroup, nsDOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(TelephonyCallGroup, nsDOMEventTargetHelper)
+
+// WebIDL
+already_AddRefed<CallsList>
+TelephonyCallGroup::Calls() const
+{
+  nsRefPtr<CallsList> list = mCallsList;
+  return list.forget();
+}
+
+void
+TelephonyCallGroup::Add(TelephonyCall& aCall,
+                        ErrorResult& aRv)
+{
+  if (!CanConference(aCall, nullptr)) {
+    aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+    return;
+  }
+
+  aRv = mTelephony->Provider()->ConferenceCall();
+}
+
+void
+TelephonyCallGroup::Add(TelephonyCall& aCall,
+                        TelephonyCall& aSecondCall,
+                        ErrorResult& aRv)
+{
+  if (!CanConference(aCall, &aSecondCall)) {
+    aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+    return;
+  }
+
+  aRv = mTelephony->Provider()->ConferenceCall();
+}
+
+void
+TelephonyCallGroup::Remove(TelephonyCall& aCall, ErrorResult& aRv)
+{
+  if (mCallState != nsITelephonyProvider::CALL_STATE_CONNECTED) {
+    NS_WARNING("Remove call from a non-connected call group. Ignore!");
+    return;
+  }
+
+  uint32_t callIndex = aCall.CallIndex();
+  bool hasCallToRemove = false;
+  for (uint32_t index = 0; index < mCalls.Length(); index++) {
+    nsRefPtr<TelephonyCall>& call = mCalls[index];
+    if (call->CallIndex() == callIndex) {
+      hasCallToRemove = true;
+      break;
+    }
+  }
+
+  if (hasCallToRemove) {
+    aRv = mTelephony->Provider()->SeparateCall(callIndex);
+  } else {
+    NS_WARNING("Didn't have this call. Ignore!");
+  }
+}
+
+void
+TelephonyCallGroup::Hold(ErrorResult& aRv)
+{
+  if (mCallState != nsITelephonyProvider::CALL_STATE_CONNECTED) {
+    NS_WARNING("Hold non-connected call ignored!");
+    return;
+  }
+
+  nsresult rv = mTelephony->Provider()->HoldConference();
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+    return;
+  }
+
+  ChangeState(nsITelephonyProvider::CALL_STATE_HOLDING);
+}
+
+void
+TelephonyCallGroup::Resume(ErrorResult& aRv)
+{
+  if (mCallState != nsITelephonyProvider::CALL_STATE_HELD) {
+    NS_WARNING("Resume non-held call ignored!");
+    return;
+  }
+
+  nsresult rv = mTelephony->Provider()->ResumeConference();
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+    return;
+  }
+
+  ChangeState(nsITelephonyProvider::CALL_STATE_RESUMING);
+}
new file mode 100644
--- /dev/null
+++ b/dom/telephony/TelephonyCallGroup.h
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_telephony_telephonycallgroup_h__
+#define mozilla_dom_telephony_telephonycallgroup_h__
+
+#include "TelephonyCommon.h"
+
+BEGIN_TELEPHONY_NAMESPACE
+
+class TelephonyCallGroup MOZ_FINAL : public nsDOMEventTargetHelper
+{
+  nsRefPtr<Telephony> mTelephony;
+
+  nsTArray<nsRefPtr<TelephonyCall> > mCalls;
+
+  nsRefPtr<CallsList> mCallsList;
+
+  nsString mState;
+
+  uint16_t mCallState;
+
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(TelephonyCallGroup,
+                                           nsDOMEventTargetHelper)
+
+  nsPIDOMWindow*
+  GetParentObject() const
+  {
+    return GetOwner();
+  }
+
+  // WrapperCache
+  virtual JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
+
+  // WebIDL interface
+  already_AddRefed<CallsList>
+  Calls() const;
+
+  void
+  Add(TelephonyCall& aCall, ErrorResult& aRv);
+
+  void
+  Add(TelephonyCall& aCall, TelephonyCall& aSecondCall, ErrorResult& aRv);
+
+  void
+  Remove(TelephonyCall& aCall, ErrorResult& aRv);
+
+  void
+  Hold(ErrorResult& aRv);
+
+  void
+  Resume(ErrorResult& aRv);
+
+  void
+  GetState(nsString& aState) const
+  {
+    aState = mState;
+  }
+
+  IMPL_EVENT_HANDLER(statechange)
+  IMPL_EVENT_HANDLER(connected)
+  IMPL_EVENT_HANDLER(holding)
+  IMPL_EVENT_HANDLER(held)
+  IMPL_EVENT_HANDLER(resuming)
+  IMPL_EVENT_HANDLER(callschanged)
+
+  static already_AddRefed<TelephonyCallGroup>
+  Create(Telephony* aTelephony);
+
+  void
+  AddCall(TelephonyCall* aCall);
+
+  void
+  RemoveCall(TelephonyCall* aCall);
+
+  already_AddRefed<TelephonyCall>
+  GetCall(uint32_t aCallIndex);
+
+  const nsTArray<nsRefPtr<TelephonyCall> >&
+  CallsArray() const
+  {
+    return mCalls;
+  }
+
+  void
+  ChangeState(uint16_t aCallState);
+
+  uint16_t
+  CallState() const
+  {
+    return mCallState;
+  }
+
+private:
+  TelephonyCallGroup();
+  ~TelephonyCallGroup();
+
+  nsresult
+  NotifyCallsChanged(TelephonyCall* aCall);
+
+  nsresult
+  DispatchCallEvent(const nsAString& aType,
+                    TelephonyCall* aCall);
+
+  bool CanConference(const TelephonyCall& aCall, TelephonyCall* aSecondCall);
+};
+
+END_TELEPHONY_NAMESPACE
+
+#endif // mozilla_dom_telephony_telephonycallgroup_h__
--- a/dom/telephony/TelephonyCommon.h
+++ b/dom/telephony/TelephonyCommon.h
@@ -28,12 +28,13 @@ BEGIN_TELEPHONY_NAMESPACE
 
 enum {
   kOutgoingPlaceholderCallIndex = UINT32_MAX
 };
 
 class CallsList;
 class Telephony;
 class TelephonyCall;
+class TelephonyCallGroup;
 
 END_TELEPHONY_NAMESPACE
 
 #endif // mozilla_dom_telephony_telephonycommon_h__
--- a/dom/telephony/moz.build
+++ b/dom/telephony/moz.build
@@ -12,10 +12,11 @@ XPIDL_MODULE = 'dom_telephony'
 
 MODULE = 'dom'
 
 CPP_SOURCES += [
     'CallEvent.cpp',
     'CallsList.cpp',
     'Telephony.cpp',
     'TelephonyCall.cpp',
+    'TelephonyCallGroup.cpp',
 ]