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