Prevent merging conference calls hosted on peer device.

With IMS, the "multiparty" bit on an ImsCall is set to "true" when a call
is merged into a conference.  This not only occurs on the device hosting
the conference call, but also on the devices of the callers merged into
the conference.

This CL adds code to ImsCall to handle changes to the multiparty state of
the session.  It also includes code to track whether the ImsCall is the
Conference Host.  A call is the conference host if the conference was
established on the current device.



Bug: 19478784
Change-Id: I764d6381d25275980a87705b7b53e21cdbffb1ea
diff --git a/src/java/com/android/ims/ImsCall.java b/src/java/com/android/ims/ImsCall.java
index 39bbbac..06e2686 100644
--- a/src/java/com/android/ims/ImsCall.java
+++ b/src/java/com/android/ims/ImsCall.java
@@ -414,6 +414,16 @@
         public void onCallHandoverFailed(ImsCall imsCall, int srcAccessTech, int targetAccessTech,
             ImsReasonInfo reasonInfo) {
         }
+
+        /**
+         * Notifies of a change to the multiparty state for this {@code ImsCall}.
+         *
+         * @param imsCall The IMS call.
+         * @param isMultiParty {@code true} if the call became multiparty, {@code false}
+         *      otherwise.
+         */
+        public void onMultipartyStateChanged(ImsCall imsCall, boolean isMultiParty) {
+        }
     }
 
     // List of update operation for IMS call control
@@ -486,6 +496,19 @@
     private boolean mCallSessionMergePending = false;
 
     /**
+     * For multi-party IMS calls (e.g. conferences), determines if this {@link ImsCall} is the one
+     * hosting the call.  This is used to distinguish between a situation where an {@link ImsCall}
+     * is {@link #isMultiparty()} because calls were merged on the device, and a situation where
+     * an {@link ImsCall} is {@link #isMultiparty()} because it is a member of a conference started
+     * on another device.
+     * <p>
+     * When {@code true}, this {@link ImsCall} is is the origin of the conference call.
+     * When {@code false}, this {@link ImsCall} is a member of a conference started on another
+     * device.
+     */
+    private boolean mIsConferenceHost = false;
+
+    /**
      * Create an IMS call object.
      *
      * @param context the context for accessing system services
@@ -757,6 +780,20 @@
     }
 
     /**
+     * Where {@link #isMultiparty()} is {@code true}, determines if this {@link ImsCall} is the
+     * origin of the conference call (i.e. {@code #isConferenceHost()} is {@code true}), or if this
+     * {@link ImsCall} is a member of a conference hosted on another device.
+     *
+     * @return {@code true} if this call is the origin of the conference call it is a member of,
+     *      {@code false} otherwise.
+     */
+    public boolean isConferenceHost() {
+        synchronized(mLockObj) {
+            return isMultiparty() && mIsConferenceHost;
+        }
+    }
+
+    /**
      * Marks whether an IMS call is merged. This should be set {@code true} when the call merges
      * into a conference.
      *
@@ -1752,6 +1789,12 @@
             // For the final host, let's just bury the disconnects that we my have received
             // during the merge process since we are now the host of the conference call.
             finalHostCall.clearSessionTerminationFlags();
+
+            // Keep track of the fact that merge host is the origin of a conference call in
+            // progress.  This is important so that we can later determine if a multiparty ImsCall
+            // is multiparty because it was the origin of a conference call, or because it is a
+            // member of a conference on another device.
+            finalHostCall.mIsConferenceHost = true;
         }
         if (listener != null) {
             try {
@@ -2595,6 +2638,36 @@
             }
         }
 
+        /**
+         * Notifies of a change to the multiparty state for this {@code ImsCallSession}.
+         *
+         * @param session The call session.
+         * @param isMultiParty {@code true} if the session became multiparty, {@code false}
+         *      otherwise.
+         */
+        @Override
+        public void callSessionMultipartyStateChanged(ImsCallSession session,
+                boolean isMultiParty) {
+            if (VDBG) {
+                log("callSessionMultipartyStateChanged isMultiParty: " + (isMultiParty ? "Y"
+                        : "N"));
+            }
+
+            ImsCall.Listener listener;
+
+            synchronized(ImsCall.this) {
+                listener = mListener;
+            }
+
+            if (listener != null) {
+                try {
+                    listener.onMultipartyStateChanged(ImsCall.this, isMultiParty);
+                } catch (Throwable t) {
+                    loge("callSessionMultipartyStateChanged :: ", t);
+                }
+            }
+        }
+
         public void callSessionHandover(ImsCallSession session, int srcAccessTech,
             int targetAccessTech, ImsReasonInfo reasonInfo) {
             logi("callSessionHandover :: session=" + session + ", srcAccessTech=" +
@@ -2904,6 +2977,8 @@
         sb.append(isMerged() ? "Y" : "N");
         sb.append(" multiParty:");
         sb.append(isMultiparty() ? "Y" : "N");
+        sb.append(" confHost:");
+        sb.append(isConferenceHost() ? "Y" : "N");
         sb.append(" buried term:");
         sb.append(mSessionEndDuringMerge ? "Y" : "N");
         sb.append(" session:");
diff --git a/src/java/com/android/ims/internal/ImsCallSession.java b/src/java/com/android/ims/internal/ImsCallSession.java
index 227c6be..e67a926 100644
--- a/src/java/com/android/ims/internal/ImsCallSession.java
+++ b/src/java/com/android/ims/internal/ImsCallSession.java
@@ -377,6 +377,18 @@
                                        int mode) {
             // no-op
         }
+
+        /**
+         * Notifies of a change to the multiparty state for this {@code ImsCallSession}.
+         *
+         * @param session The call session.
+         * @param isMultiParty {@code true} if the session became multiparty, {@code false}
+         *      otherwise.
+         */
+        public void callSessionMultipartyStateChanged(ImsCallSession session,
+                boolean isMultiParty) {
+            // no-op
+        }
     }
 
     private final IImsCallSession miSession;
@@ -1201,6 +1213,21 @@
                 mListener.callSessionTtyModeReceived(ImsCallSession.this, mode);
             }
         }
+
+        /**
+         * Notifies of a change to the multiparty state for this {@code ImsCallSession}.
+         *
+         * @param session The call session.
+         * @param isMultiParty {@code true} if the session became multiparty, {@code false}
+         *      otherwise.
+         */
+        public void callSessionMultipartyStateChanged(IImsCallSession session,
+                boolean isMultiParty) {
+
+            if (mListener != null) {
+                mListener.callSessionMultipartyStateChanged(ImsCallSession.this, isMultiParty);
+            }
+        }
     }
 
     /**