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