blob: 315ac6741c51f20663ff42c57648c392737197b7 [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;
22import android.annotation.NonNull;
23import android.annotation.Nullable;
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;
31
32import com.android.internal.telecom.ClientTransactionalServiceRepository;
33import com.android.internal.telecom.ICallControl;
34
Thomas Stuart7aeccde2022-12-21 14:47:21 -080035import java.util.List;
36import java.util.Objects;
Thomas Stuart9bfb2432022-09-27 15:02:07 -070037import java.util.concurrent.Executor;
38
39/**
40 * CallControl provides client side control of a call. Each Call will get an individual CallControl
41 * instance in which the client can alter the state of the associated call.
42 *
43 * <p>
44 * Each method is Transactional meaning that it can succeed or fail. If a transaction succeeds,
45 * the {@link OutcomeReceiver#onResult} will be called by Telecom. Otherwise, the
46 * {@link OutcomeReceiver#onError} is called and provides a {@link CallException} that details why
47 * the operation failed.
48 */
Thomas Stuart73a8b872023-01-10 16:21:51 -080049@SuppressLint("NotCloseable")
50public final class CallControl {
Thomas Stuart9bfb2432022-09-27 15:02:07 -070051 private static final String TAG = CallControl.class.getSimpleName();
52 private static final String INTERFACE_ERROR_MSG = "Call Control is not available";
53 private final String mCallId;
54 private final ICallControl mServerInterface;
55 private final PhoneAccountHandle mPhoneAccountHandle;
56 private final ClientTransactionalServiceRepository mRepository;
57
58 /** @hide */
59 public CallControl(@NonNull String callId, @Nullable ICallControl serverInterface,
60 @NonNull ClientTransactionalServiceRepository repository,
61 @NonNull PhoneAccountHandle pah) {
62 mCallId = callId;
63 mServerInterface = serverInterface;
64 mRepository = repository;
65 mPhoneAccountHandle = pah;
66 }
67
68 /**
69 * @return the callId Telecom assigned to this CallControl object which should be attached to
70 * an individual call.
71 */
72 @NonNull
73 public ParcelUuid getCallId() {
74 return ParcelUuid.fromString(mCallId);
75 }
76
77 /**
78 * Request Telecom set the call state to active.
79 *
80 * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
81 * will be called on.
82 * @param callback that will be completed on the Telecom side that details success or failure
83 * of the requested operation.
84 *
85 * {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
86 * switched the call state to active
87 *
88 * {@link OutcomeReceiver#onError} will be called if Telecom has failed to set
89 * the call state to active. A {@link CallException} will be passed
90 * that details why the operation failed.
91 */
92 public void setActive(@CallbackExecutor @NonNull Executor executor,
93 @NonNull OutcomeReceiver<Void, CallException> callback) {
94 if (mServerInterface != null) {
95 try {
96 mServerInterface.setActive(mCallId,
97 new CallControlResultReceiver("setActive", executor, callback));
98
99 } catch (RemoteException e) {
100 throw e.rethrowAsRuntimeException();
101 }
102 } else {
103 throw new IllegalStateException(INTERFACE_ERROR_MSG);
104 }
105 }
106
107 /**
108 * Request Telecom set the call state to inactive. This the same as hold for two call endpoints
109 * but can be extended to setting a meeting to inactive.
110 *
111 * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
112 * will be called on.
113 * @param callback that will be completed on the Telecom side that details success or failure
114 * of the requested operation.
115 *
116 * {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
117 * switched the call state to inactive
118 *
119 * {@link OutcomeReceiver#onError} will be called if Telecom has failed to set
120 * the call state to inactive. A {@link CallException} will be passed
121 * that details why the operation failed.
122 */
123 public void setInactive(@CallbackExecutor @NonNull Executor executor,
124 @NonNull OutcomeReceiver<Void, CallException> callback) {
125 if (mServerInterface != null) {
126 try {
127 mServerInterface.setInactive(mCallId,
128 new CallControlResultReceiver("setInactive", executor, callback));
129
130 } catch (RemoteException e) {
131 throw e.rethrowAsRuntimeException();
132 }
133 } else {
134 throw new IllegalStateException(INTERFACE_ERROR_MSG);
135 }
136 }
137
138 /**
139 * Request Telecom set the call state to disconnect.
140 *
141 * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
142 * will be called on.
143 * @param callback that will be completed on the Telecom side that details success or failure
144 * of the requested operation.
145 *
146 * {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
147 * disconnected the call.
148 *
149 * {@link OutcomeReceiver#onError} will be called if Telecom has failed to
150 * disconnect the call. A {@link CallException} will be passed
151 * that details why the operation failed.
152 */
153 public void disconnect(@NonNull DisconnectCause disconnectCause,
154 @CallbackExecutor @NonNull Executor executor,
155 @NonNull OutcomeReceiver<Void, CallException> callback) {
156 if (mServerInterface != null) {
157 try {
158 mServerInterface.disconnect(mCallId, disconnectCause,
159 new CallControlResultReceiver("disconnect", executor, callback));
160 } catch (RemoteException e) {
161 throw e.rethrowAsRuntimeException();
162 }
163 } else {
164 throw new IllegalStateException(INTERFACE_ERROR_MSG);
165 }
166 }
167
168 /**
169 * Request Telecom reject the incoming call.
170 *
171 * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
172 * will be called on.
173 * @param callback that will be completed on the Telecom side that details success or failure
174 * of the requested operation.
175 *
176 * {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
177 * rejected the incoming call.
178 *
179 * {@link OutcomeReceiver#onError} will be called if Telecom has failed to
180 * reject the incoming call. A {@link CallException} will be passed
181 * that details why the operation failed.
182 */
183 public void rejectCall(@CallbackExecutor @NonNull Executor executor,
184 @NonNull OutcomeReceiver<Void, CallException> callback) {
185 if (mServerInterface != null) {
186 try {
187 mServerInterface.rejectCall(mCallId,
188 new CallControlResultReceiver("rejectCall", executor, callback));
189 } catch (RemoteException e) {
190 throw e.rethrowAsRuntimeException();
191 }
192 } else {
193 throw new IllegalStateException(INTERFACE_ERROR_MSG);
194 }
195 }
196
197 /**
Grace Jiaef5a4cc2022-12-13 11:08:55 -0800198 * Request start a call streaming session. On receiving valid request, telecom will bind to
199 * the {@link CallStreamingService} implemented by a general call streaming sender. So that the
200 * call streaming sender can perform streaming local device audio to another remote device and
201 * control the call during streaming.
202 *
203 * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
204 * will be called on.
205 * @param callback that will be completed on the Telecom side that details success or failure
206 * of the requested operation.
207 *
208 * {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
209 * rejected the incoming call.
210 *
211 * {@link OutcomeReceiver#onError} will be called if Telecom has failed to
212 * reject the incoming call. A {@link CallException} will be passed that
213 * details why the operation failed.
214 */
215 public void startCallStreaming(@CallbackExecutor @NonNull Executor executor,
216 @NonNull OutcomeReceiver<Void, CallException> callback) {
217 if (mServerInterface != null) {
218 try {
219 mServerInterface.startCallStreaming(mCallId,
220 new CallControlResultReceiver("startCallStreaming", executor, callback));
221 } catch (RemoteException e) {
222 throw e.rethrowAsRuntimeException();
223 }
224 } else {
225 throw new IllegalStateException(INTERFACE_ERROR_MSG);
226 }
227 }
228
229 /**
Thomas Stuart7aeccde2022-12-21 14:47:21 -0800230 * Request a CallEndpoint change. Clients should not define their own CallEndpoint when
231 * requesting a change. Instead, the new endpoint should be one of the valid endpoints provided
232 * by {@link CallEventCallback#onAvailableCallEndpointsChanged(List)}.
233 *
234 * @param callEndpoint ; The {@link CallEndpoint} to change to.
235 * @param executor ; The {@link Executor} on which the {@link OutcomeReceiver} callback
236 * will be called on.
237 * @param callback ; The {@link OutcomeReceiver} that will be completed on the Telecom side
238 * that details success or failure of the requested operation.
239 *
240 * {@link OutcomeReceiver#onResult} will be called if Telecom has
241 * successfully changed the CallEndpoint that was requested.
242 *
243 * {@link OutcomeReceiver#onError} will be called if Telecom has failed to
244 * switch to the requested CallEndpoint. A {@link CallException} will be
245 * passed that details why the operation failed.
246 */
247 public void requestCallEndpointChange(@NonNull CallEndpoint callEndpoint,
248 @CallbackExecutor @NonNull Executor executor,
249 @NonNull OutcomeReceiver<Void, CallException> callback) {
250 Objects.requireNonNull(callEndpoint);
251 Objects.requireNonNull(executor);
252 Objects.requireNonNull(callback);
253 if (mServerInterface != null) {
254 try {
255 mServerInterface.requestCallEndpointChange(callEndpoint,
256 new CallControlResultReceiver("endpointChange", executor, callback));
257 } catch (RemoteException e) {
258 throw e.rethrowAsRuntimeException();
259 }
260 } else {
261 throw new IllegalStateException(INTERFACE_ERROR_MSG);
262 }
263 }
264
265 /**
Thomas Stuart9bfb2432022-09-27 15:02:07 -0700266 * Since {@link OutcomeReceiver}s cannot be passed via AIDL, a ResultReceiver (which can) must
267 * wrap the Clients {@link OutcomeReceiver} passed in and await for the Telecom Server side
268 * response in {@link ResultReceiver#onReceiveResult(int, Bundle)}.
269 * @hide */
270 private class CallControlResultReceiver extends ResultReceiver {
271 private final String mCallingMethod;
272 private final Executor mExecutor;
273 private final OutcomeReceiver<Void, CallException> mClientCallback;
274
275 CallControlResultReceiver(String method, Executor executor,
276 OutcomeReceiver<Void, CallException> clientCallback) {
277 super(null);
278 mCallingMethod = method;
279 mExecutor = executor;
280 mClientCallback = clientCallback;
281 }
282
283 @Override
284 protected void onReceiveResult(int resultCode, Bundle resultData) {
285 Log.d(CallControl.TAG, "%s: oRR: resultCode=[%s]", mCallingMethod, resultCode);
286 super.onReceiveResult(resultCode, resultData);
287 final long identity = Binder.clearCallingIdentity();
288 try {
289 if (resultCode == TelecomManager.TELECOM_TRANSACTION_SUCCESS) {
290 mExecutor.execute(() -> mClientCallback.onResult(null));
291 } else {
292 mExecutor.execute(() ->
293 mClientCallback.onError(getTransactionException(resultData)));
294 }
295 } finally {
296 Binder.restoreCallingIdentity(identity);
297 }
298 }
299
300 }
301
302 /** @hide */
303 private CallException getTransactionException(Bundle resultData) {
304 String message = "unknown error";
305 if (resultData != null && resultData.containsKey(TRANSACTION_EXCEPTION_KEY)) {
306 return resultData.getParcelable(TRANSACTION_EXCEPTION_KEY,
307 CallException.class);
308 }
309 return new CallException(message, CallException.CODE_ERROR_UNKNOWN);
310 }
311}