1 /*
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 
17 package com.android.internal.telecom;
18 
19 import static android.telecom.TelecomManager.TELECOM_TRANSACTION_SUCCESS;
20 
21 import android.os.Binder;
22 import android.os.Bundle;
23 import android.os.OutcomeReceiver;
24 import android.os.ResultReceiver;
25 import android.telecom.CallAttributes;
26 import android.telecom.CallControl;
27 import android.telecom.CallControlCallback;
28 import android.telecom.CallEndpoint;
29 import android.telecom.CallEventCallback;
30 import android.telecom.CallException;
31 import android.telecom.DisconnectCause;
32 import android.telecom.PhoneAccountHandle;
33 import android.text.TextUtils;
34 import android.util.Log;
35 
36 import com.android.server.telecom.flags.Flags;
37 
38 import java.util.List;
39 import java.util.UUID;
40 import java.util.concurrent.ConcurrentHashMap;
41 import java.util.concurrent.Executor;
42 import java.util.function.Consumer;
43 
44 /**
45  * wraps {@link CallControlCallback}, {@link CallEventCallback}, and {@link CallControl} on a
46  * per-{@link  android.telecom.PhoneAccountHandle} basis to track ongoing calls.
47  *
48  * @hide
49  */
50 public class ClientTransactionalServiceWrapper {
51 
52     private static final String TAG = ClientTransactionalServiceWrapper.class.getSimpleName();
53     private final PhoneAccountHandle mPhoneAccountHandle;
54     private final ClientTransactionalServiceRepository mRepository;
55     private final ConcurrentHashMap<String, TransactionalCall> mCallIdToTransactionalCall =
56             new ConcurrentHashMap<>();
57     private static final String EXECUTOR_FAIL_MSG =
58             "Telecom hit an exception while handling a CallEventCallback on an executor: ";
59 
ClientTransactionalServiceWrapper(PhoneAccountHandle handle, ClientTransactionalServiceRepository repo)60     public ClientTransactionalServiceWrapper(PhoneAccountHandle handle,
61             ClientTransactionalServiceRepository repo) {
62         mPhoneAccountHandle = handle;
63         mRepository = repo;
64     }
65 
66     /**
67      * remove the given call from the class HashMap
68      *
69      * @param callId that is tied to TransactionalCall object
70      */
untrackCall(String callId)71     public void untrackCall(String callId) {
72         Log.i(TAG, TextUtils.formatSimple("removeCall: with id=[%s]", callId));
73         if (mCallIdToTransactionalCall.containsKey(callId)) {
74             // remove the call from the hashmap
75             TransactionalCall call = mCallIdToTransactionalCall.remove(callId);
76             // null out interface to avoid memory leaks
77             CallControl control = call.getCallControl();
78             if (control != null) {
79                 call.setCallControl(null);
80             }
81         }
82         // possibly cleanup service wrapper if there are no more calls
83         if (mCallIdToTransactionalCall.size() == 0) {
84             mRepository.removeServiceWrapper(mPhoneAccountHandle);
85         }
86     }
87 
88     /**
89      * start tracking a newly created call for a particular package
90      *
91      * @param callAttributes of the new call
92      * @param executor       to run callbacks on
93      * @param pendingControl that allows telecom to call into the client
94      * @param handshakes     that overrides the CallControlCallback
95      * @param events         that overrides the CallStateCallback
96      * @return the callId of the newly created call
97      */
trackCall(CallAttributes callAttributes, Executor executor, OutcomeReceiver<CallControl, CallException> pendingControl, CallControlCallback handshakes, CallEventCallback events)98     public String trackCall(CallAttributes callAttributes, Executor executor,
99             OutcomeReceiver<CallControl, CallException> pendingControl,
100             CallControlCallback handshakes,
101             CallEventCallback events) {
102         // generate a new id for this new call
103         String newCallId = UUID.randomUUID().toString();
104 
105         // couple the objects passed from the client side
106         mCallIdToTransactionalCall.put(newCallId, new TransactionalCall(newCallId, callAttributes,
107                 executor, pendingControl, handshakes, events));
108 
109         return newCallId;
110     }
111 
getCallEventCallback()112     public ICallEventCallback getCallEventCallback() {
113         return mCallEventCallback;
114     }
115 
116     /**
117      * Consumers that is to be completed by the client and the result relayed back to telecom server
118      * side via a {@link ResultReceiver}. see com.android.server.telecom.TransactionalServiceWrapper
119      * for how the response is handled.
120      */
121     private class ReceiverWrapper implements Consumer<Boolean> {
122         private final ResultReceiver mRepeaterReceiver;
123 
ReceiverWrapper(ResultReceiver resultReceiver)124         ReceiverWrapper(ResultReceiver resultReceiver) {
125             mRepeaterReceiver = resultReceiver;
126         }
127 
128         @Override
accept(Boolean clientCompletedCallbackSuccessfully)129         public void accept(Boolean clientCompletedCallbackSuccessfully) {
130             if (clientCompletedCallbackSuccessfully) {
131                 mRepeaterReceiver.send(TELECOM_TRANSACTION_SUCCESS, null);
132             } else {
133                 mRepeaterReceiver.send(CallException.CODE_ERROR_UNKNOWN, null);
134             }
135         }
136 
137         @Override
andThen(Consumer<? super Boolean> after)138         public Consumer<Boolean> andThen(Consumer<? super Boolean> after) {
139             return Consumer.super.andThen(after);
140         }
141     }
142 
143     private final ICallEventCallback mCallEventCallback = new ICallEventCallback.Stub() {
144 
145         private static final String ON_SET_ACTIVE = "onSetActive";
146         private static final String ON_SET_INACTIVE = "onSetInactive";
147         private static final String ON_ANSWER = "onAnswer";
148         private static final String ON_DISCONNECT = "onDisconnect";
149         private static final String ON_STREAMING_STARTED = "onStreamingStarted";
150         private static final String ON_REQ_ENDPOINT_CHANGE = "onRequestEndpointChange";
151         private static final String ON_AVAILABLE_CALL_ENDPOINTS = "onAvailableCallEndpointsChanged";
152         private static final String ON_MUTE_STATE_CHANGED = "onMuteStateChanged";
153         private static final String ON_VIDEO_STATE_CHANGED = "onVideoStateChanged";
154         private static final String ON_CALL_STREAMING_FAILED = "onCallStreamingFailed";
155         private static final String ON_EVENT = "onEvent";
156 
157         private void handleCallEventCallback(String action, String callId,
158                 ResultReceiver ackResultReceiver, Object... args) {
159             Log.i(TAG, TextUtils.formatSimple("hCEC: id=[%s], action=[%s]", callId, action));
160             // lookup the callEventCallback associated with the particular call
161             TransactionalCall call = mCallIdToTransactionalCall.get(callId);
162 
163             if (call != null) {
164                 // Get the CallEventCallback interface
165                 CallControlCallback callback = call.getCallControlCallback();
166                 // Get Receiver to wait on client ack
167                 ReceiverWrapper outcomeReceiverWrapper = new ReceiverWrapper(ackResultReceiver);
168 
169                 // wait for the client to complete the CallEventCallback
170                 final long identity = Binder.clearCallingIdentity();
171                 try {
172                     call.getExecutor().execute(() -> {
173                         switch (action) {
174                             case ON_SET_ACTIVE:
175                                 callback.onSetActive(outcomeReceiverWrapper);
176                                 break;
177                             case ON_SET_INACTIVE:
178                                 callback.onSetInactive(outcomeReceiverWrapper);
179                                 break;
180                             case ON_DISCONNECT:
181                                 callback.onDisconnect((DisconnectCause) args[0],
182                                         outcomeReceiverWrapper);
183                                 untrackCall(callId);
184                                 break;
185                             case ON_ANSWER:
186                                 callback.onAnswer((int) args[0], outcomeReceiverWrapper);
187                                 break;
188                             case ON_STREAMING_STARTED:
189                                 callback.onCallStreamingStarted(outcomeReceiverWrapper);
190                                 break;
191                         }
192                     });
193                 } catch (Exception e) {
194                     Log.e(TAG, EXECUTOR_FAIL_MSG + e);
195                 } finally {
196                     Binder.restoreCallingIdentity(identity);
197                 }
198             }
199         }
200 
201         @Override
202         public void onAddCallControl(String callId, int resultCode, ICallControl callControl,
203                 CallException transactionalException) {
204             Log.i(TAG, TextUtils.formatSimple("oACC: id=[%s], code=[%d]", callId, resultCode));
205             TransactionalCall call = mCallIdToTransactionalCall.get(callId);
206 
207             if (call != null) {
208                 OutcomeReceiver<CallControl, CallException> pendingControl =
209                         call.getPendingControl();
210 
211                 if (resultCode == TELECOM_TRANSACTION_SUCCESS) {
212 
213                     // create the interface object that the client will interact with
214                     CallControl control = new CallControl(callId, callControl);
215                     // give the client the object via the OR that was passed into addCall
216                     pendingControl.onResult(control);
217 
218                     // store for later reference
219                     call.setCallControl(control);
220                 } else {
221                     pendingControl.onError(transactionalException);
222                     mCallIdToTransactionalCall.remove(callId);
223                 }
224 
225             } else {
226                 untrackCall(callId);
227                 Log.e(TAG, "oACC: TransactionalCall object not found for call w/ id=" + callId);
228             }
229         }
230 
231         @Override
232         public void onSetActive(String callId, ResultReceiver resultReceiver) {
233             handleCallEventCallback(ON_SET_ACTIVE, callId, resultReceiver);
234         }
235 
236         @Override
237         public void onSetInactive(String callId, ResultReceiver resultReceiver) {
238             handleCallEventCallback(ON_SET_INACTIVE, callId, resultReceiver);
239         }
240 
241         @Override
242         public void onAnswer(String callId, int videoState, ResultReceiver resultReceiver) {
243             handleCallEventCallback(ON_ANSWER, callId, resultReceiver, videoState);
244         }
245 
246         @Override
247         public void onDisconnect(String callId, DisconnectCause cause,
248                 ResultReceiver resultReceiver) {
249             handleCallEventCallback(ON_DISCONNECT, callId, resultReceiver, cause);
250         }
251 
252         @Override
253         public void onCallEndpointChanged(String callId, CallEndpoint endpoint) {
254             handleEventCallback(callId, ON_REQ_ENDPOINT_CHANGE, endpoint);
255         }
256 
257         @Override
258         public void onAvailableCallEndpointsChanged(String callId, List<CallEndpoint> endpoints) {
259             handleEventCallback(callId, ON_AVAILABLE_CALL_ENDPOINTS, endpoints);
260         }
261 
262         @Override
263         public void onMuteStateChanged(String callId, boolean isMuted) {
264             handleEventCallback(callId, ON_MUTE_STATE_CHANGED, isMuted);
265         }
266 
267         @Override
268         public void onVideoStateChanged(String callId, int videoState) {
269             handleEventCallback(callId, ON_VIDEO_STATE_CHANGED, videoState);
270         }
271 
272         public void handleEventCallback(String callId, String action, Object arg) {
273             Log.d(TAG, TextUtils.formatSimple("hEC: [%s], callId=[%s]", action, callId));
274             // lookup the callEventCallback associated with the particular call
275             TransactionalCall call = mCallIdToTransactionalCall.get(callId);
276             if (call != null) {
277                 CallEventCallback callback = call.getCallStateCallback();
278                 Executor executor = call.getExecutor();
279                 final long identity = Binder.clearCallingIdentity();
280                 try {
281                     executor.execute(() -> {
282                         switch (action) {
283                             case ON_REQ_ENDPOINT_CHANGE:
284                                 callback.onCallEndpointChanged((CallEndpoint) arg);
285                                 break;
286                             case ON_AVAILABLE_CALL_ENDPOINTS:
287                                 callback.onAvailableCallEndpointsChanged((List<CallEndpoint>) arg);
288                                 break;
289                             case ON_MUTE_STATE_CHANGED:
290                                 callback.onMuteStateChanged((boolean) arg);
291                                 break;
292                             case ON_VIDEO_STATE_CHANGED:
293                                 if (Flags.transactionalVideoState()) {
294                                     callback.onVideoStateChanged((int) arg);
295                                 }
296                                 break;
297                             case ON_CALL_STREAMING_FAILED:
298                                 callback.onCallStreamingFailed((int) arg /* reason */);
299                                 break;
300                         }
301                     });
302                 } finally {
303                     Binder.restoreCallingIdentity(identity);
304                 }
305             }
306         }
307 
308         @Override
309         public void removeCallFromTransactionalServiceWrapper(String callId) {
310             untrackCall(callId);
311         }
312 
313         @Override
314         public void onCallStreamingStarted(String callId, ResultReceiver resultReceiver) {
315             handleCallEventCallback(ON_STREAMING_STARTED, callId, resultReceiver);
316         }
317 
318         @Override
319         public void onCallStreamingFailed(String callId, int reason) {
320             Log.i(TAG, TextUtils.formatSimple("oCSF: id=[%s], reason=[%s]", callId, reason));
321             handleEventCallback(callId, ON_CALL_STREAMING_FAILED, reason);
322         }
323 
324         @Override
325         public void onEvent(String callId, String event, Bundle extras) {
326             // lookup the callEventCallback associated with the particular call
327             TransactionalCall call = mCallIdToTransactionalCall.get(callId);
328             if (call != null) {
329                 CallEventCallback callback = call.getCallStateCallback();
330                 Executor executor = call.getExecutor();
331                 final long identity = Binder.clearCallingIdentity();
332                 try {
333                     executor.execute(() -> {
334                         callback.onEvent(event, extras);
335                     });
336                 } finally {
337                     Binder.restoreCallingIdentity(identity);
338                 }
339             }
340         }
341     };
342 }
343