summaryrefslogtreecommitdiff
path: root/telecomm
diff options
context:
space:
mode:
authorXin Li <delphij@google.com>2023-08-25 12:59:08 -0700
committerXin Li <delphij@google.com>2023-08-25 12:59:08 -0700
commit7d3ffbae618e9e728644a96647ed709bf39ae759 (patch)
treeab369a30c6a0e17a69c8f80c6353be4de3692e10 /telecomm
parenta8a87bbca9162af7add830139198c4ee899fa123 (diff)
parent8a809c6e46007521f75ac035ad4b1dcc1d00d9cf (diff)
downloadbase-7d3ffbae618e9e728644a96647ed709bf39ae759.tar.gz
Merge Android U (ab/10368041)
Bug: 291102124 Merged-In: I3c9e9d15786fbead1b874636b46844f6c24bccc2 Change-Id: Id6cf6cc13baef4e67486c6271a1510146204affa
Diffstat (limited to 'telecomm')
-rw-r--r--telecomm/java/android/telecom/Call.java61
-rw-r--r--telecomm/java/android/telecom/CallAttributes.aidl22
-rw-r--r--telecomm/java/android/telecom/CallAttributes.java356
-rw-r--r--telecomm/java/android/telecom/CallAudioState.java15
-rw-r--r--telecomm/java/android/telecom/CallControl.aidl22
-rw-r--r--telecomm/java/android/telecom/CallControl.java398
-rw-r--r--telecomm/java/android/telecom/CallControlCallback.java108
-rw-r--r--telecomm/java/android/telecom/CallEndpoint.aidl22
-rw-r--r--telecomm/java/android/telecom/CallEndpoint.java222
-rw-r--r--telecomm/java/android/telecom/CallEndpointException.aidl22
-rw-r--r--telecomm/java/android/telecom/CallEndpointException.java117
-rw-r--r--telecomm/java/android/telecom/CallEventCallback.java76
-rw-r--r--telecomm/java/android/telecom/CallException.aidl22
-rw-r--r--telecomm/java/android/telecom/CallException.java149
-rw-r--r--telecomm/java/android/telecom/CallScreeningService.java3
-rw-r--r--telecomm/java/android/telecom/CallStreamingService.java220
-rw-r--r--telecomm/java/android/telecom/Conference.java74
-rw-r--r--telecomm/java/android/telecom/Connection.java180
-rwxr-xr-xtelecomm/java/android/telecom/ConnectionService.java377
-rw-r--r--telecomm/java/android/telecom/ConnectionServiceAdapter.java83
-rw-r--r--telecomm/java/android/telecom/ConnectionServiceAdapterServant.java32
-rw-r--r--telecomm/java/android/telecom/DisconnectCause.java4
-rwxr-xr-xtelecomm/java/android/telecom/InCallAdapter.java37
-rw-r--r--telecomm/java/android/telecom/InCallService.java108
-rw-r--r--telecomm/java/android/telecom/Log.java19
-rw-r--r--telecomm/java/android/telecom/Logging/EventManager.java6
-rw-r--r--telecomm/java/android/telecom/ParcelableCall.java26
-rw-r--r--telecomm/java/android/telecom/ParcelableCallAnalytics.java5
-rw-r--r--telecomm/java/android/telecom/ParcelableConference.java12
-rw-r--r--telecomm/java/android/telecom/Phone.java19
-rw-r--r--telecomm/java/android/telecom/PhoneAccount.java62
-rw-r--r--telecomm/java/android/telecom/PhoneAccountHandle.java13
-rw-r--r--telecomm/java/android/telecom/QueryLocationException.aidl22
-rw-r--r--telecomm/java/android/telecom/QueryLocationException.java129
-rw-r--r--telecomm/java/android/telecom/RemoteConnection.java2
-rw-r--r--telecomm/java/android/telecom/RemoteConnectionService.java13
-rw-r--r--telecomm/java/android/telecom/StatusHints.java53
-rw-r--r--telecomm/java/android/telecom/StreamingCall.aidl22
-rw-r--r--telecomm/java/android/telecom/StreamingCall.java191
-rw-r--r--telecomm/java/android/telecom/StreamingCallAdapter.java54
-rw-r--r--telecomm/java/android/telecom/TelecomManager.java192
-rw-r--r--telecomm/java/com/android/internal/telecom/ClientTransactionalServiceRepository.java90
-rw-r--r--telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java330
-rw-r--r--telecomm/java/com/android/internal/telecom/ICallControl.aidl36
-rw-r--r--telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl52
-rw-r--r--telecomm/java/com/android/internal/telecom/ICallStreamingService.aidl35
-rw-r--r--telecomm/java/com/android/internal/telecom/IConnectionService.aidl9
-rw-r--r--telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl8
-rwxr-xr-xtelecomm/java/com/android/internal/telecom/IInCallAdapter.aidl4
-rw-r--r--telecomm/java/com/android/internal/telecom/IInCallService.aidl7
-rw-r--r--telecomm/java/com/android/internal/telecom/IStreamingCallAdapter.aidl28
-rw-r--r--telecomm/java/com/android/internal/telecom/ITelecomService.aidl32
-rw-r--r--telecomm/java/com/android/internal/telecom/TransactionalCall.java85
53 files changed, 4223 insertions, 63 deletions
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 9c1e8dd25a0e..c152a41c8694 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -168,6 +168,18 @@ public final class Call {
public static final String AVAILABLE_PHONE_ACCOUNTS = "selectPhoneAccountAccounts";
/**
+ * Extra key intended for {@link InCallService}s that notify the user of an incoming call. When
+ * EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB returns true, the {@link InCallService} should not
+ * interrupt the user of the incoming call because the call is being suppressed by Do Not
+ * Disturb settings.
+ *
+ * This extra will be removed from the {@link Call} object for {@link InCallService}s that do
+ * not hold the {@link android.Manifest.permission#READ_CONTACTS} permission.
+ */
+ public static final String EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB =
+ "android.telecom.extra.IS_SUPPRESSED_BY_DO_NOT_DISTURB";
+
+ /**
* Key for extra used to pass along a list of {@link PhoneAccountSuggestion}s to the in-call
* UI when a call enters the {@link #STATE_SELECT_PHONE_ACCOUNT} state. The list included here
* will have the same length and be in the same order as the list passed with
@@ -348,6 +360,18 @@ public final class Call {
"android.telecom.extra.DIAGNOSTIC_MESSAGE";
/**
+ * Event reported from the Telecom stack to indicate that the {@link Connection} is not able to
+ * find any network and likely will not get connected. Upon receiving this event, the dialer
+ * app should show satellite SOS button if satellite is provisioned.
+ * <p>
+ * The dialer app receives this event via
+ * {@link Call.Callback#onConnectionEvent(Call, String, Bundle)}.
+ * @hide
+ */
+ public static final String EVENT_DISPLAY_SOS_MESSAGE =
+ "android.telecom.event.DISPLAY_SOS_MESSAGE";
+
+ /**
* Reject reason used with {@link #reject(int)} to indicate that the user is rejecting this
* call because they have declined to answer it. This typically means that they are unable
* to answer the call at this time and would prefer it be sent to voicemail.
@@ -726,6 +750,7 @@ public final class Call {
private final String mContactDisplayName;
private final @CallDirection int mCallDirection;
private final @Connection.VerificationStatus int mCallerNumberVerificationStatus;
+ private final Uri mContactPhotoUri;
/**
* Whether the supplied capabilities supports the specified capability.
@@ -933,6 +958,17 @@ public final class Call {
}
/**
+ * @return The contact photo URI which corresponds to
+ * {@link android.provider.ContactsContract.PhoneLookup#PHOTO_URI}, or {@code null} if the
+ * lookup is not yet complete, if there's no contacts entry for the caller,
+ * or if the {@link InCallService} does not hold the
+ * {@link android.Manifest.permission#READ_CONTACTS} permission.
+ */
+ public @Nullable Uri getContactPhotoUri() {
+ return mContactPhotoUri;
+ }
+
+ /**
* The display name for the caller.
* <p>
* This is the name as reported by the {@link ConnectionService} associated with this call.
@@ -1131,7 +1167,8 @@ public final class Call {
Objects.equals(mContactDisplayName, d.mContactDisplayName) &&
Objects.equals(mCallDirection, d.mCallDirection) &&
Objects.equals(mCallerNumberVerificationStatus,
- d.mCallerNumberVerificationStatus);
+ d.mCallerNumberVerificationStatus) &&
+ Objects.equals(mContactPhotoUri, d.mContactPhotoUri);
}
return false;
}
@@ -1156,7 +1193,8 @@ public final class Call {
mCreationTimeMillis,
mContactDisplayName,
mCallDirection,
- mCallerNumberVerificationStatus);
+ mCallerNumberVerificationStatus,
+ mContactPhotoUri);
}
/** {@hide} */
@@ -1180,7 +1218,8 @@ public final class Call {
long creationTimeMillis,
String contactDisplayName,
int callDirection,
- int callerNumberVerificationStatus) {
+ int callerNumberVerificationStatus,
+ Uri contactPhotoUri) {
mState = state;
mTelecomCallId = telecomCallId;
mHandle = handle;
@@ -1201,6 +1240,7 @@ public final class Call {
mContactDisplayName = contactDisplayName;
mCallDirection = callDirection;
mCallerNumberVerificationStatus = callerNumberVerificationStatus;
+ mContactPhotoUri = contactPhotoUri;
}
/** {@hide} */
@@ -1225,7 +1265,9 @@ public final class Call {
parcelableCall.getCreationTimeMillis(),
parcelableCall.getContactDisplayName(),
parcelableCall.getCallDirection(),
- parcelableCall.getCallerNumberVerificationStatus());
+ parcelableCall.getCallerNumberVerificationStatus(),
+ parcelableCall.getContactPhotoUri()
+ );
}
@Override
@@ -2100,6 +2142,14 @@ public final class Call {
* <p>
* No assumptions should be made as to how an In-Call UI or service will handle these
* extras. Keys should be fully qualified (e.g., com.example.MY_EXTRA) to avoid conflicts.
+ * <p>
+ * Extras added using this method will be made available to the {@link ConnectionService}
+ * associated with this {@link Call} and notified via
+ * {@link Connection#onExtrasChanged(Bundle)}.
+ * <p>
+ * Extras added using this method will also be available to other running {@link InCallService}s
+ * and notified via {@link Call.Callback#onDetailsChanged(Call, Details)}. The extras can be
+ * accessed via {@link Details#getExtras()}.
*
* @param extras The extras to add.
*/
@@ -2639,7 +2689,8 @@ public final class Call {
mDetails.getCreationTimeMillis(),
mDetails.getContactDisplayName(),
mDetails.getCallDirection(),
- mDetails.getCallerNumberVerificationStatus()
+ mDetails.getCallerNumberVerificationStatus(),
+ mDetails.getContactPhotoUri()
);
fireDetailsChanged(mDetails);
}
diff --git a/telecomm/java/android/telecom/CallAttributes.aidl b/telecomm/java/android/telecom/CallAttributes.aidl
new file mode 100644
index 000000000000..19bada72190f
--- /dev/null
+++ b/telecomm/java/android/telecom/CallAttributes.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 CallAttributes; \ No newline at end of file
diff --git a/telecomm/java/android/telecom/CallAttributes.java b/telecomm/java/android/telecom/CallAttributes.java
new file mode 100644
index 000000000000..b1a7d819cd17
--- /dev/null
+++ b/telecomm/java/android/telecom/CallAttributes.java
@@ -0,0 +1,356 @@
+/*
+ * 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.annotation.Nullable;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * CallAttributes represents a set of properties that define a new Call. Apps should build an
+ * instance of this class and use {@link TelecomManager#addCall} to start a new call with Telecom.
+ *
+ * <p>
+ * Apps should first register a {@link PhoneAccount} via {@link TelecomManager#registerPhoneAccount}
+ * and use the same {@link PhoneAccountHandle} registered with Telecom when creating an
+ * instance of CallAttributes.
+ */
+public final class CallAttributes implements Parcelable {
+
+ /** PhoneAccountHandle associated with the App managing calls **/
+ private final PhoneAccountHandle mPhoneAccountHandle;
+
+ /** Display name of the person on the other end of the call **/
+ private final CharSequence mDisplayName;
+
+ /** Address of the call. Note, this can be extended to a meeting link **/
+ private final Uri mAddress;
+
+ /** The direction (Outgoing/Incoming) of the new Call **/
+ @Direction private final int mDirection;
+
+ /** Information related to data being transmitted (voice, video, etc. ) **/
+ @CallType private final int mCallType;
+
+ /** Allows a package to opt into capabilities on the telecom side, on a per-call basis **/
+ @CallCapability private final int mCallCapabilities;
+
+ /** @hide **/
+ public static final String CALL_CAPABILITIES_KEY = "TelecomCapabilities";
+
+ /** @hide **/
+ public static final String DISPLAY_NAME_KEY = "DisplayName";
+
+ /** @hide **/
+ public static final String CALLER_PID_KEY = "CallerPid";
+
+ /** @hide **/
+ public static final String CALLER_UID_KEY = "CallerUid";
+
+ private CallAttributes(@NonNull PhoneAccountHandle phoneAccountHandle,
+ @NonNull CharSequence displayName,
+ @NonNull Uri address,
+ int direction,
+ int callType,
+ int callCapabilities) {
+ mPhoneAccountHandle = phoneAccountHandle;
+ mDisplayName = displayName;
+ mAddress = address;
+ mDirection = direction;
+ mCallType = callType;
+ mCallCapabilities = callCapabilities;
+ }
+
+ /** @hide */
+ @IntDef(value = {DIRECTION_INCOMING, DIRECTION_OUTGOING})
+ public @interface Direction {
+ }
+ /**
+ * Indicates that the call is an incoming call.
+ */
+ public static final int DIRECTION_INCOMING = 1;
+ /**
+ * Indicates that the call is an outgoing call.
+ */
+ public static final int DIRECTION_OUTGOING = 2;
+
+ /** @hide */
+ @IntDef(value = {AUDIO_CALL, VIDEO_CALL})
+ public @interface CallType {
+ }
+ /**
+ * Used when answering or dialing a call to indicate that the call does not have a video
+ * component
+ */
+ public static final int AUDIO_CALL = 1;
+ /**
+ * Indicates video transmission is supported
+ */
+ public static final int VIDEO_CALL = 2;
+
+ /** @hide */
+ @IntDef(value = {SUPPORTS_SET_INACTIVE, SUPPORTS_STREAM, SUPPORTS_TRANSFER}, flag = true)
+ public @interface CallCapability {
+ }
+ /**
+ * The call being created can be set to inactive (traditionally referred to as hold). This
+ * means that once a new call goes active, if the active call needs to be held in order to
+ * place or receive an incoming call, the active call will be placed on hold. otherwise, the
+ * active call may be disconnected.
+ */
+ public static final int SUPPORTS_SET_INACTIVE = 1 << 1;
+ /**
+ * The call can be streamed from a root device to another device to continue the call without
+ * completely transferring it.
+ */
+ public static final int SUPPORTS_STREAM = 1 << 2;
+ /**
+ * The call can be completely transferred from one endpoint to another.
+ */
+ public static final int SUPPORTS_TRANSFER = 1 << 3;
+
+ /**
+ * Build an instance of {@link CallAttributes}. In order to build a valid instance, a
+ * {@link PhoneAccountHandle}, call direction, display name, and {@link Uri} address
+ * are required.
+ *
+ * <p>
+ * Note: Pass in the same {@link PhoneAccountHandle} that was used to register a
+ * {@link PhoneAccount} with Telecom. see {@link TelecomManager#registerPhoneAccount}
+ */
+ public static final class Builder {
+ // required and final fields
+ private final PhoneAccountHandle mPhoneAccountHandle;
+ @Direction private final int mDirection;
+ private final CharSequence mDisplayName;
+ private final Uri mAddress;
+ // optional fields
+ @CallType private int mCallType = CallAttributes.AUDIO_CALL;
+ @CallCapability private int mCallCapabilities = SUPPORTS_SET_INACTIVE;
+
+ /**
+ * Constructor for the CallAttributes.Builder class
+ *
+ * @param phoneAccountHandle that belongs to package registered with Telecom
+ * @param callDirection of the new call that will be added to Telecom
+ * @param displayName of the caller for incoming calls or initiating user for outgoing calls
+ * @param address of the caller for incoming calls or destination for outgoing calls
+ */
+ public Builder(@NonNull PhoneAccountHandle phoneAccountHandle,
+ @Direction int callDirection, @NonNull CharSequence displayName,
+ @NonNull Uri address) {
+ if (!isInRange(DIRECTION_INCOMING, DIRECTION_OUTGOING, callDirection)) {
+ throw new IllegalArgumentException(TextUtils.formatSimple("CallDirection=[%d] is"
+ + " invalid. CallDirections value should be within [%d, %d]",
+ callDirection, DIRECTION_INCOMING, DIRECTION_OUTGOING));
+ }
+ Objects.requireNonNull(phoneAccountHandle);
+ Objects.requireNonNull(displayName);
+ Objects.requireNonNull(address);
+ mPhoneAccountHandle = phoneAccountHandle;
+ mDirection = callDirection;
+ mDisplayName = displayName;
+ mAddress = address;
+ }
+
+ /**
+ * Sets the type of call; uses to indicate if a call is a video call or audio call.
+ * @param callType The call type.
+ * @return Builder
+ */
+ @NonNull
+ public Builder setCallType(@CallType int callType) {
+ if (!isInRange(AUDIO_CALL, VIDEO_CALL, callType)) {
+ throw new IllegalArgumentException(TextUtils.formatSimple("CallType=[%d] is"
+ + " invalid. CallTypes value should be within [%d, %d]",
+ callType, AUDIO_CALL, VIDEO_CALL));
+ }
+ mCallType = callType;
+ return this;
+ }
+
+ /**
+ * Sets the capabilities of this call. Use this to indicate whether your app supports
+ * holding, streaming and call transfers.
+ * @param callCapabilities Bitmask of call capabilities.
+ * @return Builder
+ */
+ @NonNull
+ public Builder setCallCapabilities(@CallCapability int callCapabilities) {
+ mCallCapabilities = callCapabilities;
+ return this;
+ }
+
+ /**
+ * Build an instance of {@link CallAttributes} based on the last values passed to the
+ * setters or default values.
+ *
+ * @return an instance of {@link CallAttributes}
+ */
+ @NonNull
+ public CallAttributes build() {
+ return new CallAttributes(mPhoneAccountHandle, mDisplayName, mAddress, mDirection,
+ mCallType, mCallCapabilities);
+ }
+
+ /** @hide */
+ private boolean isInRange(int floor, int ceiling, int value) {
+ return value >= floor && value <= ceiling;
+ }
+ }
+
+ /**
+ * The {@link PhoneAccountHandle} that should be registered to Telecom to allow calls. The
+ * {@link PhoneAccountHandle} should be registered before creating a CallAttributes instance.
+ *
+ * @return the {@link PhoneAccountHandle} for this package that allows this call to be created
+ */
+ @NonNull public PhoneAccountHandle getPhoneAccountHandle() {
+ return mPhoneAccountHandle;
+ }
+
+ /**
+ * @return display name of the incoming caller or the person being called for an outgoing call
+ */
+ @NonNull public CharSequence getDisplayName() {
+ return mDisplayName;
+ }
+
+ /**
+ * @return address of the incoming caller
+ * or the address of the person being called for an outgoing call
+ */
+ @NonNull public Uri getAddress() {
+ return mAddress;
+ }
+
+ /**
+ * @return the direction of the new call.
+ */
+ public @Direction int getDirection() {
+ return mDirection;
+ }
+
+ /**
+ * @return Information related to data being transmitted (voice, video, etc. )
+ */
+ public @CallType int getCallType() {
+ return mCallType;
+ }
+
+ /**
+ * @return The allowed capabilities of the new call
+ */
+ public @CallCapability int getCallCapabilities() {
+ return mCallCapabilities;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@Nullable Parcel dest, int flags) {
+ dest.writeParcelable(mPhoneAccountHandle, flags);
+ dest.writeCharSequence(mDisplayName);
+ dest.writeParcelable(mAddress, flags);
+ dest.writeInt(mDirection);
+ dest.writeInt(mCallType);
+ dest.writeInt(mCallCapabilities);
+ }
+
+ /**
+ * Responsible for creating CallAttribute objects for deserialized Parcels.
+ */
+ public static final @android.annotation.NonNull
+ Parcelable.Creator<CallAttributes> CREATOR =
+ new Parcelable.Creator<>() {
+ @Override
+ public CallAttributes createFromParcel(Parcel source) {
+ return new CallAttributes(source.readParcelable(getClass().getClassLoader(),
+ android.telecom.PhoneAccountHandle.class),
+ source.readCharSequence(),
+ source.readParcelable(getClass().getClassLoader(),
+ android.net.Uri.class),
+ source.readInt(),
+ source.readInt(),
+ source.readInt());
+ }
+
+ @Override
+ public CallAttributes[] newArray(int size) {
+ return new CallAttributes[size];
+ }
+ };
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("{ CallAttributes: [phoneAccountHandle: ")
+ .append(mPhoneAccountHandle) /* PhoneAccountHandle#toString handles PII */
+ .append("], [contactName: ")
+ .append(Log.pii(mDisplayName))
+ .append("], [address=")
+ .append(Log.pii(mAddress))
+ .append("], [direction=")
+ .append(mDirection)
+ .append("], [callType=")
+ .append(mCallType)
+ .append("], [mCallCapabilities=")
+ .append(mCallCapabilities)
+ .append("] }");
+
+ return sb.toString();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || obj.getClass() != this.getClass()) {
+ return false;
+ }
+ CallAttributes that = (CallAttributes) obj;
+ return this.mDirection == that.mDirection
+ && this.mCallType == that.mCallType
+ && this.mCallCapabilities == that.mCallCapabilities
+ && Objects.equals(this.mPhoneAccountHandle, that.mPhoneAccountHandle)
+ && Objects.equals(this.mAddress, that.mAddress)
+ && Objects.equals(this.mDisplayName, that.mDisplayName);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPhoneAccountHandle, mAddress, mDisplayName,
+ mDirection, mCallType, mCallCapabilities);
+ }
+}
diff --git a/telecomm/java/android/telecom/CallAudioState.java b/telecomm/java/android/telecom/CallAudioState.java
index fccdf76372dd..c7cc1bd88bdf 100644
--- a/telecomm/java/android/telecom/CallAudioState.java
+++ b/telecomm/java/android/telecom/CallAudioState.java
@@ -27,7 +27,6 @@ import android.os.Parcelable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -58,6 +57,9 @@ public final class CallAudioState implements Parcelable {
/** Direct the audio stream through the device's speakerphone. */
public static final int ROUTE_SPEAKER = 0x00000008;
+ /** Direct the audio stream through another device. */
+ public static final int ROUTE_STREAMING = 0x00000010;
+
/**
* Direct the audio stream through the device's earpiece or wired headset if one is
* connected.
@@ -70,7 +72,7 @@ public final class CallAudioState implements Parcelable {
* @hide
**/
public static final int ROUTE_ALL = ROUTE_EARPIECE | ROUTE_BLUETOOTH | ROUTE_WIRED_HEADSET |
- ROUTE_SPEAKER;
+ ROUTE_SPEAKER | ROUTE_STREAMING;
private final boolean isMuted;
private final int route;
@@ -189,7 +191,11 @@ public final class CallAudioState implements Parcelable {
*/
@CallAudioRoute
public int getSupportedRouteMask() {
- return supportedRouteMask;
+ if (route == ROUTE_STREAMING) {
+ return ROUTE_STREAMING;
+ } else {
+ return supportedRouteMask;
+ }
}
/**
@@ -232,6 +238,9 @@ public final class CallAudioState implements Parcelable {
if ((route & ROUTE_SPEAKER) == ROUTE_SPEAKER) {
listAppend(buffer, "SPEAKER");
}
+ if ((route & ROUTE_STREAMING) == ROUTE_STREAMING) {
+ listAppend(buffer, "STREAMING");
+ }
return buffer.toString();
}
diff --git a/telecomm/java/android/telecom/CallControl.aidl b/telecomm/java/android/telecom/CallControl.aidl
new file mode 100644
index 000000000000..0f780e612d5b
--- /dev/null
+++ b/telecomm/java/android/telecom/CallControl.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 CallControl;
diff --git a/telecomm/java/android/telecom/CallControl.java b/telecomm/java/android/telecom/CallControl.java
new file mode 100644
index 000000000000..50f2ad4561cc
--- /dev/null
+++ b/telecomm/java/android/telecom/CallControl.java
@@ -0,0 +1,398 @@
+/*
+ * 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 static android.telecom.CallException.TRANSACTION_EXCEPTION_KEY;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.OutcomeReceiver;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.text.TextUtils;
+
+import com.android.internal.telecom.ClientTransactionalServiceRepository;
+import com.android.internal.telecom.ICallControl;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * CallControl provides client side control of a call. Each Call will get an individual CallControl
+ * instance in which the client can alter the state of the associated call.
+ *
+ * <p>
+ * Each method is Transactional meaning that it can succeed or fail. If a transaction succeeds,
+ * the {@link OutcomeReceiver#onResult} will be called by Telecom. Otherwise, the
+ * {@link OutcomeReceiver#onError} is called and provides a {@link CallException} that details why
+ * the operation failed.
+ */
+@SuppressLint("NotCloseable")
+public final class CallControl {
+ private static final String TAG = CallControl.class.getSimpleName();
+ private static final String INTERFACE_ERROR_MSG = "Call Control is not available";
+ private final String mCallId;
+ private final ICallControl mServerInterface;
+ private final PhoneAccountHandle mPhoneAccountHandle;
+ private final ClientTransactionalServiceRepository mRepository;
+
+ /** @hide */
+ public CallControl(@NonNull String callId, @Nullable ICallControl serverInterface,
+ @NonNull ClientTransactionalServiceRepository repository,
+ @NonNull PhoneAccountHandle pah) {
+ mCallId = callId;
+ mServerInterface = serverInterface;
+ mRepository = repository;
+ mPhoneAccountHandle = pah;
+ }
+
+ /**
+ * @return the callId Telecom assigned to this CallControl object which should be attached to
+ * an individual call.
+ */
+ @NonNull
+ public ParcelUuid getCallId() {
+ return ParcelUuid.fromString(mCallId);
+ }
+
+ /**
+ * Request Telecom set the call state to active. This method should be called when either an
+ * outgoing call is ready to go active or a held call is ready to go active again. For incoming
+ * calls that are ready to be answered, use
+ * {@link CallControl#answer(int, Executor, OutcomeReceiver)}.
+ *
+ * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
+ * will be called on.
+ * @param callback that will be completed on the Telecom side that details success or failure
+ * of the requested operation.
+ *
+ * {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
+ * switched the call state to active
+ *
+ * {@link OutcomeReceiver#onError} will be called if Telecom has failed to set
+ * the call state to active. A {@link CallException} will be passed
+ * that details why the operation failed.
+ */
+ public void setActive(@CallbackExecutor @NonNull Executor executor,
+ @NonNull OutcomeReceiver<Void, CallException> callback) {
+ if (mServerInterface != null) {
+ try {
+ mServerInterface.setActive(mCallId,
+ new CallControlResultReceiver("setActive", executor, callback));
+
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ } else {
+ throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ }
+ }
+
+ /**
+ * Request Telecom answer an incoming call. For outgoing calls and calls that have been placed
+ * on hold, use {@link CallControl#setActive(Executor, OutcomeReceiver)}.
+ *
+ * @param videoState to report to Telecom. Telecom will store VideoState in the event another
+ * service/device requests it in order to continue the call on another screen.
+ * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
+ * will be called on.
+ * @param callback that will be completed on the Telecom side that details success or failure
+ * of the requested operation.
+ *
+ * {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
+ * switched the call state to active
+ *
+ * {@link OutcomeReceiver#onError} will be called if Telecom has failed to set
+ * the call state to active. A {@link CallException} will be passed
+ * that details why the operation failed.
+ */
+ public void answer(@android.telecom.CallAttributes.CallType int videoState,
+ @CallbackExecutor @NonNull Executor executor,
+ @NonNull OutcomeReceiver<Void, CallException> callback) {
+ validateVideoState(videoState);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ if (mServerInterface != null) {
+ try {
+ mServerInterface.answer(videoState, mCallId,
+ new CallControlResultReceiver("answer", executor, callback));
+
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ } else {
+ throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ }
+ }
+
+ /**
+ * Request Telecom set the call state to inactive. This the same as hold for two call endpoints
+ * but can be extended to setting a meeting to inactive.
+ *
+ * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
+ * will be called on.
+ * @param callback that will be completed on the Telecom side that details success or failure
+ * of the requested operation.
+ *
+ * {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
+ * switched the call state to inactive
+ *
+ * {@link OutcomeReceiver#onError} will be called if Telecom has failed to set
+ * the call state to inactive. A {@link CallException} will be passed
+ * that details why the operation failed.
+ */
+ public void setInactive(@CallbackExecutor @NonNull Executor executor,
+ @NonNull OutcomeReceiver<Void, CallException> callback) {
+ if (mServerInterface != null) {
+ try {
+ mServerInterface.setInactive(mCallId,
+ new CallControlResultReceiver("setInactive", executor, callback));
+
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ } else {
+ throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ }
+ }
+
+ /**
+ * Request Telecom disconnect the call and remove the call from telecom tracking.
+ *
+ * @param disconnectCause represents the cause for disconnecting the call. The only valid
+ * codes for the {@link android.telecom.DisconnectCause} passed in are:
+ * <ul>
+ * <li>{@link DisconnectCause#LOCAL}</li>
+ * <li>{@link DisconnectCause#REMOTE}</li>
+ * <li>{@link DisconnectCause#REJECTED}</li>
+ * <li>{@link DisconnectCause#MISSED}</li>
+ * </ul>
+ * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
+ * will be called on.
+ * @param callback That will be completed on the Telecom side that details success or
+ * failure of the requested operation.
+ *
+ * {@link OutcomeReceiver#onResult} will be called if Telecom has
+ * successfully disconnected the call.
+ *
+ * {@link OutcomeReceiver#onError} will be called if Telecom has failed
+ * to disconnect the call. A {@link CallException} will be passed
+ * that details why the operation failed.
+ *
+ * <p>
+ * Note: After the call has been successfully disconnected, calling any CallControl API will
+ * result in the {@link OutcomeReceiver#onError} with
+ * {@link CallException#CODE_CALL_IS_NOT_BEING_TRACKED}.
+ */
+ public void disconnect(@NonNull DisconnectCause disconnectCause,
+ @CallbackExecutor @NonNull Executor executor,
+ @NonNull OutcomeReceiver<Void, CallException> callback) {
+ Objects.requireNonNull(disconnectCause);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ validateDisconnectCause(disconnectCause);
+ if (mServerInterface != null) {
+ try {
+ mServerInterface.disconnect(mCallId, disconnectCause,
+ new CallControlResultReceiver("disconnect", executor, callback));
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ } else {
+ throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ }
+ }
+
+ /**
+ * Request start a call streaming session. On receiving valid request, telecom will bind to
+ * the {@link CallStreamingService} implemented by a general call streaming sender. So that the
+ * call streaming sender can perform streaming local device audio to another remote device and
+ * control the call during streaming.
+ *
+ * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
+ * will be called on.
+ * @param callback that will be completed on the Telecom side that details success or failure
+ * of the requested operation.
+ *
+ * {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
+ * started the call streaming.
+ *
+ * {@link OutcomeReceiver#onError} will be called if Telecom has failed to
+ * start the call streaming. A {@link CallException} will be passed that
+ * details why the operation failed.
+ */
+ public void startCallStreaming(@CallbackExecutor @NonNull Executor executor,
+ @NonNull OutcomeReceiver<Void, CallException> callback) {
+ if (mServerInterface != null) {
+ try {
+ mServerInterface.startCallStreaming(mCallId,
+ new CallControlResultReceiver("startCallStreaming", executor, callback));
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ } else {
+ throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ }
+ }
+
+ /**
+ * Request a CallEndpoint change. Clients should not define their own CallEndpoint when
+ * requesting a change. Instead, the new endpoint should be one of the valid endpoints provided
+ * by {@link CallEventCallback#onAvailableCallEndpointsChanged(List)}.
+ *
+ * @param callEndpoint The {@link CallEndpoint} to change to.
+ * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
+ * will be called on.
+ * @param callback The {@link OutcomeReceiver} that will be completed on the Telecom side
+ * that details success or failure of the requested operation.
+ *
+ * {@link OutcomeReceiver#onResult} will be called if Telecom has
+ * successfully changed the CallEndpoint that was requested.
+ *
+ * {@link OutcomeReceiver#onError} will be called if Telecom has failed to
+ * switch to the requested CallEndpoint. A {@link CallException} will be
+ * passed that details why the operation failed.
+ */
+ public void requestCallEndpointChange(@NonNull CallEndpoint callEndpoint,
+ @CallbackExecutor @NonNull Executor executor,
+ @NonNull OutcomeReceiver<Void, CallException> callback) {
+ Objects.requireNonNull(callEndpoint);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ if (mServerInterface != null) {
+ try {
+ mServerInterface.requestCallEndpointChange(callEndpoint,
+ new CallControlResultReceiver("endpointChange", executor, callback));
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ } else {
+ throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ }
+ }
+
+ /**
+ * Raises an event to the {@link android.telecom.InCallService} implementations tracking this
+ * call via {@link android.telecom.Call.Callback#onConnectionEvent(Call, String, Bundle)}.
+ * These events and the associated extra keys for the {@code Bundle} parameter are mutually
+ * defined by a VoIP application and {@link android.telecom.InCallService}. This API is used to
+ * relay additional information about a call other than what is specified in the
+ * {@link android.telecom.CallAttributes} to {@link android.telecom.InCallService}s. This might
+ * include, for example, a change to the list of participants in a meeting, or the name of the
+ * speakers who have their hand raised. Where appropriate, the {@link InCallService}s tracking
+ * this call may choose to render this additional information about the call. An automotive
+ * calling UX, for example may have enough screen real estate to indicate the number of
+ * participants in a meeting, but to prevent distractions could suppress the list of
+ * participants.
+ *
+ * @param event a string event identifier agreed upon between a VoIP application and an
+ * {@link android.telecom.InCallService}
+ * @param extras a {@link android.os.Bundle} containing information about the event, as agreed
+ * upon between a VoIP application and {@link android.telecom.InCallService}.
+ */
+ public void sendEvent(@NonNull String event, @NonNull Bundle extras) {
+ Objects.requireNonNull(event);
+ Objects.requireNonNull(extras);
+ if (mServerInterface != null) {
+ try {
+ mServerInterface.sendEvent(mCallId, event, extras);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ } else {
+ throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ }
+ }
+
+ /**
+ * Since {@link OutcomeReceiver}s cannot be passed via AIDL, a ResultReceiver (which can) must
+ * wrap the Clients {@link OutcomeReceiver} passed in and await for the Telecom Server side
+ * response in {@link ResultReceiver#onReceiveResult(int, Bundle)}.
+ *
+ * @hide
+ */
+ private class CallControlResultReceiver extends ResultReceiver {
+ private final String mCallingMethod;
+ private final Executor mExecutor;
+ private final OutcomeReceiver<Void, CallException> mClientCallback;
+
+ CallControlResultReceiver(String method, Executor executor,
+ OutcomeReceiver<Void, CallException> clientCallback) {
+ super(null);
+ mCallingMethod = method;
+ mExecutor = executor;
+ mClientCallback = clientCallback;
+ }
+
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ Log.d(CallControl.TAG, "%s: oRR: resultCode=[%s]", mCallingMethod, resultCode);
+ super.onReceiveResult(resultCode, resultData);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (resultCode == TelecomManager.TELECOM_TRANSACTION_SUCCESS) {
+ mExecutor.execute(() -> mClientCallback.onResult(null));
+ } else {
+ mExecutor.execute(() ->
+ mClientCallback.onError(getTransactionException(resultData)));
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ }
+
+ /** @hide */
+ private CallException getTransactionException(Bundle resultData) {
+ String message = "unknown error";
+ if (resultData != null && resultData.containsKey(TRANSACTION_EXCEPTION_KEY)) {
+ return resultData.getParcelable(TRANSACTION_EXCEPTION_KEY,
+ CallException.class);
+ }
+ return new CallException(message, CallException.CODE_ERROR_UNKNOWN);
+ }
+
+ /** @hide */
+ private void validateDisconnectCause(DisconnectCause disconnectCause) {
+ final int code = disconnectCause.getCode();
+ if (code != DisconnectCause.LOCAL && code != DisconnectCause.REMOTE
+ && code != DisconnectCause.MISSED && code != DisconnectCause.REJECTED) {
+ throw new IllegalArgumentException(TextUtils.formatSimple(
+ "The DisconnectCause code provided, %d , is not a valid Disconnect code. Valid "
+ + "DisconnectCause codes are limited to [DisconnectCause.LOCAL, "
+ + "DisconnectCause.REMOTE, DisconnectCause.MISSED, or "
+ + "DisconnectCause.REJECTED]", disconnectCause.getCode()));
+ }
+ }
+
+ /** @hide */
+ private void validateVideoState(@android.telecom.CallAttributes.CallType int videoState) {
+ if (videoState != CallAttributes.AUDIO_CALL && videoState != CallAttributes.VIDEO_CALL) {
+ throw new IllegalArgumentException(TextUtils.formatSimple(
+ "The VideoState argument passed in, %d , is not a valid VideoState. The "
+ + "VideoState choices are limited to CallAttributes.AUDIO_CALL or"
+ + "CallAttributes.VIDEO_CALL", videoState));
+ }
+ }
+}
diff --git a/telecomm/java/android/telecom/CallControlCallback.java b/telecomm/java/android/telecom/CallControlCallback.java
new file mode 100644
index 000000000000..eac2e64aa2ab
--- /dev/null
+++ b/telecomm/java/android/telecom/CallControlCallback.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 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.NonNull;
+
+import java.util.function.Consumer;
+
+/**
+ * CallControlCallback relays call updates (that require a response) from the Telecom framework out
+ * to the application.This can include operations which the app must implement on a Call due to the
+ * presence of other calls on the device, requests relayed from a Bluetooth device, or from another
+ * calling surface.
+ *
+ * <p>
+ * All CallControlCallbacks are transactional, meaning that a client must
+ * complete the {@link Consumer} via {@link Consumer#accept(Object)} in order to complete the
+ * CallControlCallbacks. If a CallControlCallbacks can be completed, the
+ * {@link Consumer#accept(Object)} should be called with {@link Boolean#TRUE}. Otherwise,
+ * {@link Consumer#accept(Object)} should be called with {@link Boolean#FALSE} to represent the
+ * CallControlCallbacks cannot be completed on the client side.
+ *
+ * <p>
+ * Note: Each CallEventCallback has a timeout of 5000 milliseconds. Failing to complete the
+ * {@link Consumer} before the timeout will result in a failed transaction.
+ */
+public interface CallControlCallback {
+ /**
+ * Telecom is informing the client to set the call active
+ *
+ * @param wasCompleted The {@link Consumer} to be completed. If the client can set the call
+ * active on their end, the {@link Consumer#accept(Object)} should be
+ * called with {@link Boolean#TRUE}.
+ *
+ * Otherwise, {@link Consumer#accept(Object)} should be called with
+ * {@link Boolean#FALSE}. Telecom will effectively ignore the remote
+ * setActive request and the call will remain in whatever state it is in.
+ */
+ void onSetActive(@NonNull Consumer<Boolean> wasCompleted);
+
+ /**
+ * Telecom is informing the client to set the call inactive. This is the same as holding a call
+ * for two endpoints but can be extended to setting a meeting inactive.
+ *
+ * @param wasCompleted The {@link Consumer} to be completed. If the client can set the call
+ * inactive on their end, the {@link Consumer#accept(Object)} should be
+ * called with {@link Boolean#TRUE}.
+ *
+ * Otherwise, {@link Consumer#accept(Object)} should be called with
+ * {@link Boolean#FALSE}. Telecom will effectively ignore the remote
+ * setInactive request and the call will remain in whatever state it is in.
+ */
+ void onSetInactive(@NonNull Consumer<Boolean> wasCompleted);
+
+ /**
+ * Telecom is informing the client to answer an incoming call and set it to active.
+ *
+ * @param videoState see {@link android.telecom.CallAttributes.CallType} for valid states
+ * @param wasCompleted The {@link Consumer} to be completed. If the client can answer the call
+ * on their end, {@link Consumer#accept(Object)} should be called with
+ * {@link Boolean#TRUE}.
+ *
+ * Otherwise,{@link Consumer#accept(Object)} should be called with
+ * {@link Boolean#FALSE}. However, Telecom will still disconnect
+ * the call and remove it from tracking.
+ */
+ void onAnswer(@android.telecom.CallAttributes.CallType int videoState,
+ @NonNull Consumer<Boolean> wasCompleted);
+
+ /**
+ * Telecom is informing the client to disconnect the call
+ *
+ * @param disconnectCause represents the cause for disconnecting the call.
+ * @param wasCompleted The {@link Consumer} to be completed. If the client can disconnect
+ * the call on their end, {@link Consumer#accept(Object)} should be
+ * called with {@link Boolean#TRUE}.
+ *
+ * Otherwise,{@link Consumer#accept(Object)} should be called with
+ * {@link Boolean#FALSE}. However, Telecom will still disconnect
+ * the call and remove it from tracking.
+ */
+ void onDisconnect(@NonNull DisconnectCause disconnectCause,
+ @NonNull Consumer<Boolean> wasCompleted);
+
+ /**
+ * Telecom is informing the client to set the call in streaming.
+ *
+ * @param wasCompleted The {@link Consumer} to be completed. If the client can stream the
+ * call on their end, {@link Consumer#accept(Object)} should be called with
+ * {@link Boolean#TRUE}. Otherwise, {@link Consumer#accept(Object)}
+ * should be called with {@link Boolean#FALSE}.
+ */
+ void onCallStreamingStarted(@NonNull Consumer<Boolean> wasCompleted);
+}
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..994e1c9f9412
--- /dev/null
+++ b/telecomm/java/android/telecom/CallEndpointException.java
@@ -0,0 +1,117 @@
+/*
+ * 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, @CallEndpointErrorCode int code) {
+ super(getMessage(message, code));
+ 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/CallEventCallback.java b/telecomm/java/android/telecom/CallEventCallback.java
new file mode 100644
index 000000000000..a41c0113e933
--- /dev/null
+++ b/telecomm/java/android/telecom/CallEventCallback.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 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.NonNull;
+import android.os.Bundle;
+
+import java.util.List;
+
+/**
+ * CallEventCallback relays call updates (that do not require any action) from the Telecom framework
+ * out to the application. This can include operations which the app must implement on a Call due to
+ * the presence of other calls on the device, requests relayed from a Bluetooth device,
+ * or from another calling surface.
+ */
+public interface CallEventCallback {
+ /**
+ * Telecom is informing the client the current {@link CallEndpoint} changed.
+ *
+ * @param newCallEndpoint The new {@link CallEndpoint} through which call media flows
+ * (i.e. speaker, bluetooth, etc.).
+ */
+ void onCallEndpointChanged(@NonNull CallEndpoint newCallEndpoint);
+
+ /**
+ * Telecom is informing the client that the available {@link CallEndpoint}s have changed.
+ *
+ * @param availableEndpoints The set of available {@link CallEndpoint}s reported by Telecom.
+ */
+ void onAvailableCallEndpointsChanged(@NonNull List<CallEndpoint> availableEndpoints);
+
+ /**
+ * Called when the mute state changes.
+ *
+ * @param isMuted The current mute state.
+ */
+ void onMuteStateChanged(boolean isMuted);
+
+ /**
+ * Telecom is informing the client user requested call streaming but the stream can't be
+ * started.
+ *
+ * @param reason Code to indicate the reason of this failure
+ */
+ void onCallStreamingFailed(@CallStreamingService.StreamingFailedReason int reason);
+
+ /**
+ * Informs this {@link android.telecom.CallEventCallback} on events raised from a
+ * {@link android.telecom.InCallService} presenting this call. These events and the
+ * associated extra keys for the {@code Bundle} parameter are mutually defined by a VoIP
+ * application and {@link android.telecom.InCallService}. This enables alternative calling
+ * surfaces, such as an automotive UI, to relay requests to perform other non-standard call
+ * actions to the app. For example, an automotive calling solution may offer the ability for
+ * the user to raise their hand during a meeting.
+ *
+ * @param event a string event identifier agreed upon between a VoIP application and an
+ * {@link android.telecom.InCallService}
+ * @param extras a {@link android.os.Bundle} containing information about the event, as agreed
+ * upon between a VoIP application and {@link android.telecom.InCallService}.
+ */
+ void onEvent(@NonNull String event, @NonNull Bundle extras);
+}
diff --git a/telecomm/java/android/telecom/CallException.aidl b/telecomm/java/android/telecom/CallException.aidl
new file mode 100644
index 000000000000..a16af121145d
--- /dev/null
+++ b/telecomm/java/android/telecom/CallException.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 CallException; \ No newline at end of file
diff --git a/telecomm/java/android/telecom/CallException.java b/telecomm/java/android/telecom/CallException.java
new file mode 100644
index 000000000000..e554082fe410
--- /dev/null
+++ b/telecomm/java/android/telecom/CallException.java
@@ -0,0 +1,149 @@
+/*
+ * 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 defines exceptions that can be thrown when using Telecom APIs with
+ * {@link android.os.OutcomeReceiver}s. Most of these exceptions are thrown when changing a call
+ * state with {@link CallControl}s or {@link CallControlCallback}s.
+ */
+public final class CallException extends RuntimeException implements Parcelable {
+ /** @hide **/
+ public static final String TRANSACTION_EXCEPTION_KEY = "TelecomTransactionalExceptionKey";
+
+ /**
+ * The operation has failed due to an unknown or unspecified error.
+ */
+ public static final int CODE_ERROR_UNKNOWN = 1;
+
+ /**
+ * The operation has failed due to Telecom failing to hold the current active call for the
+ * call attempting to become the new active call. The client should end the current active call
+ * and re-try the failed operation.
+ */
+ public static final int CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL = 2;
+
+ /**
+ * The operation has failed because Telecom has already removed the call from the server side
+ * and destroyed all the objects associated with it. The client should re-add the call.
+ */
+ public static final int CODE_CALL_IS_NOT_BEING_TRACKED = 3;
+
+ /**
+ * The operation has failed because Telecom cannot set the requested call as the current active
+ * call. The client should end the current active call and re-try the operation.
+ */
+ public static final int CODE_CALL_CANNOT_BE_SET_TO_ACTIVE = 4;
+
+ /**
+ * The operation has failed because there is either no PhoneAccount registered with Telecom
+ * for the given operation, or the limit of calls has been reached. The client should end the
+ * current active call and re-try the failed operation.
+ */
+ public static final int CODE_CALL_NOT_PERMITTED_AT_PRESENT_TIME = 5;
+
+ /**
+ * The operation has failed because the operation failed to complete before the timeout
+ */
+ public static final int CODE_OPERATION_TIMED_OUT = 6;
+
+ private int mCode = CODE_ERROR_UNKNOWN;
+ 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 CallAttribute objects for deserialized Parcels.
+ */
+ public static final @android.annotation.NonNull
+ Parcelable.Creator<CallException> CREATOR = new Parcelable.Creator<>() {
+ @Override
+ public CallException createFromParcel(Parcel source) {
+ return new CallException(source.readString8(), source.readInt());
+ }
+
+ @Override
+ public CallException[] newArray(int size) {
+ return new CallException[size];
+ }
+ };
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "CODE_ERROR_", value = {
+ CODE_ERROR_UNKNOWN,
+ CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL,
+ CODE_CALL_IS_NOT_BEING_TRACKED,
+ CODE_CALL_CANNOT_BE_SET_TO_ACTIVE,
+ CODE_CALL_NOT_PERMITTED_AT_PRESENT_TIME,
+ CODE_OPERATION_TIMED_OUT
+ })
+ public @interface CallErrorCode {
+ }
+
+ /**
+ * Constructor for a new CallException that has a defined error code in this class
+ *
+ * @param message related to why the exception was created
+ * @param code defined above that caused this exception to be created
+ */
+ public CallException(@Nullable String message, @CallErrorCode int code) {
+ super(getMessage(message, code));
+ mCode = code;
+ mMessage = message;
+ }
+
+ /**
+ * @return one of the error codes defined in this class that was passed into the constructor
+ */
+ public @CallErrorCode 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/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java
index 37b4e657973b..d1d16ff8b641 100644
--- a/telecomm/java/android/telecom/CallScreeningService.java
+++ b/telecomm/java/android/telecom/CallScreeningService.java
@@ -495,6 +495,9 @@ public abstract class CallScreeningService extends Service {
* Note: Calls will still be logged with type
* {@link android.provider.CallLog.Calls#BLOCKED_TYPE}, regardless of how this property
* is set.
+ * <p>
+ * Note: Only the carrier and system call screening apps can use this parameter;
+ * this parameter is ignored otherwise.
*/
public Builder setSkipCallLog(boolean shouldSkipCallLog) {
mShouldSkipCallLog = shouldSkipCallLog;
diff --git a/telecomm/java/android/telecom/CallStreamingService.java b/telecomm/java/android/telecom/CallStreamingService.java
new file mode 100644
index 000000000000..581cd7ee6d50
--- /dev/null
+++ b/telecomm/java/android/telecom/CallStreamingService.java
@@ -0,0 +1,220 @@
+/*
+ * 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.annotation.SdkConstant;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.telecom.ICallStreamingService;
+import com.android.internal.telecom.IStreamingCallAdapter;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This service is implemented by an app that wishes to provide functionality for a general call
+ * streaming sender for voip calls.
+ * <p>
+ * Below is an example manifest registration for a {@code CallStreamingService}.
+ * <pre>
+ * {@code
+ * <service android:name=".EgCallStreamingService"
+ * android:permission="android.permission.BIND_CALL_STREAMING_SERVICE" >
+ * ...
+ * <intent-filter>
+ * <action android:name="android.telecom.CallStreamingService" />
+ * </intent-filter>
+ * </service>
+ * }
+ * </pre>
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class CallStreamingService extends Service {
+ /**
+ * The {@link android.content.Intent} that must be declared as handled by the service.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE = "android.telecom.CallStreamingService";
+
+ private static final int MSG_SET_STREAMING_CALL_ADAPTER = 1;
+ private static final int MSG_CALL_STREAMING_STARTED = 2;
+ private static final int MSG_CALL_STREAMING_STOPPED = 3;
+ private static final int MSG_CALL_STREAMING_STATE_CHANGED = 4;
+
+ /** Default Handler used to consolidate binder method calls onto a single thread. */
+ private final Handler mHandler = new Handler(Looper.getMainLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ if (mStreamingCallAdapter == null && msg.what != MSG_SET_STREAMING_CALL_ADAPTER) {
+ Log.i(this, "handleMessage: null adapter!");
+ return;
+ }
+
+ switch (msg.what) {
+ case MSG_SET_STREAMING_CALL_ADAPTER:
+ if (msg.obj != null) {
+ Log.i(this, "MSG_SET_STREAMING_CALL_ADAPTER");
+ mStreamingCallAdapter = new StreamingCallAdapter(
+ (IStreamingCallAdapter) msg.obj);
+ }
+ break;
+ case MSG_CALL_STREAMING_STARTED:
+ Log.i(this, "MSG_CALL_STREAMING_STARTED");
+ mCall = (StreamingCall) msg.obj;
+ mCall.setAdapter(mStreamingCallAdapter);
+ CallStreamingService.this.onCallStreamingStarted(mCall);
+ break;
+ case MSG_CALL_STREAMING_STOPPED:
+ Log.i(this, "MSG_CALL_STREAMING_STOPPED");
+ mCall = null;
+ mStreamingCallAdapter = null;
+ CallStreamingService.this.onCallStreamingStopped();
+ break;
+ case MSG_CALL_STREAMING_STATE_CHANGED:
+ int state = (int) msg.obj;
+ if (mStreamingCallAdapter != null) {
+ mCall.requestStreamingState(state);
+ CallStreamingService.this.onCallStreamingStateChanged(state);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+ @Nullable
+ @Override
+ public IBinder onBind(@NonNull Intent intent) {
+ Log.i(this, "onBind");
+ return new CallStreamingServiceBinder();
+ }
+
+ /** Manages the binder calls so that the implementor does not need to deal with it. */
+ private final class CallStreamingServiceBinder extends ICallStreamingService.Stub {
+ @Override
+ public void setStreamingCallAdapter(IStreamingCallAdapter streamingCallAdapter)
+ throws RemoteException {
+ Log.i(this, "setCallStreamingAdapter");
+ mHandler.obtainMessage(MSG_SET_STREAMING_CALL_ADAPTER, streamingCallAdapter)
+ .sendToTarget();
+ }
+
+ @Override
+ public void onCallStreamingStarted(StreamingCall call) throws RemoteException {
+ Log.i(this, "onCallStreamingStarted");
+ mHandler.obtainMessage(MSG_CALL_STREAMING_STARTED, call).sendToTarget();
+ }
+
+ @Override
+ public void onCallStreamingStopped() throws RemoteException {
+ mHandler.obtainMessage(MSG_CALL_STREAMING_STOPPED).sendToTarget();
+ }
+
+ @Override
+ public void onCallStreamingStateChanged(int state) throws RemoteException {
+ mHandler.obtainMessage(MSG_CALL_STREAMING_STATE_CHANGED, state).sendToTarget();
+ }
+ }
+
+ /**
+ * Call streaming request reject reason used with
+ * {@link CallEventCallback#onCallStreamingFailed(int)} to indicate that telecom is rejecting a
+ * call streaming request due to unknown reason.
+ */
+ public static final int STREAMING_FAILED_UNKNOWN = 0;
+
+ /**
+ * Call streaming request reject reason used with
+ * {@link CallEventCallback#onCallStreamingFailed(int)} to indicate that telecom is rejecting a
+ * call streaming request because there's an ongoing streaming call on this device.
+ */
+ public static final int STREAMING_FAILED_ALREADY_STREAMING = 1;
+
+ /**
+ * Call streaming request reject reason used with
+ * {@link CallEventCallback#onCallStreamingFailed(int)} to indicate that telecom is rejecting a
+ * call streaming request because telecom can't find existing general streaming sender on this
+ * device.
+ */
+ public static final int STREAMING_FAILED_NO_SENDER = 2;
+
+ /**
+ * Call streaming request reject reason used with
+ * {@link CallEventCallback#onCallStreamingFailed(int)} to indicate that telecom is rejecting a
+ * call streaming request because telecom can't bind to the general streaming sender app.
+ */
+ public static final int STREAMING_FAILED_SENDER_BINDING_ERROR = 3;
+
+ private StreamingCallAdapter mStreamingCallAdapter;
+ private StreamingCall mCall;
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = {"STREAMING_FAILED"},
+ value = {
+ STREAMING_FAILED_UNKNOWN,
+ STREAMING_FAILED_ALREADY_STREAMING,
+ STREAMING_FAILED_NO_SENDER,
+ STREAMING_FAILED_SENDER_BINDING_ERROR
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StreamingFailedReason {
+ }
+
+ ;
+
+ /**
+ * Called when a {@code StreamingCall} has been added to this call streaming session. The call
+ * streaming sender should start to intercept the device audio using audio records and audio
+ * tracks from Audio frameworks.
+ *
+ * @param call a newly added {@code StreamingCall}.
+ */
+ public void onCallStreamingStarted(@NonNull StreamingCall call) {
+ }
+
+ /**
+ * Called when a current {@code StreamingCall} has been removed from this call streaming
+ * session. The call streaming sender should notify the streaming receiver that the call is
+ * stopped streaming and stop the device audio interception.
+ */
+ public void onCallStreamingStopped() {
+ }
+
+ /**
+ * Called when the streaming state of current {@code StreamingCall} changed. General streaming
+ * sender usually get notified of the holding/unholding from the original owner voip app of the
+ * call.
+ */
+ public void onCallStreamingStateChanged(@StreamingCall.StreamingCallState int state) {
+ }
+}
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 7c60f81259c7..943d8d6cdd55 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;
@@ -32,6 +33,7 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Intent;
import android.hardware.camera2.CameraManager;
+import android.location.Location;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
@@ -39,6 +41,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;
@@ -66,8 +69,10 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
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
@@ -1046,6 +1051,13 @@ public abstract class Connection extends Conferenceable {
public static final String EXTRA_CALL_QUALITY_REPORT =
"android.telecom.extra.CALL_QUALITY_REPORT";
+ /**
+ * Key to obtain location as a result of ({@code queryLocationForEmergency} from Bundle
+ * @hide
+ */
+ public static final String EXTRA_KEY_QUERY_LOCATION =
+ "android.telecom.extra.KEY_QUERY_LOCATION";
+
// Flag controlling whether PII is emitted into the logs
private static final boolean PII_DEBUG = Log.isLoggable(android.util.Log.DEBUG);
@@ -1279,6 +1291,11 @@ 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) {}
+ public void onQueryLocation(Connection c, long timeoutMillis, @NonNull String provider,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Location, QueryLocationException> callback) {}
}
/**
@@ -2161,6 +2178,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;
@@ -2289,7 +2307,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;
}
@@ -2456,6 +2478,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.
*/
@@ -2531,11 +2590,21 @@ public abstract class Connection extends Conferenceable {
*/
public final void setCallerDisplayName(String callerDisplayName, int presentation) {
checkImmutable();
- Log.d(this, "setCallerDisplayName %s", callerDisplayName);
- mCallerDisplayName = callerDisplayName;
- mCallerDisplayNamePresentation = presentation;
- for (Listener l : mListeners) {
- l.onCallerDisplayNameChanged(this, callerDisplayName, presentation);
+ boolean nameChanged = !Objects.equals(mCallerDisplayName, callerDisplayName);
+ boolean presentationChanged = mCallerDisplayNamePresentation != presentation;
+ if (nameChanged) {
+ // Ensure the new name is not clobbering the old one with a null value due to the caller
+ // wanting to only set the presentation and not knowing the display name.
+ mCallerDisplayName = callerDisplayName;
+ }
+ if (presentationChanged) {
+ mCallerDisplayNamePresentation = presentation;
+ }
+ if (nameChanged || presentationChanged) {
+ for (Listener l : mListeners) {
+ l.onCallerDisplayNameChanged(this, mCallerDisplayName,
+ mCallerDisplayNamePresentation);
+ }
}
}
@@ -2763,6 +2832,12 @@ public abstract class Connection extends Conferenceable {
* @param isVoip True if the audio mode is VOIP.
*/
public final void setAudioModeIsVoip(boolean isVoip) {
+ if (!isVoip && (mConnectionProperties & PROPERTY_SELF_MANAGED) == PROPERTY_SELF_MANAGED) {
+ Log.i(this,
+ "setAudioModeIsVoip: Ignored request to set a self-managed connection's"
+ + " audioModeIsVoip to false. Doing so can cause unwanted behavior.");
+ return;
+ }
checkImmutable();
mAudioModeIsVoip = isVoip;
for (Listener l : mListeners) {
@@ -3064,7 +3139,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);
@@ -3084,7 +3162,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,
@@ -3093,6 +3174,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.
@@ -3129,6 +3244,36 @@ public abstract class Connection extends Conferenceable {
}
/**
+ * Query the device's location in order to place an Emergency Call.
+ * Only SIM call managers can call this method for Connections representing Emergency calls.
+ * If a previous location query request is not completed, the new location query request will
+ * be rejected and return a QueryLocationException with
+ * {@code QueryLocationException#ERROR_PREVIOUS_REQUEST_EXISTS}
+ *
+ * @param timeoutMillis long: Timeout in millis waiting for query response (MAX:5000, MIN:100).
+ * @param provider String: the location provider name, This value cannot be null.
+ * It is the caller's responsibility to select an enabled provider. The caller
+ * can use {@link android.location.LocationManager#getProviders(boolean)}
+ * to choose one of the enabled providers and pass it in.
+ * @param executor The executor of where the callback will execute.
+ * @param callback The callback to notify the result of queryLocation.
+ */
+ public final void queryLocationForEmergency(
+ @IntRange(from = 100, to = 5000) long timeoutMillis,
+ @NonNull String provider,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Location, QueryLocationException> callback) {
+ if (provider == null || executor == null || callback == null) {
+ throw new IllegalArgumentException("There are arguments that must not be null");
+ }
+ if (timeoutMillis < 100 || timeoutMillis > 5000) {
+ throw new IllegalArgumentException("The timeoutMillis should be min 100, max 5000");
+ }
+ mListeners.forEach((l) ->
+ l.onQueryLocation(this, timeoutMillis, provider, executor, callback));
+ }
+
+ /**
* Notifies this Connection that the {@link #getAudioState()} property has a new value.
*
* @param state The new connection audio state.
@@ -3143,10 +3288,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 6afc40064961..536e458159d1 100755
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -16,6 +16,7 @@
package android.telecom;
+import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -25,12 +26,14 @@ import android.annotation.TestApi;
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
+import android.location.Location;
import android.net.Uri;
import android.os.Bundle;
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 +51,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:
@@ -77,19 +81,240 @@ import java.util.concurrent.ConcurrentHashMap;
* See {@link PhoneAccount} and {@link TelecomManager#registerPhoneAccount} for more information.
* <p>
* System managed {@link ConnectionService}s must be enabled by the user in the phone app settings
- * before Telecom will bind to them. Self-managed {@link ConnectionService}s must be granted the
- * appropriate permission before Telecom will bind to them.
+ * before Telecom will bind to them. Self-managed {@link ConnectionService}s must declare the
+ * {@link android.Manifest.permission#MANAGE_OWN_CALLS} permission in their manifest before Telecom
+ * will bind to them.
* <p>
* Once registered and enabled by the user in the phone app settings or granted permission, telecom
* will bind to a {@link ConnectionService} implementation when it wants that
* {@link ConnectionService} to place a call or the service has indicated that is has an incoming
- * call through {@link TelecomManager#addNewIncomingCall}. The {@link ConnectionService} can then
- * expect a call to {@link #onCreateIncomingConnection} or {@link #onCreateOutgoingConnection}
+ * call through {@link TelecomManager#addNewIncomingCall(PhoneAccountHandle, Bundle)}. The
+ * {@link ConnectionService} can then expect a call to
+ * {@link #onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest)} or
+ * {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}
* wherein it should provide a new instance of a {@link Connection} object. It is through this
* {@link Connection} object that telecom receives state updates and the {@link ConnectionService}
* receives call-commands such as answer, reject, hold and disconnect.
* <p>
* When there are no more live calls, telecom will unbind from the {@link ConnectionService}.
+ * <p>
+ * <h1>Self-Managed Connection Services</h1>
+ * A VoIP app can implement a {@link ConnectionService} to ensure that its calls are integrated
+ * into the Android platform. There are numerous benefits to using the Telecom APIs for a VoIP app:
+ * <ul>
+ * <li>Call concurrency is handled - the user is able to swap between calls in different
+ * apps and on the mobile network.</li>
+ * <li>Simplified audio routing - the platform provides your app with a unified list of the
+ * audio routes which are available
+ * (e.g. {@link android.telecom.Connection#onAvailableCallEndpointsChanged(List)}) and a
+ * standardized way to switch audio routes
+ * (e.g. {@link android.telecom.Connection#requestCallEndpointChange(CallEndpoint, Executor,
+ * OutcomeReceiver)} ).</li>
+ * <li>Bluetooth integration - your calls will be visible on and controllable via
+ * bluetooth devices (e.g. car head units and headsets).</li>
+ * <li>Companion device integration - wearable devices such as watches which implement an
+ * {@link InCallService} can optionally subscribe to see self-managed calls. Similar to a
+ * bluetooth headunit, wearables will typically render your call using a generic call UX and
+ * provide the user with basic call controls such as hangup, answer, reject.</li>
+ * <li>Automotive calling experiences - Android supports automotive optimized experiences which
+ * provides a means for calls to be controlled and viewed in an automobile; these experiences
+ * are capable of leveraging call metadata provided by your app.</li>
+ * </ul>
+ * <h2>Registering a Phone Account</h2>
+ * Before your app can handle incoming or outgoing calls through Telecom it needs to register a
+ * {@link PhoneAccount} with Telecom indicating to the platform that your app is capable of calling.
+ * <p>
+ * Your app should create a new instance of {@link PhoneAccount} which meets the following
+ * requirements:
+ * <ul>
+ * <li>Has {@link PhoneAccount#CAPABILITY_SELF_MANAGED} (set using
+ * {@link PhoneAccount.Builder#setCapabilities(int)}). This indicates to Telecom that your
+ * app will report calls but that it provides a primary UI for the calls by itself.</li>
+ * <li>Provide a unique identifier for the {@link PhoneAccount} via the
+ * {@link PhoneAccountHandle#getId()} attribute. As per the {@link PhoneAccountHandle}
+ * documentation, you should NOT use an identifier which contains PII or other sensitive
+ * information. A typical choice is a UUID.</li>
+ * </ul>
+ * Your app should register the new {@link PhoneAccount} with Telecom using
+ * {@link TelecomManager#registerPhoneAccount(PhoneAccount)}. {@link PhoneAccount}s persist across
+ * reboot. You can use {@link TelecomManager#getOwnSelfManagedPhoneAccounts()} to confirm the
+ * {@link PhoneAccount} you registered. Your app should generally only register a single
+ * {@link PhoneAccount}.
+ *
+ * <h2>Implementing ConnectionService</h2>
+ * Your app uses {@link TelecomManager#placeCall(Uri, Bundle)} to start new outgoing calls and
+ * {@link TelecomManager#addNewIncomingCall(PhoneAccountHandle, Bundle)} to report new incoming
+ * calls. Calling these APIs causes the Telecom stack to bind to your app's
+ * {@link ConnectionService} implementation. Telecom will either inform your app that it cannot
+ * handle a call request at the current time (i.e. there could be an ongoing emergency call, which
+ * means your app is not allowed to handle calls at the current time), or it will ask your app to
+ * create a new instance of {@link Connection} to represent a call in your app.
+ *
+ * Your app should implement the following {@link ConnectionService} methods:
+ * <ul>
+ * <li>{@link ConnectionService#onCreateOutgoingConnection(PhoneAccountHandle,
+ * ConnectionRequest)} - called by Telecom to ask your app to make a new {@link Connection}
+ * to represent an outgoing call your app requested via
+ * {@link TelecomManager#placeCall(Uri, Bundle)}.</li>
+ * <li><{@link ConnectionService#onCreateOutgoingConnectionFailed(PhoneAccountHandle,
+ * ConnectionRequest)} - called by Telecom to inform your app that a call it reported via
+ * {@link TelecomManager#placeCall(Uri, Bundle)} cannot be handled at this time. Your app
+ * should NOT place a call at the current time.</li>
+ * <li>{@link ConnectionService#onCreateIncomingConnection(PhoneAccountHandle,
+ * ConnectionRequest)} - called by Telecom to ask your app to make a new {@link Connection}
+ * to represent an incoming call your app reported via
+ * {@link TelecomManager#addNewIncomingCall(PhoneAccountHandle, Bundle)}.</li>
+ * <li>{@link ConnectionService#onCreateIncomingConnectionFailed(PhoneAccountHandle,
+ * ConnectionRequest)} - called by Telecom to inform your app that an incoming call it reported
+ * via {@link TelecomManager#addNewIncomingCall(PhoneAccountHandle, Bundle)} cannot be handled
+ * at this time. Your app should NOT post a new incoming call notification and should silently
+ * reject the call.</li>
+ * </ul>
+ *
+ * <h2>Implementing a Connection</h2>
+ * Your app should extend the {@link Connection} class to represent calls in your app. When you
+ * create new instances of your {@link Connection}, you should ensure the following properties are
+ * set on the new {@link Connection} instance returned by your {@link ConnectionService}:
+ * <ul>
+ * <li>{@link Connection#setAddress(Uri, int)} - the identifier for the other party. For
+ * apps that user phone numbers the {@link Uri} can be a {@link PhoneAccount#SCHEME_TEL} URI
+ * representing the phone number.</li>
+ * <li>{@link Connection#setCallerDisplayName(String, int)} - the display name of the other
+ * party. This is what will be shown on Bluetooth devices and other calling surfaces such
+ * as wearable devices. This is particularly important for calls that do not use a phone
+ * number to identify the caller or called party.</li>
+ * <li>{@link Connection#setConnectionProperties(int)} - ensure you set
+ * {@link Connection#PROPERTY_SELF_MANAGED} to identify to the platform that the call is
+ * handled by your app.</li>
+ * <li>{@link Connection#setConnectionCapabilities(int)} - if your app supports making calls
+ * inactive (i.e. holding calls) you should get {@link Connection#CAPABILITY_SUPPORT_HOLD} and
+ * {@link Connection#CAPABILITY_HOLD} to indicate to the platform that you calls can potentially
+ * be held for concurrent calling scenarios.</li>
+ * <li>{@link Connection#setAudioModeIsVoip(boolean)} - set to {@code true} to ensure that the
+ * platform knows your call is a VoIP call.</li>
+ * <li>For newly created {@link Connection} instances, do NOT change the state of your call
+ * using {@link Connection#setActive()}, {@link Connection#setOnHold()} until the call is added
+ * to Telecom (ie you have returned it via
+ * {@link ConnectionService#onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}
+ * or
+ * {@link ConnectionService#onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest)}).
+ * </li>
+ * </ul>
+ *
+ * <h2>How to Place Outgoing Calls</h2>
+ * When your app wants to place an outgoing call it calls
+ * {@link TelecomManager#placeCall(Uri, Bundle)}. You should specify a {@link Uri} to identify
+ * who the call is being placed to, and specify the {@link PhoneAccountHandle} associated with the
+ * {@link PhoneAccount} you registered for your app using
+ * {@link TelecomManager#EXTRA_PHONE_ACCOUNT_HANDLE} in the {@link Bundle} parameter.
+ * <p>
+ * Telecom will bind to your app's {@link ConnectionService} implementation and call either:
+ * <ul>
+ * <li>{@link ConnectionService#onCreateOutgoingConnection(PhoneAccountHandle,
+ * ConnectionRequest)} - the {@link ConnectionRequest#getAddress()} will match the address
+ * you specified when placing the call. You should return a new instance of your app's
+ * {@link Connection} class to represent the outgoing call.</li>
+ * <li>{@link ConnectionService#onCreateOutgoingConnectionFailed(PhoneAccountHandle,
+ * ConnectionRequest)} - your app should not place the call at this time; the call should be
+ * cancelled and the user informed that the call cannot be placed.</li>
+ * </ul>
+ * <p>
+ * New outgoing calls will start in a {@link Connection#STATE_DIALING} state. This state indicates
+ * that your app is in the process of connecting the call to the other party.
+ * <p>
+ * Once the other party answers the call (or it is set up successfully), your app should call
+ * {@link Connection#setActive()} to inform Telecom that the call is now active.
+ *
+ * <h2>How to Add Incoming Calls</h2>
+ * When your app receives an incoming call, it should call
+ * {@link TelecomManager#addNewIncomingCall(PhoneAccountHandle, Bundle)}. Set the
+ * {@link PhoneAccountHandle} parameter to the {@link PhoneAccountHandle} associated with your
+ * app's {@link PhoneAccount}.
+ * <p>
+ * Telecom will bind to your app's {@link ConnectionService} implementation and call either:
+ * <ul>
+ * <li>{@link ConnectionService#onCreateIncomingConnection(PhoneAccountHandle,
+ * ConnectionRequest)} - You should return a new instance of your app's
+ * {@link Connection} class to represent the incoming call.</li>
+ * <li>{@link ConnectionService#onCreateIncomingConnectionFailed(PhoneAccountHandle,
+ * ConnectionRequest)} - your app should not receive the call at this time; the call should be
+ * rejected silently; the user may be informed of a missed call.</li>
+ * </ul>
+ * <p>
+ * New incoming calls will start with a {@link Connection#STATE_RINGING} state. This state
+ * indicates that your app has a new incoming call pending. Telecom will NOT play a ringtone or
+ * post a notification for your app. It is up to your app to post an incoming call notification
+ * with an associated ringtone. Telecom will call {@link Connection#onShowIncomingCallUi()} on the
+ * {@link Connection} when your app can post its incoming call notification. See
+ * {@link Connection#onShowIncomingCallUi() the docs} for more information on how to post the
+ * notification.
+ * <p>
+ * Your incoming call notification (or full screen UI) will typically have an "answer" and "decline"
+ * action which the user chooses. When your app receives the "answer" or "decline"
+ * {@link android.app.PendingIntent}, you should must call either {@link Connection#setActive()} to
+ * inform Telecom that the call was answered, or
+ * {@link Connection#setDisconnected(DisconnectCause)} to inform Telecom that the call was rejected.
+ * If the call was rejected, supply an instance of {@link DisconnectCause} with
+ * {@link DisconnectCause#REJECTED}, and then call {@link Connection#destroy()}.
+ * <p>
+ * In addition to handling requests to answer or decline the call via notification actions, your
+ * app should also be implement the {@link Connection#onAnswer(int)} and
+ * {@link Connection#onAnswer()} methods on the {@link Connection}. These will be raised if the
+ * user answers your call via a Bluetooth device or another device like a wearable or automotive
+ * calling UX. In response, your app should call {@link Connection#setActive()} to inform Telecom
+ * that the call was answered.
+ * <p>
+ * Additionally, your app should implement {@link Connection#onReject()} to handle requests to
+ * reject the call which are raised via Bluetooth or other calling surfaces. Your app should call
+ * {@link Connection#setDisconnected(DisconnectCause)} and supply an instance of
+ * {@link DisconnectCause} with {@link DisconnectCause#REJECTED} in this case.
+ *
+ * <h2>Ending Calls</h2>
+ * When an ongoing active call (incoming or outgoing) has ended, your app is responsible for
+ * informing Telecom that the call ended.
+ * <p>
+ * Your app calls:
+ * <ul>
+ * <li>{@link Connection#setDisconnected(DisconnectCause)} - this informs Telecom that the
+ * call has terminated. You should provide a new instance of {@link DisconnectCause} with
+ * either {@link DisconnectCause#LOCAL} or {@link DisconnectCause#REMOTE} to indicate where the
+ * call disconnection took place. {@link DisconnectCause#LOCAL} indicates that the call
+ * terminated in your app on the current device (i.e. via user action), where
+ * {@link DisconnectCause#REMOTE} indicates that the call terminates on the remote device.</li>
+ * <li>{@link Connection#destroy()} - this informs Telecom that your call instance can be
+ * cleaned up. You should always call this when you are finished with a call.</li>
+ * </ul>
+ * <p>
+ * Similar to answering incoming calls, requests to disconnect your call may originate from outside
+ * your app. You can handle these by implementing {@link Connection#onDisconnect()}. Your app
+ * should call {@link Connection#setDisconnected(DisconnectCause)} with an instance of
+ * {@link DisconnectCause} and reason {@link DisconnectCause#LOCAL} to indicate to Telecom that your
+ * app has disconnected the call as requested based on the user's request.
+ *
+ * <h2>Holding and Unholding Calls</h2>
+ * When your app specifies {@link Connection#CAPABILITY_SUPPORT_HOLD} and
+ * {@link Connection#CAPABILITY_HOLD} on your {@link Connection} instance, it is telling Telecom
+ * that your calls can be placed into a suspended, or "held" state if required. If your app
+ * supports holding its calls, it will be possible for the user to switch between calls in your app
+ * and holdable calls in another app or on the mobile network. If your app does not support
+ * holding its calls, you may receive a request to disconnect the call from Telecom if the user
+ * opts to answer an incoming call in another app or on the mobile network; this ensures that the
+ * user can only be in one call at a time.
+ * <p>
+ * Your app is free to change a call between the held and active state using
+ * {@link Connection#setOnHold()} and {@link Connection#setActive()}.
+ * <p>
+ * Your app may receive a request from Telecom to hold or unhold a call via
+ * {@link Connection#onHold()} and {@link Connection#onUnhold()}. Telecom can ask your app to
+ * hold or unhold its {@link Connection} either if the user requests this action through another
+ * calling surface such as Bluetooth, or if the user answers or switches to a call in a different
+ * app or on the mobile network.
+ * <p>
+ * When your app receives an {@link Connection#onHold()} it must call {@link Connection#setOnHold()}
+ * to inform Telecom that the call has been held successfully.
+ * <p>
+ * When your app receives an {@link Connection#onUnhold()} it must call
+ * {@link Connection#setActive()} to inform Telecom that the call has been resumed successfully.
*/
public abstract class ConnectionService extends Service {
/**
@@ -164,6 +389,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 +436,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 +823,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 +1803,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 +2234,25 @@ 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);
+ }
+ }
+
+ @Override
+ public void onQueryLocation(Connection c, long timeoutMillis, @NonNull String provider,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Location, QueryLocationException> callback) {
+ String id = mIdByConnection.get(c);
+ if (id != null) {
+ mAdapter.queryLocation(id, timeoutMillis, provider, executor, callback);
+ }
+ }
};
/** {@inheritDoc} */
@@ -2044,7 +2381,7 @@ public abstract class ConnectionService extends Service {
if (isHandover) {
PhoneAccountHandle fromPhoneAccountHandle = request.getExtras() != null
? (PhoneAccountHandle) request.getExtras().getParcelable(
- TelecomManager.EXTRA_HANDOVER_FROM_PHONE_ACCOUNT) : null;
+ TelecomManager.EXTRA_HANDOVER_FROM_PHONE_ACCOUNT, android.telecom.PhoneAccountHandle.class) : null;
if (!isIncoming) {
connection = onCreateOutgoingHandoverConnection(fromPhoneAccountHandle, request);
} else {
@@ -2313,6 +2650,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..a7105d349f26 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapter.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
@@ -16,10 +16,16 @@
package android.telecom;
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.location.Location;
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 +35,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 +574,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.
@@ -709,4 +751,45 @@ final class ConnectionServiceAdapter implements DeathRecipient {
}
}
}
+
+ /**
+ * Query location information.
+ * Only SIM call managers can call this method for Connections representing Emergency calls.
+ * If the previous request is not completed, the new request will be rejected.
+ *
+ * @param timeoutMillis long: Timeout in millis waiting for query response.
+ * @param provider String: the location provider name, This value cannot be null.
+ * @param executor The executor of where the callback will execute.
+ * @param callback The callback to notify the result of queryLocation.
+ */
+ void queryLocation(String callId, long timeoutMillis, @NonNull String provider,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Location, QueryLocationException> callback) {
+ Log.v(this, "queryLocation: %s %d", callId, timeoutMillis);
+ for (IConnectionServiceAdapter adapter : mAdapters) {
+ try {
+ adapter.queryLocation(callId, timeoutMillis, provider,
+ new ResultReceiver(null) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle result) {
+ super.onReceiveResult(resultCode, result);
+
+ if (resultCode == 1 /* success */) {
+ executor.execute(() -> callback.onResult(result.getParcelable(
+ Connection.EXTRA_KEY_QUERY_LOCATION, Location.class)));
+ } else {
+ executor.execute(() -> callback.onError(result.getParcelable(
+ QueryLocationException.QUERY_LOCATION_ERROR,
+ QueryLocationException.class)));
+ }
+ }
+ },
+ Log.getExternalSession());
+ } catch (RemoteException e) {
+ Log.d(this, "queryLocation: Exception e : " + e);
+ executor.execute(() -> callback.onError(new QueryLocationException(
+ e.getMessage(), QueryLocationException.ERROR_SERVICE_UNAVAILABLE)));
+ }
+ }
+ }
}
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
index 6c1ea322e66e..8a59020fc580 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;
@@ -77,6 +78,7 @@ final class ConnectionServiceAdapterServant {
private static final int MSG_SET_CONFERENCE_STATE = 36;
private static final int MSG_HANDLE_CREATE_CONFERENCE_COMPLETE = 37;
private static final int MSG_SET_CALL_DIRECTION = 38;
+ private static final int MSG_QUERY_LOCATION = 39;
private final IConnectionServiceAdapter mDelegate;
@@ -372,6 +374,18 @@ final class ConnectionServiceAdapterServant {
} finally {
args.recycle();
}
+ break;
+ }
+ case MSG_QUERY_LOCATION: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ try {
+ mDelegate.queryLocation((String) args.arg1, (long) args.arg2,
+ (String) args.arg3, (ResultReceiver) args.arg4,
+ (Session.Info) args.arg5);
+ } finally {
+ args.recycle();
+ }
+ break;
}
}
}
@@ -692,6 +706,24 @@ 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
+ }
+
+ @Override
+ public void queryLocation(String callId, long timeoutMillis, String provider,
+ ResultReceiver callback, Session.Info sessionInfo) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.arg2 = timeoutMillis;
+ args.arg3 = provider;
+ args.arg4 = callback;
+ args.arg5 = sessionInfo;
+ mHandler.obtainMessage(MSG_QUERY_LOCATION, args).sendToTarget();
+ }
};
public ConnectionServiceAdapterServant(IConnectionServiceAdapter delegate) {
diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java
index b003f59d5e81..331caa1bad7a 100644
--- a/telecomm/java/android/telecom/DisconnectCause.java
+++ b/telecomm/java/android/telecom/DisconnectCause.java
@@ -43,8 +43,8 @@ public final class DisconnectCause implements Parcelable {
/** Disconnected because of a local user-initiated action, such as hanging up. */
public static final int LOCAL = TelecomProtoEnums.LOCAL; // = 2
/**
- * Disconnected because of a remote user-initiated action, such as the other party hanging up
- * up.
+ * Disconnected because the remote party hung up an ongoing call, or because an outgoing call
+ * was not answered by the remote party.
*/
public static final int REMOTE = TelecomProtoEnums.REMOTE; // = 3
/** Disconnected because it has been canceled. */
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/Log.java b/telecomm/java/android/telecom/Log.java
index 884dcf2dfbad..a34094ce6452 100644
--- a/telecomm/java/android/telecom/Log.java
+++ b/telecomm/java/android/telecom/Log.java
@@ -69,6 +69,7 @@ public class Log {
private static final Object sSingletonSync = new Object();
private static EventManager sEventManager;
private static SessionManager sSessionManager;
+ private static Object sLock = null;
/**
* Tracks whether user-activated extended logging is enabled.
@@ -388,6 +389,19 @@ public class Log {
}
/**
+ * Sets the main telecom sync lock used within Telecom. This is used when building log messages
+ * so that we can identify places in the code where we are doing something outside of the
+ * Telecom lock.
+ * @param lock The lock.
+ */
+ public static void setLock(Object lock) {
+ // Don't do lock monitoring on user builds.
+ if (!Build.IS_USER) {
+ sLock = lock;
+ }
+ }
+
+ /**
* If user enabled extended logging is enabled and the time limit has passed, disables the
* extended logging.
*/
@@ -512,7 +526,10 @@ public class Log {
args.length);
msg = format + " (An error occurred while formatting the message.)";
}
- return String.format(Locale.US, "%s: %s%s", prefix, msg, sessionPostfix);
+ // If a lock was set, check if this thread holds that lock and output an emoji that lets
+ // the developer know whether a log message came from within the Telecom lock or not.
+ String isLocked = sLock != null ? (Thread.holdsLock(sLock) ? "\uD83D\uDD12" : "❗") : "";
+ return String.format(Locale.US, "%s: %s%s%s", prefix, msg, sessionPostfix, isLocked);
}
/**
diff --git a/telecomm/java/android/telecom/Logging/EventManager.java b/telecomm/java/android/telecom/Logging/EventManager.java
index 1342038c6477..a74c0bb99549 100644
--- a/telecomm/java/android/telecom/Logging/EventManager.java
+++ b/telecomm/java/android/telecom/Logging/EventManager.java
@@ -180,7 +180,7 @@ public class EventManager {
}
}
- private final List<Event> mEvents = Collections.synchronizedList(new LinkedList<>());
+ private final List<Event> mEvents = Collections.synchronizedList(new ArrayList<>());
private final Loggable mRecordEntry;
public EventRecord(Loggable recordEntry) {
@@ -197,7 +197,7 @@ public class EventManager {
}
public List<Event> getEvents() {
- return new LinkedList<>(mEvents);
+ return new ArrayList<>(mEvents);
}
public List<EventTiming> extractEventTimings() {
@@ -205,7 +205,7 @@ public class EventManager {
return Collections.emptyList();
}
- LinkedList<EventTiming> result = new LinkedList<>();
+ ArrayList<EventTiming> result = new ArrayList<>();
Map<String, PendingResponse> pendingResponses = new HashMap<>();
synchronized (mEvents) {
for (Event event : mEvents) {
diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java
index f412a1825e2a..6a1318982e77 100644
--- a/telecomm/java/android/telecom/ParcelableCall.java
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -69,6 +69,7 @@ public final class ParcelableCall implements Parcelable {
private int mCallerNumberVerificationStatus;
private String mContactDisplayName;
private String mActiveChildCallId;
+ private Uri mContactPhotoUri;
public ParcelableCallBuilder setId(String id) {
mId = id;
@@ -224,6 +225,11 @@ public final class ParcelableCall implements Parcelable {
return this;
}
+ public ParcelableCallBuilder setContactPhotoUri(Uri contactPhotoUri) {
+ mContactPhotoUri = contactPhotoUri;
+ return this;
+ }
+
public ParcelableCall createParcelableCall() {
return new ParcelableCall(
mId,
@@ -255,7 +261,8 @@ public final class ParcelableCall implements Parcelable {
mCallDirection,
mCallerNumberVerificationStatus,
mContactDisplayName,
- mActiveChildCallId);
+ mActiveChildCallId,
+ mContactPhotoUri);
}
public static ParcelableCallBuilder fromParcelableCall(ParcelableCall parcelableCall) {
@@ -292,6 +299,7 @@ public final class ParcelableCall implements Parcelable {
parcelableCall.mCallerNumberVerificationStatus;
newBuilder.mContactDisplayName = parcelableCall.mContactDisplayName;
newBuilder.mActiveChildCallId = parcelableCall.mActiveChildCallId;
+ newBuilder.mContactPhotoUri = parcelableCall.mContactPhotoUri;
return newBuilder;
}
}
@@ -327,6 +335,7 @@ public final class ParcelableCall implements Parcelable {
private final int mCallerNumberVerificationStatus;
private final String mContactDisplayName;
private final String mActiveChildCallId; // Only valid for CDMA conferences
+ private final Uri mContactPhotoUri;
public ParcelableCall(
String id,
@@ -358,7 +367,8 @@ public final class ParcelableCall implements Parcelable {
int callDirection,
int callerNumberVerificationStatus,
String contactDisplayName,
- String activeChildCallId
+ String activeChildCallId,
+ Uri contactPhotoUri
) {
mId = id;
mState = state;
@@ -390,6 +400,7 @@ public final class ParcelableCall implements Parcelable {
mCallerNumberVerificationStatus = callerNumberVerificationStatus;
mContactDisplayName = contactDisplayName;
mActiveChildCallId = activeChildCallId;
+ mContactPhotoUri = contactPhotoUri;
}
/** The unique ID of the call. */
@@ -607,6 +618,14 @@ public final class ParcelableCall implements Parcelable {
}
/**
+ * @return the caller photo URI.
+ */
+ public @Nullable Uri getContactPhotoUri() {
+ return mContactPhotoUri;
+ }
+
+
+ /**
* @return On a CDMA conference with two participants, returns the ID of the child call that's
* currently active.
*/
@@ -655,6 +674,7 @@ public final class ParcelableCall implements Parcelable {
int callerNumberVerificationStatus = source.readInt();
String contactDisplayName = source.readString();
String activeChildCallId = source.readString();
+ Uri contactPhotoUri = source.readParcelable(classLoader, Uri.class);
return new ParcelableCallBuilder()
.setId(id)
.setState(state)
@@ -686,6 +706,7 @@ public final class ParcelableCall implements Parcelable {
.setCallerNumberVerificationStatus(callerNumberVerificationStatus)
.setContactDisplayName(contactDisplayName)
.setActiveChildCallId(activeChildCallId)
+ .setContactPhotoUri(contactPhotoUri)
.createParcelableCall();
}
@@ -735,6 +756,7 @@ public final class ParcelableCall implements Parcelable {
destination.writeInt(mCallerNumberVerificationStatus);
destination.writeString(mContactDisplayName);
destination.writeString(mActiveChildCallId);
+ destination.writeParcelable(mContactPhotoUri, 0);
}
@Override
diff --git a/telecomm/java/android/telecom/ParcelableCallAnalytics.java b/telecomm/java/android/telecom/ParcelableCallAnalytics.java
index b8ad9e2fbe6c..a69dfb0b255f 100644
--- a/telecomm/java/android/telecom/ParcelableCallAnalytics.java
+++ b/telecomm/java/android/telecom/ParcelableCallAnalytics.java
@@ -111,6 +111,8 @@ public class ParcelableCallAnalytics implements Parcelable {
public static final int FILTERING_INITIATED = 106;
public static final int FILTERING_COMPLETED = 107;
public static final int FILTERING_TIMED_OUT = 108;
+ public static final int DND_CHECK_INITIATED = 109;
+ public static final int DND_CHECK_COMPLETED = 110;
public static final int SKIP_RINGING = 200;
public static final int SILENCE = 201;
@@ -195,6 +197,7 @@ public class ParcelableCallAnalytics implements Parcelable {
public static final int BLOCK_CHECK_FINISHED_TIMING = 9;
public static final int FILTERING_COMPLETED_TIMING = 10;
public static final int FILTERING_TIMED_OUT_TIMING = 11;
+ public static final int DND_PRE_CALL_PRE_CHECK_TIMING = 12;
/** {@hide} */
public static final int START_CONNECTION_TO_REQUEST_DISCONNECT_TIMING = 12;
@@ -359,7 +362,7 @@ public class ParcelableCallAnalytics implements Parcelable {
eventTimings = new ArrayList<>();
in.readTypedList(eventTimings, EventTiming.CREATOR);
isVideoCall = readByteAsBoolean(in);
- videoEvents = new LinkedList<>();
+ videoEvents = new ArrayList<>();
in.readTypedList(videoEvents, VideoEvent.CREATOR);
callSource = in.readInt();
}
diff --git a/telecomm/java/android/telecom/ParcelableConference.java b/telecomm/java/android/telecom/ParcelableConference.java
index e57c833e930e..6dcfa6d56ef3 100644
--- a/telecomm/java/android/telecom/ParcelableConference.java
+++ b/telecomm/java/android/telecom/ParcelableConference.java
@@ -21,12 +21,12 @@ import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.telecom.IVideoProvider;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import com.android.internal.telecom.IVideoProvider;
-
/**
* A parcelable representation of a conference connection.
* @hide
@@ -287,6 +287,14 @@ public final class ParcelableConference implements Parcelable {
return mCallDirection;
}
+ public String getCallerDisplayName() {
+ return mCallerDisplayName;
+ }
+
+ public int getCallerDisplayNamePresentation() {
+ return mCallerDisplayNamePresentation;
+ }
+
public static final @android.annotation.NonNull Parcelable.Creator<ParcelableConference> CREATOR =
new Parcelable.Creator<ParcelableConference> () {
@Override
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/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index 7a53447c1eee..94c737d61b0a 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -418,7 +418,34 @@ public final class PhoneAccount implements Parcelable {
*/
public static final int CAPABILITY_VOICE_CALLING_AVAILABLE = 0x20000;
- /* NEXT CAPABILITY: 0x40000 */
+
+ /**
+ * Flag indicating that this {@link PhoneAccount} supports the use TelecomManager APIs that
+ * utilize {@link android.os.OutcomeReceiver}s or {@link java.util.function.Consumer}s.
+ * Be aware, if this capability is set, {@link #CAPABILITY_SELF_MANAGED} will be amended by
+ * Telecom when this {@link PhoneAccount} is registered via
+ * {@link TelecomManager#registerPhoneAccount(PhoneAccount)}.
+ *
+ * <p>
+ * {@link android.os.OutcomeReceiver}s and {@link java.util.function.Consumer}s represent
+ * transactional operations because the operation can succeed or fail. An app wishing to use
+ * transactional operations should define behavior for a successful and failed TelecomManager
+ * API call.
+ *
+ * @see #CAPABILITY_SELF_MANAGED
+ * @see #getCapabilities
+ */
+ public static final int CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS = 0x40000;
+
+ /**
+ * Flag indicating that this voip app {@link PhoneAccount} supports the call streaming session
+ * to stream call audio to another remote device via streaming app.
+ *
+ * @see #getCapabilities
+ */
+ public static final int CAPABILITY_SUPPORTS_CALL_STREAMING = 0x80000;
+
+ /* NEXT CAPABILITY: [0x100000, 0x200000, 0x400000] */
/**
* URI scheme for telephone number URIs.
@@ -513,6 +540,11 @@ public final class PhoneAccount implements Parcelable {
/**
* Creates a builder with the specified {@link PhoneAccountHandle} and label.
+ * <p>
+ * Note: each CharSequence or String field is limited to 256 characters. This check is
+ * enforced when registering the PhoneAccount via
+ * {@link TelecomManager#registerPhoneAccount(PhoneAccount)} and will cause an
+ * {@link IllegalArgumentException} to be thrown if the character field limit is over 256.
*/
public Builder(PhoneAccountHandle accountHandle, CharSequence label) {
this.mAccountHandle = accountHandle;
@@ -543,6 +575,11 @@ public final class PhoneAccount implements Parcelable {
/**
* Sets the label. See {@link PhoneAccount#getLabel()}.
+ * <p>
+ * Note: Each CharSequence or String field is limited to 256 characters. This check is
+ * enforced when registering the PhoneAccount via
+ * {@link TelecomManager#registerPhoneAccount(PhoneAccount)} and will cause an
+ * {@link IllegalArgumentException} to be thrown if the character field limit is over 256.
*
* @param label The label of the phone account.
* @return The builder.
@@ -618,6 +655,11 @@ public final class PhoneAccount implements Parcelable {
/**
* Sets the short description. See {@link PhoneAccount#getShortDescription}.
+ * <p>
+ * Note: Each CharSequence or String field is limited to 256 characters. This check is
+ * enforced when registering the PhoneAccount via
+ * {@link TelecomManager#registerPhoneAccount(PhoneAccount)} and will cause an
+ * {@link IllegalArgumentException} to be thrown if the character field limit is over 256.
*
* @param value The short description.
* @return The builder.
@@ -672,6 +714,13 @@ public final class PhoneAccount implements Parcelable {
* <p>
* {@code PhoneAccount}s only support extra values of type: {@link String}, {@link Integer},
* and {@link Boolean}. Extras which are not of these types are ignored.
+ * <p>
+ * Note: Each Bundle (Key, Value) String field is limited to 256 characters. Additionally,
+ * the bundle is limited to 100 (Key, Value) pairs total. This check is
+ * enforced when registering the PhoneAccount via
+ * {@link TelecomManager#registerPhoneAccount(PhoneAccount)} and will cause an
+ * {@link IllegalArgumentException} to be thrown if the character field limit is over 256
+ * or more than 100 (Key, Value) pairs are in the Bundle.
*
* @param extras
* @return
@@ -703,6 +752,11 @@ public final class PhoneAccount implements Parcelable {
* <p>
* Note: This is an API specific to the Telephony stack; the group Id will be ignored for
* callers not holding the correct permission.
+ * <p>
+ * Additionally, each CharSequence or String field is limited to 256 characters.
+ * This check is enforced when registering the PhoneAccount via
+ * {@link TelecomManager#registerPhoneAccount(PhoneAccount)} and will cause an
+ * {@link IllegalArgumentException} to be thrown if the character field limit is over 256.
*
* @param groupId The group Id of the {@link PhoneAccount} that will replace any other
* registered {@link PhoneAccount} in Telecom with the same Group Id.
@@ -1173,6 +1227,12 @@ public final class PhoneAccount implements Parcelable {
if (hasCapabilities(CAPABILITY_VOICE_CALLING_AVAILABLE)) {
sb.append("Voice ");
}
+ if (hasCapabilities(CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS)) {
+ sb.append("TransactOps ");
+ }
+ if (hasCapabilities(CAPABILITY_SUPPORTS_CALL_STREAMING)) {
+ sb.append("Stream ");
+ }
return sb.toString();
}
diff --git a/telecomm/java/android/telecom/PhoneAccountHandle.java b/telecomm/java/android/telecom/PhoneAccountHandle.java
index ec94f8a1829f..e5db8cfa0989 100644
--- a/telecomm/java/android/telecom/PhoneAccountHandle.java
+++ b/telecomm/java/android/telecom/PhoneAccountHandle.java
@@ -70,6 +70,12 @@ public final class PhoneAccountHandle implements Parcelable {
* ID provided does not expose personally identifying information. A
* {@link ConnectionService} should use an opaque token as the
* {@link PhoneAccountHandle} identifier.
+ * <p>
+ * Note: Each String field is limited to 256 characters. This check is enforced when
+ * registering the PhoneAccount via
+ * {@link TelecomManager#registerPhoneAccount(PhoneAccount)} and will cause an
+ * {@link IllegalArgumentException} to be thrown if the character field limit is
+ * over 256.
*/
public PhoneAccountHandle(
@NonNull ComponentName componentName,
@@ -88,6 +94,13 @@ public final class PhoneAccountHandle implements Parcelable {
* {@link ConnectionService} should use an opaque token as the
* {@link PhoneAccountHandle} identifier.
* @param userHandle The {@link UserHandle} associated with this {@link PhoneAccountHandle}.
+ *
+ * <p>
+ * Note: Each String field is limited to 256 characters. This check is enforced when
+ * registering the PhoneAccount via
+ * {@link TelecomManager#registerPhoneAccount(PhoneAccount)} and will cause an
+ * {@link IllegalArgumentException} to be thrown if the character field limit is
+ * over 256.
*/
public PhoneAccountHandle(
@NonNull ComponentName componentName,
diff --git a/telecomm/java/android/telecom/QueryLocationException.aidl b/telecomm/java/android/telecom/QueryLocationException.aidl
new file mode 100644
index 000000000000..56ac4126cac4
--- /dev/null
+++ b/telecomm/java/android/telecom/QueryLocationException.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 QueryLocationException; \ No newline at end of file
diff --git a/telecomm/java/android/telecom/QueryLocationException.java b/telecomm/java/android/telecom/QueryLocationException.java
new file mode 100644
index 000000000000..fd90d1ec3572
--- /dev/null
+++ b/telecomm/java/android/telecom/QueryLocationException.java
@@ -0,0 +1,129 @@
+/*
+ * 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.OutcomeReceiver;
+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;
+import java.util.concurrent.Executor;
+
+/**
+ * This class represents a set of exceptions that can occur when requesting a
+ * {@link Connection#queryLocationForEmergency(long, String, Executor, OutcomeReceiver)}
+ */
+public final class QueryLocationException extends RuntimeException implements Parcelable {
+ /** @hide */
+ public static final String QUERY_LOCATION_ERROR = "QueryLocationErrorKey";
+
+ /**
+ * The operation was not completed on time.
+ */
+ public static final int ERROR_REQUEST_TIME_OUT = 1;
+ /**
+ * The operation was rejected due to an existing request.
+ */
+ public static final int ERROR_PREVIOUS_REQUEST_EXISTS = 2;
+ /**
+ * The operation has failed because it is not permitted.
+ */
+ public static final int ERROR_NOT_PERMITTED = 3;
+ /**
+ * The operation has failed due to a location query being requested for a non-emergency
+ * connection.
+ */
+ public static final int ERROR_NOT_ALLOWED_FOR_NON_EMERGENCY_CONNECTIONS = 4;
+ /**
+ * The operation has failed due to the service is not available.
+ */
+ public static final int ERROR_SERVICE_UNAVAILABLE = 5;
+ /**
+ * The operation has failed due to an unknown or unspecified error.
+ */
+ public static final int ERROR_UNSPECIFIED = 6;
+
+ 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 QueryLocationException objects for deserialized Parcels.
+ */
+ public static final
+ @android.annotation.NonNull Parcelable.Creator<QueryLocationException> CREATOR =
+ new Parcelable.Creator<>() {
+ @Override
+ public QueryLocationException createFromParcel(Parcel source) {
+ return new QueryLocationException(source.readString8(), source.readInt());
+ }
+ @Override
+ public QueryLocationException[] newArray(int size) {
+ return new QueryLocationException[size];
+ }
+ };
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({ERROR_REQUEST_TIME_OUT,
+ ERROR_PREVIOUS_REQUEST_EXISTS,
+ ERROR_NOT_PERMITTED,
+ ERROR_NOT_ALLOWED_FOR_NON_EMERGENCY_CONNECTIONS,
+ ERROR_SERVICE_UNAVAILABLE,
+ ERROR_UNSPECIFIED})
+ public @interface QueryLocationErrorCode {}
+ public QueryLocationException(@Nullable String message) {
+ super(getMessage(message, ERROR_UNSPECIFIED));
+ mMessage = message;
+ }
+ public QueryLocationException(@Nullable String message, @QueryLocationErrorCode int code) {
+ super(getMessage(message, code));
+ mCode = code;
+ mMessage = message;
+ }
+ public QueryLocationException(
+ @Nullable String message, @QueryLocationErrorCode int code, @Nullable Throwable cause) {
+ super(getMessage(message, code), cause);
+ mCode = code;
+ mMessage = message;
+ }
+ public @QueryLocationErrorCode 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/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..2fc6a22261b6 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,18 @@ 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
+ }
+
+ @Override
+ public void queryLocation(String callId, long timeoutMillis, String provider,
+ ResultReceiver callback, Session.Info sessionInfo) {
+ // Do nothing
+ }
};
private final ConnectionServiceAdapterServant mServant =
diff --git a/telecomm/java/android/telecom/StatusHints.java b/telecomm/java/android/telecom/StatusHints.java
index 2faecc2e3468..5f0c8d729e74 100644
--- a/telecomm/java/android/telecom/StatusHints.java
+++ b/telecomm/java/android/telecom/StatusHints.java
@@ -16,14 +16,19 @@
package android.telecom;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
+import android.os.Binder;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
+
+import com.android.internal.annotations.VisibleForTesting;
import java.util.Objects;
@@ -33,7 +38,7 @@ import java.util.Objects;
public final class StatusHints implements Parcelable {
private final CharSequence mLabel;
- private final Icon mIcon;
+ private Icon mIcon;
private final Bundle mExtras;
/**
@@ -48,11 +53,31 @@ public final class StatusHints implements Parcelable {
public StatusHints(CharSequence label, Icon icon, Bundle extras) {
mLabel = label;
- mIcon = icon;
+ mIcon = validateAccountIconUserBoundary(icon, Binder.getCallingUserHandle());
mExtras = extras;
}
/**
+ * @param icon
+ * @hide
+ */
+ @VisibleForTesting
+ public StatusHints(@Nullable Icon icon) {
+ mLabel = null;
+ mExtras = null;
+ mIcon = icon;
+ }
+
+ /**
+ *
+ * @param icon
+ * @hide
+ */
+ public void setIcon(@Nullable Icon icon) {
+ mIcon = icon;
+ }
+
+ /**
* @return A package used to load the icon.
*
* @hide
@@ -112,6 +137,30 @@ public final class StatusHints implements Parcelable {
return 0;
}
+ /**
+ * Validates the StatusHints image icon to see if it's not in the calling user space.
+ * Invalidates the icon if so, otherwise returns back the original icon.
+ *
+ * @param icon
+ * @return icon (validated)
+ * @hide
+ */
+ public static Icon validateAccountIconUserBoundary(Icon icon, UserHandle callingUserHandle) {
+ // Refer to Icon#getUriString for context. The URI string is invalid for icons of
+ // incompatible types.
+ if (icon != null && (icon.getType() == Icon.TYPE_URI
+ || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) {
+ String encodedUser = icon.getUri().getEncodedUserInfo();
+ // If there is no encoded user, the URI is calling into the calling user space
+ if (encodedUser != null) {
+ int userId = Integer.parseInt(encodedUser);
+ // Do not try to save the icon if the user id isn't in the calling user space.
+ if (userId != callingUserHandle.getIdentifier()) return null;
+ }
+ }
+ return icon;
+ }
+
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeCharSequence(mLabel);
diff --git a/telecomm/java/android/telecom/StreamingCall.aidl b/telecomm/java/android/telecom/StreamingCall.aidl
new file mode 100644
index 000000000000..d2866589a72a
--- /dev/null
+++ b/telecomm/java/android/telecom/StreamingCall.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 StreamingCall; \ No newline at end of file
diff --git a/telecomm/java/android/telecom/StreamingCall.java b/telecomm/java/android/telecom/StreamingCall.java
new file mode 100644
index 000000000000..3319fc117b4d
--- /dev/null
+++ b/telecomm/java/android/telecom/StreamingCall.java
@@ -0,0 +1,191 @@
+/*
+ * 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.annotation.SystemApi;
+import android.content.ComponentName;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Represents a voip call requested to stream to another device that the general streaming sender
+ * app should present to the receiver.
+ *
+ * @hide
+ */
+@SystemApi
+public final class StreamingCall implements Parcelable {
+ /**
+ * The state of a {@code StreamingCall} when newly created. General streaming sender should
+ * continuously stream call audio to the sender device as long as the {@code StreamingCall} is
+ * in this state.
+ */
+ public static final int STATE_STREAMING = 1;
+
+ /**
+ * The state of a {@code StreamingCall} when in a holding state.
+ */
+ public static final int STATE_HOLDING = 2;
+
+ /**
+ * The state of a {@code StreamingCall} when it's either disconnected or pulled back to the
+ * original device.
+ */
+ public static final int STATE_DISCONNECTED = 3;
+
+ /**
+ * The ID associated with this call. This is the same value as {@link CallControl#getCallId()}.
+ * @hide
+ */
+ public static final String EXTRA_CALL_ID = "android.telecom.extra.CALL_ID";
+
+ /**
+ * @hide
+ */
+ private StreamingCall(@NonNull Parcel in) {
+ mComponentName = in.readParcelable(ComponentName.class.getClassLoader());
+ mDisplayName = in.readCharSequence();
+ mAddress = in.readParcelable(Uri.class.getClassLoader());
+ mExtras = in.readBundle();
+ mState = in.readInt();
+ }
+
+ @NonNull
+ public static final Creator<StreamingCall> CREATOR = new Creator<>() {
+ @Override
+ public StreamingCall createFromParcel(@NonNull Parcel in) {
+ return new StreamingCall(in);
+ }
+
+ @Override
+ public StreamingCall[] newArray(int size) {
+ return new StreamingCall[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@androidx.annotation.NonNull Parcel dest, int flags) {
+ dest.writeParcelable(mComponentName, flags);
+ dest.writeCharSequence(mDisplayName);
+ dest.writeParcelable(mAddress, flags);
+ dest.writeBundle(mExtras);
+ dest.writeInt(mState);
+ }
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = { "STATE_" },
+ value = {
+ STATE_STREAMING,
+ STATE_HOLDING,
+ STATE_DISCONNECTED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StreamingCallState {}
+
+ private final ComponentName mComponentName;
+ private final CharSequence mDisplayName;
+ private final Uri mAddress;
+ private final Bundle mExtras;
+ @StreamingCallState
+ private int mState;
+ private StreamingCallAdapter mAdapter = null;
+
+ public StreamingCall(@NonNull ComponentName componentName, @NonNull CharSequence displayName,
+ @NonNull Uri address, @NonNull Bundle extras) {
+ mComponentName = componentName;
+ mDisplayName = displayName;
+ mAddress = address;
+ mExtras = extras;
+ mState = STATE_STREAMING;
+ }
+
+ /**
+ * @hide
+ */
+ public void setAdapter(StreamingCallAdapter adapter) {
+ mAdapter = adapter;
+ }
+
+ /**
+ * @return The {@link ComponentName} to identify the original voip app of this
+ * {@code StreamingCall}. General streaming sender app can use this to query necessary
+ * information (app icon etc.) in order to present notification of the streaming call on the
+ * receiver side.
+ */
+ @NonNull
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
+ /**
+ * @return The display name that the general streaming sender app can use this to present the
+ * {@code StreamingCall} to the receiver side.
+ */
+ @NonNull
+ public CharSequence getDisplayName() {
+ return mDisplayName;
+ }
+
+ /**
+ * @return The address (e.g., phone number) to which the {@code StreamingCall} is currently
+ * connected.
+ */
+ @NonNull
+ public Uri getAddress() {
+ return mAddress;
+ }
+
+ /**
+ * @return The state of this {@code StreamingCall}.
+ */
+ @StreamingCallState
+ public int getState() {
+ return mState;
+ }
+
+ /**
+ * @return The extra info the general streaming app need to stream the call from voip app or
+ * D2DI sdk.
+ */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ /**
+ * Sets the state of this {@code StreamingCall}. The general streaming sender app can use this
+ * to request holding, unholding and disconnecting this {@code StreamingCall}.
+ * @param state The current streaming state of the call.
+ */
+ public void requestStreamingState(@StreamingCallState int state) {
+ mAdapter.setStreamingState(state);
+ }
+}
diff --git a/telecomm/java/android/telecom/StreamingCallAdapter.java b/telecomm/java/android/telecom/StreamingCallAdapter.java
new file mode 100644
index 000000000000..54a3e247015c
--- /dev/null
+++ b/telecomm/java/android/telecom/StreamingCallAdapter.java
@@ -0,0 +1,54 @@
+/*
+ * 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.os.RemoteException;
+
+import com.android.internal.telecom.IStreamingCallAdapter;
+
+/**
+ * Receives commands from {@link CallStreamingService} implementations which should be executed by
+ * Telecom. When Telecom binds to a {@link CallStreamingService}, an instance of this class is given
+ * to the general streaming app through which it can manipulate the streaming calls. Whe the general
+ * streaming app is notified of new ongoing streaming calls, it can execute
+ * {@link StreamingCall#requestStreamingState(int)} for the ongoing streaming calls the user on the
+ * receiver side would like to hold, unhold and disconnect.
+ *
+ * @hide
+ */
+public final class StreamingCallAdapter {
+ private final IStreamingCallAdapter mAdapter;
+
+ /**
+ * {@hide}
+ */
+ public StreamingCallAdapter(IStreamingCallAdapter adapter) {
+ mAdapter = adapter;
+ }
+
+ /**
+ * Instruct telecom to change the state of the streaming call.
+ *
+ * @param state The streaming state to set
+ */
+ public void setStreamingState(@StreamingCall.StreamingCallState int state) {
+ try {
+ mAdapter.setStreamingState(state);
+ } catch (RemoteException e) {
+ }
+ }
+}
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index e3f7c1688a49..a72f7806d3ea 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -15,8 +15,10 @@
package android.telecom;
import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
+import static android.content.Intent.LOCAL_FLAG_FROM_SYSTEM;
import android.Manifest;
+import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -37,6 +39,7 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.OutcomeReceiver;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -48,6 +51,8 @@ import android.text.TextUtils;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.telecom.ClientTransactionalServiceRepository;
+import com.android.internal.telecom.ClientTransactionalServiceWrapper;
import com.android.internal.telecom.ITelecomService;
import java.lang.annotation.Retention;
@@ -739,10 +744,6 @@ public class TelecomManager {
* state of calls in the self-managed {@link ConnectionService}. An example use-case is
* exposing these calls to an automotive device via its companion app.
* <p>
- * This meta-data can only be set for an {@link InCallService} which also sets
- * {@link #METADATA_IN_CALL_SERVICE_UI}. Only the default phone/dialer app, or a car-mode
- * {@link InCallService} can see self-managed calls.
- * <p>
* See also {@link Connection#PROPERTY_SELF_MANAGED}.
*/
public static final String METADATA_INCLUDE_SELF_MANAGED_CALLS =
@@ -950,6 +951,23 @@ public class TelecomManager {
public static final String EXTRA_CALL_SOURCE = "android.telecom.extra.CALL_SOURCE";
/**
+ * Intent action to trigger "switch to managed profile" dialog for call in SystemUI
+ *
+ * @hide
+ */
+ public static final String ACTION_SHOW_SWITCH_TO_WORK_PROFILE_FOR_CALL_DIALOG =
+ "android.telecom.action.SHOW_SWITCH_TO_WORK_PROFILE_FOR_CALL_DIALOG";
+
+ /**
+ * Extra specifying the managed profile user id.
+ * This is used with {@link TelecomManager#ACTION_SHOW_SWITCH_TO_WORK_PROFILE_FOR_CALL_DIALOG}
+ *
+ * @hide
+ */
+ public static final String EXTRA_MANAGED_PROFILE_USER_ID =
+ "android.telecom.extra.MANAGED_PROFILE_USER_ID";
+
+ /**
* Indicating the call is initiated via emergency dialer's shortcut button.
*
* @hide
@@ -1059,6 +1077,14 @@ public class TelecomManager {
private final ITelecomService mTelecomServiceOverride;
+ /** @hide **/
+ private final ClientTransactionalServiceRepository mTransactionalServiceRepository =
+ new ClientTransactionalServiceRepository();
+ /** @hide **/
+ public static final int TELECOM_TRANSACTION_SUCCESS = 0;
+ /** @hide **/
+ public static final String TRANSACTION_CALL_ID_KEY = "TelecomCallId";
+
/**
* @hide
*/
@@ -1183,7 +1209,7 @@ public class TelecomManager {
if (service != null) {
try {
return service.getSimCallManager(
- SubscriptionManager.getDefaultSubscriptionId());
+ SubscriptionManager.getDefaultSubscriptionId(), mContext.getPackageName());
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelecomService#getSimCallManager");
}
@@ -1205,7 +1231,7 @@ public class TelecomManager {
ITelecomService service = getTelecomService();
if (service != null) {
try {
- return service.getSimCallManager(subscriptionId);
+ return service.getSimCallManager(subscriptionId, mContext.getPackageName());
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelecomService#getSimCallManager");
}
@@ -1229,7 +1255,7 @@ public class TelecomManager {
ITelecomService service = getTelecomService();
if (service != null) {
try {
- return service.getSimCallManagerForUser(userId);
+ return service.getSimCallManagerForUser(userId, mContext.getPackageName());
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelecomService#getSimCallManagerForUser");
}
@@ -1504,7 +1530,7 @@ public class TelecomManager {
ITelecomService service = getTelecomService();
if (service != null) {
try {
- service.registerPhoneAccount(account);
+ service.registerPhoneAccount(account, mContext.getPackageName());
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelecomService#registerPhoneAccount", e);
}
@@ -1520,7 +1546,7 @@ public class TelecomManager {
ITelecomService service = getTelecomService();
if (service != null) {
try {
- service.unregisterPhoneAccount(accountHandle);
+ service.unregisterPhoneAccount(accountHandle, mContext.getPackageName());
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelecomService#unregisterPhoneAccount", e);
}
@@ -1601,7 +1627,7 @@ public class TelecomManager {
ITelecomService service = getTelecomService();
if (service != null) {
try {
- return service.getDefaultDialerPackage();
+ return service.getDefaultDialerPackage(mContext.getPackageName());
} catch (RemoteException e) {
Log.e(TAG, "RemoteException attempting to get the default dialer package name.", e);
}
@@ -1675,7 +1701,7 @@ public class TelecomManager {
ITelecomService service = getTelecomService();
if (service != null) {
try {
- return service.getSystemDialerPackage();
+ return service.getSystemDialerPackage(mContext.getPackageName());
} catch (RemoteException e) {
Log.e(TAG, "RemoteException attempting to get the system dialer package name.", e);
}
@@ -1854,7 +1880,7 @@ public class TelecomManager {
ITelecomService service = getTelecomService();
if (service != null) {
try {
- return service.getCallStateUsingPackage(mContext.getPackageName(),
+ return service.getCallStateUsingPackage(mContext.getOpPackageName(),
mContext.getAttributionTag());
} catch (RemoteException e) {
Log.d(TAG, "RemoteException calling getCallState().", e);
@@ -2071,7 +2097,10 @@ public class TelecomManager {
* For a self-managed {@link ConnectionService}, a {@link SecurityException} will be thrown if
* the {@link PhoneAccount} has {@link PhoneAccount#CAPABILITY_SELF_MANAGED} and the calling app
* does not have {@link android.Manifest.permission#MANAGE_OWN_CALLS}.
- *
+ * <p>
+ * <p>
+ * <b>Note</b>: {@link android.app.Notification.CallStyle} notifications should be posted after
+ * the call is added to Telecom in order for the notification to be non-dismissible.
* @param phoneAccount A {@link PhoneAccountHandle} registered with
* {@link #registerPhoneAccount}.
* @param extras A bundle that will be passed through to
@@ -2088,7 +2117,8 @@ public class TelecomManager {
"acceptHandover for API > O-MR1");
return;
}
- service.addNewIncomingCall(phoneAccount, extras == null ? new Bundle() : extras);
+ service.addNewIncomingCall(phoneAccount, extras == null ? new Bundle() : extras,
+ mContext.getPackageName());
} catch (RemoteException e) {
Log.e(TAG, "RemoteException adding a new incoming call: " + phoneAccount, e);
}
@@ -2130,7 +2160,8 @@ public class TelecomManager {
if (service != null) {
try {
service.addNewIncomingConference(
- phoneAccount, extras == null ? new Bundle() : extras);
+ phoneAccount, extras == null ? new Bundle() : extras,
+ mContext.getPackageName());
} catch (RemoteException e) {
Log.e(TAG, "RemoteException adding a new incoming conference: " + phoneAccount, e);
}
@@ -2317,7 +2348,10 @@ public class TelecomManager {
* {@link PhoneAccount} with the {@link PhoneAccount#CAPABILITY_PLACE_EMERGENCY_CALLS}
* capability, depending on external factors, such as network conditions and Modem/SIM status.
* </p>
- *
+ * <p>
+ * <p>
+ * <b>Note</b>: {@link android.app.Notification.CallStyle} notifications should be posted after
+ * the call is placed in order for the notification to be non-dismissible.
* @param address The address to make the call to.
* @param extras Bundle of extras to use with the call.
*/
@@ -2427,7 +2461,11 @@ public class TelecomManager {
Intent result = null;
if (service != null) {
try {
- result = service.createManageBlockedNumbersIntent();
+ result = service.createManageBlockedNumbersIntent(mContext.getPackageName());
+ if (result != null) {
+ result.prepareToEnterProcess(LOCAL_FLAG_FROM_SYSTEM,
+ mContext.getAttributionSource());
+ }
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelecomService#createManageBlockedNumbersIntent", e);
}
@@ -2449,7 +2487,12 @@ public class TelecomManager {
ITelecomService service = getTelecomService();
if (service != null) {
try {
- return service.createLaunchEmergencyDialerIntent(number);
+ Intent result = service.createLaunchEmergencyDialerIntent(number);
+ if (result != null) {
+ result.prepareToEnterProcess(LOCAL_FLAG_FROM_SYSTEM,
+ mContext.getAttributionSource());
+ }
+ return result;
} catch (RemoteException e) {
Log.e(TAG, "Error createLaunchEmergencyDialerIntent", e);
}
@@ -2584,7 +2627,7 @@ public class TelecomManager {
ITelecomService service = getTelecomService();
if (service != null) {
try {
- service.acceptHandover(srcAddr, videoState, destAcct);
+ service.acceptHandover(srcAddr, videoState, destAcct, mContext.getPackageName());
} catch (RemoteException e) {
Log.e(TAG, "RemoteException acceptHandover: " + e);
}
@@ -2641,6 +2684,117 @@ public class TelecomManager {
}
/**
+ * Add a call to the Android system service Telecom. This allows the system to start tracking an
+ * incoming or outgoing call with the specified {@link CallAttributes}. Once a call is added,
+ * a {@link android.app.Notification.CallStyle} notification should be posted and when the
+ * call is ready to be disconnected, use {@link CallControl#disconnect(DisconnectCause,
+ * Executor, OutcomeReceiver)} which is provided by the
+ * {@code pendingControl#onResult(CallControl)}.
+ * <p>
+ * <p>
+ * <p>
+ * <b>Call Lifecycle</b>: Your app is given foreground execution priority as long as you have a
+ * valid call and are posting a {@link android.app.Notification.CallStyle} notification.
+ * When your application is given foreground execution priority, your app is treated as a
+ * foreground service. Foreground execution priority will prevent the
+ * {@link android.app.ActivityManager} from killing your application when it is placed the
+ * background. Foreground execution priority is removed from your app when all of your app's
+ * calls terminate or your app no longer posts a valid notification.
+ * <p>
+ * <p>
+ * <p>
+ * <b>Note</b>: Only packages that register with
+ * {@link PhoneAccount#CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS}
+ * can utilize this API. {@link PhoneAccount}s that set the capabilities
+ * {@link PhoneAccount#CAPABILITY_SIM_SUBSCRIPTION},
+ * {@link PhoneAccount#CAPABILITY_CALL_PROVIDER},
+ * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}
+ * are not supported and will cause an exception to be thrown.
+ * <p>
+ * <p>
+ * <p>
+ * <b>Usage example:</b>
+ * <pre>
+ * // Its up to your app on how you want to wrap the objects. One such implementation can be:
+ * class MyVoipCall {
+ * ...
+ * public CallControlCallEventCallback handshakes = new CallControlCallback() {
+ * ...
+ * }
+ *
+ * public CallEventCallback events = new CallEventCallback() {
+ * ...
+ * }
+ *
+ * public MyVoipCall(String id){
+ * ...
+ * }
+ * }
+ *
+ * MyVoipCall myFirstOutgoingCall = new MyVoipCall("1");
+ *
+ * telecomManager.addCall(callAttributes,
+ * Runnable::run,
+ * new OutcomeReceiver() {
+ * public void onResult(CallControl callControl) {
+ * // The call has been added successfully. For demonstration
+ * // purposes, the call is disconnected immediately ...
+ * callControl.disconnect(
+ * new DisconnectCause(DisconnectCause.LOCAL) )
+ * }
+ * },
+ * myFirstOutgoingCall.handshakes,
+ * myFirstOutgoingCall.events);
+ * </pre>
+ *
+ * @param callAttributes attributes of the new call (incoming or outgoing, address, etc.)
+ * @param executor execution context to run {@link CallControlCallback} updates on
+ * @param pendingControl Receives the result of addCall transaction. Upon success, a
+ * CallControl object is provided which can be used to do things like
+ * disconnect the call that was added.
+ * @param handshakes callback that receives <b>actionable</b> updates that originate from
+ * Telecom.
+ * @param events callback that receives <b>non</b>-actionable updates that originate
+ * from Telecom.
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_OWN_CALLS)
+ @SuppressLint("SamShouldBeLast")
+ public void addCall(@NonNull CallAttributes callAttributes,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<CallControl, CallException> pendingControl,
+ @NonNull CallControlCallback handshakes,
+ @NonNull CallEventCallback events) {
+ Objects.requireNonNull(callAttributes);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(pendingControl);
+ Objects.requireNonNull(handshakes);
+ Objects.requireNonNull(events);
+
+ ITelecomService service = getTelecomService();
+ if (service != null) {
+ try {
+ // create or add the new call to a service wrapper w/ the same phoneAccountHandle
+ ClientTransactionalServiceWrapper transactionalServiceWrapper =
+ mTransactionalServiceRepository.addNewCallForTransactionalServiceWrapper(
+ callAttributes.getPhoneAccountHandle());
+
+ // couple all the args passed by the client
+ String newCallId = transactionalServiceWrapper.trackCall(callAttributes, executor,
+ pendingControl, handshakes, events);
+
+ // send args to server to process new call
+ service.addCall(callAttributes, transactionalServiceWrapper.getCallEventCallback(),
+ newCallId, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException addCall: " + e);
+ e.rethrowFromSystemServer();
+ }
+ } else {
+ throw new IllegalStateException("Telecom service is not present");
+ }
+ }
+
+ /**
* Handles {@link Intent#ACTION_CALL} intents trampolined from UserCallActivity.
* @param intent The {@link Intent#ACTION_CALL} intent to handle.
* @param callingPackageProxy The original package that called this before it was trampolined.
diff --git a/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceRepository.java b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceRepository.java
new file mode 100644
index 000000000000..2eebbdb35fbb
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceRepository.java
@@ -0,0 +1,90 @@
+/*
+ * 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 com.android.internal.telecom;
+
+import android.telecom.PhoneAccountHandle;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @hide
+ */
+public class ClientTransactionalServiceRepository {
+
+ private static final Map<PhoneAccountHandle, ClientTransactionalServiceWrapper> LOOKUP_TABLE =
+ new ConcurrentHashMap<>();
+
+ /**
+ * creates a new {@link ClientTransactionalServiceWrapper} if this is the first call being
+ * tracked for a particular package Or adds a new call for an existing
+ * {@link ClientTransactionalServiceWrapper}
+ *
+ * @param phoneAccountHandle for a particular package requesting to create a call
+ * @return the {@link ClientTransactionalServiceWrapper} that is tied tot the PhoneAccountHandle
+ */
+ public ClientTransactionalServiceWrapper addNewCallForTransactionalServiceWrapper(
+ PhoneAccountHandle phoneAccountHandle) {
+
+ ClientTransactionalServiceWrapper service = null;
+ if (!hasExistingServiceWrapper(phoneAccountHandle)) {
+ service = new ClientTransactionalServiceWrapper(phoneAccountHandle, this);
+ } else {
+ service = getTransactionalServiceWrapper(phoneAccountHandle);
+ }
+
+ LOOKUP_TABLE.put(phoneAccountHandle, service);
+
+ return service;
+ }
+
+ private ClientTransactionalServiceWrapper getTransactionalServiceWrapper(
+ PhoneAccountHandle pah) {
+ return LOOKUP_TABLE.get(pah);
+ }
+
+ private boolean hasExistingServiceWrapper(PhoneAccountHandle pah) {
+ return LOOKUP_TABLE.containsKey(pah);
+ }
+
+ /**
+ * @param pah that is tied to a particular package with potential tracked calls
+ * @return if the {@link ClientTransactionalServiceWrapper} was successfully removed
+ */
+ public boolean removeServiceWrapper(PhoneAccountHandle pah) {
+ if (!hasExistingServiceWrapper(pah)) {
+ return false;
+ }
+ LOOKUP_TABLE.remove(pah);
+ return true;
+ }
+
+ /**
+ * @param pah that is tied to a particular package with potential tracked calls
+ * @param callId of the TransactionalCall that you want to remove
+ * @return if the call was successfully removed from the service wrapper
+ */
+ public boolean removeCallFromServiceWrapper(PhoneAccountHandle pah, String callId) {
+ if (!hasExistingServiceWrapper(pah)) {
+ return false;
+ }
+ ClientTransactionalServiceWrapper service = LOOKUP_TABLE.get(pah);
+ service.untrackCall(callId);
+ return true;
+ }
+
+}
diff --git a/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
new file mode 100644
index 000000000000..71e9184b7c54
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
@@ -0,0 +1,330 @@
+/*
+ * 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 com.android.internal.telecom;
+
+import static android.telecom.TelecomManager.TELECOM_TRANSACTION_SUCCESS;
+
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.OutcomeReceiver;
+import android.os.ResultReceiver;
+import android.telecom.CallAttributes;
+import android.telecom.CallControl;
+import android.telecom.CallControlCallback;
+import android.telecom.CallEndpoint;
+import android.telecom.CallEventCallback;
+import android.telecom.CallException;
+import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccountHandle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * wraps {@link CallControlCallback}, {@link CallEventCallback}, and {@link CallControl} on a
+ * per-{@link android.telecom.PhoneAccountHandle} basis to track ongoing calls.
+ *
+ * @hide
+ */
+public class ClientTransactionalServiceWrapper {
+
+ private static final String TAG = ClientTransactionalServiceWrapper.class.getSimpleName();
+ private final PhoneAccountHandle mPhoneAccountHandle;
+ private final ClientTransactionalServiceRepository mRepository;
+ private final ConcurrentHashMap<String, TransactionalCall> mCallIdToTransactionalCall =
+ new ConcurrentHashMap<>();
+ private static final String EXECUTOR_FAIL_MSG =
+ "Telecom hit an exception while handling a CallEventCallback on an executor: ";
+
+ public ClientTransactionalServiceWrapper(PhoneAccountHandle handle,
+ ClientTransactionalServiceRepository repo) {
+ mPhoneAccountHandle = handle;
+ mRepository = repo;
+ }
+
+ /**
+ * remove the given call from the class HashMap
+ *
+ * @param callId that is tied to TransactionalCall object
+ */
+ public void untrackCall(String callId) {
+ Log.i(TAG, TextUtils.formatSimple("removeCall: with id=[%s]", callId));
+ if (mCallIdToTransactionalCall.containsKey(callId)) {
+ // remove the call from the hashmap
+ TransactionalCall call = mCallIdToTransactionalCall.remove(callId);
+ // null out interface to avoid memory leaks
+ CallControl control = call.getCallControl();
+ if (control != null) {
+ call.setCallControl(null);
+ }
+ }
+ // possibly cleanup service wrapper if there are no more calls
+ if (mCallIdToTransactionalCall.size() == 0) {
+ mRepository.removeServiceWrapper(mPhoneAccountHandle);
+ }
+ }
+
+ /**
+ * start tracking a newly created call for a particular package
+ *
+ * @param callAttributes of the new call
+ * @param executor to run callbacks on
+ * @param pendingControl that allows telecom to call into the client
+ * @param handshakes that overrides the CallControlCallback
+ * @param events that overrides the CallStateCallback
+ * @return the callId of the newly created call
+ */
+ public String trackCall(CallAttributes callAttributes, Executor executor,
+ OutcomeReceiver<CallControl, CallException> pendingControl,
+ CallControlCallback handshakes,
+ CallEventCallback events) {
+ // generate a new id for this new call
+ String newCallId = UUID.randomUUID().toString();
+
+ // couple the objects passed from the client side
+ mCallIdToTransactionalCall.put(newCallId, new TransactionalCall(newCallId, callAttributes,
+ executor, pendingControl, handshakes, events));
+
+ return newCallId;
+ }
+
+ public ICallEventCallback getCallEventCallback() {
+ return mCallEventCallback;
+ }
+
+ /**
+ * Consumers that is to be completed by the client and the result relayed back to telecom server
+ * side via a {@link ResultReceiver}. see com.android.server.telecom.TransactionalServiceWrapper
+ * for how the response is handled.
+ */
+ private class ReceiverWrapper implements Consumer<Boolean> {
+ private final ResultReceiver mRepeaterReceiver;
+
+ ReceiverWrapper(ResultReceiver resultReceiver) {
+ mRepeaterReceiver = resultReceiver;
+ }
+
+ @Override
+ public void accept(Boolean clientCompletedCallbackSuccessfully) {
+ if (clientCompletedCallbackSuccessfully) {
+ mRepeaterReceiver.send(TELECOM_TRANSACTION_SUCCESS, null);
+ } else {
+ mRepeaterReceiver.send(CallException.CODE_ERROR_UNKNOWN, null);
+ }
+ }
+
+ @Override
+ public Consumer<Boolean> andThen(Consumer<? super Boolean> after) {
+ return Consumer.super.andThen(after);
+ }
+ }
+
+ private final ICallEventCallback mCallEventCallback = new ICallEventCallback.Stub() {
+
+ private static final String ON_SET_ACTIVE = "onSetActive";
+ private static final String ON_SET_INACTIVE = "onSetInactive";
+ private static final String ON_ANSWER = "onAnswer";
+ private static final String ON_DISCONNECT = "onDisconnect";
+ private static final String ON_STREAMING_STARTED = "onStreamingStarted";
+ private static final String ON_REQ_ENDPOINT_CHANGE = "onRequestEndpointChange";
+ private static final String ON_AVAILABLE_CALL_ENDPOINTS = "onAvailableCallEndpointsChanged";
+ private static final String ON_MUTE_STATE_CHANGED = "onMuteStateChanged";
+ private static final String ON_CALL_STREAMING_FAILED = "onCallStreamingFailed";
+ private static final String ON_EVENT = "onEvent";
+
+ private void handleCallEventCallback(String action, String callId,
+ ResultReceiver ackResultReceiver, Object... args) {
+ Log.i(TAG, TextUtils.formatSimple("hCEC: id=[%s], action=[%s]", callId, action));
+ // lookup the callEventCallback associated with the particular call
+ TransactionalCall call = mCallIdToTransactionalCall.get(callId);
+
+ if (call != null) {
+ // Get the CallEventCallback interface
+ CallControlCallback callback = call.getCallControlCallback();
+ // Get Receiver to wait on client ack
+ ReceiverWrapper outcomeReceiverWrapper = new ReceiverWrapper(ackResultReceiver);
+
+ // wait for the client to complete the CallEventCallback
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ call.getExecutor().execute(() -> {
+ switch (action) {
+ case ON_SET_ACTIVE:
+ callback.onSetActive(outcomeReceiverWrapper);
+ break;
+ case ON_SET_INACTIVE:
+ callback.onSetInactive(outcomeReceiverWrapper);
+ break;
+ case ON_DISCONNECT:
+ callback.onDisconnect((DisconnectCause) args[0],
+ outcomeReceiverWrapper);
+ untrackCall(callId);
+ break;
+ case ON_ANSWER:
+ callback.onAnswer((int) args[0], outcomeReceiverWrapper);
+ break;
+ case ON_STREAMING_STARTED:
+ callback.onCallStreamingStarted(outcomeReceiverWrapper);
+ break;
+ }
+ });
+ } catch (Exception e) {
+ Log.e(TAG, EXECUTOR_FAIL_MSG + e);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ @Override
+ public void onAddCallControl(String callId, int resultCode, ICallControl callControl,
+ CallException transactionalException) {
+ Log.i(TAG, TextUtils.formatSimple("oACC: id=[%s], code=[%d]", callId, resultCode));
+ TransactionalCall call = mCallIdToTransactionalCall.get(callId);
+
+ if (call != null) {
+ OutcomeReceiver<CallControl, CallException> pendingControl =
+ call.getPendingControl();
+
+ if (resultCode == TELECOM_TRANSACTION_SUCCESS) {
+
+ // create the interface object that the client will interact with
+ CallControl control = new CallControl(callId, callControl, mRepository,
+ mPhoneAccountHandle);
+ // give the client the object via the OR that was passed into addCall
+ pendingControl.onResult(control);
+
+ // store for later reference
+ call.setCallControl(control);
+ } else {
+ pendingControl.onError(transactionalException);
+ mCallIdToTransactionalCall.remove(callId);
+ }
+
+ } else {
+ untrackCall(callId);
+ Log.e(TAG, "oACC: TransactionalCall object not found for call w/ id=" + callId);
+ }
+ }
+
+ @Override
+ public void onSetActive(String callId, ResultReceiver resultReceiver) {
+ handleCallEventCallback(ON_SET_ACTIVE, callId, resultReceiver);
+ }
+
+ @Override
+ public void onSetInactive(String callId, ResultReceiver resultReceiver) {
+ handleCallEventCallback(ON_SET_INACTIVE, callId, resultReceiver);
+ }
+
+ @Override
+ public void onAnswer(String callId, int videoState, ResultReceiver resultReceiver) {
+ handleCallEventCallback(ON_ANSWER, callId, resultReceiver, videoState);
+ }
+
+ @Override
+ public void onDisconnect(String callId, DisconnectCause cause,
+ ResultReceiver resultReceiver) {
+ handleCallEventCallback(ON_DISCONNECT, callId, resultReceiver, cause);
+ }
+
+ @Override
+ public void onCallEndpointChanged(String callId, CallEndpoint endpoint) {
+ handleEventCallback(callId, ON_REQ_ENDPOINT_CHANGE, endpoint);
+ }
+
+ @Override
+ public void onAvailableCallEndpointsChanged(String callId, List<CallEndpoint> endpoints) {
+ handleEventCallback(callId, ON_AVAILABLE_CALL_ENDPOINTS, endpoints);
+ }
+
+ @Override
+ public void onMuteStateChanged(String callId, boolean isMuted) {
+ handleEventCallback(callId, ON_MUTE_STATE_CHANGED, isMuted);
+ }
+
+ public void handleEventCallback(String callId, String action, Object arg) {
+ Log.d(TAG, TextUtils.formatSimple("hEC: [%s], callId=[%s]", action, callId));
+ // lookup the callEventCallback associated with the particular call
+ TransactionalCall call = mCallIdToTransactionalCall.get(callId);
+ if (call != null) {
+ CallEventCallback callback = call.getCallStateCallback();
+ Executor executor = call.getExecutor();
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> {
+ switch (action) {
+ case ON_REQ_ENDPOINT_CHANGE:
+ callback.onCallEndpointChanged((CallEndpoint) arg);
+ break;
+ case ON_AVAILABLE_CALL_ENDPOINTS:
+ callback.onAvailableCallEndpointsChanged((List<CallEndpoint>) arg);
+ break;
+ case ON_MUTE_STATE_CHANGED:
+ callback.onMuteStateChanged((boolean) arg);
+ break;
+ case ON_CALL_STREAMING_FAILED:
+ callback.onCallStreamingFailed((int) arg /* reason */);
+ break;
+ }
+ });
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ @Override
+ public void removeCallFromTransactionalServiceWrapper(String callId) {
+ untrackCall(callId);
+ }
+
+ @Override
+ public void onCallStreamingStarted(String callId, ResultReceiver resultReceiver) {
+ handleCallEventCallback(ON_STREAMING_STARTED, callId, resultReceiver);
+ }
+
+ @Override
+ public void onCallStreamingFailed(String callId, int reason) {
+ Log.i(TAG, TextUtils.formatSimple("oCSF: id=[%s], reason=[%s]", callId, reason));
+ handleEventCallback(callId, ON_CALL_STREAMING_FAILED, reason);
+ }
+
+ @Override
+ public void onEvent(String callId, String event, Bundle extras) {
+ // lookup the callEventCallback associated with the particular call
+ TransactionalCall call = mCallIdToTransactionalCall.get(callId);
+ if (call != null) {
+ CallEventCallback callback = call.getCallStateCallback();
+ Executor executor = call.getExecutor();
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> {
+ callback.onEvent(event, extras);
+ });
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+ };
+}
diff --git a/telecomm/java/com/android/internal/telecom/ICallControl.aidl b/telecomm/java/com/android/internal/telecom/ICallControl.aidl
new file mode 100644
index 000000000000..5e2c923e4c9c
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecom/ICallControl.aidl
@@ -0,0 +1,36 @@
+/*
+ * 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 com.android.internal.telecom;
+
+import android.os.Bundle;
+import android.telecom.CallControl;
+import android.telecom.CallEndpoint;
+import android.telecom.DisconnectCause;
+import android.os.ResultReceiver;
+
+/**
+ * {@hide}
+ */
+oneway interface ICallControl {
+ void setActive(String callId, in ResultReceiver callback);
+ void answer(int videoState, String callId, in ResultReceiver callback);
+ void setInactive(String callId, in ResultReceiver callback);
+ void disconnect(String callId, in DisconnectCause disconnectCause, in ResultReceiver callback);
+ void startCallStreaming(String callId, in ResultReceiver callback);
+ void requestCallEndpointChange(in CallEndpoint callEndpoint, in ResultReceiver callback);
+ void sendEvent(String callId, String event, in Bundle extras);
+} \ No newline at end of file
diff --git a/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl b/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl
new file mode 100644
index 000000000000..213cafbbf188
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl
@@ -0,0 +1,52 @@
+/*
+ * 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 com.android.internal.telecom;
+
+import android.os.Bundle;
+import android.telecom.CallControl;
+import android.telecom.CallEndpoint;
+import com.android.internal.telecom.ICallControl;
+import android.os.ResultReceiver;
+import android.telecom.CallAudioState;
+import android.telecom.CallException;
+import android.telecom.DisconnectCause;
+import java.util.List;
+
+/**
+ * {@hide}
+ */
+oneway interface ICallEventCallback {
+ // publicly exposed. Client should override
+ void onAddCallControl(String callId, int resultCode, in ICallControl callControl,
+ in CallException exception);
+ // -- Call Event Actions / Call State Transitions
+ void onSetActive(String callId, in ResultReceiver callback);
+ void onSetInactive(String callId, in ResultReceiver callback);
+ void onAnswer(String callId, int videoState, in ResultReceiver callback);
+ void onDisconnect(String callId, in DisconnectCause cause, in ResultReceiver callback);
+ // -- Streaming related. Client registered call streaming capabilities should override
+ void onCallStreamingStarted(String callId, in ResultReceiver callback);
+ void onCallStreamingFailed(String callId, int reason);
+ // -- Audio related.
+ void onCallEndpointChanged(String callId, in CallEndpoint endpoint);
+ void onAvailableCallEndpointsChanged(String callId, in List<CallEndpoint> endpoint);
+ void onMuteStateChanged(String callId, boolean isMuted);
+ // -- Events
+ void onEvent(String callId, String event, in Bundle extras);
+ // hidden methods that help with cleanup
+ void removeCallFromTransactionalServiceWrapper(String callId);
+} \ No newline at end of file
diff --git a/telecomm/java/com/android/internal/telecom/ICallStreamingService.aidl b/telecomm/java/com/android/internal/telecom/ICallStreamingService.aidl
new file mode 100644
index 000000000000..6d53fd25bb45
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecom/ICallStreamingService.aidl
@@ -0,0 +1,35 @@
+/*
+ * 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 com.android.internal.telecom;
+
+import android.telecom.StreamingCall;
+
+import com.android.internal.telecom.IStreamingCallAdapter;
+
+/**
+ * Internal remote interface for call streaming services.
+ *
+ * @see android.telecom.CallStreamingService
+ *
+ * {@hide}
+ */
+oneway interface ICallStreamingService {
+ void setStreamingCallAdapter(in IStreamingCallAdapter streamingCallAdapter);
+ void onCallStreamingStarted(in StreamingCall call);
+ void onCallStreamingStopped();
+ void onCallStreamingStateChanged(int state);
+} \ No newline at end of file
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..8ac016155aa6 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);
@@ -134,4 +139,7 @@ oneway interface IConnectionServiceAdapter {
void setConferenceState(String callId, boolean isConference, in Session.Info sessionInfo);
void setCallDirection(String callId, int direction, in Session.Info sessionInfo);
+
+ void queryLocation(String callId, long timeoutMillis, String provider,
+ in ResultReceiver callback, 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);
diff --git a/telecomm/java/com/android/internal/telecom/IStreamingCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IStreamingCallAdapter.aidl
new file mode 100644
index 000000000000..51424a66d0df
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecom/IStreamingCallAdapter.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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 com.android.internal.telecom;
+
+/**
+ * Internal remote callback interface for call streaming services.
+ *
+ * @see android.telecom.StreamingCallAdapter
+ *
+ * {@hide}
+ */
+oneway interface IStreamingCallAdapter {
+ void setStreamingState(int state);
+} \ No newline at end of file
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index 74b5545e75de..fdcb9749c38e 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -25,6 +25,8 @@ import android.os.Bundle;
import android.os.UserHandle;
import android.telecom.PhoneAccount;
import android.content.pm.ParceledListSlice;
+import android.telecom.CallAttributes;
+import com.android.internal.telecom.ICallEventCallback;
/**
* Interface used to interact with Telecom. Mostly this is used by TelephonyManager for passing
@@ -107,22 +109,22 @@ interface ITelecomService {
/**
* @see TelecomServiceImpl#getSimCallManager
*/
- PhoneAccountHandle getSimCallManager(int subId);
+ PhoneAccountHandle getSimCallManager(int subId, String callingPackage);
/**
* @see TelecomServiceImpl#getSimCallManagerForUser
*/
- PhoneAccountHandle getSimCallManagerForUser(int userId);
+ PhoneAccountHandle getSimCallManagerForUser(int userId, String callingPackage);
/**
* @see TelecomServiceImpl#registerPhoneAccount
*/
- void registerPhoneAccount(in PhoneAccount metadata);
+ void registerPhoneAccount(in PhoneAccount metadata, String callingPackage);
/**
* @see TelecomServiceImpl#unregisterPhoneAccount
*/
- void unregisterPhoneAccount(in PhoneAccountHandle account);
+ void unregisterPhoneAccount(in PhoneAccountHandle account, String callingPackage);
/**
* @see TelecomServiceImpl#clearAccounts
@@ -155,7 +157,7 @@ interface ITelecomService {
/**
* @see TelecomServiceImpl#getDefaultDialerPackage
*/
- String getDefaultDialerPackage();
+ String getDefaultDialerPackage(String callingPackage);
/**
* @see TelecomServiceImpl#getDefaultDialerPackage
@@ -165,7 +167,7 @@ interface ITelecomService {
/**
* @see TelecomServiceImpl#getSystemDialerPackage
*/
- String getSystemDialerPackage();
+ String getSystemDialerPackage(String callingPackage);
/**
* @see TelecomServiceImpl#dumpCallAnalytics
@@ -263,12 +265,15 @@ interface ITelecomService {
/**
* @see TelecomServiceImpl#addNewIncomingCall
*/
- void addNewIncomingCall(in PhoneAccountHandle phoneAccount, in Bundle extras);
+ void addNewIncomingCall(in PhoneAccountHandle phoneAccount, in Bundle extras,
+ String callingPackage);
/**
* @see TelecomServiceImpl#addNewIncomingConference
*/
- void addNewIncomingConference(in PhoneAccountHandle phoneAccount, in Bundle extras);
+ void addNewIncomingConference(in PhoneAccountHandle phoneAccount, in Bundle extras,
+ String callingPackage);
+
/**
* @see TelecomServiceImpl#addNewUnknownCall
@@ -304,7 +309,7 @@ interface ITelecomService {
/**
* @see TelecomServiceImpl#createManageBlockedNumbersIntent
**/
- Intent createManageBlockedNumbersIntent();
+ Intent createManageBlockedNumbersIntent(String callingPackage);
/**
* @see TelecomServiceImpl#createLaunchEmergencyDialerIntent
@@ -331,7 +336,8 @@ interface ITelecomService {
/**
* @see TelecomServiceImpl#acceptHandover
*/
- void acceptHandover(in Uri srcAddr, int videoState, in PhoneAccountHandle destAcct);
+ void acceptHandover(in Uri srcAddr, int videoState, in PhoneAccountHandle destAcct,
+ String callingPackage);
/**
* @see TelecomServiceImpl#setTestEmergencyPhoneAccountPackageNameFilter
@@ -387,4 +393,10 @@ interface ITelecomService {
*/
boolean isInSelfManagedCall(String packageName, in UserHandle userHandle,
String callingPackage);
+
+ /**
+ * @see TelecomServiceImpl#addCall
+ */
+ void addCall(in CallAttributes callAttributes, in ICallEventCallback callback, String callId,
+ String callingPackage);
}
diff --git a/telecomm/java/com/android/internal/telecom/TransactionalCall.java b/telecomm/java/com/android/internal/telecom/TransactionalCall.java
new file mode 100644
index 000000000000..75f9d35470db
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecom/TransactionalCall.java
@@ -0,0 +1,85 @@
+/*
+ * 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 com.android.internal.telecom;
+
+import android.os.OutcomeReceiver;
+import android.telecom.CallAttributes;
+import android.telecom.CallControl;
+import android.telecom.CallControlCallback;
+import android.telecom.CallEventCallback;
+import android.telecom.CallException;
+
+import java.util.concurrent.Executor;
+
+/**
+ * @hide
+ */
+public class TransactionalCall {
+
+ private final String mCallId;
+ private final CallAttributes mCallAttributes;
+ private final Executor mExecutor;
+ private final OutcomeReceiver<CallControl, CallException> mPendingControl;
+ private final CallControlCallback mCallControlCallback;
+ private final CallEventCallback mCallStateCallback;
+ private CallControl mCallControl;
+
+ public TransactionalCall(String callId, CallAttributes callAttributes,
+ Executor executor, OutcomeReceiver<CallControl, CallException> pendingControl,
+ CallControlCallback callControlCallback,
+ CallEventCallback callStateCallback) {
+ mCallId = callId;
+ mCallAttributes = callAttributes;
+ mExecutor = executor;
+ mPendingControl = pendingControl;
+ mCallControlCallback = callControlCallback;
+ mCallStateCallback = callStateCallback;
+ }
+
+
+ public void setCallControl(CallControl callControl) {
+ mCallControl = callControl;
+ }
+
+ public CallControl getCallControl() {
+ return mCallControl;
+ }
+
+ public String getCallId() {
+ return mCallId;
+ }
+
+ public CallAttributes getCallAttributes() {
+ return mCallAttributes;
+ }
+
+ public Executor getExecutor() {
+ return mExecutor;
+ }
+
+ public OutcomeReceiver<CallControl, CallException> getPendingControl() {
+ return mPendingControl;
+ }
+
+ public CallControlCallback getCallControlCallback() {
+ return mCallControlCallback;
+ }
+
+ public CallEventCallback getCallStateCallback() {
+ return mCallStateCallback;
+ }
+}