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