Merge "Don't stop outgoing calls if we are the foreground call." into lmp-dev
diff --git a/src/com/android/server/telecom/BluetoothPhoneService.java b/src/com/android/server/telecom/BluetoothPhoneService.java
index efac3bf..9e02539 100644
--- a/src/com/android/server/telecom/BluetoothPhoneService.java
+++ b/src/com/android/server/telecom/BluetoothPhoneService.java
@@ -30,6 +30,7 @@
 import android.os.IBinder;
 import android.os.Message;
 import android.os.RemoteException;
+import android.telecom.CallState;
 import android.telecom.PhoneAccount;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.TelephonyManager;
@@ -37,7 +38,10 @@
 
 import com.android.server.telecom.CallsManager.CallsManagerListener;
 
+import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Bluetooth headset manager for Telecom. This class shares the call state with the bluetooth device
@@ -272,8 +276,11 @@
                     break;
 
                 case MSG_LIST_CURRENT_CALLS:
-                    // TODO - Add current calls.
-                    request.setResult(true);
+                    try {
+                        sendListOfCalls();
+                    } finally {
+                        request.setResult(true);
+                    }
                     break;
 
                 case MSG_QUERY_PHONE_STATE:
@@ -301,6 +308,7 @@
 
         @Override
         public void onCallRemoved(Call call) {
+            mClccIndexMap.remove(call);
             updateHeadsetWithCallState();
         }
 
@@ -354,6 +362,9 @@
     private BluetoothAdapter mBluetoothAdapter;
     private BluetoothHeadset mBluetoothHeadset;
 
+    // A map from Calls to indexes used to identify calls for CLCC (C* List Current Calls).
+    private Map<Call, Integer> mClccIndexMap = new HashMap<>();
+
     public BluetoothPhoneService() {
         Log.v(TAG, "Constructor");
     }
@@ -472,6 +483,65 @@
         return null;
     }
 
+    private void sendListOfCalls() {
+        Collection<Call> mCalls = getCallsManager().getCalls();
+        for (Call call : mCalls) {
+            // We don't send the parent conference call to the bluetooth device.
+            if (!call.isConference()) {
+                sendClccForCall(call);
+            }
+        }
+        sendClccEndMarker();
+    }
+
+    /**
+     * Sends a single clcc (C* List Current Calls) event for the specified call.
+     */
+    private void sendClccForCall(Call call) {
+        boolean isForeground = getCallsManager().getForegroundCall() == call;
+        int state = convertCallState(call.getState(), isForeground);
+
+        if (state == CALL_STATE_IDLE) {
+            return;
+        }
+
+        int index = getIndexForCall(call);
+        int direction = call.isIncoming() ? 1 : 0;
+        boolean isPartOfConference = call.getParentCall() != null;
+        Uri addressUri = call.getHandle();
+        String address = addressUri == null ? null : addressUri.getSchemeSpecificPart();
+        int addressType = address == null ? -1 : PhoneNumberUtils.toaFromString(address);
+
+        Log.d(this, "sending clcc for call %d, %d, %d, %b, %s, %d",
+                index, direction, state, isPartOfConference, address, addressType);
+        mBluetoothHeadset.clccResponse(
+                index, direction, state, 0, isPartOfConference, address, addressType);
+    }
+
+    private void sendClccEndMarker() {
+        // End marker is recognized with an index value of 0. All other parameters are ignored.
+        mBluetoothHeadset.clccResponse(0 /* index */, 0, 0, 0, false, null, 0);
+    }
+
+    /**
+     * Returns the caches index for the specified call.  If no such index exists, then an index is
+     * given (smallest number starting from 1 that isn't already taken).
+     */
+    private int getIndexForCall(Call call) {
+        if (mClccIndexMap.containsKey(call)) {
+            return mClccIndexMap.get(call);
+        }
+
+        int i = 1;  // Indexes for bluetooth clcc are 1-based.
+        while (mClccIndexMap.containsValue(i)) {
+            i++;
+        }
+
+        // NOTE: Indexes are removed in {@link #onCallRemoved}.
+        mClccIndexMap.put(call, i);
+        return i;
+    }
+
     private void updateHeadsetWithCallState() {
         CallsManager callsManager = getCallsManager();
         Call activeCall = callsManager.getActiveCall();
@@ -518,6 +588,7 @@
     private int getBluetoothCallStateForUpdate() {
         CallsManager callsManager = getCallsManager();
         Call ringingCall = callsManager.getRingingCall();
+        Call dialingCall = callsManager.getDialingCall();
 
         //
         // !! WARNING !!
@@ -531,12 +602,51 @@
         int bluetoothCallState = CALL_STATE_IDLE;
         if (ringingCall != null) {
             bluetoothCallState = CALL_STATE_INCOMING;
-        } else if (callsManager.getDialingOrConnectingCall() != null) {
+        } else if (dialingCall != null) {
             bluetoothCallState = CALL_STATE_ALERTING;
         }
         return bluetoothCallState;
     }
 
+    private int convertCallState(int callState, boolean isForegroundCall) {
+        switch (callState) {
+            case CallState.NEW:
+            case CallState.ABORTED:
+            case CallState.DISCONNECTED:
+            case CallState.CONNECTING:
+            case CallState.PRE_DIAL_WAIT:
+                if (callState == CallState.CONNECTING || callState == CallState.PRE_DIAL_WAIT) {
+                    Log.w(this, "convertCallState: unexpected state %s",
+                            CallState.toString(callState));
+                }
+                return CALL_STATE_IDLE;
+
+            case CallState.ACTIVE:
+                return CALL_STATE_ACTIVE;
+
+            case CallState.DIALING:
+                // Yes, this is correctly returning ALERTING.
+                // "Dialing" for BT means that we have sent information to the service provider
+                // to place the call but there is no confirmation that the call is going through.
+                // When there finally is confirmation, the ringback is played which is referred to
+                // as an "alert" tone, thus, ALERTING.
+                // TODO: We should consider using the ALERTING terms in Telecom because that
+                // seems to be more industry-standard.
+                return CALL_STATE_ALERTING;
+
+            case CallState.ON_HOLD:
+                return CALL_STATE_HELD;
+
+            case CallState.RINGING:
+                if (isForegroundCall) {
+                    return CALL_STATE_INCOMING;
+                } else {
+                    return CALL_STATE_WAITING;
+                }
+        }
+        return CALL_STATE_IDLE;
+    }
+
     private CallsManager getCallsManager() {
         return CallsManager.getInstance();
     }
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index e3b2e42..1e305e2 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -445,6 +445,7 @@
                 case CallState.ACTIVE:
                 case CallState.ON_HOLD:
                 case CallState.DIALING:
+                case CallState.CONNECTING:
                 case CallState.RINGING:
                     route = AudioState.ROUTE_BLUETOOTH;
                     break;
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index e044087..1840c7e 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -34,6 +34,7 @@
 import com.google.common.collect.ImmutableList;
 
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
@@ -114,6 +115,7 @@
     private final Context mContext;
     private final PhoneAccountRegistrar mPhoneAccountRegistrar;
     private final MissedCallNotifier mMissedCallNotifier;
+    private final Set<Call> mLocallyDisconnectingCalls = new HashSet<>();
 
     /**
      * The call the user is currently interacting with. This is the call that should have audio
@@ -571,6 +573,7 @@
         if (!mCalls.contains(call)) {
             Log.w(this, "Unknown call (%s) asked to disconnect", call);
         } else {
+            mLocallyDisconnectingCalls.add(call);
             call.disconnect();
         }
     }
@@ -709,7 +712,6 @@
     void markCallAsDisconnected(Call call, DisconnectCause disconnectCause) {
         call.setDisconnectCause(disconnectCause);
         setCallState(call, CallState.DISCONNECTED);
-        removeCall(call);
     }
 
     /**
@@ -717,6 +719,12 @@
      */
     void markCallAsRemoved(Call call) {
         removeCall(call);
+        if (mLocallyDisconnectingCalls.contains(call)) {
+            mLocallyDisconnectingCalls.remove(call);
+            if (mForegroundCall != null && mForegroundCall.getState() == CallState.ON_HOLD) {
+                mForegroundCall.unhold();
+            }
+        }
     }
 
     /**
@@ -810,8 +818,8 @@
         return getFirstCallWithState(CallState.ACTIVE);
     }
 
-    Call getDialingOrConnectingCall() {
-        return getFirstCallWithState(CallState.DIALING, CallState.CONNECTING);
+    Call getDialingCall() {
+        return getFirstCallWithState(CallState.DIALING);
     }
 
     Call getHeldCall() {
@@ -1009,9 +1017,7 @@
             Log.v(this, "Updating foreground call, %s -> %s.", mForegroundCall, newForegroundCall);
             Call oldForegroundCall = mForegroundCall;
             mForegroundCall = newForegroundCall;
-            if (mForegroundCall != null && mForegroundCall.getState() == CallState.ON_HOLD) {
-                mForegroundCall.unhold();
-            }
+
             for (CallsManagerListener listener : mListeners) {
                 listener.onForegroundCallChanged(oldForegroundCall, mForegroundCall);
             }