Merge changes from topic 'mwd-merge-042415'

* changes:
  Merge commit '3d8d22e' into merge2
  Merge commit 'c5fca4f' into merge2
  Merge commit 'e084cf0' into merge2
  Merge commit '554f349' into merge2
  Merge commit '9c5c993' into merge2
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/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index 455e034..e7a02ec 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -16,22 +16,26 @@
 
 package com.android.services.telephony;
 
+import android.content.ComponentName;
+import android.content.Context;
 import android.net.Uri;
+import android.telecom.Conference.Listener;
 import android.telecom.Conference;
 import android.telecom.ConferenceParticipant;
+import android.telecom.Connection.VideoProvider;
 import android.telecom.Connection;
 import android.telecom.DisconnectCause;
 import android.telecom.Log;
 import android.telecom.PhoneAccountHandle;
+import android.telecom.StatusHints;
 import android.telecom.VideoProfile;
-import android.telecom.Conference.Listener;
-import android.telecom.Connection.VideoProvider;
 
 import com.android.internal.telephony.Call;
 import com.android.internal.telephony.CallStateException;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.imsphone.ImsPhoneConnection;
 import com.android.phone.PhoneUtils;
+import com.android.phone.R;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -171,6 +175,12 @@
             int capabilites = ImsConference.this.getConnectionCapabilities();
             setConnectionCapabilities(applyVideoCapabilities(capabilites, connectionCapabilities));
         }
+
+        @Override
+        public void onStatusHintsChanged(Connection c, StatusHints statusHints) {
+            Log.v(this, "onStatusHintsChanged");
+            updateStatusHints();
+        }
     };
 
     /**
@@ -409,6 +419,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.
@@ -444,6 +474,7 @@
         mConferenceHost.addConnectionListener(mConferenceHostListener);
         mConferenceHost.addTelephonyConnectionListener(mTelephonyConnectionListener);
         setState(mConferenceHost.getState());
+        updateStatusHints();
     }
 
     /**
@@ -605,6 +636,8 @@
             setDisconnected(new DisconnectCause(DisconnectCause.OTHER));
             destroy();
         }
+
+        updateStatusHints();
     }
 
     /**
@@ -642,6 +675,27 @@
         }
     }
 
+    private void updateStatusHints() {
+        if (mConferenceHost == null) {
+            setStatusHints(null);
+            return;
+        }
+
+        if (mConferenceHost.isWifi()) {
+            Phone phone = mConferenceHost.getPhone();
+            if (phone != null) {
+                Context context = phone.getContext();
+                setStatusHints(new StatusHints(
+                        new ComponentName(context, TelephonyConnectionService.class),
+                        context.getString(R.string.status_hint_label_wifi_call),
+                        R.drawable.ic_signal_wifi_4_bar_24dp,
+                        null /* extras */));
+            }
+        } else {
+            setStatusHints(null);
+        }
+    }
+
     /**
      * Builds a string representation of the {@link ImsConference}.
      *
diff --git a/src/com/android/services/telephony/ImsConferenceController.java b/src/com/android/services/telephony/ImsConferenceController.java
index 249d639..b266b81 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 0c3974a..3beabf4 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -816,6 +816,13 @@
     }
 
     /**
+     * Whether the call is using wifi.
+     */
+    boolean isWifi() {
+        return mIsWifi;
+    }
+
+    /**
      * Sets the current call audio quality. Used during rebuild of the capabilities
      * to set or unset the {@link Connection#CAPABILITY_HIGH_DEF_AUDIO} capability.
      *