blob: a14078697c714fdc2c4cdb92696aab884e21ea76 [file] [log] [blame]
Thomas Stuart9bfb2432022-09-27 15:02:07 -07001/*
2 * Copyright (C) 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.telecom;
18
19import static android.telecom.CallException.TRANSACTION_EXCEPTION_KEY;
20
21import android.annotation.CallbackExecutor;
Thomas Stuartfc93e932023-12-19 10:57:06 -080022import android.annotation.FlaggedApi;
Thomas Stuart9bfb2432022-09-27 15:02:07 -070023import android.annotation.NonNull;
Thomas Stuart73a8b872023-01-10 16:21:51 -080024import android.annotation.SuppressLint;
Thomas Stuart9bfb2432022-09-27 15:02:07 -070025import android.os.Binder;
26import android.os.Bundle;
27import android.os.OutcomeReceiver;
28import android.os.ParcelUuid;
29import android.os.RemoteException;
30import android.os.ResultReceiver;
Thomas Stuartc2427a72023-01-27 17:11:02 -080031import android.text.TextUtils;
Thomas Stuart9bfb2432022-09-27 15:02:07 -070032
Thomas Stuart9bfb2432022-09-27 15:02:07 -070033import com.android.internal.telecom.ICallControl;
Thomas Stuartfc93e932023-12-19 10:57:06 -080034import com.android.server.telecom.flags.Flags;
Thomas Stuart9bfb2432022-09-27 15:02:07 -070035
Thomas Stuart7aeccde2022-12-21 14:47:21 -080036import java.util.List;
37import java.util.Objects;
Thomas Stuart9bfb2432022-09-27 15:02:07 -070038import java.util.concurrent.Executor;
39
40/**
41 * CallControl provides client side control of a call. Each Call will get an individual CallControl
42 * instance in which the client can alter the state of the associated call.
43 *
44 * <p>
45 * Each method is Transactional meaning that it can succeed or fail. If a transaction succeeds,
46 * the {@link OutcomeReceiver#onResult} will be called by Telecom. Otherwise, the
47 * {@link OutcomeReceiver#onError} is called and provides a {@link CallException} that details why
48 * the operation failed.
49 */
Thomas Stuart73a8b872023-01-10 16:21:51 -080050@SuppressLint("NotCloseable")
51public final class CallControl {
Thomas Stuart9bfb2432022-09-27 15:02:07 -070052 private static final String TAG = CallControl.class.getSimpleName();
Thomas Stuart9bfb2432022-09-27 15:02:07 -070053 private final String mCallId;
54 private final ICallControl mServerInterface;
Thomas Stuart9bfb2432022-09-27 15:02:07 -070055
56 /** @hide */
Thomas Stuart0bc7c582024-01-19 17:02:53 -080057 public CallControl(@NonNull String callId, @NonNull ICallControl serverInterface) {
Thomas Stuart9bfb2432022-09-27 15:02:07 -070058 mCallId = callId;
59 mServerInterface = serverInterface;
Thomas Stuart9bfb2432022-09-27 15:02:07 -070060 }
61
62 /**
63 * @return the callId Telecom assigned to this CallControl object which should be attached to
Thomas Stuartc2427a72023-01-27 17:11:02 -080064 * an individual call.
Thomas Stuart9bfb2432022-09-27 15:02:07 -070065 */
66 @NonNull
67 public ParcelUuid getCallId() {
68 return ParcelUuid.fromString(mCallId);
69 }
70
71 /**
Thomas Stuartb4153492023-02-07 13:30:37 -080072 * Request Telecom set the call state to active. This method should be called when either an
73 * outgoing call is ready to go active or a held call is ready to go active again. For incoming
74 * calls that are ready to be answered, use
75 * {@link CallControl#answer(int, Executor, OutcomeReceiver)}.
Thomas Stuart9bfb2432022-09-27 15:02:07 -070076 *
77 * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
Thomas Stuartc2427a72023-01-27 17:11:02 -080078 * will be called on.
Thomas Stuart9bfb2432022-09-27 15:02:07 -070079 * @param callback that will be completed on the Telecom side that details success or failure
Thomas Stuartc2427a72023-01-27 17:11:02 -080080 * of the requested operation.
Thomas Stuart9bfb2432022-09-27 15:02:07 -070081 *
82 * {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
83 * switched the call state to active
84 *
85 * {@link OutcomeReceiver#onError} will be called if Telecom has failed to set
86 * the call state to active. A {@link CallException} will be passed
87 * that details why the operation failed.
88 */
89 public void setActive(@CallbackExecutor @NonNull Executor executor,
90 @NonNull OutcomeReceiver<Void, CallException> callback) {
Thomas Stuart0bc7c582024-01-19 17:02:53 -080091 Objects.requireNonNull(executor);
92 Objects.requireNonNull(callback);
93 try {
94 mServerInterface.setActive(mCallId,
95 new CallControlResultReceiver("setActive", executor, callback));
Thomas Stuart9bfb2432022-09-27 15:02:07 -070096
Thomas Stuart0bc7c582024-01-19 17:02:53 -080097 } catch (RemoteException e) {
98 throw e.rethrowAsRuntimeException();
Thomas Stuart9bfb2432022-09-27 15:02:07 -070099 }
100 }
101
102 /**
Thomas Stuartb4153492023-02-07 13:30:37 -0800103 * Request Telecom answer an incoming call. For outgoing calls and calls that have been placed
104 * on hold, use {@link CallControl#setActive(Executor, OutcomeReceiver)}.
105 *
106 * @param videoState to report to Telecom. Telecom will store VideoState in the event another
107 * service/device requests it in order to continue the call on another screen.
108 * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
109 * will be called on.
110 * @param callback that will be completed on the Telecom side that details success or failure
111 * of the requested operation.
112 *
113 * {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
114 * switched the call state to active
115 *
116 * {@link OutcomeReceiver#onError} will be called if Telecom has failed to set
117 * the call state to active. A {@link CallException} will be passed
118 * that details why the operation failed.
119 */
120 public void answer(@android.telecom.CallAttributes.CallType int videoState,
121 @CallbackExecutor @NonNull Executor executor,
122 @NonNull OutcomeReceiver<Void, CallException> callback) {
123 validateVideoState(videoState);
124 Objects.requireNonNull(executor);
125 Objects.requireNonNull(callback);
Thomas Stuart0bc7c582024-01-19 17:02:53 -0800126 try {
127 mServerInterface.answer(videoState, mCallId,
128 new CallControlResultReceiver("answer", executor, callback));
Thomas Stuartb4153492023-02-07 13:30:37 -0800129
Thomas Stuart0bc7c582024-01-19 17:02:53 -0800130 } catch (RemoteException e) {
131 throw e.rethrowAsRuntimeException();
Thomas Stuartb4153492023-02-07 13:30:37 -0800132 }
133 }
134
135 /**
Thomas Stuart9bfb2432022-09-27 15:02:07 -0700136 * Request Telecom set the call state to inactive. This the same as hold for two call endpoints
137 * but can be extended to setting a meeting to inactive.
138 *
139 * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
Thomas Stuartc2427a72023-01-27 17:11:02 -0800140 * will be called on.
Thomas Stuart9bfb2432022-09-27 15:02:07 -0700141 * @param callback that will be completed on the Telecom side that details success or failure
Thomas Stuartc2427a72023-01-27 17:11:02 -0800142 * of the requested operation.
Thomas Stuart9bfb2432022-09-27 15:02:07 -0700143 *
144 * {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
145 * switched the call state to inactive
146 *
147 * {@link OutcomeReceiver#onError} will be called if Telecom has failed to set
148 * the call state to inactive. A {@link CallException} will be passed
149 * that details why the operation failed.
150 */
151 public void setInactive(@CallbackExecutor @NonNull Executor executor,
152 @NonNull OutcomeReceiver<Void, CallException> callback) {
Thomas Stuart0bc7c582024-01-19 17:02:53 -0800153 Objects.requireNonNull(executor);
154 Objects.requireNonNull(callback);
155 try {
156 mServerInterface.setInactive(mCallId,
157 new CallControlResultReceiver("setInactive", executor, callback));
Thomas Stuart9bfb2432022-09-27 15:02:07 -0700158
Thomas Stuart0bc7c582024-01-19 17:02:53 -0800159 } catch (RemoteException e) {
160 throw e.rethrowAsRuntimeException();
Thomas Stuart9bfb2432022-09-27 15:02:07 -0700161 }
162 }
163
164 /**
Thomas Stuartc2427a72023-01-27 17:11:02 -0800165 * Request Telecom disconnect the call and remove the call from telecom tracking.
Thomas Stuart9bfb2432022-09-27 15:02:07 -0700166 *
Thomas Stuartc2427a72023-01-27 17:11:02 -0800167 * @param disconnectCause represents the cause for disconnecting the call. The only valid
168 * codes for the {@link android.telecom.DisconnectCause} passed in are:
169 * <ul>
170 * <li>{@link DisconnectCause#LOCAL}</li>
171 * <li>{@link DisconnectCause#REMOTE}</li>
172 * <li>{@link DisconnectCause#REJECTED}</li>
173 * <li>{@link DisconnectCause#MISSED}</li>
174 * </ul>
Thomas Stuartc2427a72023-01-27 17:11:02 -0800175 * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
176 * will be called on.
Thomas Stuartc2427a72023-01-27 17:11:02 -0800177 * @param callback That will be completed on the Telecom side that details success or
178 * failure of the requested operation.
179 *
180 * {@link OutcomeReceiver#onResult} will be called if Telecom has
181 * successfully disconnected the call.
182 *
183 * {@link OutcomeReceiver#onError} will be called if Telecom has failed
184 * to disconnect the call. A {@link CallException} will be passed
185 * that details why the operation failed.
186 *
187 * <p>
188 * Note: After the call has been successfully disconnected, calling any CallControl API will
189 * result in the {@link OutcomeReceiver#onError} with
190 * {@link CallException#CODE_CALL_IS_NOT_BEING_TRACKED}.
Thomas Stuart9bfb2432022-09-27 15:02:07 -0700191 */
192 public void disconnect(@NonNull DisconnectCause disconnectCause,
193 @CallbackExecutor @NonNull Executor executor,
194 @NonNull OutcomeReceiver<Void, CallException> callback) {
Thomas Stuartc2427a72023-01-27 17:11:02 -0800195 Objects.requireNonNull(disconnectCause);
196 Objects.requireNonNull(executor);
197 Objects.requireNonNull(callback);
198 validateDisconnectCause(disconnectCause);
Thomas Stuart0bc7c582024-01-19 17:02:53 -0800199 try {
200 mServerInterface.disconnect(mCallId, disconnectCause,
201 new CallControlResultReceiver("disconnect", executor, callback));
202 } catch (RemoteException e) {
203 throw e.rethrowAsRuntimeException();
Thomas Stuart9bfb2432022-09-27 15:02:07 -0700204 }
205 }
206
207 /**
Grace Jiaef5a4cc2022-12-13 11:08:55 -0800208 * Request start a call streaming session. On receiving valid request, telecom will bind to
Anton Hansson84d6d752023-10-05 09:46:37 +0000209 * the {@code CallStreamingService} implemented by a general call streaming sender. So that the
Grace Jiaef5a4cc2022-12-13 11:08:55 -0800210 * call streaming sender can perform streaming local device audio to another remote device and
211 * control the call during streaming.
212 *
213 * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
214 * will be called on.
215 * @param callback that will be completed on the Telecom side that details success or failure
216 * of the requested operation.
217 *
218 * {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
Grace Jiaacd4dad2023-01-17 18:14:36 -0800219 * started the call streaming.
Grace Jiaef5a4cc2022-12-13 11:08:55 -0800220 *
221 * {@link OutcomeReceiver#onError} will be called if Telecom has failed to
Grace Jiaacd4dad2023-01-17 18:14:36 -0800222 * start the call streaming. A {@link CallException} will be passed that
Grace Jiaef5a4cc2022-12-13 11:08:55 -0800223 * details why the operation failed.
224 */
225 public void startCallStreaming(@CallbackExecutor @NonNull Executor executor,
226 @NonNull OutcomeReceiver<Void, CallException> callback) {
Thomas Stuart0bc7c582024-01-19 17:02:53 -0800227 Objects.requireNonNull(executor);
228 Objects.requireNonNull(callback);
229 try {
230 mServerInterface.startCallStreaming(mCallId,
231 new CallControlResultReceiver("startCallStreaming", executor, callback));
232 } catch (RemoteException e) {
233 throw e.rethrowAsRuntimeException();
Grace Jiaef5a4cc2022-12-13 11:08:55 -0800234 }
235 }
236
237 /**
Thomas Stuart7aeccde2022-12-21 14:47:21 -0800238 * Request a CallEndpoint change. Clients should not define their own CallEndpoint when
239 * requesting a change. Instead, the new endpoint should be one of the valid endpoints provided
240 * by {@link CallEventCallback#onAvailableCallEndpointsChanged(List)}.
241 *
Thomas Stuartc2427a72023-01-27 17:11:02 -0800242 * @param callEndpoint The {@link CallEndpoint} to change to.
243 * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
Thomas Stuart7aeccde2022-12-21 14:47:21 -0800244 * will be called on.
Thomas Stuartc2427a72023-01-27 17:11:02 -0800245 * @param callback The {@link OutcomeReceiver} that will be completed on the Telecom side
Thomas Stuart7aeccde2022-12-21 14:47:21 -0800246 * that details success or failure of the requested operation.
247 *
248 * {@link OutcomeReceiver#onResult} will be called if Telecom has
249 * successfully changed the CallEndpoint that was requested.
250 *
251 * {@link OutcomeReceiver#onError} will be called if Telecom has failed to
252 * switch to the requested CallEndpoint. A {@link CallException} will be
253 * passed that details why the operation failed.
254 */
255 public void requestCallEndpointChange(@NonNull CallEndpoint callEndpoint,
256 @CallbackExecutor @NonNull Executor executor,
257 @NonNull OutcomeReceiver<Void, CallException> callback) {
258 Objects.requireNonNull(callEndpoint);
259 Objects.requireNonNull(executor);
260 Objects.requireNonNull(callback);
Thomas Stuart0bc7c582024-01-19 17:02:53 -0800261 try {
262 mServerInterface.requestCallEndpointChange(callEndpoint,
263 new CallControlResultReceiver("requestCallEndpointChange", executor, callback));
264 } catch (RemoteException e) {
265 throw e.rethrowAsRuntimeException();
Thomas Stuart7aeccde2022-12-21 14:47:21 -0800266 }
267 }
268
269 /**
Thomas Stuartfc93e932023-12-19 10:57:06 -0800270 * Request a new mute state. Note: {@link CallEventCallback#onMuteStateChanged(boolean)}
271 * will be called every time the mute state is changed and can be used to track the current
272 * mute state.
273 *
274 * @param isMuted The new mute state. Passing in a {@link Boolean#TRUE} for the isMuted
275 * parameter will mute the call. {@link Boolean#FALSE} will unmute the call.
276 * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
277 * will be called on.
278 * @param callback The {@link OutcomeReceiver} that will be completed on the Telecom side
279 * that details success or failure of the requested operation.
280 *
281 * {@link OutcomeReceiver#onResult} will be called if Telecom has
282 * successfully changed the mute state.
283 *
284 * {@link OutcomeReceiver#onError} will be called if Telecom has failed to
285 * switch to the mute state. A {@link CallException} will be
286 * passed that details why the operation failed.
287 */
288 @FlaggedApi(Flags.FLAG_SET_MUTE_STATE)
Thomas Stuart0bc7c582024-01-19 17:02:53 -0800289 public void requestMuteState(boolean isMuted, @CallbackExecutor @NonNull Executor executor,
Thomas Stuartfc93e932023-12-19 10:57:06 -0800290 @NonNull OutcomeReceiver<Void, CallException> callback) {
291 Objects.requireNonNull(executor);
292 Objects.requireNonNull(callback);
Thomas Stuart0bc7c582024-01-19 17:02:53 -0800293 try {
294 mServerInterface.setMuteState(isMuted,
295 new CallControlResultReceiver("requestMuteState", executor, callback));
Thomas Stuartfc93e932023-12-19 10:57:06 -0800296
Thomas Stuart0bc7c582024-01-19 17:02:53 -0800297 } catch (RemoteException e) {
298 throw e.rethrowAsRuntimeException();
Thomas Stuartfc93e932023-12-19 10:57:06 -0800299 }
300 }
301
302 /**
Thomas Stuart9ace3392023-02-15 15:01:09 -0800303 * Raises an event to the {@link android.telecom.InCallService} implementations tracking this
304 * call via {@link android.telecom.Call.Callback#onConnectionEvent(Call, String, Bundle)}.
Thomas Stuartef584292023-03-02 13:24:46 -0800305 * These events and the associated extra keys for the {@code Bundle} parameter are mutually
306 * defined by a VoIP application and {@link android.telecom.InCallService}. This API is used to
307 * relay additional information about a call other than what is specified in the
308 * {@link android.telecom.CallAttributes} to {@link android.telecom.InCallService}s. This might
309 * include, for example, a change to the list of participants in a meeting, or the name of the
310 * speakers who have their hand raised. Where appropriate, the {@link InCallService}s tracking
311 * this call may choose to render this additional information about the call. An automotive
312 * calling UX, for example may have enough screen real estate to indicate the number of
313 * participants in a meeting, but to prevent distractions could suppress the list of
314 * participants.
Thomas Stuart9ace3392023-02-15 15:01:09 -0800315 *
Thomas Stuartef584292023-03-02 13:24:46 -0800316 * @param event a string event identifier agreed upon between a VoIP application and an
317 * {@link android.telecom.InCallService}
318 * @param extras a {@link android.os.Bundle} containing information about the event, as agreed
319 * upon between a VoIP application and {@link android.telecom.InCallService}.
Thomas Stuart9ace3392023-02-15 15:01:09 -0800320 */
321 public void sendEvent(@NonNull String event, @NonNull Bundle extras) {
322 Objects.requireNonNull(event);
323 Objects.requireNonNull(extras);
Thomas Stuart0bc7c582024-01-19 17:02:53 -0800324 try {
325 mServerInterface.sendEvent(mCallId, event, extras);
326 } catch (RemoteException e) {
327 throw e.rethrowAsRuntimeException();
Thomas Stuart9ace3392023-02-15 15:01:09 -0800328 }
329 }
330
331 /**
Thomas Stuart9bfb2432022-09-27 15:02:07 -0700332 * Since {@link OutcomeReceiver}s cannot be passed via AIDL, a ResultReceiver (which can) must
333 * wrap the Clients {@link OutcomeReceiver} passed in and await for the Telecom Server side
334 * response in {@link ResultReceiver#onReceiveResult(int, Bundle)}.
Thomas Stuartc2427a72023-01-27 17:11:02 -0800335 *
336 * @hide
337 */
Thomas Stuart9bfb2432022-09-27 15:02:07 -0700338 private class CallControlResultReceiver extends ResultReceiver {
339 private final String mCallingMethod;
340 private final Executor mExecutor;
341 private final OutcomeReceiver<Void, CallException> mClientCallback;
342
343 CallControlResultReceiver(String method, Executor executor,
344 OutcomeReceiver<Void, CallException> clientCallback) {
345 super(null);
346 mCallingMethod = method;
347 mExecutor = executor;
348 mClientCallback = clientCallback;
349 }
350
351 @Override
352 protected void onReceiveResult(int resultCode, Bundle resultData) {
353 Log.d(CallControl.TAG, "%s: oRR: resultCode=[%s]", mCallingMethod, resultCode);
354 super.onReceiveResult(resultCode, resultData);
355 final long identity = Binder.clearCallingIdentity();
356 try {
357 if (resultCode == TelecomManager.TELECOM_TRANSACTION_SUCCESS) {
358 mExecutor.execute(() -> mClientCallback.onResult(null));
359 } else {
360 mExecutor.execute(() ->
361 mClientCallback.onError(getTransactionException(resultData)));
362 }
363 } finally {
364 Binder.restoreCallingIdentity(identity);
365 }
366 }
367
368 }
369
370 /** @hide */
371 private CallException getTransactionException(Bundle resultData) {
372 String message = "unknown error";
373 if (resultData != null && resultData.containsKey(TRANSACTION_EXCEPTION_KEY)) {
374 return resultData.getParcelable(TRANSACTION_EXCEPTION_KEY,
375 CallException.class);
376 }
377 return new CallException(message, CallException.CODE_ERROR_UNKNOWN);
378 }
Thomas Stuartc2427a72023-01-27 17:11:02 -0800379
380 /** @hide */
381 private void validateDisconnectCause(DisconnectCause disconnectCause) {
382 final int code = disconnectCause.getCode();
383 if (code != DisconnectCause.LOCAL && code != DisconnectCause.REMOTE
384 && code != DisconnectCause.MISSED && code != DisconnectCause.REJECTED) {
385 throw new IllegalArgumentException(TextUtils.formatSimple(
386 "The DisconnectCause code provided, %d , is not a valid Disconnect code. Valid "
387 + "DisconnectCause codes are limited to [DisconnectCause.LOCAL, "
388 + "DisconnectCause.REMOTE, DisconnectCause.MISSED, or "
389 + "DisconnectCause.REJECTED]", disconnectCause.getCode()));
390 }
391 }
392
Thomas Stuartb4153492023-02-07 13:30:37 -0800393 /** @hide */
394 private void validateVideoState(@android.telecom.CallAttributes.CallType int videoState) {
395 if (videoState != CallAttributes.AUDIO_CALL && videoState != CallAttributes.VIDEO_CALL) {
396 throw new IllegalArgumentException(TextUtils.formatSimple(
397 "The VideoState argument passed in, %d , is not a valid VideoState. The "
398 + "VideoState choices are limited to CallAttributes.AUDIO_CALL or"
399 + "CallAttributes.VIDEO_CALL", videoState));
400 }
401 }
Thomas Stuart9bfb2432022-09-27 15:02:07 -0700402}