diff options
author | 2022-11-24 09:26:37 +0000 | |
---|---|---|
committer | 2022-12-18 12:38:44 +0000 | |
commit | edf3d8260b9cece8a6d2e5bf61f84c3fc616e5a5 (patch) | |
tree | 99715d3d8fc1c77eb20ab36a419921e9039dc957 | |
parent | e2c82f46d739b3fd956b8976140eca2f5770bedb (diff) |
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
19 files changed, 1012 insertions, 12 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 1ff2a2a6fa27..ae66f5cc9fa2 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -40978,6 +40978,36 @@ package android.telecom { 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 @@ package android.telecom { 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 @@ package android.telecom { 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 @@ package android.telecom { 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 @@ package android.telecom { 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 @@ package android.telecom { 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 @@ package android.telecom { 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 @@ package android.telecom { 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 @@ package android.telecom { 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 000000000000..45b2249c00cd --- /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 000000000000..0b2211ddb94a --- /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 000000000000..19b43c4b4a69 --- /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 000000000000..e223892847bc --- /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 f84dd7b0bb17..f8037175fb05 100644 --- a/telecomm/java/android/telecom/Conference.java +++ b/telecomm/java/android/telecom/Conference.java @@ -88,6 +88,7 @@ public abstract class Conference extends Conferenceable { 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 @@ public abstract class Conference extends Conferenceable { * @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 @@ public abstract class Conference extends Conferenceable { * 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 @@ public abstract class Conference extends Conferenceable { 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 568c8abc40a2..465622658f7d 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -19,6 +19,7 @@ package android.telecom; 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.Handler; 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.List; 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 @@ public abstract class Connection extends Conferenceable { /** @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 @@ public abstract class Connection extends Conferenceable { 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 @@ public abstract class Connection extends Conferenceable { * @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 @@ public abstract class Connection extends Conferenceable { } /** + * 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 @@ public abstract class Connection extends Conferenceable { * @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 @@ public abstract class Connection extends Conferenceable { * <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 @@ public abstract class Connection extends Conferenceable { } /** + * 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 @@ public abstract class Connection extends Conferenceable { * 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 f341bc20fe01..4d6caf834e89 100755 --- a/telecomm/java/android/telecom/ConnectionService.java +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -31,6 +31,7 @@ import android.os.Handler; 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.List; 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 @@ public abstract class ConnectionService extends Service { 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 @@ public abstract class ConnectionService extends Service { 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 @@ public abstract class ConnectionService extends Service { } @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 @@ public abstract class ConnectionService extends Service { 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 @@ public abstract class ConnectionService extends Service { 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 @@ public abstract class ConnectionService extends Service { } } + 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 f8a6cf03934a..39928ef526af 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.Iterator; 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 @@ final class ConnectionServiceAdapter implements DeathRecipient { } } + /** + * 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 6c1ea322e66e..c95e14f0113a 100644 --- a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java +++ b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java @@ -21,6 +21,7 @@ import android.os.Bundle; 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 @@ final class ConnectionServiceAdapterServant { 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 ab35affe9099..77701457484a 100755 --- a/telecomm/java/android/telecom/InCallAdapter.java +++ b/telecomm/java/android/telecom/InCallAdapter.java @@ -19,12 +19,16 @@ package android.telecom; 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 @@ public final class InCallAdapter { } /** + * 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 64a86db38396..13a045858ab1 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.Handler; 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 com.android.internal.telecom.IInCallService; 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 @@ public abstract class InCallService extends Service { 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 @@ public abstract class InCallService extends Service { 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 @@ public abstract class InCallService extends Service { } @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 @@ public abstract class InCallService extends Service { * * @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 @@ public abstract class InCallService extends Service { * 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 @@ public abstract class InCallService extends Service { * {@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 @@ public abstract class InCallService extends Service { } /** + * 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 @@ public abstract class InCallService extends Service { * 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 bc0a14667307..95a8e16ace3d 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.List; 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 @@ public final class Phone { } /** + * 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 7a6fddb6f029..8b2b51e29c91 100644 --- a/telecomm/java/android/telecom/RemoteConnection.java +++ b/telecomm/java/android/telecom/RemoteConnection.java @@ -36,12 +36,10 @@ import com.android.internal.telecom.IVideoCallback; 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 efe35d21c003..65617327e43b 100644 --- a/telecomm/java/android/telecom/RemoteConnectionService.java +++ b/telecomm/java/android/telecom/RemoteConnectionService.java @@ -21,6 +21,7 @@ import android.os.Bundle; 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 @@ final class RemoteConnectionService { 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 d72f8aa82ddb..29617f21bf95 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.net.Uri; 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 @@ oneway interface IConnectionService { 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 3fd7f949cfe6..6838fbd99953 100644 --- a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl +++ b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl @@ -19,6 +19,8 @@ package com.android.internal.telecom; 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 @@ oneway interface IConnectionServiceAdapter { 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 edf1cf4cdb18..e381ce8c080f 100755 --- a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl +++ b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl @@ -18,7 +18,9 @@ package com.android.internal.telecom; 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 @@ oneway interface IInCallAdapter { 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 b9563fa7bb18..bac295a774be 100644 --- a/telecomm/java/com/android/internal/telecom/IInCallService.aidl +++ b/telecomm/java/com/android/internal/telecom/IInCallService.aidl @@ -19,6 +19,7 @@ package com.android.internal.telecom; 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 @@ oneway interface IInCallService { 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); |