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