Ensure ImsConferenceController recalcs when a conference is no longer full.

When the capacity of the conference changes, ImsConference will now
notify ImsConferenceController to trigger a recalculate of the
conferenceable connections.

When a conference becomes full, if a participant leaves, this change means
we can now re-enable the merge button.

Test: Manual live network test; create full conference then add another
call, disconnect one of the conference participants and verify we can
now merge.
Test: Added unit test to ImsConference to verify it will signal on
fullness change.
Fixes: 242837209

Change-Id: I099d9d51b40117def6f71e6967acb0d21ed7cc41
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index c62b4fa..7bbe9cf 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -968,6 +968,7 @@
         // of the participants, we can get into a situation where the participant is added twice.
         synchronized (mUpdateSyncRoot) {
             int oldParticipantCount = mConferenceParticipantConnections.size();
+            boolean wasFullConference = isFullConference();
             boolean newParticipantsAdded = false;
             boolean oldParticipantsRemoved = false;
             ArrayList<ConferenceParticipant> newParticipants = new ArrayList<>(participants.size());
@@ -1102,6 +1103,12 @@
                 updateManageConference();
             }
 
+            // If the "fullness" of the conference changed, we need to inform listeners.
+            // Ie tell ImsConferenceController.
+            if (wasFullConference != isFullConference()) {
+                notifyConferenceCapacityChanged();
+            }
+
             // If the conference is empty and we're supposed to do a local disconnect, do so now.
             if (mCarrierConfig.shouldLocalDisconnectEmptyConference()
                     // If we dropped from > 0 participants to zero
diff --git a/src/com/android/services/telephony/ImsConferenceController.java b/src/com/android/services/telephony/ImsConferenceController.java
index 4200904..35b493a 100644
--- a/src/com/android/services/telephony/ImsConferenceController.java
+++ b/src/com/android/services/telephony/ImsConferenceController.java
@@ -52,6 +52,14 @@
     private final TelephonyConferenceBase.TelephonyConferenceListener mConferenceListener =
             new TelephonyConferenceBase.TelephonyConferenceListener() {
         @Override
+        public void onConferenceCapacityChanged() {
+            // If the conference reached or is no longer at capacity then we need to recalculate
+            // as it may be possible to merge or not merge now.
+            Log.i(ImsConferenceController.this, "onConferenceCapacityChanged: recalc");
+            recalculateConferenceable();
+        }
+
+        @Override
         public void onDestroyed(Conference conference) {
             if (Log.VERBOSE) {
                 Log.v(ImsConferenceController.class, "onDestroyed: %s", conference);
diff --git a/src/com/android/services/telephony/TelephonyConferenceBase.java b/src/com/android/services/telephony/TelephonyConferenceBase.java
index 1c81fb9..1e7f956 100644
--- a/src/com/android/services/telephony/TelephonyConferenceBase.java
+++ b/src/com/android/services/telephony/TelephonyConferenceBase.java
@@ -60,6 +60,11 @@
          * @param conference The conference.
          */
         public void onDestroyed(Conference conference) {}
+
+        /**
+         * Listener called when a conference either reaches capacity or is no longer at capacity.
+         */
+        public void onConferenceCapacityChanged() {}
     }
 
     private final Set<TelephonyConferenceListener> mListeners = Collections.newSetFromMap(
@@ -237,6 +242,14 @@
     }
 
     /**
+     * Notifies the {@link TelephonyConferenceListener}s when the capacity of the conference has
+     * changed.
+     */
+    public void notifyConferenceCapacityChanged() {
+        mListeners.forEach(l -> l.onConferenceCapacityChanged());
+    }
+
+    /**
      * Notifies {@link TelephonyConferenceListener}s of a conference being destroyed
      */
     private void notifyDestroyed() {
diff --git a/tests/src/com/android/services/telephony/ImsConferenceTest.java b/tests/src/com/android/services/telephony/ImsConferenceTest.java
index 9d2f5ac..fcef25a 100644
--- a/tests/src/com/android/services/telephony/ImsConferenceTest.java
+++ b/tests/src/com/android/services/telephony/ImsConferenceTest.java
@@ -23,6 +23,7 @@
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
@@ -121,6 +122,58 @@
         }
     }
 
+    /**
+     * Verifies that an ImsConference will inform listeners when the "fullness" of the conference
+     * changes as participants come and go.
+     */
+    @Test
+    @SmallTest
+    public void testNotifyOnConferenceCapacityChanged() {
+        ImsConference imsConference = new ImsConference(mMockTelecomAccountRegistry,
+                mMockTelephonyConnectionServiceProxy, mConferenceHost,
+                null /* phoneAccountHandle */, () -> true /* featureFlagProxy */,
+                new ImsConference.CarrierConfiguration.Builder()
+                        .setIsMaximumConferenceSizeEnforced(true)
+                        .setMaximumConferenceSize(2)
+                        .build());
+        TelephonyConferenceBase.TelephonyConferenceListener listener =
+                mock(TelephonyConferenceBase.TelephonyConferenceListener.class);
+        imsConference.addTelephonyConferenceListener(listener);
+
+        ConferenceParticipant participant1 = new ConferenceParticipant(
+                Uri.parse("tel:6505551212"),
+                "A",
+                Uri.parse("sip:6505551212@testims.com"),
+                Connection.STATE_ACTIVE,
+                Call.Details.DIRECTION_OUTGOING);
+        ConferenceParticipant participant2 = new ConferenceParticipant(
+                Uri.parse("tel:6505551213"),
+                "A",
+                Uri.parse("sip:6505551213@testims.com"),
+                Connection.STATE_ACTIVE,
+                Call.Details.DIRECTION_INCOMING);
+
+        // no capacity change since we haven't hit the limit yet.
+        imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+                Arrays.asList(participant1));
+        verify(listener, never()).onConferenceCapacityChanged();
+
+        // Now we should get a capacity change
+        imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+                Arrays.asList(participant1, participant2));
+        verify(listener, times(1)).onConferenceCapacityChanged();
+
+        // And another when we go back to a non-full conference.
+        imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+                Arrays.asList(participant1));
+        verify(listener, times(2)).onConferenceCapacityChanged();
+
+        // But not when we reduce count further.
+        imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+                Collections.emptyList());
+        verify(listener, times(2)).onConferenceCapacityChanged();
+    }
+
     @Test
     @SmallTest
     public void testSinglePartyEmulation() {