Make add-call a global property of telecom. (2/4)

ADD_CALL didn't make sense as a property of Connection or Call.
This changes it to be a global property instead.

Bug: 18285352
Change-Id: I5189e114e74dba2d81d74c6d24cf5b17f1e0922a
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index b677b85..57b9e8e 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -74,6 +74,7 @@
         void onIsConferencedChanged(Call call);
         void onIsVoipAudioModeChanged(Call call);
         void onVideoStateChanged(Call call);
+        void onCanAddCallChanged(boolean canAddCall);
     }
 
     /**
@@ -87,13 +88,14 @@
     private static final int MAXIMUM_HOLD_CALLS = 1;
     private static final int MAXIMUM_RINGING_CALLS = 1;
     private static final int MAXIMUM_OUTGOING_CALLS = 1;
-
-    private static final int[] LIVE_CALL_STATES =
-            {CallState.CONNECTING, CallState.PRE_DIAL_WAIT, CallState.DIALING, CallState.ACTIVE};
+    private static final int MAXIMUM_TOP_LEVEL_CALLS = 2;
 
     private static final int[] OUTGOING_CALL_STATES =
             {CallState.CONNECTING, CallState.PRE_DIAL_WAIT, CallState.DIALING};
 
+    private static final int[] LIVE_CALL_STATES =
+            {CallState.CONNECTING, CallState.PRE_DIAL_WAIT, CallState.DIALING, CallState.ACTIVE};
+
     /**
      * The main call repository. Keeps an instance of all live calls. New incoming and outgoing
      * calls are added to the map and removed when the calls move to the disconnected state.
@@ -128,6 +130,8 @@
     /* Handler tied to thread in which CallManager was initialized. */
     private final Handler mHandler = new Handler();
 
+    private boolean mCanAddCall = true;
+
     /**
      * The call the user is currently interacting with. This is the call that should have audio
      * focus and be visible in the in-call UI.
@@ -263,7 +267,7 @@
     @Override
     public void onParentChanged(Call call) {
         // parent-child relationship affects which call should be foreground, so do an update.
-        updateForegroundCall();
+        updateCallsManagerState();
         for (CallsManagerListener listener : mListeners) {
             listener.onIsConferencedChanged(call);
         }
@@ -272,7 +276,7 @@
     @Override
     public void onChildrenChanged(Call call) {
         // parent-child relationship affects which call should be foreground, so do an update.
-        updateForegroundCall();
+        updateCallsManagerState();
         for (CallsManagerListener listener : mListeners) {
             listener.onIsConferencedChanged(call);
         }
@@ -884,22 +888,24 @@
     }
 
     /**
-     * Checks to see if the specified call is the only top level call. If it is not, we should
-     * remove the ADD_CALL capability. We allow you to add a second call but not a third or beyond.
-     *
-     * @param call The call to check to see if it is the only top level call.
-     * @return Whether the call is the only top level call.
+     * Returns true if telecom supports adding another top-level call.
      */
-    protected boolean isOnlyTopLevelCall(Call call) {
-        if (call.getParentCall() != null) {
-            // Never true for child calls.
-            return false;
-        }
+    boolean canAddCall() {
+        int count = 0;
+        for (Call call : mCalls) {
+            if (call.isEmergencyCall()) {
+                // We never support add call if one of the calls is an emergency call.
+                return false;
+            } else if (call.getParentCall() == null) {
+                count++;
+            }
 
-        // Loop through all the other calls and there exists a top level (has no parent) call
-        // that is not the specified call, return false.
-        for (Call otherCall : mCalls) {
-            if (call != otherCall && otherCall.getParentCall() == null) {
+            // We do not check states for canAddCall. We treat disconnected calls the same
+            // and wait until they are removed instead. If we didn't count disconnected calls,
+            // we could put InCallServices into a state where they are showing two calls but
+            // also support add-call. Technically it's right, but overall looks better (UI-wise)
+            // and acts better if we wait until the call is removed.
+            if (count >= MAXIMUM_TOP_LEVEL_CALLS) {
                 return false;
             }
         }
@@ -1033,7 +1039,7 @@
         for (CallsManagerListener listener : mListeners) {
             listener.onCallAdded(call);
         }
-        updateForegroundCall();
+        updateCallsManagerState();
     }
 
     private void removeCall(Call call) {
@@ -1054,15 +1060,7 @@
             for (CallsManagerListener listener : mListeners) {
                 listener.onCallRemoved(call);
             }
-            updateForegroundCall();
-        }
-
-        // Now that a call has been removed, other calls may gain new call capabilities (for
-        // example, if only one call is left, it is now add-call capable again). Trigger the
-        // recalculation of the call's current capabilities by forcing an update. (See
-        // InCallController.toParcelableCall()).
-        for (Call otherCall : mCalls) {
-            otherCall.setCallCapabilities(otherCall.getCallCapabilities(), true /* forceUpdate */);
+            updateCallsManagerState();
         }
     }
 
@@ -1094,7 +1092,7 @@
                 for (CallsManagerListener listener : mListeners) {
                     listener.onCallStateChanged(call, oldState, newState);
                 }
-                updateForegroundCall();
+                updateCallsManagerState();
             }
         }
     }
@@ -1138,6 +1136,21 @@
         }
     }
 
+    private void updateCanAddCall() {
+        boolean newCanAddCall = canAddCall();
+        if (newCanAddCall != mCanAddCall) {
+            mCanAddCall = newCanAddCall;
+            for (CallsManagerListener listener : mListeners) {
+                listener.onCanAddCallChanged(mCanAddCall);
+            }
+        }
+    }
+
+    private void updateCallsManagerState() {
+        updateForegroundCall();
+        updateCanAddCall();
+    }
+
     private boolean isPotentialMMICode(Uri handle) {
         return (handle != null && handle.getSchemeSpecificPart() != null
                 && handle.getSchemeSpecificPart().contains("#"));
@@ -1174,7 +1187,7 @@
         int count = 0;
         for (int state : states) {
             for (Call call : mCalls) {
-                if (call.getState() == state) {
+                if (call.getParentCall() == null && call.getState() == state) {
                     count++;
                 }
             }
diff --git a/src/com/android/server/telecom/CallsManagerListenerBase.java b/src/com/android/server/telecom/CallsManagerListenerBase.java
index 93b2a24..ffc5947 100644
--- a/src/com/android/server/telecom/CallsManagerListenerBase.java
+++ b/src/com/android/server/telecom/CallsManagerListenerBase.java
@@ -72,4 +72,8 @@
     @Override
     public void onVideoStateChanged(Call call) {
     }
+
+    @Override
+    public void onCanAddCallChanged(boolean canAddCall) {
+    }
 }
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 0a15ee6..46fdcdb 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -210,6 +210,19 @@
         }
     }
 
+    @Override
+    public void onCanAddCallChanged(boolean canAddCall) {
+        if (!mInCallServices.isEmpty()) {
+            Log.i(this, "onCanAddCallChanged : %b", canAddCall);
+            for (IInCallService inCallService : mInCallServices.values()) {
+                try {
+                    inCallService.onCanAddCallChanged(canAddCall);
+                } catch (RemoteException ignored) {
+                }
+            }
+        }
+    }
+
     void onPostDialWait(Call call, String remaining) {
         if (!mInCallServices.isEmpty()) {
             Log.i(this, "Calling onPostDialWait, remaining = %s", remaining);
@@ -352,6 +365,7 @@
                 }
             }
             onAudioStateChanged(null, CallsManager.getInstance().getAudioState());
+            onCanAddCallChanged(CallsManager.getInstance().canAddCall());
         } else {
             unbind();
         }
@@ -431,17 +445,12 @@
         int state = call.getState();
         int capabilities = call.getCallCapabilities();
 
-        if (!CallsManager.getInstance().isOnlyTopLevelCall(call) || state == CallState.DIALING) {
-            capabilities = PhoneCapabilities.remove(capabilities, PhoneCapabilities.ADD_CALL);
-        }
-
         if (call.isRespondViaSmsCapable()) {
             capabilities |= PhoneCapabilities.RESPOND_VIA_TEXT;
         }
 
         if (call.isEmergencyCall()) {
             capabilities = PhoneCapabilities.remove(capabilities, PhoneCapabilities.MUTE);
-            capabilities = PhoneCapabilities.remove(capabilities, PhoneCapabilities.ADD_CALL);
         }
 
         if (state == CallState.DIALING) {
diff --git a/tests/src/com/android/server/telecom/testapps/TestConnectionService.java b/tests/src/com/android/server/telecom/testapps/TestConnectionService.java
index 389373f..a474213 100644
--- a/tests/src/com/android/server/telecom/testapps/TestConnectionService.java
+++ b/tests/src/com/android/server/telecom/testapps/TestConnectionService.java
@@ -135,7 +135,6 @@
             // Assume all calls are video capable.
             int capabilities = getCallCapabilities();
             capabilities |= PhoneCapabilities.SUPPORTS_VT_LOCAL;
-            capabilities |= PhoneCapabilities.ADD_CALL;
             capabilities |= PhoneCapabilities.MUTE;
             capabilities |= PhoneCapabilities.SUPPORT_HOLD;
             capabilities |= PhoneCapabilities.HOLD;