Merge "Use carrier config to determine which D2D transports are initialized." into sc-dev
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 7000733..44fc531 100755
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -73,6 +73,7 @@
 import com.android.internal.telephony.d2d.RtpAdapter;
 import com.android.internal.telephony.d2d.RtpTransport;
 import com.android.internal.telephony.d2d.Timeouts;
+import com.android.internal.telephony.d2d.TransportProtocol;
 import com.android.internal.telephony.gsm.SuppServiceNotification;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.imsphone.ImsPhoneCall;
@@ -3325,53 +3326,57 @@
             return;
         }
 
-        // Implement abstracted out RTP functionality the RTP transport depends on.
-        RtpAdapter rtpAdapter = new RtpAdapter() {
-            @Override
-            public Set<RtpHeaderExtensionType> getAcceptedRtpHeaderExtensions() {
-                if (!isImsConnection()) {
-                    return Collections.EMPTY_SET;
-                }
-                ImsPhoneConnection originalConnection =
-                        (ImsPhoneConnection) mOriginalConnection;
-                return originalConnection.getAcceptedRtpHeaderExtensions();
-            }
+        ArrayList<TransportProtocol> supportedTransports = new ArrayList<>(2);
 
-            @Override
-            public void sendRtpHeaderExtensions(
-                    @NonNull Set<RtpHeaderExtension> rtpHeaderExtensions) {
-                if (!isImsConnection()) {
-                    Log.w(TelephonyConnection.this, "sendRtpHeaderExtensions: not an ims conn.");
+        if (supportsD2DUsingRtp()) {
+            Log.i(this, "maybeConfigureDeviceToDeviceCommunication: carrier supports RTP.");
+            // Implement abstracted out RTP functionality the RTP transport depends on.
+            RtpAdapter rtpAdapter = new RtpAdapter() {
+                @Override
+                public Set<RtpHeaderExtensionType> getAcceptedRtpHeaderExtensions() {
+                    ImsPhoneConnection originalConnection =
+                            (ImsPhoneConnection) mOriginalConnection;
+                    return originalConnection.getAcceptedRtpHeaderExtensions();
                 }
 
-                Log.i(TelephonyConnection.this, "sendRtpHeaderExtensions: sending: %s",
-                        rtpHeaderExtensions.stream()
-                                .map(r -> r.toString())
-                                .collect(Collectors.joining(",")));
+                @Override
+                public void sendRtpHeaderExtensions(
+                        @NonNull Set<RtpHeaderExtension> rtpHeaderExtensions) {
+                    Log.i(TelephonyConnection.this, "sendRtpHeaderExtensions: sending: %s",
+                            rtpHeaderExtensions.stream()
+                                    .map(r -> r.toString())
+                                    .collect(Collectors.joining(",")));
+                    ImsPhoneConnection originalConnection =
+                            (ImsPhoneConnection) mOriginalConnection;
+                    originalConnection.sendRtpHeaderExtensions(rtpHeaderExtensions);
+                }
+            };
+            mRtpTransport = new RtpTransport(rtpAdapter, null /* TODO: not needed yet */, mHandler,
+                    supportsSdpNegotiationOfRtpHeaderExtensions());
+            supportedTransports.add(mRtpTransport);
+        }
+        if (supportsD2DUsingDtmf()) {
+            Log.i(this, "maybeConfigureDeviceToDeviceCommunication: carrier supports DTMF.");
+            DtmfAdapter dtmfAdapter = digit -> {
+                Log.i(TelephonyConnection.this, "sendDtmf: send digit %c", digit);
                 ImsPhoneConnection originalConnection =
                         (ImsPhoneConnection) mOriginalConnection;
-                originalConnection.sendRtpHeaderExtensions(rtpHeaderExtensions);
-            }
-        };
-        mRtpTransport = new RtpTransport(rtpAdapter, null /* TODO: not needed yet */, mHandler);
-
-        DtmfAdapter dtmfAdapter = digit -> {
-            if (!isImsConnection()) {
-                Log.w(TelephonyConnection.this, "sendDtmf: not an ims conn.");
-            }
-            Log.i(TelephonyConnection.this, "sendDtmf: send digit %c", digit);
-            ImsPhoneConnection originalConnection =
-                    (ImsPhoneConnection) mOriginalConnection;
-            Message dtmfComplete = mHandler.obtainMessage(MSG_DTMF_DONE);
-            dtmfComplete.replyTo = mHandlerMessenger;
-            originalConnection.getImsCall().sendDtmf(digit, dtmfComplete);
-        };
-        ContentResolver cr = getPhone().getContext().getContentResolver();
-        mDtmfTransport = new DtmfTransport(dtmfAdapter, new Timeouts.Adapter(cr),
-                Executors.newSingleThreadScheduledExecutor());
-        mCommunicator = new Communicator(List.of(mRtpTransport, mDtmfTransport), this);
-        mD2DCallStateAdapter = new D2DCallStateAdapter(mCommunicator);
-        addTelephonyConnectionListener(mD2DCallStateAdapter);
+                Message dtmfComplete = mHandler.obtainMessage(MSG_DTMF_DONE);
+                dtmfComplete.replyTo = mHandlerMessenger;
+                originalConnection.getImsCall().sendDtmf(digit, dtmfComplete);
+            };
+            ContentResolver cr = getPhone().getContext().getContentResolver();
+            mDtmfTransport = new DtmfTransport(dtmfAdapter, new Timeouts.Adapter(cr),
+                    Executors.newSingleThreadScheduledExecutor());
+            supportedTransports.add(mDtmfTransport);
+        }
+        if (supportedTransports.size() > 0) {
+            mCommunicator = new Communicator(supportedTransports, this);
+            mD2DCallStateAdapter = new D2DCallStateAdapter(mCommunicator);
+            addTelephonyConnectionListener(mD2DCallStateAdapter);
+        } else {
+            Log.i(this, "maybeConfigureDeviceToDeviceCommunication: no transports; disabled.");
+        }
     }
 
     /**
@@ -3575,4 +3580,34 @@
     public TelecomAccountRegistry getTelecomAccountRegistry(Context context) {
         return TelecomAccountRegistry.getInstance(context);
     }
+
+    /**
+     * @return {@code true} if the carrier supports D2D using RTP header extensions, {@code false}
+     * otherwise.
+     */
+    private boolean supportsD2DUsingRtp() {
+        PersistableBundle b = getCarrierConfig();
+        return b != null && b.getBoolean(
+                CarrierConfigManager.KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_RTP_BOOL);
+    }
+
+    /**
+     * @return {@code true} if the carrier supports D2D using DTMF digits, {@code false} otherwise.
+     */
+    private boolean supportsD2DUsingDtmf() {
+        PersistableBundle b = getCarrierConfig();
+        return b != null && b.getBoolean(
+                CarrierConfigManager.KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_DTMF_BOOL);
+    }
+
+    /**
+     * @return {@code true} if the carrier supports using SDP negotiation for the RTP header
+     * extensions used in D2D comms, {@code false} otherwise.
+     */
+    private boolean supportsSdpNegotiationOfRtpHeaderExtensions() {
+        PersistableBundle b = getCarrierConfig();
+        return b != null && b.getBoolean(
+                CarrierConfigManager
+                        .KEY_SUPPORTS_SDP_NEGOTIATION_OF_D2D_RTP_HEADER_EXTENSIONS_BOOL);
+    }
 }
diff --git a/tests/src/com/android/services/telephony/TelephonyConnectionTest.java b/tests/src/com/android/services/telephony/TelephonyConnectionTest.java
index 94c9063..c55dee7 100644
--- a/tests/src/com/android/services/telephony/TelephonyConnectionTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyConnectionTest.java
@@ -1,20 +1,156 @@
 package com.android.services.telephony;
 
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.Assert.fail;
 import static junit.framework.TestCase.assertFalse;
 
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
 import android.os.Bundle;
 import android.telecom.Connection;
+import android.telephony.CarrierConfigManager;
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.d2d.DtmfTransport;
+import com.android.internal.telephony.d2d.RtpTransport;
+import com.android.internal.telephony.imsphone.ImsPhoneConnection;
+import com.android.phone.R;
+
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 @RunWith(AndroidJUnit4.class)
 public class TelephonyConnectionTest {
+    @Mock
+    private ImsPhoneConnection mImsPhoneConnection;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        when(mImsPhoneConnection.getState()).thenReturn(Call.State.ACTIVE);
+        when(mImsPhoneConnection.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_IMS);
+    }
+
+    /**
+     * Ensures an Ims connection uses the D2D communicator when it is enabled.
+     */
+    @Test
+    public void testSetupCommunicator() {
+        TestTelephonyConnection c = new TestTelephonyConnection();
+        c.setIsImsConnection(true);
+        // Enable D2D comms.
+        when(c.mMockResources.getBoolean(eq(
+                R.bool.config_use_device_to_device_communication))).thenReturn(true);
+        c.getCarrierConfigBundle().putBoolean(
+                CarrierConfigManager.KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_DTMF_BOOL,
+                true);
+
+        c.setOriginalConnection(mImsPhoneConnection);
+        assertNotNull(c.getCommunicator());
+    }
+
+    /**
+     * Ensures an Ims connection does not use the D2D communicator when it is disabled.
+     */
+    @Test
+    public void testDoNotSetupCommunicatorWhenDisabled() {
+        TestTelephonyConnection c = new TestTelephonyConnection();
+        c.setIsImsConnection(true);
+        // Disable D2D comms.
+        when(c.mMockResources.getBoolean(eq(
+                R.bool.config_use_device_to_device_communication))).thenReturn(false);
+
+        c.setOriginalConnection(mImsPhoneConnection);
+        assertNull(c.getCommunicator());
+    }
+
+    /**
+     * Ensures an Ims connection does not use the D2D communicator for a non-IMS call.
+     */
+    @Test
+    public void testDoNotSetupCommunicatorForNonIms() {
+        TestTelephonyConnection c = new TestTelephonyConnection();
+        c.setIsImsConnection(false);
+        // Disable D2D comms.
+        when(c.mMockResources.getBoolean(eq(
+                R.bool.config_use_device_to_device_communication))).thenReturn(true);
+
+        c.setOriginalConnection(mImsPhoneConnection);
+        assertNull(c.getCommunicator());
+    }
+
+    /**
+     * Ensures an Ims connection does not use the D2D communicator when it is disabled.
+     */
+    @Test
+    public void testDoNotSetupCommunicatorNoTransports() {
+        TestTelephonyConnection c = new TestTelephonyConnection();
+        c.setIsImsConnection(true);
+        // Enable D2D comms.
+        when(c.mMockResources.getBoolean(eq(
+                R.bool.config_use_device_to_device_communication))).thenReturn(true);
+        // But carrier disables transports.  Womp.
+        c.getCarrierConfigBundle().putBoolean(
+                CarrierConfigManager.KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_DTMF_BOOL,
+                false);
+        c.getCarrierConfigBundle().putBoolean(
+                CarrierConfigManager.KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_RTP_BOOL,
+                false);
+        c.setOriginalConnection(mImsPhoneConnection);
+        assertNull(c.getCommunicator());
+    }
+
+    @Test
+    public void testSetupRtpOnly() {
+        TestTelephonyConnection c = new TestTelephonyConnection();
+        c.setIsImsConnection(true);
+        // Enable D2D comms.
+        when(c.mMockResources.getBoolean(eq(
+                R.bool.config_use_device_to_device_communication))).thenReturn(true);
+        // But carrier disables transports.  Womp.
+        c.getCarrierConfigBundle().putBoolean(
+                CarrierConfigManager.KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_DTMF_BOOL,
+                false);
+        c.getCarrierConfigBundle().putBoolean(
+                CarrierConfigManager.KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_RTP_BOOL,
+                true);
+        c.setOriginalConnection(mImsPhoneConnection);
+        assertNotNull(c.getCommunicator());
+        assertEquals(1, c.getCommunicator().getTransportProtocols().size());
+        assertTrue(c.getCommunicator().getTransportProtocols()
+                .stream().anyMatch(p -> p instanceof RtpTransport));
+    }
+
+    @Test
+    public void testSetupDtmfOnly() {
+        TestTelephonyConnection c = new TestTelephonyConnection();
+        c.setIsImsConnection(true);
+        // Enable D2D comms.
+        when(c.mMockResources.getBoolean(eq(
+                R.bool.config_use_device_to_device_communication))).thenReturn(true);
+        // But carrier disables transports.  Womp.
+        c.getCarrierConfigBundle().putBoolean(
+                CarrierConfigManager.KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_DTMF_BOOL,
+                true);
+        c.getCarrierConfigBundle().putBoolean(
+                CarrierConfigManager.KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_RTP_BOOL,
+                false);
+        c.setOriginalConnection(mImsPhoneConnection);
+        assertNotNull(c.getCommunicator());
+        assertEquals(1, c.getCommunicator().getTransportProtocols().size());
+        assertTrue(c.getCommunicator().getTransportProtocols()
+                .stream().anyMatch(p -> p instanceof DtmfTransport));
+    }
 
     @Test
     public void testCodecInIms() {
diff --git a/tests/src/com/android/services/telephony/TestTelephonyConnection.java b/tests/src/com/android/services/telephony/TestTelephonyConnection.java
index 596393c..940d8bb 100644
--- a/tests/src/com/android/services/telephony/TestTelephonyConnection.java
+++ b/tests/src/com/android/services/telephony/TestTelephonyConnection.java
@@ -16,6 +16,8 @@
 
 package com.android.services.telephony;
 
+import android.content.ContentResolver;
+import android.os.UserHandle;
 import android.telephony.TelephonyManager;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -66,6 +68,9 @@
     Context mMockContext;
 
     @Mock
+    ContentResolver mMockContentResolver;
+
+    @Mock
     Resources mMockResources;
 
     @Mock
@@ -97,6 +102,7 @@
     private List<String> mLastConnectionEvents = new ArrayList<>();
     private List<Bundle> mLastConnectionEventExtras = new ArrayList<>();
     private Object mLock = new Object();
+    private PersistableBundle mCarrierConfig = new PersistableBundle();
 
     @Override
     public com.android.internal.telephony.Connection getOriginalConnection() {
@@ -143,8 +149,10 @@
         when(mMockPhone.getContext()).thenReturn(mMockContext);
         when(mMockPhone.getCurrentSubscriberUris()).thenReturn(null);
         when(mMockContext.getResources()).thenReturn(mMockResources);
+        when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
         when(mMockContext.getSystemService(Context.TELEPHONY_SERVICE))
                 .thenReturn(mMockTelephonyManager);
+        when(mMockContentResolver.getUserId()).thenReturn(UserHandle.USER_CURRENT);
         when(mMockResources.getBoolean(anyInt())).thenReturn(false);
         when(mMockPhone.getDefaultPhone()).thenReturn(mMockPhone);
         when(mMockPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_IMS);
@@ -201,7 +209,7 @@
     public PersistableBundle getCarrierConfig() {
         // Depends on PhoneGlobals for context in TelephonyConnection, do not implement during
         // testing.
-        return new PersistableBundle();
+        return mCarrierConfig;
     }
 
     @Override
@@ -284,4 +292,8 @@
                 .thenReturn(mCarrierConfigManager);
         when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(bundle);
     }
+
+    public PersistableBundle getCarrierConfigBundle() {
+        return mCarrierConfig;
+    }
 }