IMS RCS API Improvements-SipDelegate am: 56980ed57e am: 92c4160245

Original change: https://android-review.googlesource.com/c/platform/packages/services/Telephony/+/1899050

Change-Id: Ib07cd3136e2e2c3d5e8611a312dcff699f736e5b
diff --git a/OWNERS b/OWNERS
index c394f15..b4ef543 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,3 +1,4 @@
+amitmahajan@google.com
 breadley@google.com
 fionaxu@google.com
 jackyu@google.com
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index 0be927a..7f088f7 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -257,7 +257,7 @@
 
                 @Override
                 public void onConnectionPropertiesChanged(Connection c, int connectionProperties) {
-                    Log.d(this, "onConnectionPropertiesChanged: Connection: %s,"
+                    Log.i(ImsConference.this, "onConnectionPropertiesChanged: Connection: %s,"
                             + " connectionProperties: %s", c, connectionProperties);
                     updateConnectionProperties(connectionProperties);
                 }
@@ -383,6 +383,11 @@
     private boolean mIsUsingSimCallManager = false;
 
     /**
+     * See {@link #isRemotelyHosted()} for details.
+     */
+    private boolean mWasRemotelyHosted = false;
+
+    /**
      * Where {@link #isMultiparty()} is {@code false}, contains the
      * {@link ConferenceParticipantConnection#getUserEntity()} and
      * {@link ConferenceParticipantConnection#getEndpoint()} of the single participant which this
@@ -522,11 +527,12 @@
                 (properties & Connection.PROPERTY_IS_EXTERNAL_CALL) != 0);
 
         conferenceProperties = changeBitmask(conferenceProperties,
-                Connection.PROPERTY_REMOTELY_HOSTED, !isConferenceHost());
+                Connection.PROPERTY_REMOTELY_HOSTED, isRemotelyHosted());
 
         conferenceProperties = changeBitmask(conferenceProperties,
                 Connection.PROPERTY_IS_ADHOC_CONFERENCE,
                 (properties & Connection.PROPERTY_IS_ADHOC_CONFERENCE) != 0);
+        Log.i(this, "applyHostProperties: confProp=%s", conferenceProperties);
 
         return conferenceProperties;
     }
@@ -774,6 +780,26 @@
     }
 
     /**
+     * Returns whether the conference is remotely hosted or not.
+     * This method will cache the current remotely hosted state when the conference host or
+     * original connection becomes null.  This is important for scenarios where the conference host
+     * or original connection changes midway through a conference such as in an SRVCC scenario.
+     * @return {@code true} if the conference was remotely hosted based on the conference host and
+     * its original connection, or based on the last known remotely hosted state.  {@code false}
+     * otherwise.
+     */
+    public boolean isRemotelyHosted() {
+        if (mConferenceHost == null || mConferenceHost.getOriginalConnection() == null) {
+            return mWasRemotelyHosted;
+        }
+        com.android.internal.telephony.Connection originalConnection =
+                mConferenceHost.getOriginalConnection();
+        mWasRemotelyHosted = originalConnection.isMultiparty()
+                && !originalConnection.isConferenceHost();
+        return mWasRemotelyHosted;
+    }
+
+    /**
      * 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
@@ -1209,6 +1235,7 @@
         ConferenceParticipantConnection connection = new ConferenceParticipantConnection(
                 parent.getOriginalConnection(), participant,
                 !isConferenceHost() /* isRemotelyHosted */);
+
         if (participant.getConnectTime() == 0) {
             connection.setConnectTimeMillis(parent.getConnectTimeMillis());
             connection.setConnectionStartElapsedRealtimeMillis(
diff --git a/src/com/android/services/telephony/TelephonyConferenceController.java b/src/com/android/services/telephony/TelephonyConferenceController.java
index 228541a..9aa3dbe 100644
--- a/src/com/android/services/telephony/TelephonyConferenceController.java
+++ b/src/com/android/services/telephony/TelephonyConferenceController.java
@@ -40,6 +40,7 @@
  */
 final class TelephonyConferenceController {
     private static final int TELEPHONY_CONFERENCE_MAX_SIZE = 5;
+    private static final String RIL_REPORTED_CONFERENCE_CALL_STRING = "Conference Call";
 
     private final TelephonyConnection.TelephonyConnectionListener mTelephonyConnectionListener =
             new TelephonyConnection.TelephonyConnectionListener() {
@@ -179,7 +180,6 @@
     private void recalculateConference() {
         Set<TelephonyConnection> conferencedConnections = new HashSet<>();
         int numGsmConnections = 0;
-
         for (TelephonyConnection connection : mTelephonyConnections) {
             com.android.internal.telephony.Connection radioConnection =
                 connection.getOriginalConnection();
@@ -271,11 +271,19 @@
                             // Remove all instances of PROPERTY_IS_DOWNGRADED_CONFERENCE. This
                             // property should only be set on the parent call (i.e. the newly
                             // created TelephonyConference.
-                            Log.d(this, "Removing PROPERTY_IS_DOWNGRADED_CONFERENCE from connection"
-                                    + " %s", connection);
-                            int newProperties = connection.getConnectionProperties()
-                                    & ~Connection.PROPERTY_IS_DOWNGRADED_CONFERENCE;
-                            connection.setTelephonyConnectionProperties(newProperties);
+                            // This doesn't apply to a connection whose address is "Conference
+                            // Call", which may be updated by some modem to create a connection
+                            // to represent a merged conference connection in SRVCC.
+                            if (connection.getAddress() == null
+                                    || !connection.getAddress().getSchemeSpecificPart()
+                                            .equalsIgnoreCase(
+                                                    RIL_REPORTED_CONFERENCE_CALL_STRING)) {
+                                Log.d(this, "Removing PROPERTY_IS_DOWNGRADED_CONFERENCE"
+                                        + " from connection %s", connection);
+                                int newProperties = connection.getConnectionProperties()
+                                        & ~Connection.PROPERTY_IS_DOWNGRADED_CONFERENCE;
+                                connection.setTelephonyConnectionProperties(newProperties);
+                            }
                             isDowngradedConference = true;
                         }
                         mTelephonyConference.addTelephonyConnection(connection);
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 18a40cf..ed07726 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -3814,4 +3814,13 @@
             mCommunicator.sendMessages(set);
         }
     }
+
+    /**
+     * Returns the current telephony connection listeners for test purposes.
+     * @return list of telephony connection listeners.
+     */
+    @VisibleForTesting
+    public List<TelephonyConnectionListener> getTelephonyConnectionListeners() {
+        return new ArrayList<>(mTelephonyListeners);
+    }
 }
diff --git a/tests/src/com/android/services/telephony/ImsConferenceTest.java b/tests/src/com/android/services/telephony/ImsConferenceTest.java
index 3bc5ee8..9d2f5ac 100644
--- a/tests/src/com/android/services/telephony/ImsConferenceTest.java
+++ b/tests/src/com/android/services/telephony/ImsConferenceTest.java
@@ -622,6 +622,46 @@
     }
 
     /**
+     * Tests a scenario where a handover connection arrives via
+     * {@link TelephonyConnection#onOriginalConnectionRedialed(
+     * com.android.internal.telephony.Connection)}.  During this process, the conference properties
+     * get updated.  Since the original connection is null at this point, we need to verify that
+     * the remotely hosted property is retained from before the original connection was nulled.
+     */
+    @Test
+    public void testIsConferenceRemotelyHostedCachingOnSRVCC() {
+        mConferenceHost.setIsImsConnection(true);
+        when(mConferenceHost.getMockImsPhoneConnection().isMultiparty()).thenReturn(true);
+        when(mConferenceHost.getMockImsPhoneConnection().isConferenceHost()).thenReturn(true);
+
+        // Start out with a valid conference host.
+        ImsConference imsConference = new ImsConference(mMockTelecomAccountRegistry,
+                mMockTelephonyConnectionServiceProxy, mConferenceHost,
+                null /* phoneAccountHandle */, () -> false /* featureFlagProxy */,
+                new ImsConference.CarrierConfiguration.Builder().build());
+
+        // By default it is not remotely hosted.
+        assertFalse(imsConference.isRemotelyHosted());
+        assertEquals(0,
+                imsConference.getConnectionProperties() & Connection.PROPERTY_REMOTELY_HOSTED);
+
+        // Simulate a change to the original connection due to srvcc
+        com.android.internal.telephony.Connection previousOriginalConnection =
+                mConferenceHost.getMockImsPhoneConnection();
+        mConferenceHost.setMockImsPhoneConnection(null);
+
+        // Trigger the property update which takes place when the original connection changes.
+        mConferenceHost.getTelephonyConnectionListeners().forEach(
+                l -> l.onConnectionPropertiesChanged(mConferenceHost,
+                        mConferenceHost.getConnectionProperties()));
+
+        // Should still NOT be remotely hosted based on cached value.
+        assertFalse(imsConference.isRemotelyHosted());
+        assertEquals(0,
+                imsConference.getConnectionProperties() & Connection.PROPERTY_REMOTELY_HOSTED);
+    }
+
+    /**
      * Verifies that an ImsConference can handle SIP and TEL URIs for both the P-Associated-Uri and
      * conference event package identities.
      */
diff --git a/tests/src/com/android/services/telephony/TelephonyConferenceControllerTest.java b/tests/src/com/android/services/telephony/TelephonyConferenceControllerTest.java
index cfdc2fd..b7fe988 100644
--- a/tests/src/com/android/services/telephony/TelephonyConferenceControllerTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyConferenceControllerTest.java
@@ -23,9 +23,11 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.net.Uri;
 import android.os.Looper;
 import android.telecom.Conference;
 import android.telecom.Connection;
+import android.telecom.PhoneAccount;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import org.junit.Before;
@@ -35,7 +37,9 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
+import java.util.List;
 
 /**
  * Tests the functionality in TelephonyConferenceController.java
@@ -111,6 +115,46 @@
     }
 
     /**
+     * Verify the connection with "Conference Call" with PROPERTY_IS_DOWNGRADED_CONFERENCE
+     * during SRVCC
+     */
+    @Test
+    @SmallTest
+    public void testSrvccConferenceConnection() {
+        when(mTestTelephonyConnectionA.mMockRadioConnection.getCall()
+                .isMultiparty()).thenReturn(true);
+        when(mTestTelephonyConnectionB.mMockRadioConnection.getCall()
+                .isMultiparty()).thenReturn(true);
+
+        List<Connection> listConnections = Arrays.asList(
+                mTestTelephonyConnectionA, mTestTelephonyConnectionB);
+        when(mMockTelephonyConnectionServiceProxy.getAllConnections()).thenReturn(listConnections);
+
+        mTestTelephonyConnectionA.setAddress(
+                Uri.fromParts(PhoneAccount.SCHEME_TEL, "Conference Call", null), 0);
+        mTestTelephonyConnectionA.setTelephonyConnectionProperties(
+                Connection.PROPERTY_IS_DOWNGRADED_CONFERENCE);
+        mTestTelephonyConnectionB.setAddress(
+                Uri.fromParts(PhoneAccount.SCHEME_TEL, "5551213", null), 0);
+        mTestTelephonyConnectionB.setTelephonyConnectionProperties(
+                Connection.PROPERTY_IS_DOWNGRADED_CONFERENCE);
+
+        // add telephony connection B
+        mControllerTest.add(mTestTelephonyConnectionB);
+
+        // add telephony connection A
+        mControllerTest.add(mTestTelephonyConnectionA);
+
+        // verify the connection with "Conference Call" has PROPERTY_IS_DOWNGRADED_CONFERENCE
+        assertTrue((mTestTelephonyConnectionA.getConnectionProperties()
+                & Connection.PROPERTY_IS_DOWNGRADED_CONFERENCE) != 0);
+
+        // verify the connection with "5551213" hasn't PROPERTY_IS_DOWNGRADED_CONFERENCE
+        assertFalse((mTestTelephonyConnectionB.getConnectionProperties()
+                & Connection.PROPERTY_IS_DOWNGRADED_CONFERENCE) != 0);
+    }
+
+    /**
      * Behavior: add telephony connection B and A to conference controller,
      *           set status for connections and merged calls, remove one call
      * Assumption: after performing the behaviours, the status of Connection A is STATE_ACTIVE;
diff --git a/tests/src/com/android/services/telephony/TestTelephonyConnection.java b/tests/src/com/android/services/telephony/TestTelephonyConnection.java
index 817e9a7..e149d3b 100644
--- a/tests/src/com/android/services/telephony/TestTelephonyConnection.java
+++ b/tests/src/com/android/services/telephony/TestTelephonyConnection.java
@@ -305,4 +305,12 @@
     public PersistableBundle getCarrierConfigBundle() {
         return mCarrierConfig;
     }
+
+    public ImsPhoneConnection getMockImsPhoneConnection() {
+        return mImsPhoneConnection;
+    }
+
+    public void setMockImsPhoneConnection(ImsPhoneConnection connection) {
+        mImsPhoneConnection = connection;
+    }
 }