CallControl & CallEventCallback should use CallEndpoint routing

CallControl
- add requestCallEndpointChange API call

CallEventCallback
- remove onCallAudioStateChanged
- add onCallEndpointChanged
- add onAvailableCallEndpointsChanged
- add onMuteStateChanged

bug: 262912490
Test: cts
Change-Id: Ia6231ee02d8b09c3e0384abf45072023a0a76a48
diff --git a/telecomm/java/android/telecom/CallControl.java b/telecomm/java/android/telecom/CallControl.java
index 867bcc7..770a374 100644
--- a/telecomm/java/android/telecom/CallControl.java
+++ b/telecomm/java/android/telecom/CallControl.java
@@ -31,6 +31,8 @@
 import com.android.internal.telecom.ClientTransactionalServiceRepository;
 import com.android.internal.telecom.ICallControl;
 
+import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 
 /**
@@ -223,6 +225,42 @@
     }
 
     /**
+     * Request a CallEndpoint change. Clients should not define their own CallEndpoint when
+     * requesting a change. Instead, the new endpoint should be one of the valid endpoints provided
+     * by {@link CallEventCallback#onAvailableCallEndpointsChanged(List)}.
+     *
+     * @param callEndpoint ; The {@link CallEndpoint} to change to.
+     * @param executor     ; The {@link Executor} on which the {@link OutcomeReceiver} callback
+     *                     will be called on.
+     * @param callback     ; The {@link OutcomeReceiver} that will be completed on the Telecom side
+     *                     that details success or failure of the requested operation.
+     *
+     *                     {@link OutcomeReceiver#onResult} will be called if Telecom has
+     *                     successfully changed the CallEndpoint that was requested.
+     *
+     *                     {@link OutcomeReceiver#onError} will be called if Telecom has failed to
+     *                     switch to the requested CallEndpoint.  A {@link CallException} will be
+     *                     passed that details why the operation failed.
+     */
+    public void requestCallEndpointChange(@NonNull CallEndpoint callEndpoint,
+            @CallbackExecutor @NonNull Executor executor,
+            @NonNull OutcomeReceiver<Void, CallException> callback) {
+        Objects.requireNonNull(callEndpoint);
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+        if (mServerInterface != null) {
+            try {
+                mServerInterface.requestCallEndpointChange(callEndpoint,
+                        new CallControlResultReceiver("endpointChange", executor, callback));
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            }
+        } else {
+            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        }
+    }
+
+    /**
      * This method should be called after
      * {@link CallControl#disconnect(DisconnectCause, Executor, OutcomeReceiver)} or
      * {@link CallControl#rejectCall(Executor, OutcomeReceiver)}
diff --git a/telecomm/java/android/telecom/CallEventCallback.java b/telecomm/java/android/telecom/CallEventCallback.java
index fd7e101..806febd 100644
--- a/telecomm/java/android/telecom/CallEventCallback.java
+++ b/telecomm/java/android/telecom/CallEventCallback.java
@@ -19,6 +19,7 @@
 
 import android.annotation.NonNull;
 
+import java.util.List;
 import java.util.function.Consumer;
 
 /**
@@ -95,13 +96,6 @@
     void onDisconnect(@NonNull Consumer<Boolean> wasCompleted);
 
     /**
-     * update the client on the new {@link CallAudioState}
-     *
-     * @param callAudioState that is currently being used
-     */
-    void onCallAudioStateChanged(@NonNull CallAudioState callAudioState);
-
-    /**
      * Telecom is informing the client to set the call in streaming.
      *
      * @param wasCompleted The {@link Consumer} to be completed. If the client can stream the
@@ -118,4 +112,26 @@
      * @param reason Code to indicate the reason of this failure
      */
     void onCallStreamingFailed(@CallStreamingService.StreamingFailedReason int reason);
+
+    /**
+     * Telecom is informing the client the current {@link CallEndpoint} changed.
+     *
+     * @param newCallEndpoint The new {@link CallEndpoint} through which call media flows
+     *                       (i.e. speaker, bluetooth, etc.).
+     */
+    void onCallEndpointChanged(@NonNull CallEndpoint newCallEndpoint);
+
+    /**
+     * Telecom is informing the client that the available {@link CallEndpoint}s have changed.
+     *
+     * @param availableEndpoints The set of available {@link CallEndpoint}s reported by Telecom.
+     */
+    void onAvailableCallEndpointsChanged(@NonNull List<CallEndpoint> availableEndpoints);
+
+    /**
+     * Called when the mute state changes.
+     *
+     * @param isMuted The current mute state.
+     */
+    void onMuteStateChanged(boolean isMuted);
 }
diff --git a/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
index 16816ff..b2e921b 100644
--- a/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
+++ b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
@@ -22,14 +22,15 @@
 import android.os.OutcomeReceiver;
 import android.os.ResultReceiver;
 import android.telecom.CallAttributes;
-import android.telecom.CallAudioState;
 import android.telecom.CallControl;
+import android.telecom.CallEndpoint;
 import android.telecom.CallEventCallback;
 import android.telecom.CallException;
 import android.telecom.PhoneAccountHandle;
 import android.text.TextUtils;
 import android.util.Log;
 
+import java.util.List;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
@@ -140,6 +141,9 @@
         private static final String ON_REJECT = "onReject";
         private static final String ON_DISCONNECT = "onDisconnect";
         private static final String ON_STREAMING_STARTED = "onStreamingStarted";
+        private static final String ON_REQ_ENDPOINT_CHANGE = "onRequestEndpointChange";
+        private static final String ON_AVAILABLE_CALL_ENDPOINTS = "onAvailableCallEndpointsChanged";
+        private static final String ON_MUTE_STATE_CHANGED = "onMuteStateChanged";
 
         private void handleCallEventCallback(String action, String callId, int code,
                 ResultReceiver ackResultReceiver) {
@@ -246,14 +250,45 @@
         }
 
         @Override
-        public void onCallAudioStateChanged(String callId, CallAudioState callAudioState) {
-            Log.i(TAG, TextUtils.formatSimple("onCallAudioStateChanged: callId=[%s]", callId));
+        public void onCallEndpointChanged(String callId, CallEndpoint endpoint) {
+            handleEndpointUpdate(callId, ON_REQ_ENDPOINT_CHANGE, endpoint);
+        }
+
+        @Override
+        public void onAvailableCallEndpointsChanged(String callId, List<CallEndpoint> endpoints) {
+            handleEndpointUpdate(callId, ON_AVAILABLE_CALL_ENDPOINTS, endpoints);
+        }
+
+        @Override
+        public void onMuteStateChanged(String callId, boolean isMuted) {
+            handleEndpointUpdate(callId, ON_MUTE_STATE_CHANGED, isMuted);
+        }
+
+        public void handleEndpointUpdate(String callId, String action, Object arg) {
+            Log.d(TAG, TextUtils.formatSimple("[%s], callId=[%s]", action, callId));
             // lookup the callEventCallback associated with the particular call
             TransactionalCall call = mCallIdToTransactionalCall.get(callId);
             if (call != null) {
                 CallEventCallback callback = call.getCallEventCallback();
                 Executor executor = call.getExecutor();
-                executor.execute(() -> callback.onCallAudioStateChanged(callAudioState));
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    executor.execute(() -> {
+                        switch (action) {
+                            case ON_REQ_ENDPOINT_CHANGE:
+                                callback.onCallEndpointChanged((CallEndpoint) arg);
+                                break;
+                            case ON_AVAILABLE_CALL_ENDPOINTS:
+                                callback.onAvailableCallEndpointsChanged((List<CallEndpoint>) arg);
+                                break;
+                            case ON_MUTE_STATE_CHANGED:
+                                callback.onMuteStateChanged((boolean) arg);
+                                break;
+                        }
+                    });
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
             }
         }
 
diff --git a/telecomm/java/com/android/internal/telecom/ICallControl.aidl b/telecomm/java/com/android/internal/telecom/ICallControl.aidl
index dc0aeac..a5c6e44 100644
--- a/telecomm/java/com/android/internal/telecom/ICallControl.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallControl.aidl
@@ -17,6 +17,7 @@
 package com.android.internal.telecom;
 
 import android.telecom.CallControl;
+import android.telecom.CallEndpoint;
 import android.telecom.DisconnectCause;
 import android.os.ResultReceiver;
 
@@ -29,4 +30,5 @@
     void disconnect(String callId, in DisconnectCause disconnectCause, in ResultReceiver callback);
     void rejectCall(String callId, in ResultReceiver callback);
     void startCallStreaming(String callId, in ResultReceiver callback);
+    void requestCallEndpointChange(in CallEndpoint callEndpoint, in ResultReceiver callback);
 }
\ No newline at end of file
diff --git a/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl b/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl
index c45ef97..fef5e9e 100644
--- a/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl
@@ -17,10 +17,12 @@
 package com.android.internal.telecom;
 
 import android.telecom.CallControl;
+import android.telecom.CallEndpoint;
 import com.android.internal.telecom.ICallControl;
 import android.os.ResultReceiver;
 import android.telecom.CallAudioState;
 import android.telecom.CallException;
+import java.util.List;
 
 /**
  * {@hide}
@@ -29,15 +31,19 @@
     // publicly exposed. Client should override
     void onAddCallControl(String callId, int resultCode, in ICallControl callControl,
      in CallException exception);
+    // -- Call Event Actions / Call State Transitions
     void onSetActive(String callId, in ResultReceiver callback);
     void onSetInactive(String callId, in ResultReceiver callback);
     void onAnswer(String callId, int videoState, in ResultReceiver callback);
     void onReject(String callId, in ResultReceiver callback);
     void onDisconnect(String callId, in ResultReceiver callback);
-    void onCallAudioStateChanged(String callId, in CallAudioState callAudioState);
-    // Streaming related. Client registered call streaming capabilities should override
+    // -- Streaming related. Client registered call streaming capabilities should override
     void onCallStreamingStarted(String callId, in ResultReceiver callback);
     void onCallStreamingFailed(String callId, int reason);
+    // -- Audio related.
+    void onCallEndpointChanged(String callId, in CallEndpoint endpoint);
+    void onAvailableCallEndpointsChanged(String callId, in List<CallEndpoint> endpoint);
+    void onMuteStateChanged(String callId, boolean isMuted);
     // hidden methods that help with cleanup
     void removeCallFromTransactionalServiceWrapper(String callId);
 }
\ No newline at end of file