Merge "Add CarrierConfigLoader service." into m-wireless-dev
diff --git a/src/com/android/phone/TimeConsumingPreferenceActivity.java b/src/com/android/phone/TimeConsumingPreferenceActivity.java
index bab9469..08a5a95 100644
--- a/src/com/android/phone/TimeConsumingPreferenceActivity.java
+++ b/src/com/android/phone/TimeConsumingPreferenceActivity.java
@@ -179,6 +179,8 @@
     public void onException(Preference preference, CommandException exception) {
         if (exception.getCommandError() == CommandException.Error.FDN_CHECK_FAILURE) {
             onError(preference, FDN_CHECK_FAILURE);
+        } else if (exception.getCommandError() == CommandException.Error.RADIO_NOT_AVAILABLE) {
+            onError(preference, RADIO_OFF_ERROR);
         } else {
             preference.setEnabled(false);
             onError(preference, EXCEPTION_ERROR);
diff --git a/src/com/android/phone/settings/PhoneAccountSettingsFragment.java b/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
index 6ca146c..7f04302 100644
--- a/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
+++ b/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
@@ -98,8 +98,8 @@
 
         mDefaultOutgoingAccount = (AccountSelectionPreference)
                 getPreferenceScreen().findPreference(DEFAULT_OUTGOING_ACCOUNT_KEY);
-        if (mTelecomManager.hasMultipleCallCapableAccounts()) {
             mDefaultOutgoingAccount.setListener(this);
+            if (mTelecomManager.getCallCapablePhoneAccounts().size() > 1) {
             updateDefaultOutgoingAccountsModel();
         } else {
             getPreferenceScreen().removePreference(mDefaultOutgoingAccount);
diff --git a/src/com/android/services/telephony/CdmaConference.java b/src/com/android/services/telephony/CdmaConference.java
index fdd2110..6a55efe 100755
--- a/src/com/android/services/telephony/CdmaConference.java
+++ b/src/com/android/services/telephony/CdmaConference.java
@@ -42,7 +42,7 @@
 
     public void updateCapabilities(int capabilities) {
         capabilities |= Connection.CAPABILITY_MUTE | Connection.CAPABILITY_GENERIC_CONFERENCE;
-        setCapabilities(capabilities);
+        setConnectionCapabilities(capabilities);
     }
 
     /**
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index 9afb4ad..02a569f 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -168,8 +168,8 @@
         public void onConnectionCapabilitiesChanged(Connection c, int connectionCapabilities) {
             Log.d(this, "onCallCapabilitiesChanged: Connection: %s, callCapabilities: %s", c,
                     connectionCapabilities);
-            int capabilites = ImsConference.this.getCapabilities();
-            setCapabilities(applyVideoCapabilities(capabilites, connectionCapabilities));
+            int capabilites = ImsConference.this.getConnectionCapabilities();
+            setConnectionCapabilities(applyVideoCapabilities(capabilites, connectionCapabilities));
         }
     };
 
@@ -202,7 +202,10 @@
     public ImsConference(TelephonyConnectionService telephonyConnectionService,
             TelephonyConnection conferenceHost) {
 
-        super(null);
+        super((conferenceHost != null && conferenceHost.getCall() != null &&
+                        conferenceHost.getCall().getPhone() != null) ?
+                PhoneUtils.makePstnPhoneAccountHandle(
+                        conferenceHost.getCall().getPhone()) : null);
 
         // Specify the connection time of the conference to be the connection time of the original
         // connection.
@@ -210,36 +213,30 @@
 
         mTelephonyConnectionService = telephonyConnectionService;
         setConferenceHost(conferenceHost);
-        if (conferenceHost != null && conferenceHost.getCall() != null
-                && conferenceHost.getCall().getPhone() != null) {
-            mPhoneAccount = PhoneUtils.makePstnPhoneAccountHandle(
-                    conferenceHost.getCall().getPhone());
-            Log.v(this, "set phacc to " + mPhoneAccount);
-        }
 
         int capabilities = Connection.CAPABILITY_SUPPORT_HOLD | Connection.CAPABILITY_HOLD |
                 Connection.CAPABILITY_MUTE;
 
-        capabilities = applyVideoCapabilities(capabilities, mConferenceHost.getCallCapabilities());
+        capabilities = applyVideoCapabilities(capabilities, mConferenceHost.getConnectionCapabilities());
         setConnectionCapabilities(capabilities);
 
     }
 
     private int applyVideoCapabilities(int conferenceCapabilities, int capabilities) {
-        if (can(capabilities, Connection.CAPABILITY_SUPPORTS_VT_LOCAL)) {
+        if (can(capabilities, Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL)) {
             conferenceCapabilities = applyCapability(conferenceCapabilities,
-                    Connection.CAPABILITY_SUPPORTS_VT_LOCAL);
+                    Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL);
         } else {
             conferenceCapabilities = removeCapability(conferenceCapabilities,
-                    Connection.CAPABILITY_SUPPORTS_VT_LOCAL);
+                    Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL);
         }
 
-        if (can(capabilities, Connection.CAPABILITY_SUPPORTS_VT_REMOTE)) {
+        if (can(capabilities, Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL)) {
             conferenceCapabilities = applyCapability(conferenceCapabilities,
-                    Connection.CAPABILITY_SUPPORTS_VT_REMOTE);
+                    Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
         } else {
             conferenceCapabilities = removeCapability(conferenceCapabilities,
-                    Connection.CAPABILITY_SUPPORTS_VT_REMOTE);
+                    Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
         }
 
         if (can(capabilities, Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO)) {
@@ -412,6 +409,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 eea079a..de5720c 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}.
@@ -203,6 +213,17 @@
         public void onCallSubstateChanged(int callSubstate) {
             setCallSubstate(callSubstate);
         }
+
+        /*
+         * 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;
@@ -481,9 +502,9 @@
         int newCapabilities = buildConnectionCapabilities();
 
         newCapabilities = changeCapability(newCapabilities,
-                CAPABILITY_SUPPORTS_VT_REMOTE, mRemoteVideoCapable);
+                CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL, mRemoteVideoCapable);
         newCapabilities = changeCapability(newCapabilities,
-                CAPABILITY_SUPPORTS_VT_LOCAL, mLocalVideoCapable);
+                CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL, mLocalVideoCapable);
         newCapabilities = changeCapability(newCapabilities,
                 CAPABILITY_HIGH_DEF_AUDIO, mHasHighDefAudio);
         newCapabilities = changeCapability(newCapabilities, CAPABILITY_WIFI, mIsWifi);
@@ -721,6 +742,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");