Support Conferences in Remote Connections

Bug: 16957997

Change-Id: I009924eac0f06a863b4c4c4148ecbcf27d220e14
diff --git a/src/com/android/telecomm/Call.java b/src/com/android/telecomm/Call.java
index 4fa39e8..dc3fa07 100644
--- a/src/com/android/telecomm/Call.java
+++ b/src/com/android/telecomm/Call.java
@@ -616,7 +616,9 @@
     }
 
     @Override
-    public void handleCreateConnectionSuccess(ParcelableConnection connection) {
+    public void handleCreateConnectionSuccess(
+            CallIdMapper idMapper,
+            ParcelableConnection connection) {
         Log.v(this, "handleCreateConnectionSuccessful %s", connection);
         mCreateConnectionProcessor = null;
         setTargetPhoneAccount(connection.getPhoneAccount());
@@ -630,6 +632,11 @@
         setAudioModeIsVoip(connection.getAudioModeIsVoip());
         setStatusHints(connection.getStatusHints());
 
+        mConferenceableCalls.clear();
+        for (String id : connection.getConferenceableConnectionIds()) {
+            mConferenceableCalls.add(idMapper.getCall(id));
+        }
+
         if (mIsIncoming) {
             // We do not handle incoming calls immediately when they are verified by the connection
             // service. We allow the caller-info-query code to execute first so that we can read the
diff --git a/src/com/android/telecomm/ConnectionServiceWrapper.java b/src/com/android/telecomm/ConnectionServiceWrapper.java
index 7d1f01a..5fcb9bd 100644
--- a/src/com/android/telecomm/ConnectionServiceWrapper.java
+++ b/src/com/android/telecomm/ConnectionServiceWrapper.java
@@ -894,7 +894,8 @@
         } else {
             // Successful connection
             if (mPendingResponses.containsKey(callId)) {
-                mPendingResponses.remove(callId).handleCreateConnectionSuccess(connection);
+                mPendingResponses.remove(callId)
+                        .handleCreateConnectionSuccess(mCallIdMapper, connection);
             }
         }
     }
diff --git a/src/com/android/telecomm/CreateConnectionProcessor.java b/src/com/android/telecomm/CreateConnectionProcessor.java
index d8fd00a..cc2e8c0 100644
--- a/src/com/android/telecomm/CreateConnectionProcessor.java
+++ b/src/com/android/telecomm/CreateConnectionProcessor.java
@@ -272,7 +272,9 @@
         }
 
         @Override
-        public void handleCreateConnectionSuccess(ParcelableConnection connection) {
+        public void handleCreateConnectionSuccess(
+                CallIdMapper idMapper,
+                ParcelableConnection connection) {
             if (mResponse == null) {
                 // Nobody is listening for this connection attempt any longer; ask the responsible
                 // ConnectionService to tear down any resources associated with the call
@@ -280,7 +282,7 @@
             } else {
                 // Success -- share the good news and remember that we are no longer interested
                 // in hearing about any more attempts
-                mResponse.handleCreateConnectionSuccess(connection);
+                mResponse.handleCreateConnectionSuccess(idMapper, connection);
                 mResponse = null;
             }
         }
diff --git a/src/com/android/telecomm/CreateConnectionResponse.java b/src/com/android/telecomm/CreateConnectionResponse.java
index b907e3a..13d1573 100644
--- a/src/com/android/telecomm/CreateConnectionResponse.java
+++ b/src/com/android/telecomm/CreateConnectionResponse.java
@@ -22,6 +22,6 @@
  * A callback for providing the result of creating a connection.
  */
 interface CreateConnectionResponse {
-    void handleCreateConnectionSuccess(ParcelableConnection connection);
+    void handleCreateConnectionSuccess(CallIdMapper idMapper, ParcelableConnection connection);
     void handleCreateConnectionFailure(int code, String message);
 }
diff --git a/tests/src/com/android/telecomm/testapps/CallNotificationReceiver.java b/tests/src/com/android/telecomm/testapps/CallNotificationReceiver.java
index e941c2d..fb44069 100644
--- a/tests/src/com/android/telecomm/testapps/CallNotificationReceiver.java
+++ b/tests/src/com/android/telecomm/testapps/CallNotificationReceiver.java
@@ -70,7 +70,7 @@
     private void sendIncomingCallIntent(Context context, boolean isVideoCall) {
         PhoneAccountHandle phoneAccount = new PhoneAccountHandle(
                 new ComponentName(context, TestConnectionService.class),
-                CallServiceNotifier.CALL_PROVIDER_ID);
+                CallServiceNotifier.SIM_SUBSCRIPTION_ID);
 
         // For the purposes of testing, indicate whether the incoming call is a video call by
         // stashing an indicator in the EXTRA_INCOMING_CALL_EXTRAS.
diff --git a/tests/src/com/android/telecomm/testapps/TestConnectionManager.java b/tests/src/com/android/telecomm/testapps/TestConnectionManager.java
index 902ace7..95f0718 100644
--- a/tests/src/com/android/telecomm/testapps/TestConnectionManager.java
+++ b/tests/src/com/android/telecomm/testapps/TestConnectionManager.java
@@ -19,28 +19,28 @@
 import android.app.PendingIntent;
 import android.net.Uri;
 import android.telecomm.AudioState;
+import android.telecomm.Conference;
 import android.telecomm.Connection;
 import android.telecomm.ConnectionRequest;
 import android.telecomm.ConnectionService;
 import android.telecomm.PhoneAccountHandle;
+import android.telecomm.RemoteConference;
 import android.telecomm.RemoteConnection;
 import android.telecomm.StatusHints;
 import android.util.Log;
 
-import java.util.Random;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 /**
  * Service which acts as a fake ConnectionManager if so configured.
  * TODO(santoscordon): Rename all classes in the directory to Dummy* (e.g., DummyConnectionService).
  */
 public class TestConnectionManager extends ConnectionService {
-    /**
-     * Random number generator used to generate phone numbers.
-     */
-    private Random mRandom = new Random();
-
-    private final class TestManagedConnection extends Connection {
-        private final RemoteConnection.Listener mProxyListener = new RemoteConnection.Listener() {
+    public final class TestManagedConnection extends Connection {
+        private final RemoteConnection.Listener mRemoteListener = new RemoteConnection.Listener() {
             @Override
             public void onStateChanged(RemoteConnection connection, int state) {
                 setState(state);
@@ -103,62 +103,76 @@
             @Override
             public void onDestroyed(RemoteConnection connection) {
                 destroy();
+                mManagedConnectionByRemote.remove(mRemote);
+            }
+
+            @Override
+            public void onConferenceableConnectionsChanged(
+                    RemoteConnection connect,
+                    List<RemoteConnection> conferenceable) {
+                List<Connection> c = new ArrayList<>();
+                for (RemoteConnection remote : conferenceable) {
+                    if (mManagedConnectionByRemote.containsKey(remote)) {
+                        c.add(mManagedConnectionByRemote.get(remote));
+                    }
+                }
+                setConferenceableConnections(c);
             }
         };
 
-        private final RemoteConnection mRemoteConnection;
+        private final RemoteConnection mRemote;
         private final boolean mIsIncoming;
 
-        TestManagedConnection(RemoteConnection remoteConnection, boolean isIncoming) {
-            mRemoteConnection = remoteConnection;
+        TestManagedConnection(RemoteConnection remote, boolean isIncoming) {
+            mRemote = remote;
             mIsIncoming = isIncoming;
-            mRemoteConnection.addListener(mProxyListener);
-            setState(mRemoteConnection.getState());
+            mRemote.addListener(mRemoteListener);
+            setState(mRemote.getState());
         }
 
         @Override
         public void onAbort() {
-            mRemoteConnection.abort();
+            mRemote.abort();
         }
 
         /** ${inheritDoc} */
         @Override
         public void onAnswer(int videoState) {
-            mRemoteConnection.answer(videoState);
+            mRemote.answer(videoState);
         }
 
         /** ${inheritDoc} */
         @Override
         public void onDisconnect() {
-            mRemoteConnection.disconnect();
+            mRemote.disconnect();
         }
 
         @Override
         public void onPlayDtmfTone(char c) {
-            mRemoteConnection.playDtmfTone(c);
+            mRemote.playDtmfTone(c);
         }
 
         /** ${inheritDoc} */
         @Override
         public void onHold() {
-            mRemoteConnection.hold();
+            mRemote.hold();
         }
 
         /** ${inheritDoc} */
         @Override
         public void onReject() {
-            mRemoteConnection.reject();
+            mRemote.reject();
         }
 
         /** ${inheritDoc} */
         @Override
         public void onUnhold() {
-            mRemoteConnection.unhold();
+            mRemote.unhold();
         }
 
         @Override
         public void onSetAudioState(AudioState state) {
-            mRemoteConnection.setAudioState(state);
+            mRemote.setAudioState(state);
         }
 
         private void setState(int state) {
@@ -180,29 +194,125 @@
         }
     }
 
+    public final class TestManagedConference extends Conference {
+        private final RemoteConference.Listener mRemoteListener = new RemoteConference.Listener() {
+            @Override
+            public void onStateChanged(RemoteConference conference, int oldState, int newState) {
+                switch (newState) {
+                    case Connection.STATE_DISCONNECTED:
+                        // See onDisconnected below
+                        break;
+                    case Connection.STATE_HOLDING:
+                        setOnHold();
+                        break;
+                    case Connection.STATE_ACTIVE:
+                        setActive();
+                        break;
+                    default:
+                        log("unrecognized state for Conference: " + newState);
+                        break;
+                }
+            }
+
+            @Override
+            public void onDisconnected(RemoteConference conference, int cause, String message) {
+                setDisconnected(cause, message);
+            }
+
+            @Override
+            public void onConnectionAdded(
+                    RemoteConference conference,
+                    RemoteConnection connection) {
+                TestManagedConnection c = mManagedConnectionByRemote.get(connection);
+                if (c == null) {
+                    log("onConnectionAdded cannot find remote connection: " + connection);
+                } else {
+                    addConnection(c);
+                }
+            }
+
+            @Override
+            public void onConnectionRemoved(
+                    RemoteConference conference,
+                    RemoteConnection connection) {
+                TestManagedConnection c = mManagedConnectionByRemote.get(connection);
+                if (c == null) {
+                    log("onConnectionRemoved cannot find remote connection: " + connection);
+                } else {
+                    removeConnection(c);
+                }
+            }
+
+            @Override
+            public void onCapabilitiesChanged(RemoteConference conference, int capabilities) {
+                setCapabilities(capabilities);
+            }
+
+            @Override
+            public void onDestroyed(RemoteConference conference) {
+                destroy();
+                mRemote.removeListener(mRemoteListener);
+                mManagedConferenceByRemote.remove(mRemote);
+            }
+        };
+
+        private final RemoteConference mRemote;
+
+        public TestManagedConference(RemoteConference remote) {
+            super(null);
+            mRemote = remote;
+            remote.addListener(mRemoteListener);
+            setActive();
+            for (RemoteConnection r : remote.getConnections()) {
+                TestManagedConnection c = mManagedConnectionByRemote.get(r);
+                if (c != null) {
+                    addConnection(c);
+                }
+            }
+        }
+    }
+
     private static void log(String msg) {
         Log.w("telecomtestcs", "[TestConnectionManager] " + msg);
     }
 
+    private final Map<RemoteConference, TestManagedConference> mManagedConferenceByRemote
+            = new HashMap<>();
+    private final Map<RemoteConnection, TestManagedConnection> mManagedConnectionByRemote
+            = new HashMap<>();
+
     @Override
     public Connection onCreateOutgoingConnection(
             PhoneAccountHandle connectionManagerAccount,
             final ConnectionRequest request) {
-        return new TestManagedConnection(
-                createRemoteOutgoingConnection(
-                        request.getAccountHandle(),
-                        request),
-                false);
+        return makeConnection(request, false);
     }
 
     @Override
     public Connection onCreateIncomingConnection(
             PhoneAccountHandle connectionManagerAccount,
             final ConnectionRequest request) {
-        return new TestManagedConnection(
-                createRemoteIncomingConnection(
-                        request.getAccountHandle(),
-                        request),
-                true);
+        return makeConnection(request, true);
+    }
+
+    @Override
+    public void onConference(Connection a, Connection b) {
+        conferenceRemoteConnections(
+                ((TestManagedConnection) a).mRemote,
+                ((TestManagedConnection) b).mRemote);
+    }
+
+    @Override
+    public void onRemoteConferenceAdded(RemoteConference remoteConference) {
+        addConference(new TestManagedConference(remoteConference));
+    }
+
+    private Connection makeConnection(ConnectionRequest request, boolean incoming) {
+        RemoteConnection remote = incoming
+                ? createRemoteIncomingConnection(request.getAccountHandle(), request)
+                : createRemoteOutgoingConnection(request.getAccountHandle(), request);
+        TestManagedConnection local = new TestManagedConnection(remote, false);
+        mManagedConnectionByRemote.put(remote, local);
+        return local;
     }
 }
diff --git a/tests/src/com/android/telecomm/testapps/TestConnectionService.java b/tests/src/com/android/telecomm/testapps/TestConnectionService.java
index 09a8929..c50e7ab 100644
--- a/tests/src/com/android/telecomm/testapps/TestConnectionService.java
+++ b/tests/src/com/android/telecomm/testapps/TestConnectionService.java
Binary files differ