• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.callsequencing.TransactionalCallSequencingAdapter;
43 import com.android.server.telecom.callsequencing.voip.CallEventCallbackAckTransaction;
44 import com.android.server.telecom.callsequencing.voip.EndpointChangeTransaction;
45 import com.android.server.telecom.callsequencing.voip.SetMuteStateTransaction;
46 import com.android.server.telecom.callsequencing.voip.RequestVideoStateTransaction;
47 import com.android.server.telecom.callsequencing.TransactionManager;
48 import com.android.server.telecom.callsequencing.CallTransaction;
49 import com.android.server.telecom.callsequencing.CallTransactionResult;
50 import com.android.server.telecom.flags.FeatureFlags;
51 
52 import java.util.Locale;
53 import java.util.Set;
54 import java.util.UUID;
55 import java.util.concurrent.CompletableFuture;
56 import java.util.concurrent.ConcurrentHashMap;
57 
58 /**
59  * Implements {@link android.telecom.CallEventCallback} and {@link android.telecom.CallControl}
60  * on a per-client basis which is tied to a {@link PhoneAccountHandle}
61  */
62 public class TransactionalServiceWrapper implements
63         ConnectionServiceFocusManager.ConnectionServiceFocus, CallSourceService {
64     private static final String TAG = TransactionalServiceWrapper.class.getSimpleName();
65 
66     // CallControl : Client (ex. voip app) --> Telecom
67     public static final String SET_ACTIVE = "SetActive";
68     public static final String SET_INACTIVE = "SetInactive";
69     public static final String ANSWER = "Answer";
70     public static final String DISCONNECT = "Disconnect";
71     public static final String START_STREAMING = "StartStreaming";
72     public static final String REQUEST_VIDEO_STATE = "RequestVideoState";
73     public static final String SET_MUTE_STATE = "SetMuteState";
74     public static final String CALL_ENDPOINT_CHANGE = "CallEndpointChange";
75 
76     // CallEventCallback : Telecom --> Client (ex. voip app)
77     public static final String ON_SET_ACTIVE = "onSetActive";
78     public static final String ON_SET_INACTIVE = "onSetInactive";
79     public static final String ON_ANSWER = "onAnswer";
80     public static final String ON_DISCONNECT = "onDisconnect";
81     public static final String ON_STREAMING_STARTED = "onStreamingStarted";
82     public static final String STOP_STREAMING = "stopStreaming";
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     private final TransactionalCallSequencingAdapter mCallSequencingAdapter;
97     private final FeatureFlags mFeatureFlags;
98     private final AnomalyReporterAdapter mAnomalyReporter;
99     public static final UUID CALL_IS_NO_LONGER_BEING_TRACKED_ERROR_UUID =
100             UUID.fromString("8187cd59-97a7-4e9f-a772-638dda4b69bb");
101     public static final String CALL_IS_NO_LONGER_BEING_TRACKED_ERROR_MSG =
102             "A call update was attempted for a call no longer being tracked";
103 
104     // Each TransactionalServiceWrapper should have their own Binder.DeathRecipient to clean up
105     // any calls in the event the application crashes or is force stopped.
106     private final IBinder.DeathRecipient mAppDeathListener = new IBinder.DeathRecipient() {
107         @Override
108         public void binderDied() {
109             Log.i(TAG, "binderDied: for package=[%s]; cleaning calls", mPackageName);
110             cleanupTransactionalServiceWrapper();
111             mICallEventCallback.asBinder().unlinkToDeath(this, 0);
112         }
113     };
114 
TransactionalServiceWrapper(ICallEventCallback callEventCallback, CallsManager callsManager, PhoneAccountHandle phoneAccountHandle, Call call, TransactionalServiceRepository repo, TransactionManager transactionManager, boolean isCallSequencingEnabled, FeatureFlags featureFlags, AnomalyReporterAdapter anomalyReporterAdapter)115     public TransactionalServiceWrapper(ICallEventCallback callEventCallback,
116             CallsManager callsManager, PhoneAccountHandle phoneAccountHandle, Call call,
117             TransactionalServiceRepository repo, TransactionManager transactionManager,
118             boolean isCallSequencingEnabled, FeatureFlags featureFlags,
119             AnomalyReporterAdapter anomalyReporterAdapter) {
120         // passed args
121         mICallEventCallback = callEventCallback;
122         mCallsManager = callsManager;
123         mPhoneAccountHandle = phoneAccountHandle;
124         mTrackedCalls.put(call.getId(), call); // service is now tracking its first call
125         mRepository = repo;
126         mTransactionManager = transactionManager;
127         // init instance vars
128         mPackageName = phoneAccountHandle.getComponentName().getPackageName();
129         mStreamingController = mCallsManager.getCallStreamingController();
130         mLock = mCallsManager.getLock();
131         mCallSequencingAdapter = new TransactionalCallSequencingAdapter(mTransactionManager,
132                 mCallsManager, isCallSequencingEnabled);
133         setDeathRecipient(callEventCallback);
134         mFeatureFlags = featureFlags;
135         mAnomalyReporter = anomalyReporterAdapter;
136     }
137 
getTransactionManager()138     public TransactionManager getTransactionManager() {
139         return mTransactionManager;
140     }
141 
142     @VisibleForTesting
getPhoneAccountHandle()143     public PhoneAccountHandle getPhoneAccountHandle() {
144         return mPhoneAccountHandle;
145     }
146 
trackCall(Call call)147     public void trackCall(Call call) {
148         synchronized (mLock) {
149             if (call != null) {
150                 mTrackedCalls.put(call.getId(), call);
151             }
152         }
153     }
154 
155     @VisibleForTesting
untrackCall(Call call)156     public boolean untrackCall(Call call) {
157         Call removedCall = null;
158         synchronized (mLock) {
159             if (call != null) {
160                 removedCall = mTrackedCalls.remove(call.getId());
161                 if (mTrackedCalls.size() == 0) {
162                     mRepository.removeServiceWrapper(mPhoneAccountHandle);
163                 }
164             }
165         }
166         Log.i(TAG, "removedCall call=" + removedCall);
167         return removedCall != null;
168     }
169 
170     @VisibleForTesting
getNumberOfTrackedCalls()171     public int getNumberOfTrackedCalls() {
172         int callCount = 0;
173         synchronized (mLock) {
174             callCount = mTrackedCalls.size();
175         }
176         return callCount;
177     }
178 
cleanupTransactionalServiceWrapper()179     private void cleanupTransactionalServiceWrapper() {
180         mCallSequencingAdapter.cleanup(mTrackedCalls.values());
181     }
182 
183     /***
184      *********************************************************************************************
185      **                        ICallControl: Client --> Server                                **
186      **********************************************************************************************
187      */
188     private final ICallControl mICallControl = new ICallControl.Stub() {
189         @Override
190         public void setActive(String callId, android.os.ResultReceiver callback) {
191             long token = Binder.clearCallingIdentity();
192             try {
193                 Log.startSession("TSW.sA");
194                 createTransactions(callId, callback, SET_ACTIVE);
195             } finally {
196                 Binder.restoreCallingIdentity(token);
197                 Log.endSession();
198             }
199         }
200 
201         @Override
202 
203         public void answer(int videoState, String callId, android.os.ResultReceiver callback) {
204             long token = Binder.clearCallingIdentity();
205             try {
206                 Log.startSession("TSW.a");
207                 createTransactions(callId, callback, ANSWER, videoState);
208             } finally {
209                 Binder.restoreCallingIdentity(token);
210                 Log.endSession();
211             }
212         }
213 
214         @Override
215         public void setInactive(String callId, android.os.ResultReceiver callback) {
216             long token = Binder.clearCallingIdentity();
217             try {
218                 Log.startSession("TSW.sI");
219                 createTransactions(callId, callback, SET_INACTIVE);
220             } finally {
221                 Binder.restoreCallingIdentity(token);
222                 Log.endSession();
223             }
224         }
225 
226         @Override
227         public void disconnect(String callId, DisconnectCause disconnectCause,
228                 android.os.ResultReceiver callback) {
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             long token = Binder.clearCallingIdentity();
242             try {
243                 Log.startSession("TSW.sMS");
244                 addTransactionsToManager(SET_MUTE_STATE,
245                         new SetMuteStateTransaction(mCallsManager, isMuted), callback);
246             } finally {
247                 Binder.restoreCallingIdentity(token);
248                 Log.endSession();
249             }
250         }
251 
252         @Override
253         public void startCallStreaming(String callId, android.os.ResultReceiver callback) {
254             long token = Binder.clearCallingIdentity();
255             try {
256                 Log.startSession("TSW.sCS");
257                 createTransactions(callId, callback, START_STREAMING);
258             } finally {
259                 Binder.restoreCallingIdentity(token);
260                 Log.endSession();
261             }
262         }
263 
264         @Override
265         public void requestVideoState(int videoState, String callId, ResultReceiver callback) {
266             long token = Binder.clearCallingIdentity();
267             try {
268                 Log.startSession("TSW.rVS");
269                 createTransactions(callId, callback, REQUEST_VIDEO_STATE, videoState);
270             } finally {
271                 Binder.restoreCallingIdentity(token);
272                 Log.endSession();
273             }
274         }
275 
276         private void createTransactions(String callId, ResultReceiver callback, String action,
277                 Object... objects) {
278             Log.d(TAG, "createTransactions: callId=" + callId);
279             Call call = mTrackedCalls.get(callId);
280             if (call != null) {
281                 switch (action) {
282                     case SET_ACTIVE:
283                         mCallSequencingAdapter.setActive(call,
284                                 getCompleteReceiver(action, callback));
285                         break;
286                     case ANSWER:
287                         mCallSequencingAdapter.setAnswered(call, (int) objects[0] /*VideoState*/,
288                                 getCompleteReceiver(action, callback));
289                         break;
290                     case DISCONNECT:
291                         DisconnectCause dc = (DisconnectCause) objects[0];
292                         mCallSequencingAdapter.setDisconnected(call, dc,
293                                 getCompleteReceiver(action, callback));
294                         break;
295                     case SET_INACTIVE:
296                         mCallSequencingAdapter.setInactive(call,
297                                 getCompleteReceiver(action,callback));
298                         break;
299                     case START_STREAMING:
300                         addTransactionsToManager(action,
301                                 mStreamingController.getStartStreamingTransaction(mCallsManager,
302                                 TransactionalServiceWrapper.this, call, mLock),  callback);
303                         break;
304                     case REQUEST_VIDEO_STATE:
305                         addTransactionsToManager(action,
306                                 new RequestVideoStateTransaction(mCallsManager, call,
307                                         (int) objects[0]), callback);
308                         break;
309                 }
310             } else {
311                 Bundle exceptionBundle = new Bundle();
312                 exceptionBundle.putParcelable(TRANSACTION_EXCEPTION_KEY,
313                         new CallException(TextUtils.formatSimple(
314                         "Telecom cannot process [%s] because the call with id=[%s] is no longer "
315                                 + "being tracked. This is most likely a result of the call "
316                                 + "already being disconnected and removed. Try re-adding the call"
317                                 + " via TelecomManager#addCall", action, callId),
318                                 CODE_CALL_IS_NOT_BEING_TRACKED));
319                 callback.send(CODE_CALL_IS_NOT_BEING_TRACKED, exceptionBundle);
320                 if (mFeatureFlags.enableCallExceptionAnomReports()) {
321                     mAnomalyReporter.reportAnomaly(
322                             CALL_IS_NO_LONGER_BEING_TRACKED_ERROR_UUID,
323                             CALL_IS_NO_LONGER_BEING_TRACKED_ERROR_MSG);
324                 }
325             }
326         }
327 
328         @Override
329         public void requestCallEndpointChange(CallEndpoint endpoint, ResultReceiver callback) {
330             long token = Binder.clearCallingIdentity();
331             try {
332                 Log.startSession("TSW.rCEC");
333                 addTransactionsToManager(CALL_ENDPOINT_CHANGE,
334                         new EndpointChangeTransaction(endpoint, mCallsManager), callback);
335             } finally {
336                 Binder.restoreCallingIdentity(token);
337                 Log.endSession();
338             }
339         }
340 
341         /**
342          * Application would like to inform InCallServices of an event
343          */
344         @Override
345         public void sendEvent(String callId, String event, Bundle extras) {
346             long token = Binder.clearCallingIdentity();
347             try {
348                 Log.startSession("TSW.sE");
349                 Call call = mTrackedCalls.get(callId);
350                 if (call != null) {
351                     call.onConnectionEvent(event, extras);
352                 } else {
353                     Log.i(TAG,
354                             "sendEvent: was called but there is no call with id=[%s] cannot be "
355                                     + "found. Most likely the call has been disconnected");
356                 }
357             } finally {
358                 Binder.restoreCallingIdentity(token);
359                 Log.endSession();
360             }
361         }
362     };
363 
addTransactionsToManager(String action, CallTransaction transaction, ResultReceiver callback)364     private void addTransactionsToManager(String action, CallTransaction transaction,
365             ResultReceiver callback) {
366         Log.d(TAG, "addTransactionsToManager");
367         CompletableFuture<Boolean> transactionResult = mTransactionManager
368                 .addTransaction(transaction, getCompleteReceiver(action, callback));
369     }
370 
getCompleteReceiver( String action, ResultReceiver callback)371     private OutcomeReceiver<CallTransactionResult, CallException> getCompleteReceiver(
372             String action, ResultReceiver callback) {
373         return new OutcomeReceiver<>() {
374             @Override
375             public void onResult(CallTransactionResult result) {
376                 Log.d(TAG, "completeReceiver: onResult[" + action + "]:" + result);
377                 callback.send(TELECOM_TRANSACTION_SUCCESS, new Bundle());
378             }
379 
380             @Override
381             public void onError(CallException exception) {
382                 Log.d(TAG, "completeReceiver: onError[" + action + "]" + exception);
383                 Bundle extras = new Bundle();
384                 extras.putParcelable(TRANSACTION_EXCEPTION_KEY, exception);
385                 callback.send(exception == null ? CallException.CODE_ERROR_UNKNOWN :
386                         exception.getCode(), extras);
387             }
388         };
389     }
390 
391     public ICallControl getICallControl() {
392         return mICallControl;
393     }
394 
395     /***
396      *********************************************************************************************
397      **                    ICallEventCallback: Server --> Client                                **
398      **********************************************************************************************
399      */
400 
401     public CompletableFuture<Boolean> onSetActive(Call call) {
402         CallTransaction callTransaction = new CallEventCallbackAckTransaction(
403                 mICallEventCallback, ON_SET_ACTIVE, call.getId(), mLock);
404         CompletableFuture<Boolean> onSetActiveFuture;
405         try {
406             Log.startSession("TSW.oSA");
407             Log.d(TAG, String.format(Locale.US, "onSetActive: callId=[%s]", call.getId()));
408             onSetActiveFuture = mCallSequencingAdapter.onSetActive(call,
409                     callTransaction, result ->
410                             Log.i(TAG, String.format(Locale.US,
411                                     "%s: onResult: callId=[%s], result=[%s]", ON_SET_ACTIVE,
412                                     call.getId(), result)));
413         } finally {
414             Log.endSession();
415         }
416         return onSetActiveFuture;
417     }
418 
419     public CompletableFuture<Boolean> onAnswer(Call call, int videoState) {
420         CompletableFuture<Boolean> onAnswerFuture;
421         try {
422             Log.startSession("TSW.oA");
423             Log.d(TAG, String.format(Locale.US, "onAnswer: callId=[%s]", call.getId()));
424             onAnswerFuture = mCallSequencingAdapter.onSetAnswered(call, videoState,
425                     new CallEventCallbackAckTransaction(mICallEventCallback,
426                             ON_ANSWER, call.getId(), videoState, mLock),
427                     result -> Log.i(TAG, String.format(Locale.US,
428                             "%s: onResult: callId=[%s], result=[%s]",
429                             ON_ANSWER, call.getId(), result)));
430         } finally {
431             Log.endSession();
432         }
433         return onAnswerFuture;
434     }
435 
436     public CompletableFuture<Boolean> onSetInactive(Call call) {
437         CallTransaction callTransaction = new CallEventCallbackAckTransaction(
438                 mICallEventCallback, ON_SET_INACTIVE, call.getId(), mLock);
439         CompletableFuture<Boolean> onSetInactiveFuture;
440         try {
441             Log.startSession("TSW.oSI");
442             Log.i(TAG, String.format(Locale.US, "onSetInactive: callId=[%s]", call.getId()));
443             onSetInactiveFuture = mCallSequencingAdapter.onSetInactive(call,
444                     callTransaction, new OutcomeReceiver<>() {
445                         @Override
446                         public void onResult(CallTransactionResult result) {
447                             Log.i(TAG, String.format(Locale.US, "onSetInactive: callId=[%s]"
448                                             + ", result=[%s]",
449                                     call.getId(), result));
450                         }
451 
452                         @Override
453                         public void onError(CallException exception) {
454                             Log.w(TAG, "onSetInactive: onError: e.code=[%d], e.msg=[%s]",
455                                     exception.getCode(), exception.getMessage());
456                         }
457                     });
458         } finally {
459             Log.endSession();
460         }
461         return onSetInactiveFuture;
462     }
463 
464     public CompletableFuture<Boolean> onDisconnect(Call call,
465             DisconnectCause cause) {
466         CallTransaction callTransaction = new CallEventCallbackAckTransaction(
467                 mICallEventCallback, ON_DISCONNECT, call.getId(), cause, mLock);
468         CompletableFuture<Boolean> onDisconnectFuture;
469         try {
470             Log.startSession("TSW.oD");
471             Log.d(TAG, String.format(Locale.US, "onDisconnect: callId=[%s]", call.getId()));
472             onDisconnectFuture = mCallSequencingAdapter.onSetDisconnected(call, cause,
473                     callTransaction,
474                     result -> Log.i(TAG, String.format(Locale.US,
475                             "%s: onResult: callId=[%s], result=[%s]",
476                             ON_DISCONNECT, call.getId(), result)));
477         } finally {
478             Log.endSession();
479         }
480         return onDisconnectFuture;
481     }
482 
483     public void onCallStreamingStarted(Call call) {
484         try {
485             Log.startSession("TSW.oCSS");
486             Log.d(TAG, String.format(Locale.US, "onCallStreamingStarted: callId=[%s]",
487                     call.getId()));
488 
489             mTransactionManager.addTransaction(
490                     new CallEventCallbackAckTransaction(mICallEventCallback, ON_STREAMING_STARTED,
491                             call.getId(), mLock), new OutcomeReceiver<>() {
492                         @Override
493                         public void onResult(CallTransactionResult result) {
494                         }
495 
496                         @Override
497                         public void onError(CallException exception) {
498                             Log.w(TAG, "onCallStreamingStarted: onError: "
499                                             + "e.code=[%d], e.msg=[%s]",
500                                     exception.getCode(), exception.getMessage());
501                             stopCallStreaming(call);
502                         }
503                     }
504             );
505         } finally {
506             Log.endSession();
507         }
508     }
509 
510     public void onCallStreamingFailed(Call call,
511             @CallStreamingService.StreamingFailedReason int streamingFailedReason) {
512         if (call != null) {
513             try {
514                 mICallEventCallback.onCallStreamingFailed(call.getId(), streamingFailedReason);
515             } catch (RemoteException e) {
516             }
517         }
518     }
519 
520     @Override
521     public void onCallEndpointChanged(Call call, CallEndpoint endpoint) {
522         if (call != null) {
523             try {
524                 mICallEventCallback.onCallEndpointChanged(call.getId(), endpoint);
525             } catch (RemoteException e) {
526             }
527         }
528     }
529 
530     @Override
531     public void onAvailableCallEndpointsChanged(Call call, Set<CallEndpoint> endpoints) {
532         if (call != null) {
533             try {
534                 mICallEventCallback.onAvailableCallEndpointsChanged(call.getId(),
535                         endpoints.stream().toList());
536             } catch (RemoteException e) {
537             }
538         }
539     }
540 
541     @Override
542     public void onMuteStateChanged(Call call, boolean isMuted) {
543         if (call != null) {
544             try {
545                 mICallEventCallback.onMuteStateChanged(call.getId(), isMuted);
546             } catch (RemoteException e) {
547             }
548         }
549     }
550 
551     @Override
552     public void onVideoStateChanged(Call call, int videoState) {
553         if (call != null) {
554             try {
555                 mICallEventCallback.onVideoStateChanged(call.getId(), videoState);
556             } catch (RemoteException e) {
557             }
558         }
559     }
560 
561     public void removeCallFromWrappers(Call call) {
562         if (call != null) {
563             try {
564                 // remove the call from frameworks wrapper (client side)
565                 mICallEventCallback.removeCallFromTransactionalServiceWrapper(call.getId());
566             } catch (RemoteException e) {
567             }
568             // remove the call from this class/wrapper (server side)
569             untrackCall(call);
570         }
571     }
572 
573     @Override
574     public void sendCallEvent(Call call, String event, Bundle extras) {
575         if (call != null) {
576             try {
577                 mICallEventCallback.onEvent(call.getId(), event, extras);
578             } catch (RemoteException e) {
579             }
580         }
581     }
582 
583     /***
584      *********************************************************************************************
585      **                                Helpers                                                  **
586      **********************************************************************************************
587      */
588     private void setDeathRecipient(ICallEventCallback callEventCallback) {
589         try {
590             callEventCallback.asBinder().linkToDeath(mAppDeathListener, 0);
591         } catch (Exception e) {
592             Log.w(TAG, "setDeathRecipient: hit exception=[%s] trying to link binder to death",
593                     e.toString());
594         }
595     }
596 
597     /***
598      *********************************************************************************************
599      **                    FocusManager                                                       **
600      **********************************************************************************************
601      */
602 
603     @Override
604     public void connectionServiceFocusLost() {
605         if (mConnSvrFocusListener != null) {
606             mConnSvrFocusListener.onConnectionServiceReleased(this);
607         }
608         Log.i(TAG, String.format(Locale.US, "connectionServiceFocusLost for package=[%s]",
609                 mPackageName));
610     }
611 
612     @Override
613     public void connectionServiceFocusGained() {
614         Log.i(TAG, String.format(Locale.US, "connectionServiceFocusGained for package=[%s]",
615                 mPackageName));
616     }
617 
618     @Override
619     public void setConnectionServiceFocusListener(
620             ConnectionServiceFocusManager.ConnectionServiceFocusListener listener) {
621         mConnSvrFocusListener = listener;
622     }
623 
624     @Override
625     public ComponentName getComponentName() {
626         return mPhoneAccountHandle.getComponentName();
627     }
628 
629     /***
630      *********************************************************************************************
631      **                    CallStreaming                                                        **
632      *********************************************************************************************
633      */
634 
635     public void stopCallStreaming(Call call) {
636         Log.i(this, "stopCallStreaming; callid=%s", call.getId());
637         if (call != null && call.isStreaming()) {
638             CallTransaction stopStreamingTransaction = mStreamingController
639                     .getStopStreamingTransaction(call, mLock);
640             addTransactionsToManager(STOP_STREAMING, stopStreamingTransaction,
641                     new ResultReceiver(null));
642         }
643     }
644 }
645