1 /* 2 * Copyright (C) 2024 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.callsequencing; 18 19 import static android.Manifest.permission.CALL_PRIVILEGED; 20 21 import static com.android.server.telecom.CallsManager.CALL_FILTER_ALL; 22 import static com.android.server.telecom.CallsManager.LIVE_CALL_STUCK_CONNECTING_EMERGENCY_ERROR_MSG; 23 import static com.android.server.telecom.CallsManager.LIVE_CALL_STUCK_CONNECTING_EMERGENCY_ERROR_UUID; 24 import static com.android.server.telecom.CallsManager.LIVE_CALL_STUCK_CONNECTING_ERROR_MSG; 25 import static com.android.server.telecom.CallsManager.LIVE_CALL_STUCK_CONNECTING_ERROR_UUID; 26 import static com.android.server.telecom.CallsManager.ONGOING_CALL_STATES; 27 import static com.android.server.telecom.CallsManager.OUTGOING_CALL_STATES; 28 import static com.android.server.telecom.UserUtil.showErrorDialogForRestrictedOutgoingCall; 29 30 import android.content.Context; 31 import android.content.Intent; 32 import android.content.pm.PackageManager; 33 import android.net.Uri; 34 import android.os.Bundle; 35 import android.os.Handler; 36 import android.os.HandlerThread; 37 import android.os.OutcomeReceiver; 38 import android.telecom.CallAttributes; 39 import android.telecom.CallException; 40 import android.telecom.Connection; 41 import android.telecom.DisconnectCause; 42 import android.telecom.Log; 43 import android.telecom.PhoneAccount; 44 import android.telecom.PhoneAccountHandle; 45 import android.telephony.AnomalyReporter; 46 import android.telephony.CarrierConfigManager; 47 import android.util.Pair; 48 49 import com.android.internal.annotations.VisibleForTesting; 50 import com.android.server.telecom.AnomalyReporterAdapter; 51 import com.android.server.telecom.Call; 52 import com.android.server.telecom.CallState; 53 import com.android.server.telecom.CallsManager; 54 import com.android.server.telecom.ClockProxy; 55 import com.android.server.telecom.LogUtils; 56 import com.android.server.telecom.LoggedHandlerExecutor; 57 import com.android.server.telecom.MmiUtils; 58 import com.android.server.telecom.R; 59 import com.android.server.telecom.Timeouts; 60 import com.android.server.telecom.callsequencing.voip.OutgoingCallTransaction; 61 import com.android.server.telecom.callsequencing.voip.OutgoingCallTransactionSequencing; 62 import com.android.server.telecom.flags.FeatureFlags; 63 import com.android.server.telecom.metrics.ErrorStats; 64 import com.android.server.telecom.metrics.TelecomMetricsController; 65 import com.android.server.telecom.stats.CallFailureCause; 66 67 import java.util.HashSet; 68 import java.util.List; 69 import java.util.Objects; 70 import java.util.Set; 71 import java.util.UUID; 72 import java.util.concurrent.CompletableFuture; 73 74 /** 75 * Controls the sequencing between calls when moving between the user ACTIVE (RINGING/ACTIVE) and 76 * user INACTIVE (INCOMING/HOLD/DISCONNECTED) states. This controller is gated by the 77 * {@link FeatureFlags#enableCallSequencing()} flag. Call state changes are verified on a 78 * transactional basis where each operation is verified step by step for cross-phone account calls 79 * or just for the focus call in the case of processing calls on the same phone account. 80 */ 81 public class CallSequencingController { 82 private final CallsManager mCallsManager; 83 private final ClockProxy mClockProxy; 84 private final AnomalyReporterAdapter mAnomalyReporter; 85 private final Timeouts.Adapter mTimeoutsAdapter; 86 private final TelecomMetricsController mMetricsController; 87 private final Handler mHandler; 88 private final Context mContext; 89 private final MmiUtils mMmiUtils; 90 private final FeatureFlags mFeatureFlags; 91 private static String TAG = CallSequencingController.class.getSimpleName(); 92 public static final UUID SEQUENCING_CANNOT_HOLD_ACTIVE_CALL_UUID = 93 UUID.fromString("ea094d77-6ea9-4e40-891e-14bff5d485d7"); 94 public static final String SEQUENCING_CANNOT_HOLD_ACTIVE_CALL_MSG = 95 "Cannot hold active call"; 96 CallSequencingController(CallsManager callsManager, Context context, ClockProxy clockProxy, AnomalyReporterAdapter anomalyReporter, Timeouts.Adapter timeoutsAdapter, TelecomMetricsController metricsController, MmiUtils mmiUtils, FeatureFlags featureFlags)97 public CallSequencingController(CallsManager callsManager, Context context, 98 ClockProxy clockProxy, AnomalyReporterAdapter anomalyReporter, 99 Timeouts.Adapter timeoutsAdapter, TelecomMetricsController metricsController, 100 MmiUtils mmiUtils, FeatureFlags featureFlags) { 101 mCallsManager = callsManager; 102 mClockProxy = clockProxy; 103 mAnomalyReporter = anomalyReporter; 104 mMetricsController = metricsController; 105 mTimeoutsAdapter = timeoutsAdapter; 106 HandlerThread handlerThread = new HandlerThread(this.toString()); 107 handlerThread.start(); 108 mHandler = new Handler(handlerThread.getLooper()); 109 mMmiUtils = mmiUtils; 110 mFeatureFlags = featureFlags; 111 mContext = context; 112 } 113 114 /** 115 * Creates the outgoing call transaction given that call sequencing is enabled. Two separate 116 * transactions are being tracked here; one is if room needs to be made for the outgoing call 117 * and another to verify that the new call was placed. We need to ensure that the transaction 118 * to make room for the outgoing call is processed beforehand (i.e. see 119 * {@link OutgoingCallTransaction}. 120 * @param callAttributes The call attributes associated with the call. 121 * @param extras The extras that are associated with the call. 122 * @param callingPackage The calling package representing where the request was invoked from. 123 * @return The {@link CompletableFuture<CallTransaction>} that encompasses the request to 124 * place/receive the transactional call. 125 */ createTransactionalOutgoingCall(String callId, CallAttributes callAttributes, Bundle extras, String callingPackage)126 public CompletableFuture<CallTransaction> createTransactionalOutgoingCall(String callId, 127 CallAttributes callAttributes, Bundle extras, String callingPackage) { 128 PhoneAccountHandle requestedAccountHandle = callAttributes.getPhoneAccountHandle(); 129 Uri address = callAttributes.getAddress(); 130 if (mCallsManager.isOutgoingCallPermitted(requestedAccountHandle)) { 131 Log.d(this, "createTransactionalOutgoingCall: outgoing call permitted"); 132 final boolean hasCallPrivilegedPermission = mContext.checkCallingPermission( 133 CALL_PRIVILEGED) == PackageManager.PERMISSION_GRANTED; 134 135 final Intent intent = new Intent(hasCallPrivilegedPermission ? 136 Intent.ACTION_CALL_PRIVILEGED : Intent.ACTION_CALL, address); 137 Bundle updatedExtras = OutgoingCallTransaction.generateExtras(callId, extras, 138 callAttributes, mFeatureFlags); 139 // Note that this may start a potential transaction to make room for the outgoing call 140 // so we want to ensure that transaction is queued up first and then create another 141 // transaction to complete the call future. 142 CompletableFuture<Call> callFuture = mCallsManager.startOutgoingCall(address, 143 requestedAccountHandle, updatedExtras, requestedAccountHandle.getUserHandle(), 144 intent, callingPackage); 145 // The second transaction is represented below which will contain the result of whether 146 // the new outgoing call was placed or not. To simplify the logic, we will wait on the 147 // result of the outgoing call future before adding the transaction so that we can wait 148 // for the make room future to complete first. 149 if (callFuture == null) { 150 Log.d(this, "createTransactionalOutgoingCall: Outgoing call not permitted at the " 151 + "current time."); 152 return CompletableFuture.completedFuture(new OutgoingCallTransactionSequencing( 153 mCallsManager, null, true /* callNotPermitted */, mFeatureFlags)); 154 } 155 return callFuture.thenComposeAsync((call) -> CompletableFuture.completedFuture( 156 new OutgoingCallTransactionSequencing(mCallsManager, callFuture, 157 false /* callNotPermitted */, mFeatureFlags)), 158 new LoggedHandlerExecutor(mHandler, "CSC.aC", mCallsManager.getLock())); 159 } else { 160 Log.d(this, "createTransactionalOutgoingCall: outgoing call not permitted at the " 161 + "current time."); 162 return CompletableFuture.completedFuture(new OutgoingCallTransactionSequencing( 163 mCallsManager, null, true /* callNotPermitted */, mFeatureFlags)); 164 } 165 } 166 167 /** 168 * Processes the answer call request from the app and verifies the call state changes with 169 * sequencing provided that the calls that are being manipulated are across phone accounts. 170 * @param incomingCall The incoming call to be answered. 171 * @param videoState The video state configuration for the provided call. 172 * @param requestOrigin The origin of the request to answer the call; this can impact sequencing 173 * decisions as requests that Telecom makes can override rules we have set 174 * for actions which originate from outside. 175 */ answerCall(Call incomingCall, int videoState, @CallsManager.RequestOrigin int requestOrigin)176 public void answerCall(Call incomingCall, int videoState, 177 @CallsManager.RequestOrigin int requestOrigin) { 178 Log.i(this, "answerCall: Beginning call sequencing transaction for answering " 179 + "incoming call."); 180 holdActiveCallForNewCallWithSequencing(incomingCall, requestOrigin) 181 .thenComposeAsync((result) -> { 182 if (result) { 183 mCallsManager.requestFocusActionAnswerCall(incomingCall, videoState); 184 } else { 185 Log.i(this, "answerCall: Hold active call transaction failed. Aborting " 186 + "request to answer the incoming call."); 187 } 188 return CompletableFuture.completedFuture(result); 189 }, new LoggedHandlerExecutor(mHandler, "CSC.aC", 190 mCallsManager.getLock())); 191 } 192 193 /** 194 * Handles the case of setting a self-managed call active with call sequencing support. 195 * @param call The self-managed call that's waiting to go active. 196 */ handleSetSelfManagedCallActive(Call call)197 public void handleSetSelfManagedCallActive(Call call) { 198 holdActiveCallForNewCallWithSequencing(call, CallsManager.REQUEST_ORIGIN_UNKNOWN) 199 .thenComposeAsync((result) -> { 200 if (result) { 201 Log.i(this, "markCallAsActive: requesting focus for self managed call " 202 + "before setting active."); 203 mCallsManager.requestActionSetActiveCall(call, 204 "active set explicitly for self-managed"); 205 } else { 206 Log.i(this, "markCallAsActive: Unable to hold active call. " 207 + "Aborting transaction to set self managed call active."); 208 } 209 return CompletableFuture.completedFuture(result); 210 }, new LoggedHandlerExecutor(mHandler, 211 "CM.mCAA", mCallsManager.getLock())); 212 } 213 214 /** 215 * This applies to transactional calls which request to hold the active call with call 216 * sequencing support. The resulting future is an indication of whether the hold request 217 * succeeded which is then used to create additional transactions to request call focus for the 218 * new call. 219 * @param newCall The new transactional call that's waiting to go active. 220 * @param callback The callback used to report the result of holding the active call and if 221 * the new call can go active. 222 * @return The {@code CompletableFuture} indicating the result of holding the active call 223 * (if applicable). 224 */ transactionHoldPotentialActiveCallForNewCallSequencing( Call newCall, OutcomeReceiver<Boolean, CallException> callback)225 public void transactionHoldPotentialActiveCallForNewCallSequencing( 226 Call newCall, OutcomeReceiver<Boolean, CallException> callback) { 227 holdActiveCallForNewCallWithSequencing(newCall, CallsManager.REQUEST_ORIGIN_UNKNOWN) 228 .thenComposeAsync((result) -> { 229 if (result) { 230 // Either we were able to hold the active call or the active call was 231 // disconnected in favor of the new call. 232 callback.onResult(true); 233 } else { 234 Log.i(this, "transactionHoldPotentialActiveCallForNewCallSequencing: " 235 + "active call could not be held or disconnected"); 236 callback.onError( 237 new CallException("activeCall could not be held or disconnected", 238 CallException.CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL)); 239 if (mFeatureFlags.enableCallExceptionAnomReports()) { 240 mAnomalyReporter.reportAnomaly( 241 SEQUENCING_CANNOT_HOLD_ACTIVE_CALL_UUID, 242 SEQUENCING_CANNOT_HOLD_ACTIVE_CALL_MSG 243 ); 244 } 245 } 246 return CompletableFuture.completedFuture(result); 247 }, new LoggedHandlerExecutor(mHandler, "CM.mCAA", mCallsManager.getLock())); 248 } 249 250 /** 251 * Attempts to hold the active call so that the provided call can go active. This is done via 252 * call sequencing and the resulting future is an indication of whether that request 253 * has succeeded. 254 * 255 * @param call The call that's waiting to go active. 256 * @return The {@link CompletableFuture} indicating the result of whether the 257 * active call was able to be held (if applicable). 258 */ 259 @VisibleForTesting holdActiveCallForNewCallWithSequencing( Call call, int requestOrigin)260 public CompletableFuture<Boolean> holdActiveCallForNewCallWithSequencing( 261 Call call, int requestOrigin) { 262 Call activeCall = (Call) mCallsManager.getConnectionServiceFocusManager() 263 .getCurrentFocusCall(); 264 Log.i(this, "holdActiveCallForNewCallWithSequencing, newCall: %s, " 265 + "activeCall: %s", call.getId(), 266 (activeCall == null ? "<none>" : activeCall.getId())); 267 if (activeCall != null && activeCall != call) { 268 boolean isSequencingRequiredActiveAndCall = !arePhoneAccountsSame(call, activeCall); 269 if (mCallsManager.canHold(activeCall)) { 270 CompletableFuture<Boolean> holdFuture = activeCall.hold("swap to " + call.getId()); 271 return isSequencingRequiredActiveAndCall 272 ? holdFuture 273 : CompletableFuture.completedFuture(true); 274 } else if (mCallsManager.supportsHold(activeCall)) { 275 // Handle the case where active call supports hold but can't currently be held. 276 // In this case, we'll look for the currently held call to disconnect prior to 277 // holding the active call. 278 // E.g. 279 // Call A - Held (Supports hold, can't hold) 280 // Call B - Active (Supports hold, can't hold) 281 // Call C - Incoming 282 // Here we need to disconnect A prior to holding B so that C can be answered. 283 // This case is driven by telephony requirements ultimately. 284 // 285 // These cases can further be broken down at the phone account level: 286 // E.g. All cases not outlined below... 287 // (1) (2) 288 // Call A (Held) - PA1 Call A (Held) - PA1 289 // Call B (Active) - PA2 Call B (Active) - PA2 290 // Call C (Incoming) - PA1 Call C (Incoming) - PA2 291 // We should ensure that only operations across phone accounts require sequencing. 292 // Otherwise, we can send the requests up til the focus call state in question. 293 Call heldCall = mCallsManager.getFirstCallWithState(CallState.ON_HOLD); 294 CompletableFuture<Boolean> disconnectFutureHandler = null; 295 296 boolean isSequencingRequiredHeldAndActive = false; 297 if (heldCall != null) { 298 // If the calls are from the same source or the incoming call isn't a VOIP call 299 // and the held call is a carrier call, then disconnect the held call. The 300 // idea is that if we have a held carrier call and the incoming call is a 301 // VOIP call, we don't want to force the carrier call to auto-disconnect). 302 // Note: If the origin of this request was from the Telecom call incoming call 303 // disambiguation notification, we will allow the request to continue. 304 if (isManagedCall(heldCall) && isVoipCall(call) && requestOrigin 305 != CallsManager.REQUEST_ORIGIN_TELECOM_DISAMBIGUATION) { 306 // Otherwise, fail the transaction. 307 Log.w(this, "holdActiveCallForNewCallWithSequencing: ignoring request to " 308 + "disconnect carrier call %s for voip call %s.", activeCall, 309 heldCall); 310 return CompletableFuture.completedFuture(false); 311 } else { 312 isSequencingRequiredHeldAndActive = !arePhoneAccountsSame( 313 heldCall, activeCall); 314 disconnectFutureHandler = heldCall.disconnect(); 315 Log.i(this, "holdActiveCallForNewCallWithSequencing: " 316 + "Disconnect held call %s before holding active call %s.", 317 heldCall.getId(), activeCall.getId()); 318 } 319 } 320 Log.i(this, "holdActiveCallForNewCallWithSequencing: Holding active " 321 + "%s before making %s active.", activeCall.getId(), call.getId()); 322 323 CompletableFuture<Boolean> holdFutureHandler; 324 if (isSequencingRequiredHeldAndActive && disconnectFutureHandler != null) { 325 holdFutureHandler = disconnectFutureHandler 326 .thenComposeAsync((result) -> { 327 if (result) { 328 return activeCall.hold().thenCompose((holdSuccess) -> { 329 if (holdSuccess) { 330 // Increase hold count only if hold succeeds. 331 call.increaseHeldByThisCallCount(); 332 } 333 return CompletableFuture.completedFuture(holdSuccess); 334 }); 335 } 336 return CompletableFuture.completedFuture(false); 337 }, new LoggedHandlerExecutor(mHandler, 338 "CSC.hACFNCWS", mCallsManager.getLock())); 339 } else { 340 holdFutureHandler = activeCall.hold(); 341 call.increaseHeldByThisCallCount(); 342 } 343 // Next transaction will be performed on the call passed in and the last transaction 344 // was performed on the active call so ensure that the caller has this information 345 // to determine if sequencing is required. 346 return isSequencingRequiredActiveAndCall 347 ? holdFutureHandler 348 : CompletableFuture.completedFuture(true); 349 } else { 350 // This call does not support hold. If it is from a different connection 351 // service or connection manager, then disconnect it, otherwise allow the connection 352 // service or connection manager to figure out the right states. 353 Log.i(this, "holdActiveCallForNewCallWithSequencing: evaluating disconnecting %s " 354 + "so that %s can be made active.", activeCall.getId(), call.getId()); 355 if (!activeCall.isEmergencyCall()) { 356 // We don't want to allow VOIP apps to disconnect carrier calls. We are 357 // purposely completing the future with false so that the call isn't 358 // answered. 359 if (isSequencingRequiredActiveAndCall && isVoipCall(call) 360 && isManagedCall(activeCall)) { 361 Log.w(this, "holdActiveCallForNewCallWithSequencing: ignore " 362 + "disconnecting carrier call for making VOIP call active"); 363 return CompletableFuture.completedFuture(false); 364 } else { 365 if (isSequencingRequiredActiveAndCall) { 366 // Disconnect all calls with the same phone account as the active call 367 // as they do would not support holding. 368 Log.i(this, "Disconnecting non-holdable calls from account (%s).", 369 activeCall.getTargetPhoneAccount()); 370 return disconnectAllCallsWithPhoneAccount( 371 activeCall.getTargetPhoneAccount(), false /* excludeAccount */); 372 } else { 373 // Disconnect calls on other phone accounts and allow CS to handle 374 // holding/disconnecting calls from the same CS. 375 Log.i(this, "holdActiveCallForNewCallWithSequencing: " 376 + "disconnecting calls on other phone accounts and allowing " 377 + "ConnectionService to determine how to handle this case."); 378 return disconnectAllCallsWithPhoneAccount( 379 activeCall.getTargetPhoneAccount(), true /* excludeAccount */); 380 } 381 } 382 } else { 383 // It's not possible to hold the active call, and it's an emergency call so 384 // we will silently reject the incoming call instead of answering it. 385 Log.w(this, "holdActiveCallForNewCallWithSequencing: rejecting incoming " 386 + "call %s as the active call is an emergency call and " 387 + "it cannot be held.", call.getId()); 388 call.reject(false /* rejectWithMessage */, "" /* message */, 389 "active emergency call can't be held"); 390 return CompletableFuture.completedFuture(false); 391 } 392 } 393 } 394 return CompletableFuture.completedFuture(true); 395 } 396 397 /** 398 * Processes the unhold call request sent by the app with call sequencing support. 399 * @param call The call to be unheld. 400 */ unholdCall(Call call)401 public void unholdCall(Call call) { 402 // Cases: set active call on hold and then set this call to active 403 // Calls could be made on different phone accounts, in which case, we need to verify state 404 // change for each call. 405 CompletableFuture<Boolean> unholdCallFutureHandler = null; 406 Call activeCall = (Call) mCallsManager.getConnectionServiceFocusManager() 407 .getCurrentFocusCall(); 408 String activeCallId = null; 409 boolean isSequencingRequiredActiveAndCall = false; 410 if (activeCall != null && !activeCall.isLocallyDisconnecting()) { 411 activeCallId = activeCall.getId(); 412 // Determine whether the calls are placed on different phone accounts. 413 isSequencingRequiredActiveAndCall = !arePhoneAccountsSame(activeCall, call); 414 boolean canSwapCalls = canSwap(activeCall, call); 415 416 // If the active + held call are from different phone accounts, ensure that the call 417 // sequencing states are verified at each step. 418 if (canSwapCalls) { 419 unholdCallFutureHandler = activeCall.hold("Swap to " + call.getId()); 420 Log.addEvent(activeCall, LogUtils.Events.SWAP, "To " + call.getId()); 421 Log.addEvent(call, LogUtils.Events.SWAP, "From " + activeCallId); 422 } else { 423 if (isSequencingRequiredActiveAndCall) { 424 // If hold isn't supported and the active and held call are on 425 // different phone accounts where the held call is self-managed and active call 426 // is managed, abort the transaction. Otherwise, disconnect the call. We also 427 // don't want to drop an emergency call. 428 if (!activeCall.isEmergencyCall()) { 429 Log.w(this, "unholdCall: Unable to hold the active call (%s)," 430 + " aborting swap to %s", activeCallId, call.getId(), 431 call.getId()); 432 showErrorDialogForCannotHoldCall(call, false); 433 } else { 434 Log.w(this, "unholdCall: %s is an emergency call, aborting swap to %s", 435 activeCallId, call.getId()); 436 } 437 return; 438 } else { 439 activeCall.hold("Swap to " + call.getId()); 440 } 441 } 442 } 443 444 // Verify call state was changed to ACTIVE state 445 if (isSequencingRequiredActiveAndCall && unholdCallFutureHandler != null) { 446 String fixedActiveCallId = activeCallId; 447 // Only attempt to unhold call if previous request to hold/disconnect call (on different 448 // phone account) succeeded. 449 unholdCallFutureHandler.thenComposeAsync((result) -> { 450 if (result) { 451 Log.i(this, "unholdCall: Request to hold active call transaction succeeded."); 452 mCallsManager.requestActionUnholdCall(call, fixedActiveCallId); 453 } else { 454 Log.i(this, "unholdCall: Request to hold active call transaction failed. " 455 + "Aborting unhold transaction."); 456 } 457 return CompletableFuture.completedFuture(result); 458 }, new LoggedHandlerExecutor(mHandler, "CSC.uC", 459 mCallsManager.getLock())); 460 } else { 461 // Otherwise, we should verify call unhold succeeded for focus call. 462 mCallsManager.requestActionUnholdCall(call, activeCallId); 463 } 464 } 465 makeRoomForOutgoingCall(boolean isEmergency, Call call)466 public CompletableFuture<Boolean> makeRoomForOutgoingCall(boolean isEmergency, Call call) { 467 return isEmergency 468 ? makeRoomForOutgoingEmergencyCall(call) 469 : makeRoomForOutgoingCall(call); 470 } 471 472 /** 473 * This function tries to make room for the new emergency outgoing call via call sequencing. 474 * The resulting future is an indication of whether room was able to be made for the emergency 475 * call if needed. 476 * @param emergencyCall The outgoing emergency call to be placed. 477 * @return The {@code CompletableFuture} indicating the result of whether room was able to be 478 * made for the emergency call. 479 */ makeRoomForOutgoingEmergencyCall(Call emergencyCall)480 private CompletableFuture<Boolean> makeRoomForOutgoingEmergencyCall(Call emergencyCall) { 481 // Disconnect all self-managed + transactional calls + calls that don't support holding for 482 // emergency. We will never use these accounts for emergency calling. For the single sim 483 // case (like Verizon), we should support the existing behavior of disconnecting the active 484 // call; refrain from disconnecting the held call in this case if it exists. 485 Pair<Set<Call>, CompletableFuture<Boolean>> disconnectCallsForEmergencyPair = 486 disconnectCallsForEmergencyCall(emergencyCall); 487 // The list of calls that were disconnected 488 Set<Call> disconnectedCalls = disconnectCallsForEmergencyPair.first; 489 // The future encompassing the result of the disconnect transaction(s). Because of the 490 // bulk transaction, we will always opt to perform sequencing on this future. Note that this 491 // future will always be completed with true if no disconnects occurred. 492 CompletableFuture<Boolean> transactionFuture = disconnectCallsForEmergencyPair.second; 493 494 Call ringingCall; 495 if (mCallsManager.hasRingingOrSimulatedRingingCall() && !disconnectedCalls 496 .contains(mCallsManager.getRingingOrSimulatedRingingCall())) { 497 // Always disconnect any ringing/incoming calls when an emergency call is placed to 498 // minimize distraction. This does not affect live call count. 499 ringingCall = mCallsManager.getRingingOrSimulatedRingingCall(); 500 ringingCall.getAnalytics().setCallIsAdditional(true); 501 ringingCall.getAnalytics().setCallIsInterrupted(true); 502 if (ringingCall.getState() == CallState.SIMULATED_RINGING) { 503 if (!ringingCall.hasGoneActiveBefore()) { 504 // If this is an incoming call that is currently in SIMULATED_RINGING only 505 // after a call screen, disconnect to make room and mark as missed, since 506 // the user didn't get a chance to accept/reject. 507 transactionFuture = transactionFuture.thenComposeAsync((result) -> 508 ringingCall.disconnect("emergency call dialed during simulated " 509 + "ringing after screen."), 510 new LoggedHandlerExecutor(mHandler, "CSC.mRFOEC", 511 mCallsManager.getLock())); 512 } else { 513 // If this is a simulated ringing call after being active and put in 514 // AUDIO_PROCESSING state again, disconnect normally. 515 transactionFuture = transactionFuture.thenComposeAsync((result) -> 516 ringingCall.reject(false, null, 517 "emergency call dialed during simulated ringing."), 518 new LoggedHandlerExecutor(mHandler, "CSC.mRFOEC", 519 mCallsManager.getLock())); 520 } 521 } else { // normal incoming ringing call. 522 // Hang up the ringing call to make room for the emergency call and mark as missed, 523 // since the user did not reject. 524 ringingCall.setOverrideDisconnectCauseCode( 525 new DisconnectCause(DisconnectCause.MISSED)); 526 transactionFuture = transactionFuture.thenComposeAsync((result) -> 527 ringingCall.reject(false, null, 528 "emergency call dialed during ringing."), 529 new LoggedHandlerExecutor(mHandler, "CSC.mRFOEC", 530 mCallsManager.getLock())); 531 } 532 disconnectedCalls.add(ringingCall); 533 } else { 534 ringingCall = null; 535 } 536 537 // There is already room! 538 if (!mCallsManager.hasMaximumLiveCalls(emergencyCall)) { 539 return transactionFuture; 540 } 541 542 Call liveCall = mCallsManager.getFirstCallWithLiveState(); 543 Log.i(this, "makeRoomForOutgoingEmergencyCall: call = " + emergencyCall 544 + " livecall = " + liveCall); 545 546 // Don't need to proceed further if we already disconnected the live call or if the live 547 // call is the emergency call being placed (not likely). 548 if (emergencyCall == liveCall || disconnectedCalls.contains(liveCall)) { 549 return transactionFuture; 550 } 551 552 // After having rejected any potential ringing call as well as calls that aren't supported 553 // during emergency calls (refer to disconnectCallsForEmergencyCall logic), we can 554 // re-evaluate whether we still have multiple phone accounts in use in order to disconnect 555 // non-holdable calls: 556 // If (yes) - disconnect call the non-holdable calls (this would be just the active call) 557 // If (no) - skip the disconnect and instead let the logic be handled explicitly for the 558 // single sim behavior. 559 boolean areMultiplePhoneAccountsActive = areMultiplePhoneAccountsActive(disconnectedCalls); 560 if (areMultiplePhoneAccountsActive && !liveCall.can(Connection.CAPABILITY_SUPPORT_HOLD)) { 561 // After disconnecting, we should be able to place the ECC now (we either have no calls 562 // or a held call after this point). 563 String disconnectReason = "disconnecting non-holdable call to make room " 564 + "for emergency call"; 565 emergencyCall.getAnalytics().setCallIsAdditional(true); 566 liveCall.getAnalytics().setCallIsInterrupted(true); 567 return disconnectOngoingCallForEmergencyCall(transactionFuture, liveCall, 568 disconnectReason); 569 } 570 571 // If we already disconnected the outgoing call, then don't perform any additional ops on 572 // it. 573 if (mCallsManager.hasMaximumOutgoingCalls(emergencyCall) && !disconnectedCalls 574 .contains(mCallsManager.getFirstCallWithState(OUTGOING_CALL_STATES))) { 575 Call outgoingCall = mCallsManager.getFirstCallWithState(OUTGOING_CALL_STATES); 576 String disconnectReason = null; 577 if (!outgoingCall.isEmergencyCall()) { 578 emergencyCall.getAnalytics().setCallIsAdditional(true); 579 outgoingCall.getAnalytics().setCallIsInterrupted(true); 580 disconnectReason = "Disconnecting dialing call in favor of new dialing" 581 + " emergency call."; 582 } 583 if (outgoingCall.getState() == CallState.SELECT_PHONE_ACCOUNT) { 584 // Correctness check: if there is an orphaned emergency call in the 585 // {@link CallState#SELECT_PHONE_ACCOUNT} state, just disconnect it since the user 586 // has explicitly started a new call. 587 emergencyCall.getAnalytics().setCallIsAdditional(true); 588 outgoingCall.getAnalytics().setCallIsInterrupted(true); 589 disconnectReason = "Disconnecting call in SELECT_PHONE_ACCOUNT in favor" 590 + " of new outgoing call."; 591 } 592 if (disconnectReason != null) { 593 // Skip auto-unhold for when the outgoing call is disconnected. Consider a scenario 594 // where we have a held non-holdable call (VZW) and the dialing call (also VZW). If 595 // we auto unhold the VZW while placing the emergency call, then we may end up with 596 // two active calls. The auto-unholding logic really only applies for the 597 // non-holdable phone account. 598 outgoingCall.setSkipAutoUnhold(true); 599 boolean isSequencingRequiredRingingAndOutgoing = ringingCall == null 600 || !arePhoneAccountsSame(ringingCall, outgoingCall); 601 return disconnectOngoingCallForEmergencyCall(transactionFuture, outgoingCall, 602 disconnectReason); 603 } 604 // If the user tries to make two outgoing calls to different emergency call numbers, 605 // we will try to connect the first outgoing call and reject the second. 606 emergencyCall.setStartFailCause(CallFailureCause.IN_EMERGENCY_CALL); 607 return CompletableFuture.completedFuture(false); 608 } 609 610 if (liveCall.getState() == CallState.AUDIO_PROCESSING) { 611 emergencyCall.getAnalytics().setCallIsAdditional(true); 612 liveCall.getAnalytics().setCallIsInterrupted(true); 613 // Skip auto-unhold for when the live call is disconnected. Consider a scenario where 614 // we have a held non-holdable call (VZW) and the live call (also VZW) is stuck in 615 // audio processing. If we auto unhold the VZW while placing the emergency call, then we 616 // may end up with two active calls. The auto-unholding logic really only applies for 617 // the non-holdable phone account. 618 liveCall.setSkipAutoUnhold(true); 619 final String disconnectReason = "disconnecting audio processing call for emergency"; 620 return disconnectOngoingCallForEmergencyCall(transactionFuture, liveCall, 621 disconnectReason); 622 } 623 624 // If the live call is stuck in a connecting state, prompt the user to generate a bugreport. 625 if (liveCall.getState() == CallState.CONNECTING) { 626 AnomalyReporter.reportAnomaly(LIVE_CALL_STUCK_CONNECTING_EMERGENCY_ERROR_UUID, 627 LIVE_CALL_STUCK_CONNECTING_EMERGENCY_ERROR_MSG); 628 } 629 630 // If we have the max number of held managed calls and we're placing an emergency call, 631 // we'll disconnect the active call if it cannot be held. If we have a self-managed call 632 // that can't be held, then we should disconnect the call in favor of the emergency call. 633 // This will only happen for the single sim scenario to support backwards compatibility. 634 // For dual sim, we should try disconnecting the held call and hold the active call. Also 635 // note that in a scenario where we don't have any held calls and the live call can't be 636 // held (only applies for single sim case), we should try holding the active call (and 637 // disconnect on fail) before placing the ECC (i.e. Verizon swap case). The latter is being 638 // handled further down in this method. 639 Call heldCall = mCallsManager.getFirstCallWithState(CallState.ON_HOLD); 640 if (mCallsManager.hasMaximumManagedHoldingCalls(emergencyCall) 641 && !disconnectedCalls.contains(heldCall)) { 642 final String disconnectReason = "disconnecting to make room for emergency call " 643 + emergencyCall.getId(); 644 emergencyCall.getAnalytics().setCallIsAdditional(true); 645 // Single sim case 646 if (!areMultiplePhoneAccountsActive) { 647 liveCall.getAnalytics().setCallIsInterrupted(true); 648 // Skip auto-unhold for when the live call is disconnected. Consider a scenario 649 // where we have a held non-holdable call (VZW) and an active call (also VZW). If 650 // we auto unhold the VZW while placing the emergency call, then we may end up with 651 // two active calls. The auto-unholding logic really only applies for the 652 // non-holdable phone account. 653 liveCall.setSkipAutoUnhold(true); 654 // Disconnect the active call instead of the holding call because it is historically 655 // easier to do, rather than disconnecting a held call and holding the active call. 656 disconnectOngoingCallForEmergencyCall(transactionFuture, liveCall, 657 disconnectReason); 658 // Don't wait on the live call disconnect future result above since we're handling 659 // the same phone account case. It's possible that disconnect may time out in the 660 // case that two calls are being merged while the disconnect for the live call is 661 // sent. 662 return transactionFuture; 663 } else if (heldCall != null) { // Dual sim case 664 // Note at this point, we should always have a held call then that should 665 // be disconnected (over the active call) but still enforce with a null check and 666 // ensure we haven't disconnected it already. 667 heldCall.getAnalytics().setCallIsInterrupted(true); 668 // Disconnect the held call. 669 transactionFuture = disconnectOngoingCallForEmergencyCall(transactionFuture, 670 heldCall, disconnectReason); 671 } 672 } 673 674 // TODO: Remove once b/23035408 has been corrected. 675 // If the live call is a conference, it will not have a target phone account set. This 676 // means the check to see if the live call has the same target phone account as the new 677 // call will not cause us to bail early. As a result, we'll end up holding the 678 // ongoing conference call. However, the ConnectionService is already doing that. This 679 // has caused problems with some carriers. As a workaround until b/23035408 is 680 // corrected, we will try and get the target phone account for one of the conference's 681 // children and use that instead. 682 PhoneAccountHandle liveCallPhoneAccount = liveCall.getTargetPhoneAccount(); 683 if (liveCallPhoneAccount == null && liveCall.isConference() && 684 !liveCall.getChildCalls().isEmpty()) { 685 liveCallPhoneAccount = mCallsManager.getFirstChildPhoneAccount(liveCall); 686 Log.i(this, "makeRoomForOutgoingEmergencyCall: using child call PhoneAccount = " + 687 liveCallPhoneAccount); 688 } 689 690 // We may not know which PhoneAccount the emergency call will be placed on yet, but if 691 // the liveCall PhoneAccount does not support placing emergency calls, then we know it 692 // will not be that one and we do not want multiple PhoneAccounts active during an 693 // emergency call if possible. Disconnect the active call in favor of the emergency call 694 // instead of trying to hold. 695 if (liveCallPhoneAccount != null) { 696 PhoneAccount pa = mCallsManager.getPhoneAccountRegistrar().getPhoneAccountUnchecked( 697 liveCallPhoneAccount); 698 if((pa.getCapabilities() & PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS) == 0) { 699 liveCall.setOverrideDisconnectCauseCode(new DisconnectCause( 700 DisconnectCause.LOCAL, DisconnectCause.REASON_EMERGENCY_CALL_PLACED)); 701 final String disconnectReason = "outgoing call does not support emergency calls, " 702 + "disconnecting."; 703 return disconnectOngoingCallForEmergencyCall(transactionFuture, liveCall, 704 disconnectReason); 705 } 706 } 707 708 // At this point, if we still have an active call, then it supports holding for emergency 709 // and is a managed call. It may not support holding but we will still try to hold anyway 710 // (i.e. swap for Verizon). Note that there will only be one call at this stage which is 711 // the active call so that means that we will attempt to place the emergency call on the 712 // same phone account unless it's not using a Telephony phone account (Fi wifi call), in 713 // which case, we would want to verify holding happened. For cases like backup calling, the 714 // shared data call will be over Telephony as well as the emergency call, so the shared 715 // data call would get disconnected by the CS. 716 717 // We want to verify if the live call was placed via the connection manager. Don't use 718 // the manipulated liveCallPhoneAccount since the delegate would pull directly from the 719 // target phone account. 720 boolean isLiveUsingConnectionManager = !Objects.equals(liveCall.getTargetPhoneAccount(), 721 liveCall.getDelegatePhoneAccountHandle()); 722 return maybeHoldLiveCallForEmergency(transactionFuture, liveCall, 723 emergencyCall, isLiveUsingConnectionManager); 724 } 725 726 /** 727 * This function tries to make room for the new outgoing call via call sequencing. The 728 * resulting future is an indication of whether room was able to be made for the call if 729 * needed. 730 * @param call The outgoing call to make room for. 731 * @return The {@code CompletableFuture} indicating the result of whether room was able to be 732 * made for the outgoing call. 733 */ makeRoomForOutgoingCall(Call call)734 private CompletableFuture<Boolean> makeRoomForOutgoingCall(Call call) { 735 // For the purely managed CS cases, check if there's a ringing call, in which case we will 736 // disallow the outgoing call. 737 if (isManagedCall(call) && mCallsManager.hasManagedRingingOrSimulatedRingingCall()) { 738 showErrorDialogForOutgoingDuringRingingCall(call); 739 return CompletableFuture.completedFuture(false); 740 } 741 // Already room! 742 if (!mCallsManager.hasMaximumLiveCalls(call)) { 743 return CompletableFuture.completedFuture(true); 744 } 745 746 // NOTE: If the amount of live calls changes beyond 1, this logic will probably 747 // have to change. 748 Call liveCall = mCallsManager.getFirstCallWithLiveState(); 749 Log.i(this, "makeRoomForOutgoingCall call = " + call + " livecall = " + 750 liveCall); 751 752 if (call == liveCall) { 753 // If the call is already the foreground call, then we are golden. 754 // This can happen after the user selects an account in the SELECT_PHONE_ACCOUNT 755 // state since the call was already populated into the list. 756 return CompletableFuture.completedFuture(true); 757 } 758 759 // If the live call is stuck in a connecting state for longer than the transitory timeout, 760 // then we should disconnect it in favor of the new outgoing call and prompt the user to 761 // generate a bugreport. 762 // TODO: In the future we should let the CallAnomalyWatchDog do this disconnection of the 763 // live call stuck in the connecting state. Unfortunately that code will get tripped up by 764 // calls that have a longer than expected new outgoing call broadcast response time. This 765 // mitigation is intended to catch calls stuck in a CONNECTING state for a long time that 766 // block outgoing calls. However, if the user dials two calls in quick succession it will 767 // result in both calls getting disconnected, which is not optimal. 768 if (liveCall.getState() == CallState.CONNECTING 769 && ((mClockProxy.elapsedRealtime() - liveCall.getCreationElapsedRealtimeMillis()) 770 > mTimeoutsAdapter.getNonVoipCallTransitoryStateTimeoutMillis())) { 771 if (mFeatureFlags.telecomMetricsSupport()) { 772 mMetricsController.getErrorStats().log(ErrorStats.SUB_CALL_MANAGER, 773 ErrorStats.ERROR_STUCK_CONNECTING); 774 } 775 mAnomalyReporter.reportAnomaly(LIVE_CALL_STUCK_CONNECTING_ERROR_UUID, 776 LIVE_CALL_STUCK_CONNECTING_ERROR_MSG); 777 // Skip auto-unhold for when the live call is disconnected. Consider a scenario where 778 // we have a held non-holdable call (VZW) and the live call (also VZW) is stuck in 779 // connecting. If we auto unhold the VZW while placing the emergency call, then we may 780 // end up with two active calls. The auto-unholding logic really only applies for 781 // the non-holdable phone account. 782 liveCall.setSkipAutoUnhold(true); 783 return liveCall.disconnect("Force disconnect CONNECTING call."); 784 } 785 786 if (mCallsManager.hasMaximumOutgoingCalls(call)) { 787 Call outgoingCall = mCallsManager.getFirstCallWithState(OUTGOING_CALL_STATES); 788 if (outgoingCall.getState() == CallState.SELECT_PHONE_ACCOUNT) { 789 // If there is an orphaned call in the {@link CallState#SELECT_PHONE_ACCOUNT} 790 // state, just disconnect it since the user has explicitly started a new call. 791 call.getAnalytics().setCallIsAdditional(true); 792 outgoingCall.getAnalytics().setCallIsInterrupted(true); 793 // Skip auto-unhold for when the outgoing call is disconnected. Consider a scenario 794 // where we have a held non-holdable call (VZW) and a dialing call (also VZW). If we 795 // auto unhold the VZW while placing the emergency call, then we may end up with 796 // two active calls. The auto-unholding logic really only applies for the 797 // non-holdable phone account. 798 outgoingCall.setSkipAutoUnhold(true); 799 return outgoingCall.disconnect( 800 "Disconnecting call in SELECT_PHONE_ACCOUNT in favor of new " 801 + "outgoing call."); 802 } 803 showErrorDialogForMaxOutgoingCallOutgoingPresent(call); 804 return CompletableFuture.completedFuture(false); 805 } 806 807 // If we detect a MMI code, allow it to go through since we are not treating it as an actual 808 // call. 809 if (mMmiUtils.isPotentialMMICode(call.getHandle())) { 810 Log.i(this, "makeRoomForOutgoingCall: Detected mmi code. Allowing to go through."); 811 return CompletableFuture.completedFuture(true); 812 } 813 814 // Early check to see if we already have a held call + live call. It's possible if a device 815 // switches to DSDS with two ongoing calls for the phone account to be null in which case, 816 // based on the logic below, we would've completed the future with true and reported a 817 // different failure cause. Now, we perform this early check to ensure the right max 818 // outgoing call restriction error is displayed instead. 819 if (mCallsManager.hasMaximumManagedHoldingCalls(call) && !mCallsManager.canHold(liveCall)) { 820 Call heldCall = mCallsManager.getFirstCallWithState(CallState.ON_HOLD); 821 showErrorDialogForMaxOutgoingCallTooManyCalls(call, 822 arePhoneAccountsSame(heldCall, liveCall)); 823 return CompletableFuture.completedFuture(false); 824 } 825 826 // Self-Managed + Transactional calls require Telecom to manage calls in the same 827 // PhoneAccount, whereas managed calls require the ConnectionService to manage calls in the 828 // same PhoneAccount for legacy reasons (Telephony). 829 if (arePhoneAccountsSame(call, liveCall) && isManagedCall(call)) { 830 Log.i(this, "makeRoomForOutgoingCall: allowing managed CS to handle " 831 + "calls from the same self-managed account"); 832 return CompletableFuture.completedFuture(true); 833 } else if (call.getTargetPhoneAccount() == null) { 834 Log.i(this, "makeRoomForOutgoingCall: no PA specified, allowing"); 835 // Without a phone account, we can't say reliably that the call will fail. 836 // If the user chooses the same phone account as the live call, then it's 837 // still possible that the call can be made (like with CDMA calls not supporting 838 // hold but they still support adding a call by going immediately into conference 839 // mode). Return true here and we'll run this code again after user chooses an 840 // account. 841 return CompletableFuture.completedFuture(true); 842 } 843 844 // Try to hold the live call before attempting the new outgoing call. 845 if (mCallsManager.canHold(liveCall)) { 846 Log.i(this, "makeRoomForOutgoingCall: holding live call."); 847 call.getAnalytics().setCallIsAdditional(true); 848 liveCall.getAnalytics().setCallIsInterrupted(true); 849 return liveCall.hold("calling " + call.getId()); 850 } 851 852 // The live call cannot be held so we're out of luck here. There's no room. 853 showErrorDialogForCannotHoldCall(call, true); 854 return CompletableFuture.completedFuture(false); 855 } 856 857 /** 858 * Processes the request from the app to disconnect a call. This is done via call sequencing 859 * so that Telecom properly cleans up the call locally provided that the call has been 860 * properly disconnected on the connection side. 861 * @param call The call to disconnect. 862 * @param previousState The previous state of the call before disconnecting. 863 */ disconnectCall(Call call, int previousState)864 public void disconnectCall(Call call, int previousState) { 865 CompletableFuture<Boolean> disconnectFuture = call.disconnect(); 866 disconnectFuture.thenComposeAsync((result) -> { 867 if (result) { 868 Log.i(this, "disconnectCall: Disconnect call transaction succeeded. " 869 + "Processing associated cleanup."); 870 mCallsManager.processDisconnectCallAndCleanup(call, previousState); 871 } else { 872 Log.i(this, "disconnectCall: Disconnect call transaction failed. " 873 + "Aborting associated cleanup."); 874 } 875 return CompletableFuture.completedFuture(false); 876 }, new LoggedHandlerExecutor(mHandler, "CSC.dC", 877 mCallsManager.getLock())); 878 } 879 880 /* HELPERS */ 881 882 /* makeRoomForOutgoingEmergencyCall helpers */ 883 884 /** 885 * Tries to hold the live call before placing the emergency call. If the hold fails, then we 886 * will instead disconnect the call. This only applies for when the emergency call and live call 887 * are from the same phone account or there's only one ongoing call, in which case, we should 888 * place the emergency call on the ongoing call's phone account. 889 * 890 * Note: This only applies when the live call and emergency call are from the same phone 891 * account. 892 */ maybeHoldLiveCallForEmergency( CompletableFuture<Boolean> transactionFuture, Call liveCall, Call emergencyCall, boolean isLiveUsingConnectionManager)893 private CompletableFuture<Boolean> maybeHoldLiveCallForEmergency( 894 CompletableFuture<Boolean> transactionFuture, 895 Call liveCall, Call emergencyCall, boolean isLiveUsingConnectionManager) { 896 emergencyCall.getAnalytics().setCallIsAdditional(true); 897 liveCall.getAnalytics().setCallIsInterrupted(true); 898 final String holdReason = "calling " + emergencyCall.getId(); 899 CompletableFuture<Boolean> holdResultFuture; 900 holdResultFuture = transactionFuture.thenComposeAsync((result) -> { 901 if (result) { 902 Log.i(this, "makeRoomForOutgoingEmergencyCall: Previous transaction " 903 + "succeeded. Attempting to hold live call."); 904 } else { // Log the failure but proceed with hold transaction. 905 Log.i(this, "makeRoomForOutgoingEmergencyCall: Previous transaction " 906 + "failed. Still attempting to hold live call."); 907 } 908 Log.i(this, "makeRoomForOutgoingEmergencyCall: Attempt to hold live call. " 909 + "Verifying hold: %b", isLiveUsingConnectionManager); 910 return liveCall.hold(holdReason); 911 }, new LoggedHandlerExecutor(mHandler, "CSC.mRFOEC", mCallsManager.getLock())); 912 913 // If the live call was placed using a connection manager, we should verify that holding 914 // happened before placing the emergency call. We should disconnect the call if hold fails. 915 // Otherwise, let Telephony handle additional sequencing that may be required. 916 if (!isLiveUsingConnectionManager) { 917 return transactionFuture; 918 } 919 920 // Otherwise, verify hold succeeded and if it didn't, then hangup the call. 921 return holdResultFuture.thenComposeAsync((result) -> { 922 if (!result) { 923 Log.i(this, "makeRoomForOutgoingEmergencyCall: Attempt to hold live call " 924 + "failed. Disconnecting live call in favor of emergency call."); 925 return liveCall.disconnect("Disconnecting live call which failed to be held"); 926 } else { 927 Log.i(this, "makeRoomForOutgoingEmergencyCall: Attempt to hold live call " 928 + "transaction succeeded."); 929 emergencyCall.increaseHeldByThisCallCount(); 930 return CompletableFuture.completedFuture(true); 931 } 932 }, new LoggedHandlerExecutor(mHandler, "CSC.mRFOEC", mCallsManager.getLock())); 933 } 934 935 /** 936 * Disconnects all VOIP (SM + Transactional) as well as those that don't support placing 937 * emergency calls before placing an emergency call. 938 * 939 * Note: If a call can't be held, it will be active to begin with. 940 * @return The list of calls to be disconnected alongside the future keeping track of the 941 * disconnect transaction. 942 */ 943 private Pair<Set<Call>, CompletableFuture<Boolean>> disconnectCallsForEmergencyCall( 944 Call emergencyCall) { 945 Set<Call> callsDisconnected = new HashSet<>(); 946 Call previousCall = null; 947 Call ringingCall = mCallsManager.getRingingOrSimulatedRingingCall(); 948 CompletableFuture<Boolean> disconnectFuture = CompletableFuture.completedFuture(true); 949 for (Call call: mCallsManager.getCalls()) { 950 if (skipDisconnectForEmergencyCall(call, ringingCall)) { 951 continue; 952 } 953 emergencyCall.getAnalytics().setCallIsAdditional(true); 954 call.getAnalytics().setCallIsInterrupted(true); 955 call.setOverrideDisconnectCauseCode(new DisconnectCause( 956 DisconnectCause.LOCAL, DisconnectCause.REASON_EMERGENCY_CALL_PLACED)); 957 958 Call finalPreviousCall = previousCall; 959 disconnectFuture = disconnectFuture.thenComposeAsync((result) -> { 960 if (!result) { 961 // Log the failure if it happens but proceed with the disconnects. 962 Log.i(this, "Call (%s) failed to be disconnected", 963 finalPreviousCall); 964 } 965 return call.disconnect("Disconnecting call with phone account that does not " 966 + "support emergency call"); 967 }, new LoggedHandlerExecutor(mHandler, "CSC.dAVC", 968 mCallsManager.getLock())); 969 previousCall = call; 970 callsDisconnected.add(call); 971 } 972 return new Pair<>(callsDisconnected, disconnectFuture); 973 } 974 975 private boolean skipDisconnectForEmergencyCall(Call call, Call ringingCall) { 976 // Conditions for checking if call doesn't need to be disconnected immediately. 977 boolean isVoip = isVoipCall(call); 978 boolean callSupportsHoldingEmergencyCall = shouldHoldForEmergencyCall( 979 call.getTargetPhoneAccount()); 980 981 // Skip the ringing call; we'll handle the disconnect explicitly later. Also, if we have 982 // a conference call, only disconnect the host call. 983 if (call.equals(ringingCall) || call.getParentCall() != null) { 984 return true; 985 } 986 987 // If the call is managed and supports holding for emergency calls, don't disconnect the 988 // call. 989 if (!isVoip && callSupportsHoldingEmergencyCall) { 990 return true; 991 } 992 // Otherwise, we will disconnect the call because it doesn't meet one of the conditions 993 // above. 994 Log.i(this, "Disconnecting call (%s). isManaged: %b, call " 995 + "supports holding emergency call: %b", call.getId(), !isVoip, 996 callSupportsHoldingEmergencyCall); 997 return false; 998 } 999 1000 /** 1001 * Waiting on the passed future completion when sequencing is required, this will try to the 1002 * disconnect the call passed in. 1003 */ 1004 private CompletableFuture<Boolean> disconnectOngoingCallForEmergencyCall( 1005 CompletableFuture<Boolean> transactionFuture, Call callToDisconnect, 1006 String disconnectReason) { 1007 return transactionFuture.thenComposeAsync((result) -> { 1008 if (result) { 1009 Log.i(this, "makeRoomForOutgoingEmergencyCall: Request to disconnect " 1010 + "previous call succeeded. Attempting to disconnect ongoing call" 1011 + " %s.", callToDisconnect); 1012 } else { 1013 Log.i(this, "makeRoomForOutgoingEmergencyCall: Request to disconnect " 1014 + "previous call failed. Still attempting to disconnect ongoing call" 1015 + " %s.", callToDisconnect); 1016 } 1017 return callToDisconnect.disconnect(disconnectReason); 1018 }, new LoggedHandlerExecutor(mHandler, "CSC.mRFOEC", mCallsManager.getLock())); 1019 } 1020 1021 /** 1022 * Determines if DSDA is being used (i.e. calls present on more than one phone account). 1023 * @param callsToExclude The list of calls to exclude (these will be calls that have been 1024 * disconnected but may still be being tracked by CallsManager depending 1025 * on timing). 1026 */ 1027 private boolean areMultiplePhoneAccountsActive(Set<Call> callsToExclude) { 1028 for (Call excludedCall: callsToExclude) { 1029 Log.i(this, "Calls to exclude: %s", excludedCall); 1030 } 1031 List<Call> calls = mCallsManager.getCalls().stream() 1032 .filter(c -> !callsToExclude.contains(c)).toList(); 1033 PhoneAccountHandle handle1 = null; 1034 if (!calls.isEmpty()) { 1035 // Find the first handle different from the one retrieved from the first call in 1036 // the list. 1037 for(int i = 0; i < calls.size(); i++) { 1038 if (handle1 == null && calls.get(i).getTargetPhoneAccount() != null) { 1039 handle1 = calls.getFirst().getTargetPhoneAccount(); 1040 } 1041 if (handle1 != null && calls.get(i).getTargetPhoneAccount() != null 1042 && !handle1.equals(calls.get(i).getTargetPhoneAccount())) { 1043 return true; 1044 } 1045 } 1046 } 1047 return false; 1048 } 1049 1050 /** 1051 * Checks the carrier config to see if the carrier supports holding emergency calls. 1052 * @param handle The {@code PhoneAccountHandle} to check 1053 * @return {@code true} if the carrier supports holding emergency calls, {@code} false 1054 * otherwise. 1055 */ 1056 private boolean shouldHoldForEmergencyCall(PhoneAccountHandle handle) { 1057 return mCallsManager.getCarrierConfigForPhoneAccount(handle).getBoolean( 1058 CarrierConfigManager.KEY_ALLOW_HOLD_CALL_DURING_EMERGENCY_BOOL, true); 1059 } 1060 1061 @VisibleForTesting 1062 public boolean arePhoneAccountsSame(Call call1, Call call2) { 1063 if (call1 == null || call2 == null) { 1064 return false; 1065 } 1066 return Objects.equals(call1.getTargetPhoneAccount(), call2.getTargetPhoneAccount()); 1067 } 1068 1069 /** 1070 * Checks to see if two calls can be swapped. This is granted that the call to be unheld is 1071 * already ON_HOLD and the active call supports holding. Note that in HoldTracker, there can 1072 * only be one top call that is holdable (if there are two, the calls are not holdable) and only 1073 * that connection would have the CAPABILITY_HOLD present. For swapping logic, we should take 1074 * this into account and request to hold regardless. 1075 */ 1076 @VisibleForTesting 1077 private boolean canSwap(Call callToBeHeld, Call callToUnhold) { 1078 return callToBeHeld.can(Connection.CAPABILITY_SUPPORT_HOLD) 1079 && callToBeHeld.getState() != CallState.DIALING 1080 && callToUnhold.getState() == CallState.ON_HOLD; 1081 } 1082 1083 private CompletableFuture<Boolean> disconnectAllCallsWithPhoneAccount( 1084 PhoneAccountHandle handle, boolean excludeAccount) { 1085 CompletableFuture<Boolean> disconnectFuture = CompletableFuture.completedFuture(true); 1086 // Filter out the corresponding phone account and ensure that we don't consider conference 1087 // participants as part of the bulk disconnect (we'll just disconnect the host directly). 1088 List<Call> calls = mCallsManager.getCalls().stream() 1089 .filter(c -> excludeAccount != c.getTargetPhoneAccount().equals(handle) 1090 && c.getParentCall() == null).toList(); 1091 for (Call call: calls) { 1092 // Wait for all disconnects before we accept the new call. 1093 disconnectFuture = disconnectFuture.thenComposeAsync((result) -> { 1094 if (!result) { 1095 Log.i(this, "disconnectAllCallsWithPhoneAccount: " 1096 + "Failed to disconnect %s.", call); 1097 } 1098 return call.disconnect("Call " + call + " disconnected " 1099 + "in favor of new call."); 1100 }, new LoggedHandlerExecutor(mHandler, "CSC.dACWPA", mCallsManager.getLock())); 1101 } 1102 return disconnectFuture; 1103 } 1104 1105 /** 1106 * Generic helper to log the result of the {@link CompletableFuture} containing the transactions 1107 * that are being processed in the context of call sequencing. 1108 * @param future The {@link CompletableFuture} encompassing the transaction that's being 1109 * computed. 1110 * @param methodName The method name to describe the type of transaction being processed. 1111 * @param sessionName The session name to identify the log. 1112 * @param successMsg The message to be logged if the transaction succeeds. 1113 * @param failureMsg The message to be logged if the transaction fails. 1114 */ 1115 public void logFutureResultTransaction(CompletableFuture<Boolean> future, String methodName, 1116 String sessionName, String successMsg, String failureMsg) { 1117 future.thenApplyAsync((result) -> { 1118 String msg = methodName + ": " + (result ? successMsg : failureMsg); 1119 Log.i(this, msg); 1120 return CompletableFuture.completedFuture(result); 1121 }, new LoggedHandlerExecutor(mHandler, sessionName, mCallsManager.getLock())); 1122 } 1123 1124 public boolean hasMmiCodeRestriction(Call call) { 1125 if (mCallsManager.getNumCallsWithStateWithoutHandle( 1126 CALL_FILTER_ALL, call, call.getTargetPhoneAccount(), ONGOING_CALL_STATES) > 0) { 1127 // Set disconnect cause so that error will be printed out when call is disconnected. 1128 CharSequence msg = mContext.getText(R.string.callFailed_reject_mmi); 1129 call.setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.ERROR, msg, msg, 1130 "Rejected MMI code due to an ongoing call on another phone account.")); 1131 return true; 1132 } 1133 return false; 1134 } 1135 1136 public void maybeAddAnsweringCallDropsFg(Call activeCall, Call incomingCall) { 1137 // Don't set the extra when we have an incoming self-managed call that would potentially 1138 // disconnect the active managed call. 1139 if (activeCall == null || (isVoipCall(incomingCall) && isManagedCall(activeCall))) { 1140 return; 1141 } 1142 // Check if the active call doesn't support hold. If it doesn't we should indicate to the 1143 // user via the EXTRA_ANSWERING_DROPS_FG_CALL extra that the call would be dropped by 1144 // answering the incoming call. 1145 if (!mCallsManager.supportsHold(activeCall)) { 1146 CharSequence droppedApp = activeCall.getTargetPhoneAccountLabel(); 1147 Bundle dropCallExtras = new Bundle(); 1148 dropCallExtras.putBoolean(Connection.EXTRA_ANSWERING_DROPS_FG_CALL, true); 1149 1150 // Include the name of the app which will drop the call. 1151 dropCallExtras.putCharSequence( 1152 Connection.EXTRA_ANSWERING_DROPS_FG_CALL_APP_NAME, droppedApp); 1153 Log.i(this, "Incoming call will drop %s call.", droppedApp); 1154 incomingCall.putConnectionServiceExtras(dropCallExtras); 1155 } 1156 } 1157 1158 private void showErrorDialogForMaxOutgoingCallOutgoingPresent(Call call) { 1159 int resourceId = R.string.callFailed_outgoing_already_present; 1160 String reason = " there is already another call connecting. Wait for the " 1161 + "call to be answered or disconnect before placing another call."; 1162 showErrorDialogForFailedCall(call, CallFailureCause.MAX_OUTGOING_CALLS, resourceId, reason); 1163 } 1164 1165 private void showErrorDialogForMaxOutgoingCallTooManyCalls( 1166 Call call, boolean arePhoneAccountsSame) { 1167 int resourceId = arePhoneAccountsSame 1168 ? R.string.callFailed_too_many_calls_include_merge 1169 : R.string.callFailed_too_many_calls_exclude_merge; 1170 String reason = " there are two calls already in progress. Disconnect one " 1171 + "of the calls or merge the calls (if possible)."; 1172 showErrorDialogForFailedCall(call, CallFailureCause.MAX_OUTGOING_CALLS, resourceId, reason); 1173 } 1174 1175 private void showErrorDialogForOutgoingDuringRingingCall(Call call) { 1176 int resourceId = R.string.callFailed_already_ringing; 1177 String reason = " can't place outgoing call with an unanswered incoming call."; 1178 showErrorDialogForFailedCall(call, null, resourceId, reason); 1179 } 1180 1181 private void showErrorDialogForCannotHoldCall(Call call, boolean setCallFailure) { 1182 CallFailureCause cause = null; 1183 if (setCallFailure) { 1184 cause = CallFailureCause.CANNOT_HOLD_CALL; 1185 } 1186 int resourceId = R.string.callFailed_unholdable_call; 1187 String reason = " unable to hold live call. Disconnect the unholdable call."; 1188 showErrorDialogForFailedCall(call, cause, resourceId, reason); 1189 } 1190 1191 private void showErrorDialogForFailedCall(Call call, CallFailureCause cause, int resourceId, 1192 String reason) { 1193 if (cause != null) { 1194 call.setStartFailCause(cause); 1195 } 1196 showErrorDialogForRestrictedOutgoingCall(mContext, resourceId, TAG, reason); 1197 } 1198 1199 public Handler getHandler() { 1200 return mHandler; 1201 } 1202 1203 private boolean isVoipCall(Call call) { 1204 if (call == null) { 1205 return false; 1206 } 1207 return call.isSelfManaged() || call.isTransactionalCall(); 1208 } 1209 1210 private boolean isManagedCall(Call call) { 1211 if (call == null) { 1212 return false; 1213 } 1214 return !call.isSelfManaged() && !call.isTransactionalCall() && !call.isExternalCall(); 1215 } 1216 } 1217