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.server.telecom;
18 
19 import static android.telecom.CallException.CODE_CALL_IS_NOT_BEING_TRACKED;
20 import static android.telecom.CallException.TRANSACTION_EXCEPTION_KEY;
21 import static android.telecom.TelecomManager.TELECOM_TRANSACTION_SUCCESS;
22 
23 import android.content.ComponentName;
24 import android.os.Binder;
25 import android.os.Bundle;
26 import android.os.IBinder;
27 import android.os.OutcomeReceiver;
28 import android.os.RemoteException;
29 import android.os.ResultReceiver;
30 import android.telecom.CallEndpoint;
31 import android.telecom.CallException;
32 import android.telecom.CallStreamingService;
33 import android.telecom.DisconnectCause;
34 import android.telecom.Log;
35 import android.telecom.PhoneAccountHandle;
36 import android.text.TextUtils;
37 
38 import androidx.annotation.VisibleForTesting;
39 
40 import com.android.internal.telecom.ICallControl;
41 import com.android.internal.telecom.ICallEventCallback;
42 import com.android.server.telecom.voip.CallEventCallbackAckTransaction;
43 import com.android.server.telecom.voip.EndpointChangeTransaction;
44 import com.android.server.telecom.voip.HoldCallTransaction;
45 import com.android.server.telecom.voip.EndCallTransaction;
46 import com.android.server.telecom.voip.MaybeHoldCallForNewCallTransaction;
47 import com.android.server.telecom.voip.RequestNewActiveCallTransaction;
48 import com.android.server.telecom.voip.SerialTransaction;
49 import com.android.server.telecom.voip.SetMuteStateTransaction;
50 import com.android.server.telecom.voip.RequestVideoStateTransaction;
51 import com.android.server.telecom.voip.TransactionManager;
52 import com.android.server.telecom.voip.VoipCallTransaction;
53 import com.android.server.telecom.voip.VoipCallTransactionResult;
54 
55 import java.util.ArrayList;
56 import java.util.List;
57 import java.util.Locale;
58 import java.util.Set;
59 import java.util.concurrent.ConcurrentHashMap;
60 
61 /**
62  * Implements {@link android.telecom.CallEventCallback} and {@link android.telecom.CallControl}
63  * on a per-client basis which is tied to a {@link PhoneAccountHandle}
64  */
65 public class TransactionalServiceWrapper implements
66         ConnectionServiceFocusManager.ConnectionServiceFocus, CallSourceService {
67     private static final String TAG = TransactionalServiceWrapper.class.getSimpleName();
68 
69     // CallControl : Client (ex. voip app) --> Telecom
70     public static final String SET_ACTIVE = "SetActive";
71     public static final String SET_INACTIVE = "SetInactive";
72     public static final String ANSWER = "Answer";
73     public static final String DISCONNECT = "Disconnect";
74     public static final String START_STREAMING = "StartStreaming";
75     public static final String REQUEST_VIDEO_STATE = "RequestVideoState";
76 
77     // CallEventCallback : Telecom --> Client (ex. voip app)
78     public static final String ON_SET_ACTIVE = "onSetActive";
79     public static final String ON_SET_INACTIVE = "onSetInactive";
80     public static final String ON_ANSWER = "onAnswer";
81     public static final String ON_DISCONNECT = "onDisconnect";
82     public static final String ON_STREAMING_STARTED = "onStreamingStarted";
83 
84     private final CallsManager mCallsManager;
85     private final ICallEventCallback mICallEventCallback;
86     private final PhoneAccountHandle mPhoneAccountHandle;
87     private final TransactionalServiceRepository mRepository;
88     private ConnectionServiceFocusManager.ConnectionServiceFocusListener mConnSvrFocusListener;
89     // init when constructor is called
90     private final ConcurrentHashMap<String, Call> mTrackedCalls = new ConcurrentHashMap<>();
91     private final TelecomSystem.SyncRoot mLock;
92     private final String mPackageName;
93     // needs to be non-final for testing
94     private TransactionManager mTransactionManager;
95     private CallStreamingController mStreamingController;
96 
97 
98     // Each TransactionalServiceWrapper should have their own Binder.DeathRecipient to clean up
99     // any calls in the event the application crashes or is force stopped.
100     private final IBinder.DeathRecipient mAppDeathListener = new IBinder.DeathRecipient() {
101         @Override
102         public void binderDied() {
103             Log.i(TAG, "binderDied: for package=[%s]; cleaning calls", mPackageName);
104             cleanupTransactionalServiceWrapper();
105             mICallEventCallback.asBinder().unlinkToDeath(this, 0);
106         }
107     };
108 
TransactionalServiceWrapper(ICallEventCallback callEventCallback, CallsManager callsManager, PhoneAccountHandle phoneAccountHandle, Call call, TransactionalServiceRepository repo)109     public TransactionalServiceWrapper(ICallEventCallback callEventCallback,
110             CallsManager callsManager, PhoneAccountHandle phoneAccountHandle, Call call,
111             TransactionalServiceRepository repo) {
112         // passed args
113         mICallEventCallback = callEventCallback;
114         mCallsManager = callsManager;
115         mPhoneAccountHandle = phoneAccountHandle;
116         mTrackedCalls.put(call.getId(), call); // service is now tracking its first call
117         mRepository = repo;
118         // init instance vars
119         mPackageName = phoneAccountHandle.getComponentName().getPackageName();
120         mTransactionManager = TransactionManager.getInstance();
121         mStreamingController = mCallsManager.getCallStreamingController();
122         mLock = mCallsManager.getLock();
123         setDeathRecipient(callEventCallback);
124     }
125 
126     @VisibleForTesting
setTransactionManager(TransactionManager transactionManager)127     public void setTransactionManager(TransactionManager transactionManager) {
128         mTransactionManager = transactionManager;
129     }
130 
getTransactionManager()131     public TransactionManager getTransactionManager() {
132         return mTransactionManager;
133     }
134 
135     @VisibleForTesting
getPhoneAccountHandle()136     public PhoneAccountHandle getPhoneAccountHandle() {
137         return mPhoneAccountHandle;
138     }
139 
trackCall(Call call)140     public void trackCall(Call call) {
141         synchronized (mLock) {
142             if (call != null) {
143                 mTrackedCalls.put(call.getId(), call);
144             }
145         }
146     }
147 
148     @VisibleForTesting
untrackCall(Call call)149     public boolean untrackCall(Call call) {
150         Call removedCall = null;
151         synchronized (mLock) {
152             if (call != null) {
153                 removedCall = mTrackedCalls.remove(call.getId());
154                 if (mTrackedCalls.size() == 0) {
155                     mRepository.removeServiceWrapper(mPhoneAccountHandle);
156                 }
157             }
158         }
159         Log.i(TAG, "removedCall call=" + removedCall);
160         return removedCall != null;
161     }
162 
163     @VisibleForTesting
getNumberOfTrackedCalls()164     public int getNumberOfTrackedCalls() {
165         int callCount = 0;
166         synchronized (mLock) {
167             callCount = mTrackedCalls.size();
168         }
169         return callCount;
170     }
171 
cleanupTransactionalServiceWrapper()172     private void cleanupTransactionalServiceWrapper() {
173         for (Call call : mTrackedCalls.values()) {
174             mCallsManager.markCallAsDisconnected(call,
175                     new DisconnectCause(DisconnectCause.ERROR, "process died"));
176             mCallsManager.removeCall(call); // This will clear mTrackedCalls && ClientTWS
177         }
178     }
179 
180     /***
181      *********************************************************************************************
182      **                        ICallControl: Client --> Server                                **
183      **********************************************************************************************
184      */
185     private final ICallControl mICallControl = new ICallControl.Stub() {
186         @Override
187         public void setActive(String callId, android.os.ResultReceiver callback)
188                 throws RemoteException {
189             long token = Binder.clearCallingIdentity();
190             try {
191                 Log.startSession("TSW.sA");
192                 createTransactions(callId, callback, SET_ACTIVE);
193             } finally {
194                 Binder.restoreCallingIdentity(token);
195                 Log.endSession();
196             }
197         }
198 
199         @Override
200         public void answer(int videoState, String callId, android.os.ResultReceiver callback)
201                 throws RemoteException {
202             long token = Binder.clearCallingIdentity();
203             try {
204                 Log.startSession("TSW.a");
205                 createTransactions(callId, callback, ANSWER, videoState);
206             } finally {
207                 Binder.restoreCallingIdentity(token);
208                 Log.endSession();
209             }
210         }
211 
212         @Override
213         public void setInactive(String callId, android.os.ResultReceiver callback)
214                 throws RemoteException {
215             long token = Binder.clearCallingIdentity();
216             try {
217                 Log.startSession("TSW.sI");
218                 createTransactions(callId, callback, SET_INACTIVE);
219             } finally {
220                 Binder.restoreCallingIdentity(token);
221                 Log.endSession();
222             }
223         }
224 
225         @Override
226         public void disconnect(String callId, DisconnectCause disconnectCause,
227                 android.os.ResultReceiver callback)
228                 throws RemoteException {
229             long token = Binder.clearCallingIdentity();
230             try {
231                 Log.startSession("TSW.d");
232                 createTransactions(callId, callback, DISCONNECT, disconnectCause);
233             } finally {
234                 Binder.restoreCallingIdentity(token);
235                 Log.endSession();
236             }
237         }
238 
239         @Override
240         public void setMuteState(boolean isMuted, android.os.ResultReceiver callback)
241                 throws RemoteException {
242             long token = Binder.clearCallingIdentity();
243             try {
244                 Log.startSession("TSW.sMS");
245                 addTransactionsToManager(
246                         new SetMuteStateTransaction(mCallsManager, isMuted), callback);
247             } finally {
248                 Binder.restoreCallingIdentity(token);
249                 Log.endSession();
250             }
251         }
252 
253         @Override
254         public void startCallStreaming(String callId, android.os.ResultReceiver callback)
255                 throws RemoteException {
256             long token = Binder.clearCallingIdentity();
257             try {
258                 Log.startSession("TSW.sCS");
259                 createTransactions(callId, callback, START_STREAMING);
260             } finally {
261                 Binder.restoreCallingIdentity(token);
262                 Log.endSession();
263             }
264         }
265 
266         @Override
267         public void requestVideoState(int videoState, String callId, ResultReceiver callback)
268                 throws RemoteException {
269             long token = Binder.clearCallingIdentity();
270             try {
271                 Log.startSession("TSW.rVS");
272                 createTransactions(callId, callback, REQUEST_VIDEO_STATE, videoState);
273             } finally {
274                 Binder.restoreCallingIdentity(token);
275                 Log.endSession();
276             }
277         }
278 
279         private void createTransactions(String callId, ResultReceiver callback, String action,
280                 Object... objects) {
281             Log.d(TAG, "createTransactions: callId=" + callId);
282             Call call = mTrackedCalls.get(callId);
283             if (call != null) {
284                 switch (action) {
285                     case SET_ACTIVE:
286                         handleCallControlNewCallFocusTransactions(call, SET_ACTIVE,
287                                 false /* isAnswer */, 0/*VideoState (ignored)*/, callback);
288                         break;
289                     case ANSWER:
290                         handleCallControlNewCallFocusTransactions(call, ANSWER,
291                                 true /* isAnswer */, (int) objects[0] /*VideoState*/, callback);
292                         break;
293                     case DISCONNECT:
294                         addTransactionsToManager(new EndCallTransaction(mCallsManager,
295                                 (DisconnectCause) objects[0], call), callback);
296                         break;
297                     case SET_INACTIVE:
298                         addTransactionsToManager(
299                                 new HoldCallTransaction(mCallsManager, call), callback);
300                         break;
301                     case START_STREAMING:
302                         addTransactionsToManager(mStreamingController.getStartStreamingTransaction(mCallsManager,
303                                 TransactionalServiceWrapper.this, call, mLock), callback);
304                         break;
305                     case REQUEST_VIDEO_STATE:
306                         addTransactionsToManager(
307                                 new RequestVideoStateTransaction(mCallsManager, call,
308                                         (int) objects[0]), callback);
309                         break;
310                 }
311             } else {
312                 Bundle exceptionBundle = new Bundle();
313                 exceptionBundle.putParcelable(TRANSACTION_EXCEPTION_KEY,
314                         new CallException(TextUtils.formatSimple(
315                         "Telecom cannot process [%s] because the call with id=[%s] is no longer "
316                                 + "being tracked. This is most likely a result of the call "
317                                 + "already being disconnected and removed. Try re-adding the call"
318                                 + " via TelecomManager#addCall", action, callId),
319                                 CODE_CALL_IS_NOT_BEING_TRACKED));
320                 callback.send(CODE_CALL_IS_NOT_BEING_TRACKED, exceptionBundle);
321             }
322         }
323 
324         // The client is request their VoIP call state go ACTIVE/ANSWERED.
325         // This request is originating from the VoIP application.
326         private void handleCallControlNewCallFocusTransactions(Call call, String action,
327                 boolean isAnswer, int potentiallyNewVideoState, ResultReceiver callback) {
328             mTransactionManager.addTransaction(
329                     createSetActiveTransactions(call, true /* isCallControlRequest */),
330                     new OutcomeReceiver<>() {
331                         @Override
332                         public void onResult(VoipCallTransactionResult result) {
333                             Log.i(TAG, String.format(Locale.US,
334                                     "%s: onResult: callId=[%s]", action, call.getId()));
335                             if (isAnswer) {
336                                 call.setVideoState(potentiallyNewVideoState);
337                             }
338                             callback.send(TELECOM_TRANSACTION_SUCCESS, new Bundle());
339                         }
340 
341                         @Override
342                         public void onError(CallException exception) {
343                             Bundle extras = new Bundle();
344                             extras.putParcelable(TRANSACTION_EXCEPTION_KEY, exception);
345                             callback.send(exception == null ? CallException.CODE_ERROR_UNKNOWN :
346                                     exception.getCode(), extras);
347                         }
348                     });
349         }
350 
351         @Override
352         public void requestCallEndpointChange(CallEndpoint endpoint, ResultReceiver callback) {
353             long token = Binder.clearCallingIdentity();
354             try {
355                 Log.startSession("TSW.rCEC");
356                 addTransactionsToManager(new EndpointChangeTransaction(endpoint, mCallsManager),
357                         callback);
358             } finally {
359                 Binder.restoreCallingIdentity(token);
360                 Log.endSession();
361             }
362         }
363 
364         /**
365          * Application would like to inform InCallServices of an event
366          */
367         @Override
368         public void sendEvent(String callId, String event, Bundle extras) {
369             long token = Binder.clearCallingIdentity();
370             try {
371                 Log.startSession("TSW.sE");
372                 Call call = mTrackedCalls.get(callId);
373                 if (call != null) {
374                     call.onConnectionEvent(event, extras);
375                 } else {
376                     Log.i(TAG,
377                             "sendEvent: was called but there is no call with id=[%s] cannot be "
378                                     + "found. Most likely the call has been disconnected");
379                 }
380             } finally {
381                 Binder.restoreCallingIdentity(token);
382                 Log.endSession();
383             }
384         }
385     };
386 
addTransactionsToManager(VoipCallTransaction transaction, ResultReceiver callback)387     private void addTransactionsToManager(VoipCallTransaction transaction,
388             ResultReceiver callback) {
389         Log.d(TAG, "addTransactionsToManager");
390 
391         mTransactionManager.addTransaction(transaction, new OutcomeReceiver<>() {
392             @Override
393             public void onResult(VoipCallTransactionResult result) {
394                 Log.d(TAG, "addTransactionsToManager: onResult:");
395                 callback.send(TELECOM_TRANSACTION_SUCCESS, new Bundle());
396             }
397 
398             @Override
399             public void onError(CallException exception) {
400                 Log.d(TAG, "addTransactionsToManager: onError");
401                 Bundle extras = new Bundle();
402                 extras.putParcelable(TRANSACTION_EXCEPTION_KEY, exception);
403                 callback.send(exception == null ? CallException.CODE_ERROR_UNKNOWN :
404                         exception.getCode(), extras);
405             }
406         });
407     }
408 
getICallControl()409     public ICallControl getICallControl() {
410         return mICallControl;
411     }
412 
413     /***
414      *********************************************************************************************
415      **                    ICallEventCallback: Server --> Client                                **
416      **********************************************************************************************
417      */
418 
onSetActive(Call call)419     public void onSetActive(Call call) {
420         try {
421             Log.startSession("TSW.oSA");
422             Log.d(TAG, String.format(Locale.US, "onSetActive: callId=[%s]", call.getId()));
423             handleCallEventCallbackNewFocus(call, ON_SET_ACTIVE, false /*isAnswerRequest*/,
424                     0 /*VideoState*/);
425         } finally {
426             Log.endSession();
427         }
428     }
429 
onAnswer(Call call, int videoState)430     public void onAnswer(Call call, int videoState) {
431         try {
432             Log.startSession("TSW.oA");
433             Log.d(TAG, String.format(Locale.US, "onAnswer: callId=[%s]", call.getId()));
434             handleCallEventCallbackNewFocus(call, ON_ANSWER, true /*isAnswerRequest*/,
435                     videoState /*VideoState*/);
436         } finally {
437             Log.endSession();
438         }
439     }
440 
441     // handle a CallEventCallback to set a call ACTIVE/ANSWERED. Must get ack from client since the
442     // request has come from another source (ex. Android Auto is requesting a call to go active)
handleCallEventCallbackNewFocus(Call call, String action, boolean isAnswerRequest, int potentiallyNewVideoState)443     private void handleCallEventCallbackNewFocus(Call call, String action, boolean isAnswerRequest,
444             int potentiallyNewVideoState) {
445         // save CallsManager state before sending client state changes
446         Call foregroundCallBeforeSwap = mCallsManager.getForegroundCall();
447         boolean wasActive = foregroundCallBeforeSwap != null && foregroundCallBeforeSwap.isActive();
448 
449         SerialTransaction serialTransactions = createSetActiveTransactions(call,
450                 false /* isCallControlRequest */);
451         // 3. get ack from client (that the requested call can go active)
452         if (isAnswerRequest) {
453             serialTransactions.appendTransaction(
454                     new CallEventCallbackAckTransaction(mICallEventCallback,
455                             action, call.getId(), potentiallyNewVideoState, mLock));
456         } else {
457             serialTransactions.appendTransaction(
458                     new CallEventCallbackAckTransaction(mICallEventCallback,
459                             action, call.getId(), mLock));
460         }
461 
462         // do CallsManager workload before asking client and
463         //   reset CallsManager state if client does NOT ack
464         mTransactionManager.addTransaction(serialTransactions,
465                 new OutcomeReceiver<>() {
466                     @Override
467                     public void onResult(VoipCallTransactionResult result) {
468                         Log.i(TAG, String.format(Locale.US,
469                                 "%s: onResult: callId=[%s]", action, call.getId()));
470                         if (isAnswerRequest) {
471                             call.setVideoState(potentiallyNewVideoState);
472                         }
473                     }
474 
475                     @Override
476                     public void onError(CallException exception) {
477                         if (isAnswerRequest) {
478                             // This also sends the signal to untrack from TSW and the client_TSW
479                             removeCallFromCallsManager(call,
480                                     new DisconnectCause(DisconnectCause.REJECTED,
481                                             "client rejected to answer the call;"
482                                                     + " force disconnecting"));
483                         } else {
484                             mCallsManager.markCallAsOnHold(call);
485                         }
486                         maybeResetForegroundCall(foregroundCallBeforeSwap, wasActive);
487                     }
488                 });
489     }
490 
491 
onSetInactive(Call call)492     public void onSetInactive(Call call) {
493         try {
494             Log.startSession("TSW.oSI");
495             Log.i(TAG, String.format(Locale.US, "onSetInactive: callId=[%s]", call.getId()));
496             mTransactionManager.addTransaction(
497                     new CallEventCallbackAckTransaction(mICallEventCallback,
498                             ON_SET_INACTIVE, call.getId(), mLock), new OutcomeReceiver<>() {
499                         @Override
500                         public void onResult(VoipCallTransactionResult result) {
501                             mCallsManager.markCallAsOnHold(call);
502                         }
503 
504                         @Override
505                         public void onError(CallException exception) {
506                             Log.w(TAG, "onSetInactive: onError: e.code=[%d], e.msg=[%s]",
507                                     exception.getCode(), exception.getMessage());
508                         }
509                     });
510         } finally {
511             Log.endSession();
512         }
513     }
514 
onDisconnect(Call call, DisconnectCause cause)515     public void onDisconnect(Call call, DisconnectCause cause) {
516         try {
517             Log.startSession("TSW.oD");
518             Log.d(TAG, String.format(Locale.US, "onDisconnect: callId=[%s]", call.getId()));
519 
520             mTransactionManager.addTransaction(
521                     new CallEventCallbackAckTransaction(mICallEventCallback, ON_DISCONNECT,
522                             call.getId(), cause, mLock), new OutcomeReceiver<>() {
523                         @Override
524                         public void onResult(VoipCallTransactionResult result) {
525                             removeCallFromCallsManager(call, cause);
526                         }
527 
528                         @Override
529                         public void onError(CallException exception) {
530                             removeCallFromCallsManager(call, cause);
531                         }
532                     }
533             );
534         } finally {
535             Log.endSession();
536         }
537     }
538 
onCallStreamingStarted(Call call)539     public void onCallStreamingStarted(Call call) {
540         try {
541             Log.startSession("TSW.oCSS");
542             Log.d(TAG, String.format(Locale.US, "onCallStreamingStarted: callId=[%s]",
543                     call.getId()));
544 
545             mTransactionManager.addTransaction(
546                     new CallEventCallbackAckTransaction(mICallEventCallback, ON_STREAMING_STARTED,
547                             call.getId(), mLock), new OutcomeReceiver<>() {
548                         @Override
549                         public void onResult(VoipCallTransactionResult result) {
550                         }
551 
552                         @Override
553                         public void onError(CallException exception) {
554                             Log.w(TAG, "onCallStreamingStarted: onError: "
555                                             + "e.code=[%d], e.msg=[%s]",
556                                     exception.getCode(), exception.getMessage());
557                             stopCallStreaming(call);
558                         }
559                     }
560             );
561         } finally {
562             Log.endSession();
563         }
564     }
565 
onCallStreamingFailed(Call call, @CallStreamingService.StreamingFailedReason int streamingFailedReason)566     public void onCallStreamingFailed(Call call,
567             @CallStreamingService.StreamingFailedReason int streamingFailedReason) {
568         if (call != null) {
569             try {
570                 mICallEventCallback.onCallStreamingFailed(call.getId(), streamingFailedReason);
571             } catch (RemoteException e) {
572             }
573         }
574     }
575 
576     @Override
onCallEndpointChanged(Call call, CallEndpoint endpoint)577     public void onCallEndpointChanged(Call call, CallEndpoint endpoint) {
578         if (call != null) {
579             try {
580                 mICallEventCallback.onCallEndpointChanged(call.getId(), endpoint);
581             } catch (RemoteException e) {
582             }
583         }
584     }
585 
586     @Override
onAvailableCallEndpointsChanged(Call call, Set<CallEndpoint> endpoints)587     public void onAvailableCallEndpointsChanged(Call call, Set<CallEndpoint> endpoints) {
588         if (call != null) {
589             try {
590                 mICallEventCallback.onAvailableCallEndpointsChanged(call.getId(),
591                         endpoints.stream().toList());
592             } catch (RemoteException e) {
593             }
594         }
595     }
596 
597     @Override
onMuteStateChanged(Call call, boolean isMuted)598     public void onMuteStateChanged(Call call, boolean isMuted) {
599         if (call != null) {
600             try {
601                 mICallEventCallback.onMuteStateChanged(call.getId(), isMuted);
602             } catch (RemoteException e) {
603             }
604         }
605     }
606 
607     @Override
onVideoStateChanged(Call call, int videoState)608     public void onVideoStateChanged(Call call, int videoState) {
609         if (call != null) {
610             try {
611                 mICallEventCallback.onVideoStateChanged(call.getId(), videoState);
612             } catch (RemoteException e) {
613             }
614         }
615     }
616 
removeCallFromWrappers(Call call)617     public void removeCallFromWrappers(Call call) {
618         if (call != null) {
619             try {
620                 // remove the call from frameworks wrapper (client side)
621                 mICallEventCallback.removeCallFromTransactionalServiceWrapper(call.getId());
622             } catch (RemoteException e) {
623             }
624             // remove the call from this class/wrapper (server side)
625             untrackCall(call);
626         }
627     }
628 
onEvent(Call call, String event, Bundle extras)629     public void onEvent(Call call, String event, Bundle extras) {
630         if (call != null) {
631             try {
632                 mICallEventCallback.onEvent(call.getId(), event, extras);
633             } catch (RemoteException e) {
634             }
635         }
636     }
637 
638     /***
639      *********************************************************************************************
640      **                                Helpers                                                  **
641      **********************************************************************************************
642      */
maybeResetForegroundCall(Call foregroundCallBeforeSwap, boolean wasActive)643     private void maybeResetForegroundCall(Call foregroundCallBeforeSwap, boolean wasActive) {
644         if (foregroundCallBeforeSwap == null) {
645             return;
646         }
647         if (wasActive && !foregroundCallBeforeSwap.isActive()) {
648             mCallsManager.markCallAsActive(foregroundCallBeforeSwap);
649         }
650     }
651 
removeCallFromCallsManager(Call call, DisconnectCause cause)652     private void removeCallFromCallsManager(Call call, DisconnectCause cause) {
653         if (cause.getCode() != DisconnectCause.REJECTED) {
654             mCallsManager.markCallAsDisconnected(call, cause);
655         }
656         mCallsManager.removeCall(call);
657     }
658 
createSetActiveTransactions(Call call, boolean isCallControlRequest)659     private SerialTransaction createSetActiveTransactions(Call call, boolean isCallControlRequest) {
660         // create list for multiple transactions
661         List<VoipCallTransaction> transactions = new ArrayList<>();
662 
663         // potentially hold the current active call in order to set a new call (active/answered)
664         transactions.add(
665                 new MaybeHoldCallForNewCallTransaction(mCallsManager, call, isCallControlRequest));
666         // And request a new focus call update
667         transactions.add(new RequestNewActiveCallTransaction(mCallsManager, call));
668 
669         return new SerialTransaction(transactions, mLock);
670     }
671 
setDeathRecipient(ICallEventCallback callEventCallback)672     private void setDeathRecipient(ICallEventCallback callEventCallback) {
673         try {
674             callEventCallback.asBinder().linkToDeath(mAppDeathListener, 0);
675         } catch (Exception e) {
676             Log.w(TAG, "setDeathRecipient: hit exception=[%s] trying to link binder to death",
677                     e.toString());
678         }
679     }
680 
681     /***
682      *********************************************************************************************
683      **                    FocusManager                                                       **
684      **********************************************************************************************
685      */
686 
687     @Override
connectionServiceFocusLost()688     public void connectionServiceFocusLost() {
689         if (mConnSvrFocusListener != null) {
690             mConnSvrFocusListener.onConnectionServiceReleased(this);
691         }
692         Log.i(TAG, String.format(Locale.US, "connectionServiceFocusLost for package=[%s]",
693                 mPackageName));
694     }
695 
696     @Override
connectionServiceFocusGained()697     public void connectionServiceFocusGained() {
698         Log.i(TAG, String.format(Locale.US, "connectionServiceFocusGained for package=[%s]",
699                 mPackageName));
700     }
701 
702     @Override
setConnectionServiceFocusListener( ConnectionServiceFocusManager.ConnectionServiceFocusListener listener)703     public void setConnectionServiceFocusListener(
704             ConnectionServiceFocusManager.ConnectionServiceFocusListener listener) {
705         mConnSvrFocusListener = listener;
706     }
707 
708     @Override
getComponentName()709     public ComponentName getComponentName() {
710         return mPhoneAccountHandle.getComponentName();
711     }
712 
713     /***
714      *********************************************************************************************
715      **                    CallStreaming                                                        **
716      *********************************************************************************************
717      */
718 
stopCallStreaming(Call call)719     public void stopCallStreaming(Call call) {
720         Log.i(this, "stopCallStreaming; callid=%s", call.getId());
721         if (call != null && call.isStreaming()) {
722             VoipCallTransaction stopStreamingTransaction = mStreamingController
723                     .getStopStreamingTransaction(call, mLock);
724             addTransactionsToManager(stopStreamingTransaction, new ResultReceiver(null));
725         }
726     }
727 }
728