• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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