Add API for cross device calling.
Test: CTS test
Change-Id: I1a3aa6c1ae6d445a2f3b55e5f0d11918da5bed33
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index ce9530c..02c1379 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -43,6 +43,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
/**
@@ -571,7 +572,7 @@
public static final int CAPABILITY_REMOTE_PARTY_SUPPORTS_RTT = 0x10000000;
//******************************************************************************************
- // Next CAPABILITY value: 0x20000000
+ // Next CAPABILITY value: 0x40000000
//******************************************************************************************
/**
@@ -733,6 +734,8 @@
private final String mContactDisplayName;
private final @CallDirection int mCallDirection;
private final @Connection.VerificationStatus int mCallerNumberVerificationStatus;
+ private final CallEndpoint mActiveCallEndpoint;
+ private final Set<CallEndpoint> mAvailableCallEndpoint;
/**
* Whether the supplied capabilities supports the specified capability.
@@ -1116,32 +1119,52 @@
return mCallerNumberVerificationStatus;
}
+ /**
+ * Return set of available {@link CallEndpoint} which can be used to push or answer this
+ * call via {@link #pushCall(CallEndpoint)} or {@link #answerCall(CallEndpoint, int)}.
+ * @return Set of available call endpoints.
+ */
+ public @NonNull Set<CallEndpoint> getAvailableCallEndpoints() {
+ return mAvailableCallEndpoint;
+ }
+
+ /**
+ * Return the {@link CallEndpoint} which is currently active for a call. If the call does
+ * not take place via any {@link CallEndpoint}, return {@code null}.
+ * @return Current active endpoint.
+ */
+ public @Nullable CallEndpoint getActiveCallEndpoint() {
+ return mActiveCallEndpoint;
+ }
+
@Override
public boolean equals(Object o) {
if (o instanceof Details) {
Details d = (Details) o;
return
- Objects.equals(mState, d.mState) &&
- Objects.equals(mHandle, d.mHandle) &&
- Objects.equals(mHandlePresentation, d.mHandlePresentation) &&
- Objects.equals(mCallerDisplayName, d.mCallerDisplayName) &&
- Objects.equals(mCallerDisplayNamePresentation,
- d.mCallerDisplayNamePresentation) &&
- Objects.equals(mAccountHandle, d.mAccountHandle) &&
- Objects.equals(mCallCapabilities, d.mCallCapabilities) &&
- Objects.equals(mCallProperties, d.mCallProperties) &&
- Objects.equals(mDisconnectCause, d.mDisconnectCause) &&
- Objects.equals(mConnectTimeMillis, d.mConnectTimeMillis) &&
- Objects.equals(mGatewayInfo, d.mGatewayInfo) &&
- Objects.equals(mVideoState, d.mVideoState) &&
- Objects.equals(mStatusHints, d.mStatusHints) &&
- areBundlesEqual(mExtras, d.mExtras) &&
- areBundlesEqual(mIntentExtras, d.mIntentExtras) &&
- Objects.equals(mCreationTimeMillis, d.mCreationTimeMillis) &&
- Objects.equals(mContactDisplayName, d.mContactDisplayName) &&
- Objects.equals(mCallDirection, d.mCallDirection) &&
- Objects.equals(mCallerNumberVerificationStatus,
- d.mCallerNumberVerificationStatus);
+ Objects.equals(mState, d.mState)
+ && Objects.equals(mHandle, d.mHandle)
+ && Objects.equals(mHandlePresentation, d.mHandlePresentation)
+ && Objects.equals(mCallerDisplayName, d.mCallerDisplayName)
+ && Objects.equals(mCallerDisplayNamePresentation,
+ d.mCallerDisplayNamePresentation)
+ && Objects.equals(mAccountHandle, d.mAccountHandle)
+ && Objects.equals(mCallCapabilities, d.mCallCapabilities)
+ && Objects.equals(mCallProperties, d.mCallProperties)
+ && Objects.equals(mDisconnectCause, d.mDisconnectCause)
+ && Objects.equals(mConnectTimeMillis, d.mConnectTimeMillis)
+ && Objects.equals(mGatewayInfo, d.mGatewayInfo)
+ && Objects.equals(mVideoState, d.mVideoState)
+ && Objects.equals(mStatusHints, d.mStatusHints)
+ && areBundlesEqual(mExtras, d.mExtras)
+ && areBundlesEqual(mIntentExtras, d.mIntentExtras)
+ && Objects.equals(mCreationTimeMillis, d.mCreationTimeMillis)
+ && Objects.equals(mContactDisplayName, d.mContactDisplayName)
+ && Objects.equals(mCallDirection, d.mCallDirection)
+ && Objects.equals(mCallerNumberVerificationStatus,
+ d.mCallerNumberVerificationStatus)
+ && Objects.equals(mActiveCallEndpoint, d.mActiveCallEndpoint)
+ && Objects.equals(mAvailableCallEndpoint, d.mAvailableCallEndpoint);
}
return false;
}
@@ -1190,7 +1213,9 @@
long creationTimeMillis,
String contactDisplayName,
int callDirection,
- int callerNumberVerificationStatus) {
+ int callerNumberVerificationStatus,
+ CallEndpoint activeCallEndpoint,
+ Set<CallEndpoint> availableCallEndpoints) {
mState = state;
mTelecomCallId = telecomCallId;
mHandle = handle;
@@ -1211,6 +1236,8 @@
mContactDisplayName = contactDisplayName;
mCallDirection = callDirection;
mCallerNumberVerificationStatus = callerNumberVerificationStatus;
+ mActiveCallEndpoint = activeCallEndpoint;
+ mAvailableCallEndpoint = availableCallEndpoints;
}
/** {@hide} */
@@ -1235,7 +1262,9 @@
parcelableCall.getCreationTimeMillis(),
parcelableCall.getContactDisplayName(),
parcelableCall.getCallDirection(),
- parcelableCall.getCallerNumberVerificationStatus());
+ parcelableCall.getCallerNumberVerificationStatus(),
+ parcelableCall.getActiveCallEndpoint(),
+ parcelableCall.getAvailableCallEndpoints());
}
@Override
@@ -1257,6 +1286,10 @@
sb.append(capabilitiesToString(mCallCapabilities));
sb.append(", props: ");
sb.append(propertiesToString(mCallProperties));
+ sb.append(", activeEndpoint: ");
+ sb.append(mActiveCallEndpoint);
+ sb.append(", availableEndpoints: ");
+ sb.append(mAvailableCallEndpoint);
sb.append("]");
return sb.toString();
}
@@ -1356,6 +1389,121 @@
public static final int HANDOVER_FAILURE_UNKNOWN = 5;
/**
+ * @hide
+ */
+ @IntDef(prefix = { "PUSH_FAILED_" },
+ value = {PUSH_FAILED_UNKNOWN_REASON, PUSH_FAILED_ENDPOINT_UNAVAILABLE,
+ PUSH_FAILED_ENDPOINT_TIMEOUT, PUSH_FAILED_ENDPOINT_REJECTED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PushFailedReason {}
+
+ /**
+ * Answer failure reason returned via {@link #onAnswerFailed(CallEndpoint, int)} when a push
+ * fails due to unknown reason.
+ * <p>
+ * For more information on push call, see {@link #pushCall(CallEndpoint)}.
+ */
+ public static final int PUSH_FAILED_UNKNOWN_REASON = 0;
+
+ /**
+ * Push failure reason returned via {@link #onCallPushFailed(CallEndpoint, int)} when a push
+ * fails due to requested endpoint is unavailable.
+ * <p>
+ * For more information on push call, see {@link #pushCall(CallEndpoint)}.
+ */
+ public static final int PUSH_FAILED_ENDPOINT_UNAVAILABLE = 1;
+
+ /**
+ * Push failure reason returned via {@link #onCallPushFailed(CallEndpoint, int)} when a push
+ * fails due to requested endpoint takes too long to handle the request.
+ * <p>
+ * For more information on push call, see {@link #pushCall(CallEndpoint)}.
+ */
+ public static final int PUSH_FAILED_ENDPOINT_TIMEOUT = 2;
+
+ /**
+ * Push failure reason returned via {@link #onCallPushFailed(CallEndpoint, int)} when a push
+ * fails due to endpoint rejected the request.
+ * <p>
+ * For more information on push call, see {@link #pushCall(CallEndpoint)}.
+ */
+ public static final int PUSH_FAILED_ENDPOINT_REJECTED = 3;
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = { "ANSWER_FAILED_" },
+ value = {ANSWER_FAILED_UNKNOWN_REASON, ANSWER_FAILED_ENDPOINT_UNAVAILABLE,
+ ANSWER_FAILED_ENDPOINT_TIMEOUT, ANSWER_FAILED_ENDPOINT_REJECTED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AnswerFailedReason {}
+
+ /**
+ * Answer failure reason returned via {@link #onAnswerFailed(CallEndpoint, int)} when it
+ * fails due to unknown reason.
+ * <p>
+ * For more information on answer call, see {@link #answerCall(CallEndpoint, int)}.
+ */
+ public static final int ANSWER_FAILED_UNKNOWN_REASON = 0;
+
+ /**
+ * Answer failure reason returned via {@link #onAnswerFailed(CallEndpoint, int)} when it
+ * fails due to requested endpoint is unavailable.
+ * <p>
+ * For more information on answer call, see {@link #answerCall(CallEndpoint, int)}.
+ */
+ public static final int ANSWER_FAILED_ENDPOINT_UNAVAILABLE = 1;
+
+ /**
+ * Answer failure reason returned via {@link #onAnswerFailed(CallEndpoint, int)} when it
+ * fails due to requested endpoint takes too long to handle the request.
+ * <p>
+ * For more information on answer call, see {@link #answerCall(CallEndpoint, int)}.
+ */
+ public static final int ANSWER_FAILED_ENDPOINT_TIMEOUT = 2;
+
+ /**
+ * Answer failure reason returned via {@link #onAnswerFailed(CallEndpoint, int)} when it
+ * fails due to endpoint rejected the request.
+ * <p>
+ * For more information on answer call, see {@link #answerCall(CallEndpoint, int)}.
+ */
+ public static final int ANSWER_FAILED_ENDPOINT_REJECTED = 3;
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = { "PULL_FAILED_" },
+ value = {PULL_FAILED_UNKNOWN_REASON, PULL_FAILED_ENDPOINT_TIMEOUT,
+ PULL_FAILED_ENDPOINT_REJECTED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PullFailedReason {}
+
+ /**
+ * Pull failure reason returned via {@link #onCallPullFailed(int)} when it fails due to
+ * unknown reason.
+ * <p>
+ * For more information on pull call, see {@link #pullCall()}.
+ */
+ public static final int PULL_FAILED_UNKNOWN_REASON = 0;
+
+ /**
+ * Pull failure reason returned via {@link #onCallPullFailed(int)} when it fails due to
+ * requested endpoint takes too long to handle the request.
+ * <p>
+ * For more information on pull call, see {@link #pullCall()}.
+ */
+ public static final int PULL_FAILED_ENDPOINT_TIMEOUT = 1;
+
+ /**
+ * Pull failure reason returned via {@link #onCallPullFailed(int)} when it fails due to
+ * endpoint rejected the request.
+ * <p>
+ * For more information on pull call, see {@link #pullCall()}.
+ */
+ public static final int PULL_FAILED_ENDPOINT_REJECTED = 2;
+
+ /**
* Invoked when the state of this {@code Call} has changed. See {@link #getState()}.
*
* @param call The {@code Call} invoking this method.
@@ -1515,6 +1663,31 @@
* @param failureReason Error reason for failure.
*/
public void onHandoverFailed(Call call, @HandoverFailureErrors int failureReason) {}
+
+ /**
+ * Invoked when call push request via {@link #pushCall(CallEndpoint)} has failed.
+ *
+ * @param endpoint The endpoint requested to push the call to.
+ * @param reason Failed reason.
+ */
+ public void onCallPushFailed(@NonNull CallEndpoint endpoint, @PushFailedReason int reason)
+ {}
+
+ /**
+ * Invoked when answer call request via {@link #answerCall(CallEndpoint, int)} has failed.
+ *
+ * @param endpoint The endpoint requested to answer the call.
+ * @param reason Failed reason
+ */
+ public void onAnswerFailed(@NonNull CallEndpoint endpoint, @AnswerFailedReason int reason)
+ {}
+
+ /**
+ * Invoked when pull call request via {@link #pullCall()} has failed.
+ *
+ * @param reason Failed reason
+ */
+ public void onCallPullFailed(@PullFailedReason int reason) {}
}
/**
@@ -1936,8 +2109,21 @@
}
/**
+ * @deprecated Use {@link #pullCall()} instead
+ */
+ @Deprecated
+ public void pullExternalCall() {
+ // If this isn't an external call, ignore the request.
+ if (!mDetails.hasProperty(Details.PROPERTY_IS_EXTERNAL_CALL)) {
+ return;
+ }
+
+ mInCallAdapter.pullExternalCall(mTelecomCallId);
+ }
+
+ /**
* Initiates a request to the {@link ConnectionService} to pull an external call to the local
- * device.
+ * device, or to bring a tethered call back to the local device.
* <p>
* Calls to this method are ignored if the call does not have the
* {@link Call.Details#PROPERTY_IS_EXTERNAL_CALL} property set.
@@ -1946,13 +2132,34 @@
* {@link TelecomManager#METADATA_INCLUDE_EXTERNAL_CALLS} metadata set to {@code true}
* in its manifest.
*/
- public void pullExternalCall() {
- // If this isn't an external call, ignore the request.
- if (!mDetails.hasProperty(Details.PROPERTY_IS_EXTERNAL_CALL)) {
- return;
- }
+ public void pullCall() {
+ pullExternalCall();
+ }
- mInCallAdapter.pullExternalCall(mTelecomCallId);
+ /**
+ * Initiates a request to the {@link ConnectionService} to push a call to a
+ * {@link CallEndpoint}.
+ * <p>
+ *
+ * @param endpoint The call endpoint to which the call will be pushed.
+ */
+ public void pushCall(@NonNull CallEndpoint endpoint) {
+ mInCallAdapter.pushCall(mTelecomCallId, endpoint);
+ }
+
+ /**
+ * Initiates a request to the {@link ConnectionService} to answer a call to a
+ * {@link CallEndpoint}.
+ * <p>
+ * Calls to this method are ignored if the call does not have the
+ * {@link Call.Details#CAPABILITY_CAN_PULL_CALL} capability set.
+ *
+ * @param endpoint The call endpoint on which to answer the call.
+ * @param videoState The video state in which to answer the call.
+ */
+ public void answerCall(@NonNull CallEndpoint endpoint,
+ @VideoProfile.VideoState int videoState) {
+ mInCallAdapter.answerCall(mTelecomCallId, endpoint, videoState);
}
/**
@@ -2633,7 +2840,9 @@
mDetails.getCreationTimeMillis(),
mDetails.getContactDisplayName(),
mDetails.getCallDirection(),
- mDetails.getCallerNumberVerificationStatus()
+ mDetails.getCallerNumberVerificationStatus(),
+ mDetails.getActiveCallEndpoint(),
+ mDetails.getAvailableCallEndpoints()
);
fireDetailsChanged(mDetails);
}
@@ -2675,7 +2884,7 @@
}
/** {@hide} */
- final void internalOnHandoverComplete() {
+ void internalOnHandoverComplete() {
for (CallbackRecord<Callback> record : mCallbackRecords) {
final Call call = this;
final Callback callback = record.getCallback();
@@ -2683,6 +2892,32 @@
}
}
+ /** {@hide} */
+ void internalOnCallPullFailed(@Callback.PullFailedReason int reason) {
+ for (CallbackRecord<Callback> record : mCallbackRecords) {
+ final Callback callback = record.getCallback();
+ record.getHandler().post(() -> callback.onCallPullFailed(reason));
+ }
+ }
+
+ /** {@hide} */
+ void internalOnCallPushFailed(CallEndpoint callEndpoint,
+ @Callback.PushFailedReason int reason) {
+ for (CallbackRecord<Callback> record : mCallbackRecords) {
+ final Callback callback = record.getCallback();
+ record.getHandler().post(() -> callback.onCallPushFailed(callEndpoint, reason));
+ }
+ }
+
+ /** {@hide} */
+ void internalOnAnswerFailed(CallEndpoint callEndpoint,
+ @Callback.AnswerFailedReason int reason) {
+ for (CallbackRecord<Callback> record : mCallbackRecords) {
+ final Callback callback = record.getCallback();
+ record.getHandler().post(() -> callback.onAnswerFailed(callEndpoint, reason));
+ }
+ }
+
private void fireStateChanged(final int newState) {
for (CallbackRecord<Callback> record : mCallbackRecords) {
final Call call = this;
diff --git a/telecomm/java/android/telecom/CallEndpoint.aidl b/telecomm/java/android/telecom/CallEndpoint.aidl
new file mode 100644
index 0000000..5030ffd
--- /dev/null
+++ b/telecomm/java/android/telecom/CallEndpoint.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021 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;
diff --git a/telecomm/java/android/telecom/CallEndpoint.java b/telecomm/java/android/telecom/CallEndpoint.java
new file mode 100644
index 0000000..dc70656
--- /dev/null
+++ b/telecomm/java/android/telecom/CallEndpoint.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2021 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.content.ComponentName;
+import android.os.Parcel;
+import android.os.ParcelUuid;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Represents the endpoint on which a call can be carried by the user.
+ *
+ * For example, the user may be able to carry out a call on another device on their local network
+ * using a call streaming solution, or may be able to carry out a call on another device registered
+ * with the same mobile line of service.
+ */
+public final class CallEndpoint implements Parcelable {
+ /**
+ * @hide
+ */
+ @IntDef(prefix = {"ENDPOINT_TYPE_"},
+ value = {ENDPOINT_TYPE_TETHERED, ENDPOINT_TYPE_UNTETHERED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EndpointType {}
+
+ /** Indicates the endpoint contains a complete calling stack and is capable of carrying out a
+ * call on its own. Untethered endpoints are typically other devices which share the same
+ * mobile line of service as the current device.
+ */
+ public static final int ENDPOINT_TYPE_UNTETHERED = 1;
+
+ /** Indicates the endpoint itself doesn't have the required calling infrastructure in order to
+ * complete a call on its own. Tethered endpoints depend on a call streaming solution to
+ * transport the media and control for a call to another device, while depending on the current
+ * device to connect the call to the mobile network.
+ */
+ public static final int ENDPOINT_TYPE_TETHERED = 2;
+
+ private final ParcelUuid mUuid;
+ private CharSequence mDescription;
+ private final int mType;
+ private final ComponentName mComponentName;
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ mUuid.writeToParcel(dest, flags);
+ dest.writeCharSequence(mDescription);
+ dest.writeInt(mType);
+ mComponentName.writeToParcel(dest, flags);
+ }
+
+ public static final @android.annotation.NonNull Creator<CallEndpoint> CREATOR =
+ new Creator<CallEndpoint>() {
+ @Override
+ public CallEndpoint createFromParcel(Parcel in) {
+ return new CallEndpoint(in);
+ }
+
+ @Override
+ public CallEndpoint[] newArray(int size) {
+ return new CallEndpoint[size];
+ }
+ };
+
+ public CallEndpoint(@NonNull ParcelUuid uuid, @NonNull CharSequence description, int type,
+ @NonNull ComponentName componentName) {
+ mUuid = uuid;
+ mDescription = description;
+ mType = type;
+ mComponentName = componentName;
+ }
+
+ private CallEndpoint(@NonNull Parcel in) {
+ this(ParcelUuid.CREATOR.createFromParcel(in), in.readCharSequence(), in.readInt(),
+ ComponentName.CREATOR.createFromParcel(in));
+ }
+
+ /**
+ * A unique identifier for this call endpoint. An endpoint provider should take care to use an
+ * identifier which is stable for the current association between an endpoint and the current
+ * device, but which is not globally identifying.
+ * @return the unique identifier.
+ */
+ public @NonNull ParcelUuid getIdentifier() {
+ return mUuid;
+ }
+
+ /**
+ * A human-readable description of this {@link CallEndpoint}. An {@link InCallService} uses
+ * when informing the user of the endpoint.
+ * @return the description.
+ */
+ public @NonNull CharSequence getDescription() {
+ return mDescription;
+ }
+
+ public @EndpointType int getType() {
+ return mType;
+ }
+
+ /**
+ * @hide
+ */
+ public @NonNull ComponentName getComponentName() {
+ return mComponentName;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof CallEndpoint) {
+ CallEndpoint d = (CallEndpoint) o;
+ return Objects.equals(mUuid, d.mUuid)
+ && Objects.equals(mDescription, d.mDescription)
+ && Objects.equals(mType, d.mType)
+ && Objects.equals(mComponentName, d.mComponentName);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mUuid, mDescription, mType, mComponentName);
+ }
+}
diff --git a/telecomm/java/android/telecom/CallEndpointCallback.java b/telecomm/java/android/telecom/CallEndpointCallback.java
new file mode 100644
index 0000000..6ba55f1
--- /dev/null
+++ b/telecomm/java/android/telecom/CallEndpointCallback.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2021 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;
+
+/**
+ * Provides callbacks from telecom to the cross device call streaming app with lifecycle events
+ * related to an {@link CallEndpointSession}.
+ */
+public interface CallEndpointCallback {
+ /**
+ * Invoked by telecom when a {@link CallEndpointSession} is started but the streaming app has
+ * not activated the endpoint in a timely manner and the framework deems the activation request
+ * to have timed out.
+ */
+ void onCallEndpointSessionActivationTimeout();
+
+ /**
+ * Invoked by telecom when {@link CallEndpointSession#setCallEndpointSessionDeactivated()}
+ * called by a cross device call streaming app, or when the app uninstalled. When a tethered
+ * {@link CallEndpoint} is deactivated, the call streaming app should clean up any
+ * audio/network resources and stop relaying call controls from the endpoint.
+ */
+ void onCallEndpointSessionDeactivated();
+}
diff --git a/telecomm/java/android/telecom/CallEndpointSession.java b/telecomm/java/android/telecom/CallEndpointSession.java
new file mode 100644
index 0000000..1e7b30c
--- /dev/null
+++ b/telecomm/java/android/telecom/CallEndpointSession.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2021 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.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import com.android.internal.telecom.ICallEndpointSession;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+
+/**
+ * Provides method and necessary information for cross device call streaming app to streams calls
+ * and updates to the status of the endpoint.
+ *
+ */
+public class CallEndpointSession {
+ /**
+ * Indicates that this call endpoint session is activated by
+ * {@link Call#answerCall(CallEndpoint, int)} from the original device.
+ */
+ public static final int ANSWER_REQUEST = 1;
+
+ /**
+ * Indicates that this call endpoint session is activated by {@link Call#pushCall(CallEndpoint)}
+ * from the original device.
+ */
+ public static final int PUSH_REQUEST = 2;
+
+ /**
+ * Indicates that this call endpoint session is activated by
+ * {@link TelecomManager#placeCall(Uri, Bundle)} with extra
+ * {@link TelecomManager#EXTRA_START_CALL_ON_ENDPOINT} set.
+ */
+ public static final int PLACE_REQUEST = 3;
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = {"ACTIVATION_FAILURE_"},
+ value = {ACTIVATION_FAILURE_REJECTED, ACTIVATION_FAILURE_UNAVAILABLE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ActivationFailureReason {}
+ /**
+ * Used as reason for {@link #setCallEndpointSessionActivationFailed(int)} to inform the
+ * endpoint is no longer present on the network.
+ */
+ public static final int ACTIVATION_FAILURE_UNAVAILABLE = 0;
+
+ /**
+ * Used as reason for {@link #setCallEndpointSessionActivationFailed(int)} to inform the
+ * remote endpoint rejected the request to start streaming a cross device call.
+ */
+ public static final int ACTIVATION_FAILURE_REJECTED = 1;
+
+ private final ICallEndpointSession mCallEndpointSession;
+
+ /**
+ * {@hide}
+ */
+ public CallEndpointSession(ICallEndpointSession callEndpointSession) {
+ mCallEndpointSession = callEndpointSession;
+ }
+
+ /**
+ * Invoked by cross device call streaming app to inform telecom stack that the call endpoint is
+ * now activated and that the call is being streamed to the endpoint.
+ */
+ public void setCallEndpointSessionActivated() {
+ try {
+ mCallEndpointSession.setCallEndpointSessionActivated();
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Invoked by cross device call streaming app to inform telecom stack that the call endpoint
+ * could not be activated due to error.
+ * Possible errors are:
+ * <ul>
+ * <li>{@link #ACTIVATION_FAILURE_UNAVAILABLE}</li>
+ * <li>{@link #ACTIVATION_FAILURE_REJECTED}</li>
+ * </ul>
+ *
+ * @param reason The reason for activation failure
+ */
+ public void setCallEndpointSessionActivationFailed(@ActivationFailureReason int reason) {
+ try {
+ mCallEndpointSession.setCallEndpointSessionActivationFailed(reason);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Invoked by cross device call streaming app to inform telecom stack that the call endpoint is
+ * no longer active.
+ */
+ public void setCallEndpointSessionDeactivated() {
+ try {
+ mCallEndpointSession.setCallEndpointSessionDeactivated();
+ } catch (RemoteException e) {
+ }
+ }
+}
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 30d4959..21a1804 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -561,15 +561,6 @@
*/
public static final int PROPERTY_CROSS_SIM = 1 << 13;
- /**
- * Connection is a tethered external call.
- * <p>
- * Indicates that the {@link Connection} is fixed on this device but the audio streams are
- * re-routed to another device.
- * <p>
- */
- public static final int PROPERTY_TETHERED_CALL = 1 << 14;
-
//**********************************************************************************************
// Next PROPERTY value: 1<<14
//**********************************************************************************************
diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java
index 0f034ad..63b9548 100644
--- a/telecomm/java/android/telecom/DisconnectCause.java
+++ b/telecomm/java/android/telecom/DisconnectCause.java
@@ -111,6 +111,22 @@
*/
public static final String REASON_EMERGENCY_CALL_PLACED = "REASON_EMERGENCY_CALL_PLACED";
+ /**
+ * This reason is set when an call is ended due to {@link CallEndpoint} rejection.
+ * This reason string should only be associated with the {@link #LOCAL} disconnect code returned
+ * from {@link #getCode()}.
+ */
+ public static final String REASON_ENDPOINT_REJECTED = "REASON_ENDPOINT_REJECTED";
+
+ /**
+ * This reason is set when a call is ended due to {@link CallEndpoint} deactivated by
+ * call disconnection or user terminated streaming.
+ * This reason string should only be associated with the {@link #LOCAL} disconnect code returned
+ * from {@link #getCode()}
+ */
+ public static final String REASON_ENDPOINT_SESSION_DEACTIVATED =
+ "REASON_ENDPOINT_SESSION_DEACTIVATED";
+
private int mDisconnectCode;
private CharSequence mDisconnectLabel;
private CharSequence mDisconnectDescription;
diff --git a/telecomm/java/android/telecom/InCallAdapter.java b/telecomm/java/android/telecom/InCallAdapter.java
index ab35aff..34e9942 100755
--- a/telecomm/java/android/telecom/InCallAdapter.java
+++ b/telecomm/java/android/telecom/InCallAdapter.java
@@ -373,6 +373,34 @@
}
/**
+ * Instructs Telecom to push a call to the given endpoint.
+ *
+ * @param callId The callId to push.
+ * @param callEndpoint The endpoint to which the call will be pushed.
+ */
+ public void pushCall(String callId, CallEndpoint callEndpoint) {
+ try {
+ mAdapter.pushCall(callId, callEndpoint);
+ } catch (RemoteException ignored) {
+ }
+ }
+
+ /**
+ * Instructs Telecom to answer a call via the given endpoint.
+ *
+ * @param callId The callId to push.
+ * @param callEndpoint The endpoint on which the call will be answered.
+ * @param videoState The video state in which to answer the call.
+ */
+ public void answerCall(String callId, CallEndpoint callEndpoint,
+ @VideoProfile.VideoState int videoState) {
+ try {
+ mAdapter.answerCallViaEndpoint(callId, callEndpoint, videoState);
+ } catch (RemoteException ignored) {
+ }
+ }
+
+ /**
* Intructs Telecom to send a call event.
*
* @param callId The callId to send the event for.
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index 0ddd52d..ecd6596 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -30,9 +30,12 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.RemoteException;
import android.view.Surface;
import com.android.internal.os.SomeArgs;
+import com.android.internal.telecom.ICallEndpointCallback;
+import com.android.internal.telecom.ICallEndpointSession;
import com.android.internal.telecom.IInCallAdapter;
import com.android.internal.telecom.IInCallService;
@@ -258,6 +261,10 @@
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_PUSH_FAILED = 14;
+ private static final int MSG_ON_PULL_FAILED = 15;
+ private static final int MSG_ON_ANSWER_EXTERNAL_FAILED = 16;
+ private static final int MSG_ON_CALL_ENDPOINT_ACTIVATION_REQUEST = 17;
/** Default Handler used to consolidate binder method calls onto a single thread. */
private final Handler mHandler = new Handler(Looper.getMainLooper()) {
@@ -339,6 +346,66 @@
mPhone.internalOnHandoverComplete(callId);
break;
}
+ case MSG_ON_PUSH_FAILED: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ try {
+ String callId = (String) args.arg1;
+ CallEndpoint callEndpoint = (CallEndpoint) args.arg2;
+ int reason = (int) args.arg3;
+ mPhone.internalOnCallPushFailed(callId, callEndpoint, reason);
+ } finally {
+ args.recycle();
+ }
+ break;
+ }
+ case MSG_ON_PULL_FAILED: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ try {
+ String callId = (String) args.arg1;
+ int reason = (int) args.arg2;
+ mPhone.internalOnCallPullFailed(callId, reason);
+ } finally {
+ args.recycle();
+ }
+ break;
+ }
+ case MSG_ON_ANSWER_EXTERNAL_FAILED: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ try {
+ String callId = (String) args.arg1;
+ CallEndpoint callEndpoint = (CallEndpoint) args.arg2;
+ int reason = (int) args.arg3;
+ mPhone.internalOnAnswerFailed(callId, callEndpoint, reason);
+ } finally {
+ args.recycle();
+ }
+ break;
+ }
+ case MSG_ON_CALL_ENDPOINT_ACTIVATION_REQUEST: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ try {
+ CallEndpoint callEndpoint = (CallEndpoint) args.arg1;
+ ICallEndpointSession iCallEndpointSession =
+ (ICallEndpointSession) args.arg2;
+ try {
+ mCallEndpointCallback = onCallEndpointActivationRequested(callEndpoint,
+ new CallEndpointSession(iCallEndpointSession));
+ } catch (UnsupportedOperationException e) {
+ // This InCallService neglected to implement
+ // onCallEndpointActivationRequested, immediately signal back to Telecom
+ // that the activation failed.
+ try {
+ iCallEndpointSession.setCallEndpointSessionActivationFailed(
+ CallEndpointSession.ACTIVATION_FAILURE_UNAVAILABLE);
+ } catch (RemoteException re) {
+ // Ignore
+ }
+ }
+ } finally {
+ args.recycle();
+ }
+ break;
+ }
default:
break;
}
@@ -353,6 +420,36 @@
}
@Override
+ public ICallEndpointCallback requestCallEndpointActivation(CallEndpoint callEndpoint,
+ ICallEndpointSession callEndpointSession) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callEndpoint;
+ args.arg2 = callEndpointSession;
+ mHandler.obtainMessage(MSG_ON_CALL_ENDPOINT_ACTIVATION_REQUEST, args).sendToTarget();
+
+ return new ICallEndpointCallback.Stub() {
+ @Override
+ public void onCallEndpointSessionActivationTimeout() throws RemoteException {
+ if (mCallEndpointCallback != null) {
+ mCallEndpointCallback.onCallEndpointSessionActivationTimeout();
+ }
+ }
+
+ @Override
+ public void onCallEndpointSessionDeactivated() throws RemoteException {
+ if (mCallEndpointCallback != null) {
+ mCallEndpointCallback.onCallEndpointSessionDeactivated();
+ }
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return this;
+ }
+ };
+ }
+
+ @Override
public void addCall(ParcelableCall call) {
mHandler.obtainMessage(MSG_ADD_CALL, call).sendToTarget();
}
@@ -424,6 +521,32 @@
public void onHandoverComplete(String callId) {
mHandler.obtainMessage(MSG_ON_HANDOVER_COMPLETE, callId).sendToTarget();
}
+
+ @Override
+ public void onCallPullFailed(String callId, int reason) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.arg2 = reason;
+ mHandler.obtainMessage(MSG_ON_PULL_FAILED, args).sendToTarget();
+ }
+
+ @Override
+ public void onCallPushFailed(String callId, CallEndpoint endpoint, int reason) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.arg2 = endpoint;
+ args.arg3 = reason;
+ mHandler.obtainMessage(MSG_ON_PUSH_FAILED, args).sendToTarget();
+ }
+
+ @Override
+ public void onAnswerFailed(String callId, CallEndpoint endpoint, int reason) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.arg2 = endpoint;
+ args.arg3 = reason;
+ mHandler.obtainMessage(MSG_ON_ANSWER_EXTERNAL_FAILED, args).sendToTarget();
+ }
}
private Phone.Listener mPhoneListener = new Phone.Listener() {
@@ -470,6 +593,8 @@
};
private Phone mPhone;
+ private CallEndpointSession mCallEndpointSession;
+ private CallEndpointCallback mCallEndpointCallback;
public InCallService() {
}
@@ -494,6 +619,14 @@
onPhoneDestroyed(oldPhone);
}
+ if (mCallEndpointCallback != null) {
+ mCallEndpointCallback = null;
+ }
+
+ if (mCallEndpointSession != null) {
+ mCallEndpointSession = null;
+ }
+
return false;
}
@@ -704,6 +837,21 @@
}
/**
+ * To handle the request from telecom to activate an endpoint session. Streaming app with
+ * meta-data {@link TelecomManager#METADATA_STREAMING_TETHERED_CALLS}.
+ * @param endpoint The endpoint which is to be activated.
+ * @param session An instance of {@link CallEndpointSession} to let streaming app report updates
+ * of the endpoint.
+ * @return CallEndpointCallback The implementation provided by streaming app. Telecom use this
+ * to report events related to the call endpoint session.
+ */
+ public @NonNull CallEndpointCallback onCallEndpointActivationRequested(
+ @NonNull CallEndpoint endpoint, @NonNull CallEndpointSession session)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
* Used to issue commands to the {@link Connection.VideoProvider} associated with a
* {@link Call}.
*/
diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java
index f412a18..c429183 100644
--- a/telecomm/java/android/telecom/ParcelableCall.java
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -16,6 +16,7 @@
package android.telecom;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.net.Uri;
@@ -29,8 +30,11 @@
import com.android.internal.telecom.IVideoProvider;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* Information about a call that is used between InCallService and Telecom.
@@ -69,6 +73,8 @@
private int mCallerNumberVerificationStatus;
private String mContactDisplayName;
private String mActiveChildCallId;
+ private CallEndpoint mActiveCallEndpoint;
+ private Set<CallEndpoint> mAvailableCallEndpoints = new HashSet<>();
public ParcelableCallBuilder setId(String id) {
mId = id;
@@ -224,6 +230,27 @@
return this;
}
+ /**
+ * Set active call endpoint
+ * @param callEndpoint
+ * @return
+ */
+ public ParcelableCallBuilder setActiveCallEndpoint(CallEndpoint callEndpoint) {
+ mActiveCallEndpoint = callEndpoint;
+ return this;
+ }
+
+ /**
+ * Set available call endpoints
+ * @param availableCallEndpoints
+ * @return
+ */
+ public ParcelableCallBuilder setAvailableCallEndpoints(
+ Set<CallEndpoint> availableCallEndpoints) {
+ mAvailableCallEndpoints = availableCallEndpoints;
+ return this;
+ }
+
public ParcelableCall createParcelableCall() {
return new ParcelableCall(
mId,
@@ -255,7 +282,9 @@
mCallDirection,
mCallerNumberVerificationStatus,
mContactDisplayName,
- mActiveChildCallId);
+ mActiveChildCallId,
+ mActiveCallEndpoint,
+ mAvailableCallEndpoints);
}
public static ParcelableCallBuilder fromParcelableCall(ParcelableCall parcelableCall) {
@@ -292,6 +321,8 @@
parcelableCall.mCallerNumberVerificationStatus;
newBuilder.mContactDisplayName = parcelableCall.mContactDisplayName;
newBuilder.mActiveChildCallId = parcelableCall.mActiveChildCallId;
+ newBuilder.mActiveCallEndpoint = parcelableCall.mActiveCallEndpoint;
+ newBuilder.mAvailableCallEndpoints = parcelableCall.mAvailableCallEndpoints;
return newBuilder;
}
}
@@ -327,6 +358,8 @@
private final int mCallerNumberVerificationStatus;
private final String mContactDisplayName;
private final String mActiveChildCallId; // Only valid for CDMA conferences
+ private final CallEndpoint mActiveCallEndpoint;
+ private final Set<CallEndpoint> mAvailableCallEndpoints;
public ParcelableCall(
String id,
@@ -358,7 +391,9 @@
int callDirection,
int callerNumberVerificationStatus,
String contactDisplayName,
- String activeChildCallId
+ String activeChildCallId,
+ CallEndpoint activeCallEndpoint,
+ Set<CallEndpoint> availableCallEndpoints
) {
mId = id;
mState = state;
@@ -390,6 +425,8 @@
mCallerNumberVerificationStatus = callerNumberVerificationStatus;
mContactDisplayName = contactDisplayName;
mActiveChildCallId = activeChildCallId;
+ mActiveCallEndpoint = activeCallEndpoint;
+ mAvailableCallEndpoints = availableCallEndpoints;
}
/** The unique ID of the call. */
@@ -614,6 +651,21 @@
return mActiveChildCallId;
}
+ /**
+ * @return The {@link CallEndpoint} which is currently active for this call, or null if the call
+ * does not take place via an {@link CallEndpoint}.
+ */
+ public @Nullable CallEndpoint getActiveCallEndpoint() {
+ return mActiveCallEndpoint;
+ }
+
+ /**
+ * @return A set of available {@link CallEndpoint}
+ */
+ public @NonNull Set<CallEndpoint> getAvailableCallEndpoints() {
+ return mAvailableCallEndpoints;
+ }
+
/** Responsible for creating ParcelableCall objects for deserialized Parcels. */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public static final @android.annotation.NonNull Parcelable.Creator<ParcelableCall> CREATOR =
@@ -655,6 +707,9 @@
int callerNumberVerificationStatus = source.readInt();
String contactDisplayName = source.readString();
String activeChildCallId = source.readString();
+ CallEndpoint activeCallEndpoint = source.readParcelable(classLoader);
+ List<CallEndpoint> availablableCallEndpoints = new ArrayList<>();
+ source.readList(availablableCallEndpoints, classLoader);
return new ParcelableCallBuilder()
.setId(id)
.setState(state)
@@ -686,6 +741,8 @@
.setCallerNumberVerificationStatus(callerNumberVerificationStatus)
.setContactDisplayName(contactDisplayName)
.setActiveChildCallId(activeChildCallId)
+ .setActiveCallEndpoint(activeCallEndpoint)
+ .setAvailableCallEndpoints(new HashSet<>(availablableCallEndpoints))
.createParcelableCall();
}
@@ -735,6 +792,8 @@
destination.writeInt(mCallerNumberVerificationStatus);
destination.writeString(mContactDisplayName);
destination.writeString(mActiveChildCallId);
+ destination.writeParcelable(mActiveCallEndpoint, 0);
+ destination.writeList(Arrays.asList(mAvailableCallEndpoints.toArray()));
}
@Override
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index bc0a146..ac91a92 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -292,6 +292,29 @@
}
}
+ void internalOnCallPullFailed(String callId, @Call.Callback.PullFailedReason int reason) {
+ Call call = getCallById(callId);
+ if (call != null) {
+ call.internalOnCallPullFailed(reason);
+ }
+ }
+
+ void internalOnAnswerFailed(String callId, CallEndpoint callEndpoint,
+ @Call.Callback.AnswerFailedReason int reason) {
+ Call call = getCallById(callId);
+ if (call != null) {
+ call.internalOnAnswerFailed(callEndpoint, reason);
+ }
+ }
+
+ void internalOnCallPushFailed(String callId, CallEndpoint callEndpoint,
+ @Call.Callback.PushFailedReason int reason) {
+ Call call = getCallById(callId);
+ if (call != null) {
+ call.internalOnCallPushFailed(callEndpoint, reason);
+ }
+ }
+
/**
* Called to destroy the phone and cleanup any lingering calls.
*/
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 6279bf8..0bb40a7 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -54,8 +54,10 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.Executor;
/**
@@ -586,6 +588,14 @@
"android.telecom.extra.START_CALL_WITH_RTT";
/**
+ * A parcelable extra, which when set on the bundle passed into {@link #placeCall(Uri, Bundle)},
+ * indicates that the call should be initiated with an active {@link CallEndpoint} to stream
+ * the call as a tethered call.
+ */
+ public static final String EXTRA_START_CALL_ON_ENDPOINT =
+ "android.telecom.extra.START_CALL_ON_ENDPOINT";
+
+ /**
* Start an activity indicating that the completion of an outgoing call or an incoming call
* which was not blocked by the {@link CallScreeningService}, and which was NOT terminated
* while the call was in {@link Call#STATE_AUDIO_PROCESSING}.
@@ -745,6 +755,23 @@
"android.telecom.INCLUDE_SELF_MANAGED_CALLS";
/**
+ * A boolean meta-data value indicating this {@link InCallService} implementation is aimed at
+ * working as a streaming app for a tethered call. When there's a tethered call
+ * requesting to a {@link CallEndpoint} registered with this app, Telecom will bind to this
+ * streaming app and let the app streaming the call to the requested endpoint.
+ * <p>
+ * This meta-data can only be set for an {@link InCallService} which doesn't set neither
+ * {@link #METADATA_IN_CALL_SERVICE_UI} nor {@link #METADATA_IN_CALL_SERVICE_CAR_MODE_UI}.
+ * Otherwise, the app will be treated as a phone/dialer app or a car-mode app.
+ * <p>
+ * The {@link InCallService} declared this meta-data must implement
+ * {@link InCallService#onCallEndpointActivationRequested(CallEndpoint, CallEndpointSession)}.
+ * See this method for more information.
+ */
+ public static final String METADATA_STREAMING_TETHERED_CALLS =
+ "android.telecom.STREAMING_TETHERED_CALLS";
+
+ /**
* The dual tone multi-frequency signaling character sent to indicate the dialing system should
* pause for a predefined period.
*/
@@ -2250,6 +2277,7 @@
* <li>{@link #EXTRA_PHONE_ACCOUNT_HANDLE}</li>
* <li>{@link #EXTRA_START_CALL_WITH_SPEAKERPHONE}</li>
* <li>{@link #EXTRA_START_CALL_WITH_VIDEO_STATE}</li>
+ * <li>{@link #EXTRA_START_CALL_ON_ENDPOINT}</li>
* </ul>
* <p>
* An app which implements the self-managed {@link ConnectionService} API uses
@@ -2579,6 +2607,79 @@
}
}
+ /**
+ * Register a set of {@link CallEndpoint} to telecom. All registered {@link CallEndpoint} can
+ * be provided as options for push, place or answer call externally.
+ *
+ * @param endpoints Endpoints to be registered.
+ */
+ // TODO: add permission requirements
+ // @RequiresPermission{}
+ public void registerCallEndpoints(@NonNull Set<CallEndpoint> endpoints) {
+ ITelecomService service = getTelecomService();
+ List<CallEndpoint> endpointList = new ArrayList<>(endpoints);
+ if (service != null) {
+ try {
+ service.registerCallEndpoints(endpointList, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException registerCallEndpoints: " + e);
+ e.rethrowAsRuntimeException();
+ }
+ } else {
+ throw new IllegalStateException("Telecom service is null.");
+ }
+ }
+
+ /**
+ * Unregister all {@link CallEndpoint} from telecom in the set provided. After un-registration,
+ * telecom will stop tracking and maintaining these {@link CallEndpoint}, user can no longer
+ * carry a call on them.
+ *
+ * @param endpoints
+ */
+ // TODO: add permission requirements
+ // @RequiresPermission{}
+ public void unregisterCallEndpoints(@NonNull Set<CallEndpoint> endpoints) {
+ ITelecomService service = getTelecomService();
+ List<CallEndpoint> endpointList = new ArrayList<>(endpoints);
+ if (service != null) {
+ try {
+ service.unregisterCallEndpoints(endpointList, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException unregisterCallEndpoints: " + e);
+ e.rethrowAsRuntimeException();
+ }
+ } else {
+ throw new IllegalStateException("Telecom service is null.");
+ }
+ }
+
+ /**
+ * Return a set all registered {@link CallEndpoint} that can be used to stream and carry an
+ * external call.
+ *
+ * @return A set of all available {@link CallEndpoint}.
+ */
+ // TODO: add permission requirements
+ // @RequiresPermission{}
+ public @NonNull Set<CallEndpoint> getCallEndpoints() {
+ Set<CallEndpoint> endpoints = new HashSet<>();
+ List<CallEndpoint> endpointList;
+ ITelecomService service = getTelecomService();
+ if (service != null) {
+ try {
+ endpointList = service.getCallEndpoints(mContext.getOpPackageName());
+ return new HashSet<>(endpointList);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException registerCallEndpoints: " + e);
+ e.rethrowAsRuntimeException();
+ }
+ } else {
+ throw new IllegalStateException("Telecom service is null.");
+ }
+ return endpoints;
+ }
+
private boolean isSystemProcess() {
return Process.myUid() == Process.SYSTEM_UID;
}
diff --git a/telecomm/java/com/android/internal/telecom/ICallEndpointCallback.aidl b/telecomm/java/com/android/internal/telecom/ICallEndpointCallback.aidl
new file mode 100644
index 0000000..dc1cc0f
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecom/ICallEndpointCallback.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 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 CallEndpointCallback interface for Telecom framework to report event related to
+ * the endpoint session.
+ *
+ * {@hide}
+ */
+oneway interface ICallEndpointCallback {
+ void onCallEndpointSessionActivationTimeout();
+
+ void onCallEndpointSessionDeactivated();
+}
\ No newline at end of file
diff --git a/telecomm/java/com/android/internal/telecom/ICallEndpointSession.aidl b/telecomm/java/com/android/internal/telecom/ICallEndpointSession.aidl
new file mode 100644
index 0000000..1c1c29a
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecom/ICallEndpointSession.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 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 CallEndpointSession interface for streaming app to update the status of the
+ * endpoint.
+ *
+ * @see android.telecom.CallEndpointSession
+ *
+ * {@hide}
+ */
+
+oneway interface ICallEndpointSession {
+ void setCallEndpointSessionActivated();
+
+ void setCallEndpointSessionActivationFailed(int reason);
+
+ void setCallEndpointSessionDeactivated();
+}
\ 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 d72f8aa..986871f 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -20,6 +20,7 @@
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.telecom.CallAudioState;
+import android.telecom.CallEndpoint;
import android.telecom.Connection;
import android.telecom.ConnectionRequest;
import android.telecom.Logging.Session;
diff --git a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
index edf1cf4..ecca835 100755
--- a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
@@ -18,6 +18,7 @@
import android.net.Uri;
import android.os.Bundle;
+import android.telecom.CallEndpoint;
import android.telecom.PhoneAccountHandle;
/**
@@ -95,4 +96,8 @@
void handoverTo(String callId, in PhoneAccountHandle destAcct, int videoState,
in Bundle extras);
+
+ void pushCall(String callId, in CallEndpoint endpoint);
+
+ void answerCallViaEndpoint(String callId, in CallEndpoint endpoint, int videoState);
}
diff --git a/telecomm/java/com/android/internal/telecom/IInCallService.aidl b/telecomm/java/com/android/internal/telecom/IInCallService.aidl
index b9563fa..93d9f28 100644
--- a/telecomm/java/com/android/internal/telecom/IInCallService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallService.aidl
@@ -19,9 +19,12 @@
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;
+import com.android.internal.telecom.ICallEndpointCallback;
+import com.android.internal.telecom.ICallEndpointSession;
/**
* Internal remote interface for in-call services.
@@ -30,9 +33,12 @@
*
* {@hide}
*/
-oneway interface IInCallService {
+interface IInCallService {
void setInCallAdapter(in IInCallAdapter inCallAdapter);
+ ICallEndpointCallback requestCallEndpointActivation(in CallEndpoint callEndpoint,
+ in ICallEndpointSession callEndpointSession);
+
void addCall(in ParcelableCall call);
void updateCall(in ParcelableCall call);
@@ -58,4 +64,10 @@
void onHandoverFailed(String callId, int error);
void onHandoverComplete(String callId);
+
+ void onCallPullFailed(String callId, int reason);
+
+ void onCallPushFailed(String callId, in CallEndpoint endpoint, int reason);
+
+ void onAnswerFailed(String callId, in CallEndpoint endpoint, int reason);
}
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index a75f79c..38f5783 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -18,6 +18,7 @@
import android.content.ComponentName;
import android.content.Intent;
+import android.telecom.CallEndpoint;
import android.telecom.TelecomAnalytics;
import android.telecom.PhoneAccountHandle;
import android.net.Uri;
@@ -366,4 +367,19 @@
* @see TelecomServiceImpl#setTestCallDiagnosticService
*/
void setTestCallDiagnosticService(in String packageName);
+
+ /**
+ * @see TelecomServiceImpl#registerCallEndpoints(in List<CallEndpoint>, in String);
+ */
+ void registerCallEndpoints(in List<CallEndpoint> endpoints, in String packageName);
+
+ /**
+ * @see TelecomServiceImpl#unregisterCallEndpoints(in List<CallEndpoint>, String);
+ */
+ void unregisterCallEndpoints(in List<CallEndpoint> endpoints, in String packageName);
+
+ /**
+ * @see TelecomServiceImpl#getCallEndpoints(in String packageName);
+ */
+ List<CallEndpoint> getCallEndpoints(in String packageName);
}