Implement CallEndpoint representation and related APIs

Add CallEndpoint representation related APIs that request change CallEndpoint and to receive changed information

bug: 260952109
Test: CTS test
Change-Id: I60d0c1c73152a4c17f0227f101a844c83ecae1ee
diff --git a/core/api/current.txt b/core/api/current.txt
index 1ff2a2a..ae66f5c 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -40978,6 +40978,36 @@
     field public static final int ROUTE_WIRED_OR_EARPIECE = 5; // 0x5
   }
 
+  public final class CallEndpoint implements android.os.Parcelable {
+    ctor public CallEndpoint(@NonNull CharSequence, int, @NonNull android.os.ParcelUuid);
+    method public int describeContents();
+    method @NonNull public CharSequence getEndpointName();
+    method public int getEndpointType();
+    method @NonNull public android.os.ParcelUuid getIdentifier();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telecom.CallEndpoint> CREATOR;
+    field public static final int TYPE_BLUETOOTH = 2; // 0x2
+    field public static final int TYPE_EARPIECE = 1; // 0x1
+    field public static final int TYPE_SPEAKER = 4; // 0x4
+    field public static final int TYPE_STREAMING = 5; // 0x5
+    field public static final int TYPE_UNKNOWN = -1; // 0xffffffff
+    field public static final int TYPE_WIRED_HEADSET = 3; // 0x3
+  }
+
+  public final class CallEndpointException extends java.lang.RuntimeException implements android.os.Parcelable {
+    ctor public CallEndpointException(@Nullable String);
+    ctor public CallEndpointException(@Nullable String, int);
+    ctor public CallEndpointException(@Nullable String, int, @Nullable Throwable);
+    method public int describeContents();
+    method public int getCode();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telecom.CallEndpointException> CREATOR;
+    field public static final int ERROR_ANOTHER_REQUEST = 3; // 0x3
+    field public static final int ERROR_ENDPOINT_DOES_NOT_EXIST = 1; // 0x1
+    field public static final int ERROR_REQUEST_TIME_OUT = 2; // 0x2
+    field public static final int ERROR_UNSPECIFIED = 4; // 0x4
+  }
+
   public abstract class CallRedirectionService extends android.app.Service {
     ctor public CallRedirectionService();
     method public final void cancelCall();
@@ -41027,13 +41057,14 @@
     method public final boolean addConnection(android.telecom.Connection);
     method @NonNull public static android.telecom.Conference createFailedConference(@NonNull android.telecom.DisconnectCause, @NonNull android.telecom.PhoneAccountHandle);
     method public final void destroy();
-    method public final android.telecom.CallAudioState getCallAudioState();
+    method @Deprecated public final android.telecom.CallAudioState getCallAudioState();
     method public final java.util.List<android.telecom.Connection> getConferenceableConnections();
     method public final int getConnectionCapabilities();
     method public final int getConnectionProperties();
     method public final long getConnectionStartElapsedRealtimeMillis();
     method @IntRange(from=0) public final long getConnectionTime();
     method public final java.util.List<android.telecom.Connection> getConnections();
+    method @NonNull public final android.telecom.CallEndpoint getCurrentCallEndpoint();
     method public final android.telecom.DisconnectCause getDisconnectCause();
     method public final android.os.Bundle getExtras();
     method public final android.telecom.PhoneAccountHandle getPhoneAccountHandle();
@@ -41044,13 +41075,16 @@
     method public final boolean isRingbackRequested();
     method public void onAddConferenceParticipants(@NonNull java.util.List<android.net.Uri>);
     method public void onAnswer(int);
-    method public void onCallAudioStateChanged(android.telecom.CallAudioState);
+    method public void onAvailableCallEndpointsChanged(@NonNull java.util.List<android.telecom.CallEndpoint>);
+    method @Deprecated public void onCallAudioStateChanged(android.telecom.CallAudioState);
+    method public void onCallEndpointChanged(@NonNull android.telecom.CallEndpoint);
     method public void onConnectionAdded(android.telecom.Connection);
     method public void onDisconnect();
     method public void onExtrasChanged(android.os.Bundle);
     method public void onHold();
     method public void onMerge(android.telecom.Connection);
     method public void onMerge();
+    method public void onMuteStateChanged(boolean);
     method public void onPlayDtmfTone(char);
     method public void onReject();
     method public void onSeparate(android.telecom.Connection);
@@ -41093,7 +41127,7 @@
     method public final android.net.Uri getAddress();
     method public final int getAddressPresentation();
     method public final boolean getAudioModeIsVoip();
-    method public final android.telecom.CallAudioState getCallAudioState();
+    method @Deprecated public final android.telecom.CallAudioState getCallAudioState();
     method public final String getCallerDisplayName();
     method public final int getCallerDisplayNamePresentation();
     method public final int getCallerNumberVerificationStatus();
@@ -41101,6 +41135,7 @@
     method public final java.util.List<android.telecom.Conferenceable> getConferenceables();
     method public final int getConnectionCapabilities();
     method public final int getConnectionProperties();
+    method @NonNull public final android.telecom.CallEndpoint getCurrentCallEndpoint();
     method public final android.telecom.DisconnectCause getDisconnectCause();
     method public final android.os.Bundle getExtras();
     method public final int getState();
@@ -41114,13 +41149,16 @@
     method public void onAddConferenceParticipants(@NonNull java.util.List<android.net.Uri>);
     method public void onAnswer(int);
     method public void onAnswer();
-    method public void onCallAudioStateChanged(android.telecom.CallAudioState);
+    method public void onAvailableCallEndpointsChanged(@NonNull java.util.List<android.telecom.CallEndpoint>);
+    method @Deprecated public void onCallAudioStateChanged(android.telecom.CallAudioState);
+    method public void onCallEndpointChanged(@NonNull android.telecom.CallEndpoint);
     method public void onCallEvent(String, android.os.Bundle);
     method public void onDeflect(android.net.Uri);
     method public void onDisconnect();
     method public void onExtrasChanged(android.os.Bundle);
     method public void onHandoverComplete();
     method public void onHold();
+    method public void onMuteStateChanged(boolean);
     method public void onPlayDtmfTone(char);
     method public void onPostDialContinue(boolean);
     method public void onPullExternalCall();
@@ -41141,7 +41179,8 @@
     method public final void putExtras(@NonNull android.os.Bundle);
     method public final void removeExtras(java.util.List<java.lang.String>);
     method public final void removeExtras(java.lang.String...);
-    method public void requestBluetoothAudio(@NonNull android.bluetooth.BluetoothDevice);
+    method @Deprecated public void requestBluetoothAudio(@NonNull android.bluetooth.BluetoothDevice);
+    method public final void requestCallEndpointChange(@NonNull android.telecom.CallEndpoint, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallEndpointException>);
     method public void sendConnectionEvent(String, android.os.Bundle);
     method public final void sendRemoteRttRequest();
     method public final void sendRttInitiationFailure(int);
@@ -41150,7 +41189,7 @@
     method public final void setActive();
     method public final void setAddress(android.net.Uri, int);
     method public final void setAudioModeIsVoip(boolean);
-    method public final void setAudioRoute(int);
+    method @Deprecated public final void setAudioRoute(int);
     method public final void setCallerDisplayName(String, int);
     method public final void setCallerNumberVerificationStatus(int);
     method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>);
@@ -41402,18 +41441,23 @@
   public abstract class InCallService extends android.app.Service {
     ctor public InCallService();
     method public final boolean canAddCall();
-    method public final android.telecom.CallAudioState getCallAudioState();
+    method @Deprecated public final android.telecom.CallAudioState getCallAudioState();
     method public final java.util.List<android.telecom.Call> getCalls();
+    method @NonNull public final android.telecom.CallEndpoint getCurrentCallEndpoint();
+    method public void onAvailableCallEndpointsChanged(@NonNull java.util.List<android.telecom.CallEndpoint>);
     method public android.os.IBinder onBind(android.content.Intent);
     method public void onBringToForeground(boolean);
     method public void onCallAdded(android.telecom.Call);
-    method public void onCallAudioStateChanged(android.telecom.CallAudioState);
+    method @Deprecated public void onCallAudioStateChanged(android.telecom.CallAudioState);
+    method public void onCallEndpointChanged(@NonNull android.telecom.CallEndpoint);
     method public void onCallRemoved(android.telecom.Call);
     method public void onCanAddCallChanged(boolean);
     method public void onConnectionEvent(android.telecom.Call, String, android.os.Bundle);
+    method public void onMuteStateChanged(boolean);
     method public void onSilenceRinger();
-    method public final void requestBluetoothAudio(@NonNull android.bluetooth.BluetoothDevice);
-    method public final void setAudioRoute(int);
+    method @Deprecated public final void requestBluetoothAudio(@NonNull android.bluetooth.BluetoothDevice);
+    method public final void requestCallEndpointChange(@NonNull android.telecom.CallEndpoint, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallEndpointException>);
+    method @Deprecated public final void setAudioRoute(int);
     method public final void setMuted(boolean);
     field public static final String SERVICE_INTERFACE = "android.telecom.InCallService";
   }
diff --git a/telecomm/java/android/telecom/CallEndpoint.aidl b/telecomm/java/android/telecom/CallEndpoint.aidl
new file mode 100644
index 0000000..45b2249
--- /dev/null
+++ b/telecomm/java/android/telecom/CallEndpoint.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+/**
+ * {@hide}
+ */
+parcelable CallEndpoint;
\ No newline at end of file
diff --git a/telecomm/java/android/telecom/CallEndpoint.java b/telecomm/java/android/telecom/CallEndpoint.java
new file mode 100644
index 0000000..0b2211d
--- /dev/null
+++ b/telecomm/java/android/telecom/CallEndpoint.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.ParcelUuid;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ * Encapsulates the endpoint where call media can flow
+ */
+public final class CallEndpoint implements Parcelable {
+    /** @hide */
+    public static final int ENDPOINT_OPERATION_SUCCESS = 0;
+    /** @hide */
+    public static final int ENDPOINT_OPERATION_FAILED = 1;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({TYPE_UNKNOWN, TYPE_EARPIECE, TYPE_BLUETOOTH, TYPE_WIRED_HEADSET, TYPE_SPEAKER,
+            TYPE_STREAMING})
+    public @interface EndpointType {}
+
+    /** Indicates that the type of endpoint through which call media flows is unknown type. */
+    public static final int TYPE_UNKNOWN       = -1;
+
+    /** Indicates that the type of endpoint through which call media flows is an earpiece. */
+    public static final int TYPE_EARPIECE      = 1;
+
+    /** Indicates that the type of endpoint through which call media flows is a Bluetooth. */
+    public static final int TYPE_BLUETOOTH     = 2;
+
+    /** Indicates that the type of endpoint through which call media flows is a wired headset. */
+    public static final int TYPE_WIRED_HEADSET = 3;
+
+    /** Indicates that the type of endpoint through which call media flows is a speakerphone. */
+    public static final int TYPE_SPEAKER       = 4;
+
+    /** Indicates that the type of endpoint through which call media flows is an external. */
+    public static final int TYPE_STREAMING     = 5;
+
+    private final CharSequence mName;
+    private final int mType;
+    private final ParcelUuid mIdentifier;
+
+    /**
+     * Constructor for a {@link CallEndpoint} object.
+     *
+     * @param name Human-readable name associated with the endpoint
+     * @param type The type of endpoint through which call media being routed
+     * Allowed values:
+     * {@link #TYPE_EARPIECE}
+     * {@link #TYPE_BLUETOOTH}
+     * {@link #TYPE_WIRED_HEADSET}
+     * {@link #TYPE_SPEAKER}
+     * {@link #TYPE_STREAMING}
+     * {@link #TYPE_UNKNOWN}
+     * @param id A unique identifier for this endpoint on the device
+     */
+    public CallEndpoint(@NonNull CharSequence name, @EndpointType int type,
+            @NonNull ParcelUuid id) {
+        this.mName = name;
+        this.mType = type;
+        this.mIdentifier = id;
+    }
+
+    /** @hide */
+    public CallEndpoint(@NonNull CharSequence name, @EndpointType int type) {
+        this(name, type, new ParcelUuid(UUID.randomUUID()));
+    }
+
+    /** @hide */
+    public CallEndpoint(CallEndpoint endpoint) {
+        mName = endpoint.getEndpointName();
+        mType = endpoint.getEndpointType();
+        mIdentifier = endpoint.getIdentifier();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof CallEndpoint)) {
+            return false;
+        }
+        CallEndpoint endpoint = (CallEndpoint) obj;
+        return getEndpointName().toString().contentEquals(endpoint.getEndpointName())
+                && getEndpointType() == endpoint.getEndpointType()
+                && getIdentifier().equals(endpoint.getIdentifier());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int hashCode() {
+        return Objects.hash(mName, mType, mIdentifier);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String toString() {
+        return TextUtils.formatSimple("[CallEndpoint Name: %s, Type: %s, Identifier: %s]",
+                mName.toString(), endpointTypeToString(mType), mIdentifier.toString());
+    }
+
+    /**
+     * @return Human-readable name associated with the endpoint
+     */
+    @NonNull
+    public CharSequence getEndpointName() {
+        return mName;
+    }
+
+    /**
+     * @return The type of endpoint through which call media being routed
+     */
+    @EndpointType
+    public int getEndpointType() {
+        return mType;
+    }
+
+    /**
+     * @return A unique identifier for this endpoint on the device
+     */
+    @NonNull
+    public ParcelUuid getIdentifier() {
+        return mIdentifier;
+    }
+
+    /**
+     * Converts the provided endpoint type into a human-readable string representation.
+     *
+     * @param endpointType to convert into a string.
+     * @return String representation of the provided endpoint type.
+     * @hide
+     */
+    @NonNull
+    public static String endpointTypeToString(int endpointType) {
+        switch (endpointType) {
+            case TYPE_EARPIECE:
+                return "EARPIECE";
+            case TYPE_BLUETOOTH:
+                return "BLUETOOTH";
+            case TYPE_WIRED_HEADSET:
+                return "WIRED_HEADSET";
+            case TYPE_SPEAKER:
+                return "SPEAKER";
+            case TYPE_STREAMING:
+                return "EXTERNAL";
+            default:
+                return "UNKNOWN (" + endpointType + ")";
+        }
+    }
+
+    /**
+     * Responsible for creating CallEndpoint objects for deserialized Parcels.
+     */
+    public static final @android.annotation.NonNull Parcelable.Creator<CallEndpoint> CREATOR =
+            new Parcelable.Creator<CallEndpoint>() {
+
+        @Override
+        public CallEndpoint createFromParcel(Parcel source) {
+            CharSequence name = source.readCharSequence();
+            int type = source.readInt();
+            ParcelUuid id = ParcelUuid.CREATOR.createFromParcel(source);
+
+            return new CallEndpoint(name, type, id);
+        }
+
+        @Override
+        public CallEndpoint[] newArray(int size) {
+            return new CallEndpoint[size];
+        }
+    };
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void writeToParcel(@NonNull Parcel destination, int flags) {
+        destination.writeCharSequence(mName);
+        destination.writeInt(mType);
+        mIdentifier.writeToParcel(destination, flags);
+    }
+}
diff --git a/telecomm/java/android/telecom/CallEndpointException.aidl b/telecomm/java/android/telecom/CallEndpointException.aidl
new file mode 100644
index 0000000..19b43c4b4
--- /dev/null
+++ b/telecomm/java/android/telecom/CallEndpointException.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+/**
+ * {@hide}
+ */
+parcelable CallEndpointException;
\ No newline at end of file
diff --git a/telecomm/java/android/telecom/CallEndpointException.java b/telecomm/java/android/telecom/CallEndpointException.java
new file mode 100644
index 0000000..e2238928
--- /dev/null
+++ b/telecomm/java/android/telecom/CallEndpointException.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class represents a set of exceptions that can occur when requesting a
+ * {@link CallEndpoint} change.
+ */
+public final class CallEndpointException extends RuntimeException implements Parcelable {
+    /** @hide */
+    public static final String CHANGE_ERROR = "ChangeErrorKey";
+
+    /**
+     * The operation has failed because requested CallEndpoint does not exist.
+     */
+    public static final int ERROR_ENDPOINT_DOES_NOT_EXIST = 1;
+
+    /**
+     * The operation was not completed on time.
+     */
+    public static final int ERROR_REQUEST_TIME_OUT = 2;
+
+    /**
+     * The operation was canceled by another request.
+     */
+    public static final int ERROR_ANOTHER_REQUEST = 3;
+
+    /**
+     * The operation has failed due to an unknown or unspecified error.
+     */
+    public static final int ERROR_UNSPECIFIED = 4;
+
+    private int mCode = ERROR_UNSPECIFIED;
+    private final String mMessage;
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mMessage);
+        dest.writeInt(mCode);
+    }
+
+    /**
+     * Responsible for creating CallEndpointException objects for deserialized Parcels.
+     */
+    public static final @android.annotation.NonNull Parcelable.Creator<CallEndpointException>
+            CREATOR = new Parcelable.Creator<>() {
+                @Override
+                public CallEndpointException createFromParcel(Parcel source) {
+                    return new CallEndpointException(source.readString8(), source.readInt());
+                }
+
+                @Override
+                public CallEndpointException[] newArray(int size) {
+                    return new CallEndpointException[size];
+                }
+            };
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({ERROR_ENDPOINT_DOES_NOT_EXIST, ERROR_REQUEST_TIME_OUT, ERROR_ANOTHER_REQUEST,
+            ERROR_UNSPECIFIED})
+    public @interface CallEndpointErrorCode {
+    }
+
+    public CallEndpointException(@Nullable String message) {
+        super(getMessage(message, ERROR_UNSPECIFIED));
+        mMessage = message;
+    }
+
+    public CallEndpointException(@Nullable String message, @CallEndpointErrorCode int code) {
+        super(getMessage(message, code));
+        mCode = code;
+        mMessage = message;
+    }
+
+    public CallEndpointException(@Nullable String message, @CallEndpointErrorCode int code,
+            @Nullable Throwable cause) {
+        super(getMessage(message, code), cause);
+        mCode = code;
+        mMessage = message;
+    }
+
+
+    public @CallEndpointErrorCode int getCode() {
+        return mCode;
+    }
+
+    private static String getMessage(String message, int code) {
+        StringBuilder builder;
+        if (!TextUtils.isEmpty(message)) {
+            builder = new StringBuilder(message);
+            builder.append(" (code: ");
+            builder.append(code);
+            builder.append(")");
+            return builder.toString();
+        } else {
+            return "code: " + code;
+        }
+    }
+}
diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java
index f84dd7b..f803717 100644
--- a/telecomm/java/android/telecom/Conference.java
+++ b/telecomm/java/android/telecom/Conference.java
@@ -88,6 +88,7 @@
     private String mTelecomCallId;
     private PhoneAccountHandle mPhoneAccount;
     private CallAudioState mCallAudioState;
+    private CallEndpoint mCallEndpoint;
     private int mState = Connection.STATE_NEW;
     private DisconnectCause mDisconnectCause;
     private int mConnectionCapabilities;
@@ -223,12 +224,26 @@
      * @return The audio state of the conference, describing how its audio is currently
      *         being routed by the system. This is {@code null} if this Conference
      *         does not directly know about its audio state.
+     * @deprecated Use {@link #getCurrentCallEndpoint()},
+     * {@link #onAvailableCallEndpointsChanged(List)} and
+     * {@link #onMuteStateChanged(boolean)} instead.
      */
+    @Deprecated
     public final CallAudioState getCallAudioState() {
         return mCallAudioState;
     }
 
     /**
+     * Obtains the current CallEndpoint.
+     *
+     * @return An object encapsulating the CallEndpoint.
+     */
+    @NonNull
+    public final CallEndpoint getCurrentCallEndpoint() {
+        return mCallEndpoint;
+    }
+
+    /**
      * Returns VideoProvider of the primary call. This can be null.
      */
     public VideoProvider getVideoProvider() {
@@ -314,10 +329,35 @@
      * value.
      *
      * @param state The new call audio state.
+     * @deprecated Use {@link #onCallEndpointChanged(CallEndpoint)},
+     * {@link #onAvailableCallEndpointsChanged(List)} and
+     * {@link #onMuteStateChanged(boolean)} instead.
      */
+    @Deprecated
     public void onCallAudioStateChanged(CallAudioState state) {}
 
     /**
+     * Notifies the {@link Conference} that the audio endpoint has been changed.
+     *
+     * @param callEndpoint The new call endpoint.
+     */
+    public void onCallEndpointChanged(@NonNull CallEndpoint callEndpoint) {}
+
+    /**
+     * Notifies the {@link Conference} that the available call endpoints have been changed.
+     *
+     * @param availableEndpoints The available call endpoints.
+     */
+    public void onAvailableCallEndpointsChanged(@NonNull List<CallEndpoint> availableEndpoints) {}
+
+    /**
+     * Notifies the {@link Conference} that its audio mute state has been changed.
+     *
+     * @param isMuted The new mute state.
+     */
+    public void onMuteStateChanged(boolean isMuted) {}
+
+    /**
      * Notifies the {@link Conference} that a {@link Connection} has been added to it.
      *
      * @param connection The newly added connection.
@@ -730,6 +770,40 @@
         onCallAudioStateChanged(state);
     }
 
+    /**
+     * Inform this Conference that the audio endpoint has been changed.
+     *
+     * @param endpoint The new call endpoint.
+     * @hide
+     */
+    final void setCallEndpoint(CallEndpoint endpoint) {
+        Log.d(this, "setCallEndpoint %s", endpoint);
+        mCallEndpoint = endpoint;
+        onCallEndpointChanged(endpoint);
+    }
+
+    /**
+     * Inform this Conference that the available call endpoints have been changed.
+     *
+     * @param availableEndpoints The available call endpoints.
+     * @hide
+     */
+    final void setAvailableCallEndpoints(List<CallEndpoint> availableEndpoints) {
+        Log.d(this, "setAvailableCallEndpoints");
+        onAvailableCallEndpointsChanged(availableEndpoints);
+    }
+
+    /**
+     * Inform this Conference that its audio mute state has been changed.
+     *
+     * @param isMuted The new mute state.
+     * @hide
+     */
+    final void setMuteState(boolean isMuted) {
+        Log.d(this, "setMuteState %s", isMuted);
+        onMuteStateChanged(isMuted);
+    }
+
     private void setState(int newState) {
         if (mState != newState) {
             int oldState = mState;
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 568c8ab..4656226 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.MODIFY_PHONE_STATE;
 
 import android.Manifest;
+import android.annotation.CallbackExecutor;
 import android.annotation.ElapsedRealtimeLong;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
@@ -39,6 +40,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.OutcomeReceiver;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
@@ -69,6 +71,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
 
 /**
  * Represents a phone call or connection to a remote endpoint that carries voice and/or video
@@ -1280,6 +1283,8 @@
         /** @hide */
         public void onPhoneAccountChanged(Connection c, PhoneAccountHandle pHandle) {}
         public void onConnectionTimeReset(Connection c) {}
+        public void onEndpointChanged(Connection c, CallEndpoint endpoint, Executor executor,
+                OutcomeReceiver<Void, CallEndpointException> callback) {}
     }
 
     /**
@@ -2162,6 +2167,7 @@
     private PhoneAccountHandle mPhoneAccountHandle;
     private int mState = STATE_NEW;
     private CallAudioState mCallAudioState;
+    private CallEndpoint mCallEndpoint;
     private Uri mAddress;
     private int mAddressPresentation;
     private String mCallerDisplayName;
@@ -2290,7 +2296,11 @@
      * @return The audio state of the connection, describing how its audio is currently
      *         being routed by the system. This is {@code null} if this Connection
      *         does not directly know about its audio state.
+     * @deprecated Use {@link #getCurrentCallEndpoint()},
+     * {@link #onAvailableCallEndpointsChanged(List)} and
+     * {@link #onMuteStateChanged(boolean)} instead.
      */
+    @Deprecated
     public final CallAudioState getCallAudioState() {
         return mCallAudioState;
     }
@@ -2457,6 +2467,43 @@
     }
 
     /**
+     * Inform this Connection that the audio endpoint has been changed.
+     *
+     * @param endpoint The new call endpoint.
+     * @hide
+     */
+    final void setCallEndpoint(CallEndpoint endpoint) {
+        checkImmutable();
+        Log.d(this, "setCallEndpoint %s", endpoint);
+        mCallEndpoint = endpoint;
+        onCallEndpointChanged(endpoint);
+    }
+
+    /**
+     * Inform this Connection that the available call endpoints have been changed.
+     *
+     * @param availableEndpoints The available call endpoints.
+     * @hide
+     */
+    final void setAvailableCallEndpoints(List<CallEndpoint> availableEndpoints) {
+        checkImmutable();
+        Log.d(this, "setAvailableCallEndpoints");
+        onAvailableCallEndpointsChanged(availableEndpoints);
+    }
+
+    /**
+     * Inform this Connection that its audio mute state has been changed.
+     *
+     * @param isMuted The new mute state.
+     * @hide
+     */
+    final void setMuteState(boolean isMuted) {
+        checkImmutable();
+        Log.d(this, "setMuteState %s", isMuted);
+        onMuteStateChanged(isMuted);
+    }
+
+    /**
      * @param state An integer value of a {@code STATE_*} constant.
      * @return A string representation of the value.
      */
@@ -3081,7 +3128,10 @@
      * @param route The audio route to use (one of {@link CallAudioState#ROUTE_BLUETOOTH},
      *              {@link CallAudioState#ROUTE_EARPIECE}, {@link CallAudioState#ROUTE_SPEAKER}, or
      *              {@link CallAudioState#ROUTE_WIRED_HEADSET}).
+     * @deprecated Use {@link #requestCallEndpointChange(CallEndpoint, Executor, OutcomeReceiver)}
+     * instead.
      */
+    @Deprecated
     public final void setAudioRoute(int route) {
         for (Listener l : mListeners) {
             l.onAudioRouteChanged(this, route, null);
@@ -3101,7 +3151,10 @@
      * <p>
      * See also {@link InCallService#requestBluetoothAudio(BluetoothDevice)}
      * @param bluetoothDevice The bluetooth device to connect to.
+     * @deprecated Use {@link #requestCallEndpointChange(CallEndpoint, Executor, OutcomeReceiver)}
+     * instead.
      */
+    @Deprecated
     public void requestBluetoothAudio(@NonNull BluetoothDevice bluetoothDevice) {
         for (Listener l : mListeners) {
             l.onAudioRouteChanged(this, CallAudioState.ROUTE_BLUETOOTH,
@@ -3110,6 +3163,40 @@
     }
 
     /**
+     * Request audio routing to a specific CallEndpoint. 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 #onAvailableCallEndpointsChanged(List)}.
+     * When this request is honored, there will be change to the {@link #getCurrentCallEndpoint()}.
+     * <p>
+     * Used by self-managed {@link ConnectionService}s which wish to change the CallEndpoint for a
+     * self-managed {@link Connection} (see {@link PhoneAccount#CAPABILITY_SELF_MANAGED}.)
+     * <p>
+     * See also
+     * {@link InCallService#requestCallEndpointChange(CallEndpoint, Executor, OutcomeReceiver)}.
+     *
+     * @param endpoint The call endpoint to use.
+     * @param executor The executor of where the callback will execute.
+     * @param callback The callback to notify the result of the endpoint change.
+     */
+    public final void requestCallEndpointChange(@NonNull CallEndpoint endpoint,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OutcomeReceiver<Void, CallEndpointException> callback) {
+        for (Listener l : mListeners) {
+            l.onEndpointChanged(this, endpoint, executor, callback);
+        }
+    }
+
+    /**
+     * Obtains the current CallEndpoint.
+     *
+     * @return An object encapsulating the CallEndpoint.
+     */
+    @NonNull
+    public final CallEndpoint getCurrentCallEndpoint() {
+        return mCallEndpoint;
+    }
+
+    /**
      * Informs listeners that a previously requested RTT session via
      * {@link ConnectionRequest#isRequestingRtt()} or
      * {@link #onStartRtt(RttTextStream)} has succeeded.
@@ -3160,10 +3247,35 @@
      * Notifies this Connection that the {@link #getCallAudioState()} property has a new value.
      *
      * @param state The new connection audio state.
+     * @deprecated Use {@link #onCallEndpointChanged(CallEndpoint)},
+     * {@link #onAvailableCallEndpointsChanged(List)} and
+     * {@link #onMuteStateChanged(boolean)} instead.
      */
+    @Deprecated
     public void onCallAudioStateChanged(CallAudioState state) {}
 
     /**
+     * Notifies this Connection that the audio endpoint has been changed.
+     *
+     * @param callEndpoint The current CallEndpoint.
+     */
+    public void onCallEndpointChanged(@NonNull CallEndpoint callEndpoint) {}
+
+    /**
+     * Notifies this Connection that the available call endpoints have been changed.
+     *
+     * @param availableEndpoints The set of available CallEndpoint.
+     */
+    public void onAvailableCallEndpointsChanged(@NonNull List<CallEndpoint> availableEndpoints) {}
+
+    /**
+     * Notifies this Connection that its audio mute state has been changed.
+     *
+     * @param isMuted The current mute state.
+     */
+    public void onMuteStateChanged(boolean isMuted) {}
+
+    /**
      * Inform this Connection when it will or will not be tracked by an {@link InCallService} which
      * can provide an InCall UI.
      * This is primarily intended for use by Connections reported by self-managed
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index f341bc2..4d6caf8 100755
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -31,6 +31,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.OutcomeReceiver;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.telecom.Logging.Session;
@@ -48,6 +49,7 @@
 import java.util.Map;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
 
 /**
  * An abstract service that should be implemented by any apps which either:
@@ -164,6 +166,9 @@
     private static final String SESSION_CREATE_CONF = "CS.crConf";
     private static final String SESSION_CREATE_CONF_COMPLETE = "CS.crConfC";
     private static final String SESSION_CREATE_CONF_FAILED = "CS.crConfF";
+    private static final String SESSION_CALL_ENDPOINT_CHANGED = "CS.oCEC";
+    private static final String SESSION_AVAILABLE_CALL_ENDPOINTS_CHANGED = "CS.oACEC";
+    private static final String SESSION_MUTE_STATE_CHANGED = "CS.oMSC";
 
     private static final int MSG_ADD_CONNECTION_SERVICE_ADAPTER = 1;
     private static final int MSG_CREATE_CONNECTION = 2;
@@ -208,6 +213,9 @@
     private static final int MSG_ON_CALL_FILTERING_COMPLETED = 42;
     private static final int MSG_ON_USING_ALTERNATIVE_UI = 43;
     private static final int MSG_ON_TRACKED_BY_NON_UI_SERVICE = 44;
+    private static final int MSG_ON_CALL_ENDPOINT_CHANGED = 45;
+    private static final int MSG_ON_AVAILABLE_CALL_ENDPOINTS_CHANGED = 46;
+    private static final int MSG_ON_MUTE_STATE_CHANGED = 47;
 
     private static Connection sNullConnection;
 
@@ -592,6 +600,51 @@
         }
 
         @Override
+        public void onCallEndpointChanged(String callId, CallEndpoint callEndpoint,
+                Session.Info sessionInfo) {
+            Log.startSession(sessionInfo, SESSION_CALL_ENDPOINT_CHANGED);
+            try {
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = callId;
+                args.arg2 = callEndpoint;
+                args.arg3 = Log.createSubsession();
+                mHandler.obtainMessage(MSG_ON_CALL_ENDPOINT_CHANGED, args).sendToTarget();
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
+        public void onAvailableCallEndpointsChanged(String callId,
+                List<CallEndpoint> availableCallEndpoints, Session.Info sessionInfo) {
+            Log.startSession(sessionInfo, SESSION_AVAILABLE_CALL_ENDPOINTS_CHANGED);
+            try {
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = callId;
+                args.arg2 = availableCallEndpoints;
+                args.arg3 = Log.createSubsession();
+                mHandler.obtainMessage(MSG_ON_AVAILABLE_CALL_ENDPOINTS_CHANGED, args)
+                       .sendToTarget();
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
+        public void onMuteStateChanged(String callId, boolean isMuted, Session.Info sessionInfo) {
+            Log.startSession(sessionInfo, SESSION_MUTE_STATE_CHANGED);
+            try {
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = callId;
+                args.arg2 = isMuted;
+                args.arg3 = Log.createSubsession();
+                mHandler.obtainMessage(MSG_ON_MUTE_STATE_CHANGED, args).sendToTarget();
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
         public void onUsingAlternativeUi(String callId, boolean usingAlternativeUiShowing,
                 Session.Info sessionInfo) {
             Log.startSession(sessionInfo, SESSION_USING_ALTERNATIVE_UI);
@@ -1527,6 +1580,48 @@
                 case MSG_CONNECTION_SERVICE_FOCUS_LOST:
                     onConnectionServiceFocusLost();
                     break;
+                case MSG_ON_CALL_ENDPOINT_CHANGED: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    Log.continueSession((Session) args.arg3,
+                            SESSION_HANDLER + SESSION_CALL_AUDIO_SC);
+                    try {
+                        String callId = (String) args.arg1;
+                        CallEndpoint callEndpoint = (CallEndpoint) args.arg2;
+                        onCallEndpointChanged(callId, callEndpoint);
+                    } finally {
+                        args.recycle();
+                        Log.endSession();
+                    }
+                    break;
+                }
+                case MSG_ON_AVAILABLE_CALL_ENDPOINTS_CHANGED: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    Log.continueSession((Session) args.arg3,
+                            SESSION_HANDLER + SESSION_CALL_AUDIO_SC);
+                    try {
+                        String callId = (String) args.arg1;
+                        List<CallEndpoint>  availableCallEndpoints = (List<CallEndpoint>) args.arg2;
+                        onAvailableCallEndpointsChanged(callId, availableCallEndpoints);
+                    } finally {
+                        args.recycle();
+                        Log.endSession();
+                    }
+                    break;
+                }
+                case MSG_ON_MUTE_STATE_CHANGED: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    Log.continueSession((Session) args.arg3,
+                            SESSION_HANDLER + SESSION_CALL_AUDIO_SC);
+                    try {
+                        String callId = (String) args.arg1;
+                        boolean isMuted = (boolean) args.arg2;
+                        onMuteStateChanged(callId, isMuted);
+                    } finally {
+                        args.recycle();
+                        Log.endSession();
+                    }
+                    break;
+                }
                 default:
                     break;
             }
@@ -1916,6 +2011,15 @@
                 mAdapter.resetConnectionTime(id);
             }
         }
+
+        @Override
+        public void onEndpointChanged(Connection c, CallEndpoint endpoint, Executor executor,
+                OutcomeReceiver<Void, CallEndpointException> callback) {
+            String id = mIdByConnection.get(c);
+            if (id != null) {
+                mAdapter.requestCallEndpointChange(id, endpoint, executor, callback);
+            }
+        }
     };
 
     /** {@inheritDoc} */
@@ -2313,6 +2417,36 @@
         }
     }
 
+    private void onCallEndpointChanged(String callId, CallEndpoint callEndpoint) {
+        Log.i(this, "onCallEndpointChanged %s %s", callId, callEndpoint);
+        if (mConnectionById.containsKey(callId)) {
+            findConnectionForAction(callId, "onCallEndpointChanged").setCallEndpoint(callEndpoint);
+        } else {
+            findConferenceForAction(callId, "onCallEndpointChanged").setCallEndpoint(callEndpoint);
+        }
+    }
+
+    private void onAvailableCallEndpointsChanged(String callId,
+            List<CallEndpoint> availableCallEndpoints) {
+        Log.i(this, "onAvailableCallEndpointsChanged %s", callId);
+        if (mConnectionById.containsKey(callId)) {
+            findConnectionForAction(callId, "onAvailableCallEndpointsChanged")
+                    .setAvailableCallEndpoints(availableCallEndpoints);
+        } else {
+            findConferenceForAction(callId, "onAvailableCallEndpointsChanged")
+                    .setAvailableCallEndpoints(availableCallEndpoints);
+        }
+    }
+
+    private void onMuteStateChanged(String callId, boolean isMuted) {
+        Log.i(this, "onMuteStateChanged %s %s", callId, isMuted);
+        if (mConnectionById.containsKey(callId)) {
+            findConnectionForAction(callId, "onMuteStateChanged").setMuteState(isMuted);
+        } else {
+            findConferenceForAction(callId, "onMuteStateChanged").setMuteState(isMuted);
+        }
+    }
+
     private void onUsingAlternativeUi(String callId, boolean isUsingAlternativeUi) {
         Log.i(this, "onUsingAlternativeUi %s %s", callId, isUsingAlternativeUi);
         if (mConnectionById.containsKey(callId)) {
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapter.java b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
index f8a6cf0..39928ef 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapter.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
@@ -17,9 +17,12 @@
 package android.telecom;
 
 import android.net.Uri;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder.DeathRecipient;
+import android.os.OutcomeReceiver;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 
 import com.android.internal.telecom.IConnectionServiceAdapter;
 import com.android.internal.telecom.RemoteServiceCallback;
@@ -29,6 +32,7 @@
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
 
 /**
  * Provides methods for IConnectionService implementations to interact with the system phone app.
@@ -567,6 +571,41 @@
         }
     }
 
+    /**
+     * Sets the call endpoint associated with a {@link Connection}.
+     *
+     * @param callId The unique ID of the call.
+     * @param endpoint The new call endpoint (see {@link CallEndpoint}).
+     * @param executor The executor of where the callback will execute.
+     * @param callback The callback to notify the result of the endpoint change.
+     */
+    void requestCallEndpointChange(String callId, CallEndpoint endpoint, Executor executor,
+            OutcomeReceiver<Void, CallEndpointException> callback) {
+        Log.v(this, "requestCallEndpointChange");
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.requestCallEndpointChange(callId, endpoint, new ResultReceiver(null) {
+                    @Override
+                    protected void onReceiveResult(int resultCode, Bundle result) {
+                        super.onReceiveResult(resultCode, result);
+                        final long identity = Binder.clearCallingIdentity();
+                        try {
+                            if (resultCode == CallEndpoint.ENDPOINT_OPERATION_SUCCESS) {
+                                executor.execute(() -> callback.onResult(null));
+                            } else {
+                                executor.execute(() -> callback.onError(result.getParcelable(
+                                        CallEndpointException.CHANGE_ERROR,
+                                        CallEndpointException.class)));
+                            }
+                        } finally {
+                            Binder.restoreCallingIdentity(identity);
+                        }
+                    }}, Log.getExternalSession());
+            } catch (RemoteException ignored) {
+                Log.d(this, "Remote exception calling requestCallEndpointChange");
+            }
+        }
+    }
 
     /**
      * Informs Telecom of a connection level event.
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
index 6c1ea32..c95e14f 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
@@ -21,6 +21,7 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.telecom.Logging.Session;
 
 import com.android.internal.os.SomeArgs;
@@ -692,6 +693,12 @@
             args.arg2 = sessionInfo;
             mHandler.obtainMessage(MSG_SET_CALL_DIRECTION, args).sendToTarget();
         }
+
+        @Override
+        public void requestCallEndpointChange(String callId, CallEndpoint endpoint,
+                ResultReceiver callback, Session.Info sessionInfo) {
+            // Do nothing
+        }
     };
 
     public ConnectionServiceAdapterServant(IConnectionServiceAdapter delegate) {
diff --git a/telecomm/java/android/telecom/InCallAdapter.java b/telecomm/java/android/telecom/InCallAdapter.java
index ab35aff..7770145 100755
--- a/telecomm/java/android/telecom/InCallAdapter.java
+++ b/telecomm/java/android/telecom/InCallAdapter.java
@@ -19,12 +19,16 @@
 import android.annotation.NonNull;
 import android.bluetooth.BluetoothDevice;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.Bundle;
+import android.os.OutcomeReceiver;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 
 import com.android.internal.telecom.IInCallAdapter;
 
 import java.util.List;
+import java.util.concurrent.Executor;
 
 /**
  * Receives commands from {@link InCallService} implementations which should be executed by
@@ -227,6 +231,39 @@
     }
 
     /**
+     * Request audio routing to a specific CallEndpoint.. See {@link CallEndpoint}.
+     *
+     * @param endpoint The call endpoint to use.
+     * @param executor The executor of where the callback will execute.
+     * @param callback The callback to notify the result of the endpoint change.
+     */
+    public void requestCallEndpointChange(CallEndpoint endpoint, Executor executor,
+            OutcomeReceiver<Void, CallEndpointException> callback) {
+        try {
+            mAdapter.requestCallEndpointChange(endpoint, new ResultReceiver(null) {
+                @Override
+                protected void onReceiveResult(int resultCode, Bundle result) {
+                    super.onReceiveResult(resultCode, result);
+                    final long identity = Binder.clearCallingIdentity();
+                    try {
+                        if (resultCode == CallEndpoint.ENDPOINT_OPERATION_SUCCESS) {
+                            executor.execute(() -> callback.onResult(null));
+                        } else {
+                            executor.execute(() -> callback.onError(
+                                    result.getParcelable(CallEndpointException.CHANGE_ERROR,
+                                            CallEndpointException.class)));
+                        }
+                    } finally {
+                        Binder.restoreCallingIdentity(identity);
+                    }
+                }
+            });
+        } catch (RemoteException e) {
+            Log.d(this, "Remote exception calling requestCallEndpointChange");
+        }
+    }
+
+    /**
      * Instructs Telecom to play a dual-tone multi-frequency signaling (DTMF) tone in a call.
      *
      * Any other currently playing DTMF tone in the specified call is immediately stopped.
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index 64a86db..13a0458 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -16,6 +16,7 @@
 
 package android.telecom;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
@@ -31,6 +32,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.OutcomeReceiver;
 import android.view.Surface;
 
 import com.android.internal.os.SomeArgs;
@@ -39,6 +41,8 @@
 
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
 
 /**
  * This service is implemented by an app that wishes to provide functionality for managing
@@ -269,6 +273,11 @@
     private static final int MSG_ON_RTT_INITIATION_FAILURE = 11;
     private static final int MSG_ON_HANDOVER_FAILED = 12;
     private static final int MSG_ON_HANDOVER_COMPLETE = 13;
+    private static final int MSG_ON_CALL_ENDPOINT_CHANGED = 14;
+    private static final int MSG_ON_AVAILABLE_CALL_ENDPOINTS_CHANGED = 15;
+    private static final int MSG_ON_MUTE_STATE_CHANGED = 16;
+
+    private CallEndpoint mCallEndpoint;
 
     /** Default Handler used to consolidate binder method calls onto a single thread. */
     private final Handler mHandler = new Handler(Looper.getMainLooper()) {
@@ -350,6 +359,23 @@
                     mPhone.internalOnHandoverComplete(callId);
                     break;
                 }
+                case MSG_ON_CALL_ENDPOINT_CHANGED: {
+                    CallEndpoint endpoint = (CallEndpoint) msg.obj;
+                    if (!Objects.equals(mCallEndpoint, endpoint)) {
+                        mCallEndpoint = endpoint;
+                        InCallService.this.onCallEndpointChanged(mCallEndpoint);
+                    }
+                    break;
+                }
+                case MSG_ON_AVAILABLE_CALL_ENDPOINTS_CHANGED: {
+                    InCallService.this.onAvailableCallEndpointsChanged(
+                            (List<CallEndpoint>) msg.obj);
+                    break;
+                }
+                case MSG_ON_MUTE_STATE_CHANGED: {
+                    InCallService.this.onMuteStateChanged((boolean) msg.obj);
+                    break;
+                }
                 default:
                     break;
             }
@@ -392,6 +418,22 @@
         }
 
         @Override
+        public void onCallEndpointChanged(CallEndpoint callEndpoint) {
+            mHandler.obtainMessage(MSG_ON_CALL_ENDPOINT_CHANGED, callEndpoint).sendToTarget();
+        }
+
+        @Override
+        public void onAvailableCallEndpointsChanged(List<CallEndpoint> availableEndpoints) {
+            mHandler.obtainMessage(MSG_ON_AVAILABLE_CALL_ENDPOINTS_CHANGED, availableEndpoints)
+                    .sendToTarget();
+        }
+
+        @Override
+        public void onMuteStateChanged(boolean isMuted) {
+            mHandler.obtainMessage(MSG_ON_MUTE_STATE_CHANGED, isMuted).sendToTarget();
+        }
+
+        @Override
         public void bringToForeground(boolean showDialpad) {
             mHandler.obtainMessage(MSG_BRING_TO_FOREGROUND, showDialpad ? 1 : 0, 0).sendToTarget();
         }
@@ -559,7 +601,11 @@
      *
      * @return An object encapsulating the audio state. Returns null if the service is not
      *         fully initialized.
+     * @deprecated Use {@link #getCurrentCallEndpoint()},
+     * {@link #onAvailableCallEndpointsChanged(List)} and
+     * {@link #onMuteStateChanged(boolean)} instead.
      */
+    @Deprecated
     public final CallAudioState getCallAudioState() {
         return mPhone == null ? null : mPhone.getCallAudioState();
     }
@@ -581,7 +627,10 @@
      * be change to the {@link #getCallAudioState()}.
      *
      * @param route The audio route to use.
+     * @deprecated Use {@link #requestCallEndpointChange(CallEndpoint, Executor, OutcomeReceiver)}
+     * instead.
      */
+    @Deprecated
     public final void setAudioRoute(int route) {
         if (mPhone != null) {
             mPhone.setAudioRoute(route);
@@ -596,7 +645,10 @@
      * {@link CallAudioState#getSupportedBluetoothDevices()}
      *
      * @param bluetoothDevice The bluetooth device to connect to.
+     * @deprecated Use {@link #requestCallEndpointChange(CallEndpoint, Executor, OutcomeReceiver)}
+     * instead.
      */
+    @Deprecated
     public final void requestBluetoothAudio(@NonNull BluetoothDevice bluetoothDevice) {
         if (mPhone != null) {
             mPhone.requestBluetoothAudio(bluetoothDevice.getAddress());
@@ -604,6 +656,34 @@
     }
 
     /**
+     * Request audio routing to a specific CallEndpoint. 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 #onAvailableCallEndpointsChanged(List)}.
+     * When this request is honored, there will be change to the {@link #getCurrentCallEndpoint()}.
+     *
+     * @param endpoint The call endpoint to use.
+     * @param executor The executor of where the callback will execute.
+     * @param callback The callback to notify the result of the endpoint change.
+     */
+    public final void requestCallEndpointChange(@NonNull CallEndpoint endpoint,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OutcomeReceiver<Void, CallEndpointException> callback) {
+        if (mPhone != null) {
+            mPhone.requestCallEndpointChange(endpoint, executor, callback);
+        }
+    }
+
+    /**
+     * Obtains the current CallEndpoint.
+     *
+     * @return An object encapsulating the CallEndpoint.
+     */
+    @NonNull
+    public final CallEndpoint getCurrentCallEndpoint() {
+        return mCallEndpoint;
+    }
+
+    /**
      * Invoked when the {@code Phone} has been created. This is a signal to the in-call experience
      * to start displaying in-call information to the user. Each instance of {@code InCallService}
      * will have only one {@code Phone}, and this method will be called exactly once in the lifetime
@@ -648,11 +728,39 @@
      * Called when the audio state changes.
      *
      * @param audioState The new {@link CallAudioState}.
+     * @deprecated Use {@link #onCallEndpointChanged(CallEndpoint)},
+     * {@link #onAvailableCallEndpointsChanged(List)} and
+     * {@link #onMuteStateChanged(boolean)} instead.
      */
+    @Deprecated
     public void onCallAudioStateChanged(CallAudioState audioState) {
     }
 
     /**
+     * Called when the current CallEndpoint changes.
+     *
+     * @param callEndpoint The current CallEndpoint {@link CallEndpoint}.
+     */
+    public void onCallEndpointChanged(@NonNull CallEndpoint callEndpoint) {
+    }
+
+    /**
+     * Called when the available CallEndpoint changes.
+     *
+     * @param availableEndpoints The set of available CallEndpoint {@link CallEndpoint}.
+     */
+    public void onAvailableCallEndpointsChanged(@NonNull List<CallEndpoint> availableEndpoints) {
+    }
+
+    /**
+     * Called when the mute state changes.
+     *
+     * @param isMuted The current mute state.
+     */
+    public void onMuteStateChanged(boolean isMuted) {
+    }
+
+    /**
      * Called to bring the in-call screen to the foreground. The in-call experience should
      * respond immediately by coming to the foreground to inform the user of the state of
      * ongoing {@code Call}s.
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index bc0a146..95a8e16 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -16,11 +16,14 @@
 
 package android.telecom;
 
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.bluetooth.BluetoothDevice;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.OutcomeReceiver;
 import android.util.ArrayMap;
 
 import com.android.internal.annotations.GuardedBy;
@@ -30,6 +33,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
 
 /**
  * A unified virtual device providing a means of voice (and other) communication on a device.
@@ -378,6 +382,21 @@
     }
 
     /**
+     * Request audio routing to a specific CallEndpoint. When this request is honored, there will
+     * be change to the {@link #getCurrentCallEndpoint()}.
+     *
+     * @param endpoint The call endpoint to use.
+     * @param executor The executor of where the callback will execute.
+     * @param callback The callback to notify the result of the endpoint change.
+     * @hide
+     */
+    public void requestCallEndpointChange(@NonNull CallEndpoint endpoint,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OutcomeReceiver<Void, CallEndpointException> callback) {
+        mInCallAdapter.requestCallEndpointChange(endpoint, executor, callback);
+    }
+
+    /**
      * Turns the proximity sensor on. When this request is made, the proximity sensor will
      * become active, and the touch screen and display will be turned off when the user's face
      * is detected to be in close proximity to the screen. This operation is a no-op on devices
diff --git a/telecomm/java/android/telecom/RemoteConnection.java b/telecomm/java/android/telecom/RemoteConnection.java
index 7a6fddb..8b2b51e 100644
--- a/telecomm/java/android/telecom/RemoteConnection.java
+++ b/telecomm/java/android/telecom/RemoteConnection.java
@@ -36,12 +36,10 @@
 import com.android.internal.telecom.IVideoProvider;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.stream.Collectors;
 
 /**
  * A connection provided to a {@link ConnectionService} by another {@code ConnectionService}
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
index efe35d2..6561732 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -21,6 +21,7 @@
 import android.os.IBinder;
 import android.os.IBinder.DeathRecipient;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.telecom.Logging.Session;
 
 import com.android.internal.telecom.IConnectionService;
@@ -510,6 +511,12 @@
         public void setCallDirection(String callId, int direction, Session.Info sessionInfo) {
             // Do nothing
         }
+
+        @Override
+        public void requestCallEndpointChange(String callId, CallEndpoint endpoint,
+                ResultReceiver callback, Session.Info sessionInfo) {
+            // Do nothing
+        }
     };
 
     private final ConnectionServiceAdapterServant mServant =
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
index d72f8aa..29617f2 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -20,6 +20,7 @@
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.telecom.CallAudioState;
+import android.telecom.CallEndpoint;
 import android.telecom.Connection;
 import android.telecom.ConnectionRequest;
 import android.telecom.Logging.Session;
@@ -98,6 +99,14 @@
     void onCallAudioStateChanged(String activeCallId, in CallAudioState callAudioState,
     in Session.Info sessionInfo);
 
+    void onCallEndpointChanged(String activeCallId, in CallEndpoint callEndpoint,
+    in Session.Info sessionInfo);
+
+    void onAvailableCallEndpointsChanged(String activeCallId,
+    in List<CallEndpoint> availableCallEndpoints, in Session.Info sessionInfo);
+
+    void onMuteStateChanged(String activeCallId, boolean isMuted, in Session.Info sessionInfo);
+
     void playDtmfTone(String callId, char digit, in Session.Info sessionInfo);
 
     void stopDtmfTone(String callId, in Session.Info sessionInfo);
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
index 3fd7f949..6838fbd 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
@@ -19,6 +19,8 @@
 import android.app.PendingIntent;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.ResultReceiver;
+import android.telecom.CallEndpoint;
 import android.telecom.ConnectionRequest;
 import android.telecom.DisconnectCause;
 import android.telecom.Logging.Session;
@@ -113,6 +115,9 @@
     void setAudioRoute(String callId, int audioRoute, String bluetoothAddress,
             in Session.Info sessionInfo);
 
+    void requestCallEndpointChange(String callId, in CallEndpoint endpoint,
+            in ResultReceiver callback, in Session.Info sessionInfo);
+
     void onConnectionEvent(String callId, String event, in Bundle extras,
     in Session.Info sessionInfo);
 
diff --git a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
index edf1cf4..e381ce8 100755
--- a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
@@ -18,7 +18,9 @@
 
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.ResultReceiver;
 import android.telecom.PhoneAccountHandle;
+import android.telecom.CallEndpoint;
 
 /**
  * Internal remote callback interface for in-call services.
@@ -50,6 +52,8 @@
 
     void setAudioRoute(int route, String bluetoothAddress);
 
+    void requestCallEndpointChange(in CallEndpoint endpoint, in ResultReceiver callback);
+
     void enterBackgroundAudioProcessing(String callId);
 
     void exitBackgroundAudioProcessing(String callId, boolean shouldRing);
diff --git a/telecomm/java/com/android/internal/telecom/IInCallService.aidl b/telecomm/java/com/android/internal/telecom/IInCallService.aidl
index b9563fa..bac295a 100644
--- a/telecomm/java/com/android/internal/telecom/IInCallService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallService.aidl
@@ -19,6 +19,7 @@
 import android.app.PendingIntent;
 import android.os.Bundle;
 import android.telecom.CallAudioState;
+import android.telecom.CallEndpoint;
 import android.telecom.ParcelableCall;
 
 import com.android.internal.telecom.IInCallAdapter;
@@ -43,6 +44,12 @@
 
     void onCallAudioStateChanged(in CallAudioState callAudioState);
 
+    void onCallEndpointChanged(in CallEndpoint callEndpoint);
+
+    void onAvailableCallEndpointsChanged(in List<CallEndpoint> availableCallEndpoints);
+
+    void onMuteStateChanged(boolean isMuted);
+
     void bringToForeground(boolean showDialpad);
 
     void onCanAddCallChanged(boolean canAddCall);