Merge "Fix incorrect PreferenceScreen key for SIP account settings" into lmp-dev
diff --git a/src/com/android/phone/HfaLogic.java b/src/com/android/phone/HfaLogic.java
index e4a3205..b064b18 100644
--- a/src/com/android/phone/HfaLogic.java
+++ b/src/com/android/phone/HfaLogic.java
@@ -71,7 +71,8 @@
     private PendingIntent mResponseIntent;
     private Context mContext;
 
-    private static final int DEFAULT_RETRY_COUNT = 1;
+    // No retry at the moment. Increase later if necessary.
+    private static final int DEFAULT_RETRY_COUNT = 0;
     private int mRetryCount;
 
     public interface HfaLogicCallback {
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 5d00163..26e31b0 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -1674,7 +1674,7 @@
 
         IccIoResult response =
             (IccIoResult)sendRequest(CMD_EXCHANGE_SIM_IO,
-                    new IccAPDUArgument(fileID, command, -1, p1, p2, p3, filePath));
+                    new IccAPDUArgument(-1, fileID, command, p1, p2, p3, filePath));
 
         if (DBG) {
           log("Exchange SIM_IO [R]" + response);
diff --git a/src/com/android/services/telephony/CdmaConferenceController.java b/src/com/android/services/telephony/CdmaConferenceController.java
index ab126ca..d0cf6de 100644
--- a/src/com/android/services/telephony/CdmaConferenceController.java
+++ b/src/com/android/services/telephony/CdmaConferenceController.java
@@ -89,15 +89,27 @@
 
     void add(final CdmaConnection connection) {
         if (!mCdmaConnections.isEmpty() && connection.isOutgoing()) {
-            connection.forceAsDialing(true);
             // There already exists a connection, so this will probably result in a conference once
-            // it is added. For connections which are added while another connection exists, we
-            // mark them as "dialing" for set amount of time to give the user time to see their
-            // new call as "Dialing" before it turns into a conference call.
+            // it is added. For outgoing connections which are added while another connection
+            // exists, we mark them as "dialing" for a set amount of time to give the user time to
+            // see their new call as "Dialing" before it turns into a conference call.
+            // During that time, we also mark the other calls as "held" or else it can cause issues
+            // due to having an ACTIVE and a DIALING call simultaneously.
+            connection.forceAsDialing(true);
+            final List<CdmaConnection> connectionsToReset =
+                    new ArrayList<>(mCdmaConnections.size());
+            for (CdmaConnection current : mCdmaConnections) {
+                if (current.setHoldingForConference()) {
+                    connectionsToReset.add(current);
+                }
+            }
             mHandler.postDelayed(new Runnable() {
                 @Override
                 public void run() {
                     connection.forceAsDialing(false);
+                    for (CdmaConnection current : connectionsToReset) {
+                        current.resetStateForConference();
+                    }
                     addInternal(connection);
                 }
             }, ADD_OUTGOING_CONNECTION_DELAY_MILLIS);
diff --git a/src/com/android/services/telephony/GsmConferenceController.java b/src/com/android/services/telephony/GsmConferenceController.java
index ecac404..b6aaaf9 100644
--- a/src/com/android/services/telephony/GsmConferenceController.java
+++ b/src/com/android/services/telephony/GsmConferenceController.java
@@ -33,6 +33,8 @@
  * call functionality.
  */
 final class GsmConferenceController {
+    private static final int GSM_CONFERENCE_MAX_SIZE = 5;
+
     private final Connection.Listener mConnectionListener = new Connection.Listener() {
         @Override
         public void onStateChanged(Connection c, int state) {
@@ -80,6 +82,15 @@
         recalculateConference();
     }
 
+    private boolean isFullConference(Conference conference) {
+        return conference.getConnections().size() >= GSM_CONFERENCE_MAX_SIZE;
+    }
+
+    private boolean participatesInFullConference(Connection connection) {
+        return connection.getConference() != null &&
+                isFullConference(connection.getConference());
+    }
+
     /**
      * Calculates the conference-capable state of all GSM connections in this connection service.
      */
@@ -91,25 +102,22 @@
 
         // Loop through and collect all calls which are active or holding
         for (GsmConnection connection : mGsmConnections) {
-            com.android.internal.telephony.Connection radioConnection =
-                    connection.getOriginalConnection();
-            Log.d(this, "recalc - %s %s",
-                    radioConnection == null ? null : radioConnection.getState(), connection);
+            Log.d(this, "recalc - %s %s", connection.getState(), connection);
 
-            if (radioConnection != null) {
-                switch(radioConnection.getState()) {
-                    case ACTIVE:
+            if (!participatesInFullConference(connection)) {
+                switch (connection.getState()) {
+                    case Connection.STATE_ACTIVE:
                         activeConnections.add(connection);
-                        break;
-                    case HOLDING:
+                        continue;
+                    case Connection.STATE_HOLDING:
                         backgroundConnections.add(connection);
-                        break;
+                        continue;
                     default:
-                        connection.setConferenceableConnections(
-                                Collections.<Connection>emptyList());
                         break;
                 }
             }
+
+            connection.setConferenceableConnections(Collections.<Connection>emptyList());
         }
 
         Log.v(this, "active: %d, holding: %d",
@@ -128,7 +136,7 @@
         }
 
         // Set the conference as conferenceable with all the connections
-        if (mGsmConference != null) {
+        if (mGsmConference != null && !isFullConference(mGsmConference)) {
             List<Connection> nonConferencedConnections = new ArrayList<>(mGsmConnections.size());
             for (GsmConnection c : mGsmConnections) {
                 if (c.getConference() == null) {
diff --git a/src/com/android/services/telephony/PstnIncomingCallNotifier.java b/src/com/android/services/telephony/PstnIncomingCallNotifier.java
index 0c8f8bc..654150c 100644
--- a/src/com/android/services/telephony/PstnIncomingCallNotifier.java
+++ b/src/com/android/services/telephony/PstnIncomingCallNotifier.java
@@ -17,7 +17,6 @@
 package com.android.services.telephony;
 
 import android.content.BroadcastReceiver;
-
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -27,6 +26,7 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.UserHandle;
+import android.telecom.CallState;
 import android.telecom.PhoneAccount;
 import android.telecom.TelecomManager;
 import android.telephony.TelephonyManager;
@@ -39,6 +39,7 @@
 import com.android.internal.telephony.PhoneProxy;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.cdma.CdmaCallWaitingNotification;
+
 import com.google.common.base.Preconditions;
 
 import java.util.Objects;
@@ -51,6 +52,7 @@
     /** New ringing connection event code. */
     private static final int EVENT_NEW_RINGING_CONNECTION = 100;
     private static final int EVENT_CDMA_CALL_WAITING = 101;
+    private static final int EVENT_UNKNOWN_CONNECTION = 102;
 
     /** The phone proxy object to listen to. */
     private final PhoneProxy mPhoneProxy;
@@ -76,6 +78,9 @@
                 case EVENT_CDMA_CALL_WAITING:
                     handleCdmaCallWaiting((AsyncResult) msg.obj);
                     break;
+                case EVENT_UNKNOWN_CONNECTION:
+                    handleNewUnknownConnection((AsyncResult) msg.obj);
+                    break;
                 default:
                     break;
             }
@@ -142,6 +147,8 @@
                         mHandler, EVENT_NEW_RINGING_CONNECTION, null);
                 mPhoneBase.registerForCallWaiting(
                         mHandler, EVENT_CDMA_CALL_WAITING, null);
+                mPhoneBase.registerForUnknownConnection(mHandler, EVENT_UNKNOWN_CONNECTION,
+                        null);
             }
         }
     }
@@ -151,6 +158,7 @@
             Log.i(this, "Unregistering: %s", mPhoneBase);
             mPhoneBase.unregisterForNewRingingConnection(mHandler);
             mPhoneBase.unregisterForCallWaiting(mHandler);
+            mPhoneBase.unregisterForUnknownConnection(mHandler);
         }
     }
 
@@ -187,6 +195,29 @@
         }
     }
 
+    private void handleNewUnknownConnection(AsyncResult asyncResult) {
+        Log.i(this, "handleNewUnknownConnection");
+        Connection connection = (Connection) asyncResult.result;
+        if (connection != null) {
+            Call call = connection.getCall();
+            if (call != null && call.getState().isAlive()) {
+                addNewUnknownCall(connection);
+            }
+        }
+    }
+
+    private void addNewUnknownCall(Connection connection) {
+        Bundle extras = null;
+        if (connection.getNumberPresentation() == TelecomManager.PRESENTATION_ALLOWED &&
+                !TextUtils.isEmpty(connection.getAddress())) {
+            extras = new Bundle();
+            Uri uri = Uri.fromParts(PhoneAccount.SCHEME_TEL, connection.getAddress(), null);
+            extras.putParcelable(TelecomManager.EXTRA_UNKNOWN_CALL_HANDLE, uri);
+        }
+        TelecomManager.from(mPhoneProxy.getContext()).addNewUnknownCall(
+                TelecomAccountRegistry.makePstnPhoneAccountHandle(mPhoneProxy), extras);
+    }
+
     /**
      * Sends the incoming call intent to telecom.
      */
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 9394b94..f7c6510 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -41,6 +41,7 @@
     private static final int MSG_PRECISE_CALL_STATE_CHANGED = 1;
     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 final Handler mHandler = new Handler() {
         @Override
@@ -68,6 +69,9 @@
                     }
                     setRingbackRequested((Boolean) ((AsyncResult) msg.obj).result);
                     break;
+                case MSG_DISCONNECT:
+                    updateState();
+                    break;
             }
         }
     };
@@ -364,6 +368,7 @@
             getPhone().unregisterForPreciseCallStateChanged(mHandler);
             getPhone().unregisterForRingbackTone(mHandler);
             getPhone().unregisterForHandoverStateChanged(mHandler);
+            getPhone().unregisterForDisconnect(mHandler);
         }
         mOriginalConnection = originalConnection;
         getPhone().registerForPreciseCallStateChanged(
@@ -371,6 +376,7 @@
         getPhone().registerForHandoverStateChanged(
                 mHandler, MSG_HANDOVER_STATE_CHANGED, null);
         getPhone().registerForRingbackTone(mHandler, MSG_RINGBACK_TONE, null);
+        getPhone().registerForDisconnect(mHandler, MSG_DISCONNECT, null);
         mOriginalConnection.addPostDialListener(mPostDialListener);
         mOriginalConnection.addListener(mOriginalConnectionListener);
 
@@ -381,7 +387,7 @@
         setVideoProvider(mOriginalConnection.getVideoProvider());
         setAudioQuality(mOriginalConnection.getAudioQuality());
 
-        updateAddress();
+        updateState();
     }
 
     protected void hangup(int telephonyDisconnectCode) {
@@ -655,6 +661,22 @@
         return mAudioQuality;
     }
 
+    void resetStateForConference() {
+        if (getState() == Connection.STATE_HOLDING) {
+            if (mOriginalConnection.getState() == Call.State.ACTIVE) {
+                setActive();
+            }
+        }
+    }
+
+    boolean setHoldingForConference() {
+        if (getState() == Connection.STATE_ACTIVE) {
+            setOnHold();
+            return true;
+        }
+        return false;
+    }
+
     private static Uri getAddressFromNumber(String number) {
         // Address can be null for blocked calls.
         if (number == null) {
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index cb7894c..e3b78d4 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -40,6 +40,8 @@
 import com.android.internal.telephony.cdma.CDMAPhone;
 import com.android.phone.MMIDialogActivity;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -238,6 +240,57 @@
     }
 
     @Override
+    public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount,
+            ConnectionRequest request) {
+        Log.i(this, "onCreateUnknownConnection, request: " + request);
+
+        Phone phone = getPhoneForAccount(request.getAccountHandle(), false);
+        if (phone == null) {
+            return Connection.createFailedConnection(
+                    DisconnectCauseUtil.toTelecomDisconnectCause(
+                            android.telephony.DisconnectCause.ERROR_UNSPECIFIED));
+        }
+
+        final List<com.android.internal.telephony.Connection> allConnections = new ArrayList<>();
+        final Call ringingCall = phone.getRingingCall();
+        if (ringingCall.hasConnections()) {
+            allConnections.addAll(ringingCall.getConnections());
+        }
+        final Call foregroundCall = phone.getForegroundCall();
+        if (foregroundCall.hasConnections()) {
+            allConnections.addAll(foregroundCall.getConnections());
+        }
+        final Call backgroundCall = phone.getBackgroundCall();
+        if (backgroundCall.hasConnections()) {
+            allConnections.addAll(phone.getBackgroundCall().getConnections());
+        }
+
+        com.android.internal.telephony.Connection unknownConnection = null;
+        for (com.android.internal.telephony.Connection telephonyConnection : allConnections) {
+            if (!isOriginalConnectionKnown(telephonyConnection)) {
+                unknownConnection = telephonyConnection;
+                break;
+            }
+        }
+
+        if (unknownConnection == null) {
+            Log.i(this, "onCreateUnknownConnection, did not find previously unknown connection.");
+            return Connection.createCanceledConnection();
+        }
+
+        Connection connection =
+                createConnectionFor(phone, unknownConnection,
+                        !unknownConnection.isIncoming() /* isOutgoing */);
+
+        if (connection == null) {
+            connection = Connection.createCanceledConnection();
+            return Connection.createCanceledConnection();
+        } else {
+            return connection;
+        }
+    }
+
+    @Override
     public void onConference(Connection connection1, Connection connection2) {
         if (connection1 instanceof TelephonyConnection &&
                 connection2 instanceof TelephonyConnection) {