Support conference calling. (2/4)

Bug: 15006702
Change-Id: I2764ea242f783ba478c9eae86618dd33e9fc792a
diff --git a/src/com/android/telecomm/Call.java b/src/com/android/telecomm/Call.java
index 66f9366..607aeb1 100644
--- a/src/com/android/telecomm/Call.java
+++ b/src/com/android/telecomm/Call.java
@@ -40,6 +40,9 @@
 import com.google.android.collect.Sets;
 import com.google.common.base.Preconditions;
 
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Locale;
 import java.util.Set;
 
@@ -60,6 +63,11 @@
         void onFailedIncomingCall(Call call);
         void onRequestingRingback(Call call, boolean requestingRingback);
         void onPostDialWait(Call call, String remaining);
+        void onIsConferenceCapableChanged(Call call, boolean isConferenceCapable);
+        void onExpiredConferenceCall(Call call);
+        void onConfirmedConferenceCall(Call call);
+        void onParentChanged(Call call);
+        void onChildrenChanged(Call call);
     }
 
     private static final OnQueryCompleteListener sCallerInfoQueryListener =
@@ -180,13 +188,21 @@
     /** Incoming call-info to use when direct-to-voicemail query finishes. */
     private CallInfo mPendingDirectToVoicemailCallInfo;
 
+    private boolean mIsConferenceCapable = false;
+
+    private boolean mIsConference = false;
+
+    private Call mParentCall = null;
+
+    private List<Call> mChildCalls = new LinkedList<>();
+
     /**
      * Creates an empty call object.
      *
      * @param isIncoming True if this is an incoming call.
      */
-    Call(boolean isIncoming) {
-        this(null, null, isIncoming);
+    Call(boolean isIncoming, boolean isConference) {
+        this(null, null, isIncoming, isConference);
     }
 
     /**
@@ -196,11 +212,12 @@
      * @param gatewayInfo Gateway information to use for the call.
      * @param isIncoming True if this is an incoming call.
      */
-    Call(Uri handle, GatewayInfo gatewayInfo, boolean isIncoming) {
-        mState = CallState.NEW;
+    Call(Uri handle, GatewayInfo gatewayInfo, boolean isIncoming, boolean isConference) {
+        mState = isConference ? CallState.ACTIVE : CallState.NEW;
         setHandle(handle);
         mGatewayInfo = gatewayInfo;
         mIsIncoming = isIncoming;
+        mIsConference = isConference;
     }
 
     void addListener(Listener listener) {
@@ -221,7 +238,15 @@
     }
 
     CallState getState() {
-        return mState;
+        if (mIsConference) {
+            if (!mChildCalls.isEmpty()) {
+                // If we have child calls, just return the child call.
+                return mChildCalls.get(0).getState();
+            }
+            return CallState.ACTIVE;
+        } else {
+            return mState;
+        }
     }
 
     /**
@@ -343,6 +368,27 @@
         mConnectTimeMillis = connectTimeMillis;
     }
 
+    boolean isConferenceCapable() {
+        return mIsConferenceCapable;
+    }
+
+    void setIsConferenceCapable(boolean isConferenceCapable) {
+        if (mIsConferenceCapable != isConferenceCapable) {
+            mIsConferenceCapable = isConferenceCapable;
+            for (Listener l : mListeners) {
+                l.onIsConferenceCapableChanged(this, mIsConferenceCapable);
+            }
+        }
+    }
+
+    Call getParentCall() {
+        return mParentCall;
+    }
+
+    List<Call> getChildCalls() {
+        return mChildCalls;
+    }
+
     CallServiceWrapper getCallService() {
         return mCallService;
     }
@@ -450,7 +496,7 @@
             public void run() {
                 processDirectToVoicemail();
             }
-        }, Timeouts.getDirectToVoicemail());
+        }, Timeouts.getDirectToVoicemailMillis());
     }
 
     void processDirectToVoicemail() {
@@ -740,6 +786,73 @@
         getCallService().onPostDialContinue(this, proceed);
     }
 
+    void conferenceInto(Call conferenceCall) {
+        if (mCallService == null) {
+            Log.w(this, "conference requested on a call without a call service.");
+        } else {
+            mCallService.conference(conferenceCall, this);
+        }
+    }
+
+    void expireConference() {
+        // The conference call expired before we got a confirmation of the conference from the
+        // call service...so start shutting down.
+        clearCallService();
+        for (Listener l : mListeners) {
+            l.onExpiredConferenceCall(this);
+        }
+    }
+
+    void confirmConference() {
+        Log.v(this, "confirming Conf call %s", mListeners);
+        for (Listener l : mListeners) {
+            l.onConfirmedConferenceCall(this);
+        }
+    }
+
+    void splitFromConference() {
+        // TODO(santoscordon): todo
+    }
+
+    void setParentCall(Call parentCall) {
+        if (parentCall == this) {
+            Log.e(this, new Exception(), "setting the parent to self");
+            return;
+        }
+        Preconditions.checkState(parentCall == null || mParentCall == null);
+
+        Call oldParent = mParentCall;
+        if (mParentCall != null) {
+            mParentCall.removeChildCall(this);
+        }
+        mParentCall = parentCall;
+        if (mParentCall != null) {
+            mParentCall.addChildCall(this);
+        }
+
+        for (Listener l : mListeners) {
+            l.onParentChanged(this);
+        }
+    }
+
+    private void addChildCall(Call call) {
+        if (!mChildCalls.contains(call)) {
+            mChildCalls.add(call);
+
+            for (Listener l : mListeners) {
+                l.onChildrenChanged(this);
+            }
+        }
+    }
+
+    private void removeChildCall(Call call) {
+        if (mChildCalls.remove(call)) {
+            for (Listener l : mListeners) {
+                l.onChildrenChanged(this);
+            }
+        }
+    }
+
     /**
      * @return True if the call is ringing, else logs the action name.
      */