Merge "Add handling of RADIO_NOT_AVAILABLE in TimeConsumingPreferenceActivity" into lmp-mr1-wfc-dev
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index e692a30..b63b75c 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -322,6 +322,26 @@
     }
 
     /**
+     * Determines if this conference is hosted on the current device or the peer device.
+     *
+     * @return {@code true} if this conference is hosted on the current device, {@code false} if it
+     *      is hosted on the peer device.
+     */
+    public boolean isConferenceHost() {
+        if (mConferenceHost == null) {
+            return false;
+        }
+        com.android.internal.telephony.Connection originalConnection =
+                mConferenceHost.getOriginalConnection();
+        if (!(originalConnection instanceof ImsPhoneConnection)) {
+            return false;
+        }
+
+        ImsPhoneConnection imsPhoneConnection = (ImsPhoneConnection) originalConnection;
+        return imsPhoneConnection.isMultiparty() && imsPhoneConnection.isConferenceHost();
+    }
+
+    /**
      * Updates the manage conference capability of the conference.  Where there are one or more
      * conference event package participants, the conference management is permitted.  Where there
      * are no conference event package participants, conference management is not permitted.
diff --git a/src/com/android/services/telephony/ImsConferenceController.java b/src/com/android/services/telephony/ImsConferenceController.java
index e93ebd4..21c61f8 100644
--- a/src/com/android/services/telephony/ImsConferenceController.java
+++ b/src/com/android/services/telephony/ImsConferenceController.java
@@ -16,6 +16,8 @@
 
 package com.android.services.telephony;
 
+import com.android.internal.telephony.imsphone.ImsPhoneConnection;
+
 import android.net.Uri;
 import android.telecom.Conference;
 import android.telecom.Connection;
@@ -155,6 +157,15 @@
                 Log.d(this, "recalc - %s %s", connection.getState(), connection);
             }
 
+            // If this connection is a member of a conference hosted on another device, it is not
+            // conferenceable with any other connections.
+            if (isMemberOfPeerConference(connection)) {
+                if (Log.VERBOSE) {
+                    Log.v(this, "Skipping connection in peer conference: %s", connection);
+                }
+                continue;
+            }
+
             switch (connection.getState()) {
                 case Connection.STATE_ACTIVE:
                     activeConnections.add(connection);
@@ -168,11 +179,18 @@
             connection.setConferenceableConnections(Collections.<Connection>emptyList());
         }
 
-        for (Conference conference : mImsConferences) {
+        for (ImsConference conference : mImsConferences) {
             if (Log.DEBUG) {
                 Log.d(this, "recalc - %s %s", conference.getState(), conference);
             }
 
+            if (!conference.isConferenceHost()) {
+                if (Log.VERBOSE) {
+                    Log.v(this, "skipping conference (not hosted on this device): %s", conference);
+                }
+                continue;
+            }
+
             switch (conference.getState()) {
                 case Connection.STATE_ACTIVE:
                     activeConnections.add(conference);
@@ -209,6 +227,16 @@
 
         // Set the conference as conferenceable with all the connections
         for (ImsConference conference : mImsConferences) {
+            // If this conference is not being hosted on the current device, we cannot conference it
+            // with any other connections.
+            if (!conference.isConferenceHost()) {
+                if (Log.VERBOSE) {
+                    Log.v(this, "skipping conference (not hosted on this device): %s",
+                            conference);
+                }
+                continue;
+            }
+
             List<Connection> nonConferencedConnections =
                 new ArrayList<>(mTelephonyConnections.size());
             for (Connection c : mTelephonyConnections) {
@@ -224,6 +252,27 @@
     }
 
     /**
+     * Determines if a connection is a member of a conference hosted on another device.
+     *
+     * @param connection The connection.
+     * @return {@code true} if the connection is a member of a conference hosted on another device.
+     */
+    private boolean isMemberOfPeerConference(Connection connection) {
+        if (!(connection instanceof TelephonyConnection)) {
+            return false;
+        }
+        TelephonyConnection telephonyConnection = (TelephonyConnection) connection;
+        com.android.internal.telephony.Connection originalConnection =
+                telephonyConnection.getOriginalConnection();
+        if (!(originalConnection instanceof ImsPhoneConnection)) {
+            return false;
+        }
+
+        ImsPhoneConnection imsPhoneConnection = (ImsPhoneConnection) originalConnection;
+        return imsPhoneConnection.isMultiparty() && !imsPhoneConnection.isConferenceHost();
+    }
+
+    /**
      * Starts a new ImsConference for a connection which just entered a multiparty state.
      */
     private void recalculateConference() {
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index ac384fd..d079df7 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -50,6 +50,7 @@
     private static final int MSG_RINGBACK_TONE = 2;
     private static final int MSG_HANDOVER_STATE_CHANGED = 3;
     private static final int MSG_DISCONNECT = 4;
+    private static final int MSG_MULTIPARTY_STATE_CHANGED = 5;
 
     private final Handler mHandler = new Handler() {
         @Override
@@ -87,6 +88,14 @@
                 case MSG_DISCONNECT:
                     updateState();
                     break;
+                case MSG_MULTIPARTY_STATE_CHANGED:
+                    boolean isMultiParty = (Boolean) msg.obj;
+                    Log.i(this, "Update multiparty state to %s", isMultiParty ? "Y" : "N");
+                    mIsMultiParty = isMultiParty;
+                    if (isMultiParty) {
+                        notifyConferenceStarted();
+                    }
+                    break;
             }
         }
     };
@@ -182,6 +191,7 @@
         public void onAudioQualityChanged(int audioQuality) {
             setAudioQuality(audioQuality);
         }
+
         /**
          * Handles a change in the state of conference participant(s), as reported by the
          * {@link com.android.internal.telephony.Connection}.
@@ -192,6 +202,17 @@
         public void onConferenceParticipantsChanged(List<ConferenceParticipant> participants) {
             updateConferenceParticipants(participants);
         }
+
+        /**
+         * Handles a change to the multiparty state for this connection.
+         *
+         * @param isMultiParty {@code true} if the call became multiparty, {@code false}
+         *      otherwise.
+         */
+        @Override
+        public void onMultipartyStateChanged(boolean isMultiParty) {
+            handleMultipartyStateChange(isMultiParty);
+        }
     };
 
     private com.android.internal.telephony.Connection mOriginalConnection;
@@ -693,6 +714,24 @@
         }
     }
 
+    /**
+     * Handles requests to update the multiparty state received via the
+     * {@link com.android.internal.telephony.Connection.Listener#onMultipartyStateChanged(boolean)}
+     * listener.
+     * <p>
+     * Note: We post this to the mHandler to ensure that if a conference must be created as a
+     * result of the multiparty state change, the conference creation happens on the correct
+     * thread.  This ensures that the thread check in
+     * {@link com.android.internal.telephony.PhoneBase#checkCorrectThread(android.os.Handler)}
+     * does not fire.
+     *
+     * @param isMultiParty {@code true} if this connection is multiparty, {@code false} otherwise.
+     */
+    private void handleMultipartyStateChange(boolean isMultiParty) {
+        Log.i(this, "Update multiparty state to %s", isMultiParty ? "Y" : "N");
+        mHandler.obtainMessage(MSG_MULTIPARTY_STATE_CHANGED, isMultiParty).sendToTarget();
+    }
+
     private void setActiveInternal() {
         if (getState() == STATE_ACTIVE) {
             Log.w(this, "Should not be called if this is already ACTIVE");