• 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 android.content.Context;
20 import android.os.Bundle;
21 import android.os.Handler;
22 import android.os.OutcomeReceiver;
23 import android.telecom.CallAttributes;
24 import android.telecom.CallException;
25 import android.telecom.Connection;
26 import android.telecom.Log;
27 import android.telecom.PhoneAccountHandle;
28 
29 import com.android.server.telecom.Call;
30 import com.android.server.telecom.CallAudioManager;
31 import com.android.server.telecom.CallState;
32 import com.android.server.telecom.CallsManager;
33 import com.android.server.telecom.callsequencing.voip.OutgoingCallTransaction;
34 import com.android.server.telecom.flags.FeatureFlags;
35 import com.android.server.telecom.R;
36 
37 import java.util.Collection;
38 import java.util.HashSet;
39 import java.util.Set;
40 import java.util.concurrent.CompletableFuture;
41 
42 /**
43  * Abstraction layer for CallsManager to perform call sequencing operations through CallsManager
44  * or CallSequencingController, which is controlled by {@link FeatureFlags#enableCallSequencing()}.
45  */
46 public class CallsManagerCallSequencingAdapter {
47 
48     private final CallsManager mCallsManager;
49     private final Context mContext;
50     private final CallSequencingController mSequencingController;
51     private final CallAudioManager mCallAudioManager;
52     private final Handler mHandler;
53     private final FeatureFlags mFeatureFlags;
54     private final boolean mIsCallSequencingEnabled;
55 
CallsManagerCallSequencingAdapter(CallsManager callsManager, Context context, CallSequencingController sequencingController, CallAudioManager callAudioManager, FeatureFlags featureFlags)56     public CallsManagerCallSequencingAdapter(CallsManager callsManager, Context context,
57             CallSequencingController sequencingController, CallAudioManager callAudioManager,
58             FeatureFlags featureFlags) {
59         mCallsManager = callsManager;
60         mContext = context;
61         mSequencingController = sequencingController;
62         mCallAudioManager = callAudioManager;
63         mHandler = sequencingController.getHandler();
64         mFeatureFlags = featureFlags;
65         mIsCallSequencingEnabled = featureFlags.enableCallSequencing();
66     }
67 
68     /**
69      * Conditionally try to answer the call depending on whether call sequencing
70      * (mIsCallSequencingEnabled) is enabled.
71      * @param incomingCall The incoming call that should be answered.
72      * @param videoState The video state configuration associated with the call.
73      * @param requestOrigin The origin of the request.
74      */
answerCall(Call incomingCall, int videoState, @CallsManager.RequestOrigin int requestOrigin)75     public void answerCall(Call incomingCall, int videoState,
76             @CallsManager.RequestOrigin int requestOrigin) {
77         if (mIsCallSequencingEnabled && !incomingCall.isTransactionalCall()) {
78             mSequencingController.answerCall(incomingCall, videoState, requestOrigin);
79         } else {
80             mCallsManager.answerCallOld(incomingCall, videoState, requestOrigin);
81         }
82     }
83 
84     /**
85      * Conditionally attempt to unhold the provided call depending on whether call sequencing
86      * (mIsCallSequencingEnabled) is enabled.
87      * @param call The call to unhold.
88      */
unholdCall(Call call)89     public void unholdCall(Call call) {
90         if (mIsCallSequencingEnabled) {
91             mSequencingController.unholdCall(call);
92         } else {
93             mCallsManager.unholdCallOld(call);
94         }
95     }
96 
97     /**
98      * Conditionally attempt to hold the provided call depending on whether call sequencing
99      * (mIsCallSequencingEnabled) is enabled.
100      * @param call The call to hold.
101      */
holdCall(Call call)102     public void holdCall(Call call) {
103         // Sequencing already taken care of for CSW/TSW in Call class.
104         CompletableFuture<Boolean> holdFuture = call.hold();
105         maybeLogFutureResultTransaction(holdFuture, "holdCall", "CMCSA.hC",
106                 "hold call transaction succeeded.", "hold call transaction failed.");
107     }
108 
109     /**
110      * Conditionally disconnect the provided call depending on whether call sequencing
111      * (mIsCallSequencingEnabled) is enabled. The sequencing functionality ensures that we wait for
112      * the call to be disconnected as signalled by CSW/TSW as to ensure that subsequent call
113      * operations don't overlap with this one.
114      * @param call The call to disconnect.
115      */
disconnectCall(Call call)116     public void disconnectCall(Call call) {
117         int previousState = call.getState();
118         if (mIsCallSequencingEnabled) {
119             mSequencingController.disconnectCall(call, previousState);
120         } else {
121             mCallsManager.disconnectCallOld(call, previousState);
122         }
123     }
124 
125     /**
126      * Conditionally make room for the outgoing call depending on whether call sequencing
127      * (mIsCallSequencingEnabled) is enabled.
128      * @param isEmergency Indicator of whether the call is an emergency call.
129      * @param call The call to potentially make room for.
130      * @return {@link CompletableFuture} which will contain the result of the transaction if room
131      *         was able to made for the call.
132      */
makeRoomForOutgoingCall(boolean isEmergency, Call call)133     public CompletableFuture<Boolean> makeRoomForOutgoingCall(boolean isEmergency, Call call) {
134         if (mIsCallSequencingEnabled) {
135             return mSequencingController.makeRoomForOutgoingCall(isEmergency, call);
136         } else {
137             return isEmergency
138                     ? CompletableFuture.completedFuture(
139                             mCallsManager.makeRoomForOutgoingEmergencyCall(call))
140                     : CompletableFuture.completedFuture(
141                             mCallsManager.makeRoomForOutgoingCall(call));
142         }
143     }
144 
145     /**
146      * Attempts to mark the self-managed call as active by first holding the active call and then
147      * requesting call focus for the self-managed call.
148      * @param call The self-managed call to set active
149      */
markCallAsActiveSelfManagedCall(Call call)150     public void markCallAsActiveSelfManagedCall(Call call) {
151         if (mIsCallSequencingEnabled) {
152             mSequencingController.handleSetSelfManagedCallActive(call);
153         } else {
154             mCallsManager.holdActiveCallForNewCall(call);
155             mCallsManager.requestActionSetActiveCall(call,
156                     "active set explicitly for self-managed");
157         }
158     }
159 
160     /**
161      * Helps create the transaction representing the outgoing transactional call. For outgoing
162      * calls, there can be more than one transaction that will need to complete when
163      * mIsCallSequencingEnabled is true. Otherwise, rely on the old behavior of creating an
164      * {@link OutgoingCallTransaction}.
165      * @param callAttributes The call attributes associated with the call.
166      * @param extras The extras that are associated with the call.
167      * @param callingPackage The calling package representing where the request was invoked from.
168      * @return The {@link CompletableFuture<CallTransaction>} that encompasses the request to
169      *         place/receive the transactional call.
170      */
createTransactionalOutgoingCall(String callId, CallAttributes callAttributes, Bundle extras, String callingPackage)171     public CompletableFuture<CallTransaction> createTransactionalOutgoingCall(String callId,
172             CallAttributes callAttributes, Bundle extras, String callingPackage) {
173         return mIsCallSequencingEnabled
174                 ? mSequencingController.createTransactionalOutgoingCall(callId,
175                 callAttributes, extras, callingPackage)
176                 : CompletableFuture.completedFuture(new OutgoingCallTransaction(callId,
177                         mCallsManager.getContext(), callAttributes, mCallsManager, extras,
178                         mFeatureFlags));
179     }
180 
181     /**
182      * attempt to hold or swap the current active call in favor of a new call request. The
183      * OutcomeReceiver will return onResult if the current active call is held or disconnected.
184      * Otherwise, the OutcomeReceiver will fail.
185      * @param newCall The new (transactional) call that's waiting to go active.
186      * @param isCallControlRequest Indication of whether this is a call control request.
187      * @param callback The callback to report the result of the aforementioned hold
188      *      transaction.
189      */
transactionHoldPotentialActiveCallForNewCall(Call newCall, boolean isCallControlRequest, OutcomeReceiver<Boolean, CallException> callback)190     public void transactionHoldPotentialActiveCallForNewCall(Call newCall,
191             boolean isCallControlRequest, OutcomeReceiver<Boolean, CallException> callback) {
192         String mTag = "transactionHoldPotentialActiveCallForNewCall: ";
193         Call activeCall = (Call) mCallsManager.getConnectionServiceFocusManager()
194                 .getCurrentFocusCall();
195         Log.i(this, mTag + "newCall=[%s], activeCall=[%s]", newCall, activeCall);
196 
197         if (activeCall == null || activeCall == newCall) {
198             Log.i(this, mTag + "no need to hold activeCall");
199             callback.onResult(true);
200             return;
201         }
202 
203         if (mFeatureFlags.transactionalHoldDisconnectsUnholdable()) {
204             // prevent bad actors from disconnecting the activeCall. Instead, clients will need to
205             // notify the user that they need to disconnect the ongoing call before making the
206             // new call ACTIVE.
207             if (isCallControlRequest
208                     && !mCallsManager.canHoldOrSwapActiveCall(activeCall, newCall)) {
209                 Log.i(this, mTag + "CallControlRequest exit");
210                 callback.onError(new CallException("activeCall is NOT holdable or swappable, please"
211                         + " request the user disconnect the call.",
212                         CallException.CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL));
213                 return;
214             }
215 
216             if (mIsCallSequencingEnabled) {
217                 mSequencingController.transactionHoldPotentialActiveCallForNewCallSequencing(
218                         newCall, callback);
219             } else {
220                 // The code path without sequencing but where transactionalHoldDisconnectsUnholdable
221                 // flag is enabled.
222                 mCallsManager.transactionHoldPotentialActiveCallForNewCallOld(newCall,
223                         activeCall, callback);
224             }
225         } else {
226             // The unflagged path (aka original code with no flags).
227             mCallsManager.transactionHoldPotentialActiveCallForNewCallUnflagged(activeCall,
228                     newCall, callback);
229         }
230     }
231 
232     /**
233      * Attempts to move the held call to the foreground in cases where we need to auto-unhold the
234      * call.
235      */
maybeMoveHeldCallToForeground(Call removedCall, boolean isLocallyDisconnecting)236     public void maybeMoveHeldCallToForeground(Call removedCall, boolean isLocallyDisconnecting) {
237         CompletableFuture<Boolean> unholdForegroundCallFuture = null;
238         Call foregroundCall = mCallAudioManager.getPossiblyHeldForegroundCall();
239         // There are some cases (non-holdable calls) where we may want to skip auto-unholding when
240         // we're processing a new outgoing call and waiting for it to go active. Skip the
241         // auto-unholding in this case so that we don't end up with two active calls. If the new
242         // call fails, we will auto-unhold on that removed call. This is only set in
243         // CallSequencingController because the legacy code doesn't wait for disconnects to occur
244         // in order to place an outgoing (emergency) call, so we don't see this issue.
245         if (removedCall.getSkipAutoUnhold()) {
246             return;
247         }
248 
249         if (isLocallyDisconnecting) {
250             boolean isDisconnectingChildCall = removedCall.isDisconnectingChildCall();
251             Log.v(this, "maybeMoveHeldCallToForeground: isDisconnectingChildCall = "
252                     + isDisconnectingChildCall + "call -> %s", removedCall);
253             // Auto-unhold the foreground call due to a locally disconnected call, except if the
254             // call which was disconnected is a member of a conference (don't want to auto
255             // un-hold the conference if we remove a member of the conference).
256             // Also, ensure that the call we're removing is from the same ConnectionService as
257             // the one we're removing.  We don't want to auto-unhold between ConnectionService
258             // implementations, especially if one is managed and the other is a VoIP CS.
259             if (!isDisconnectingChildCall && foregroundCall != null
260                     && foregroundCall.getState() == CallState.ON_HOLD
261                     && CallsManager.areFromSameSource(foregroundCall, removedCall)) {
262                 unholdForegroundCallFuture = foregroundCall.unhold();
263             }
264         } else if (foregroundCall != null &&
265                 !foregroundCall.can(Connection.CAPABILITY_SUPPORT_HOLD) &&
266                 foregroundCall.getState() == CallState.ON_HOLD) {
267 
268             // The new foreground call is on hold, however the carrier does not display the hold
269             // button in the UI.  Therefore, we need to auto unhold the held call since the user
270             // has no means of unholding it themselves.
271             Log.i(this, "maybeMoveHeldCallToForeground: Auto-unholding held foreground call (call "
272                     + "doesn't support hold)");
273             unholdForegroundCallFuture = foregroundCall.unhold();
274         }
275         maybeLogFutureResultTransaction(unholdForegroundCallFuture,
276                 "maybeMoveHeldCallToForeground", "CM.mMHCTF",
277                 "Successfully unheld the foreground call.",
278                 "Failed to unhold the foreground call.");
279     }
280 
281     /**
282      * Generic helper to log the result of the {@link CompletableFuture} containing the transactions
283      * that are being processed in the context of call sequencing.
284      * @param future The {@link CompletableFuture} encompassing the transaction that's being
285      *               computed.
286      * @param methodName The method name to describe the type of transaction being processed.
287      * @param sessionName The session name to identify the log.
288      * @param successMsg The message to be logged if the transaction succeeds.
289      * @param failureMsg The message to be logged if the transaction fails.
290      */
maybeLogFutureResultTransaction(CompletableFuture<Boolean> future, String methodName, String sessionName, String successMsg, String failureMsg)291     public void maybeLogFutureResultTransaction(CompletableFuture<Boolean> future,
292             String methodName, String sessionName, String successMsg, String failureMsg) {
293         if (mIsCallSequencingEnabled && future != null) {
294             mSequencingController.logFutureResultTransaction(future, methodName, sessionName,
295                     successMsg, failureMsg);
296         }
297     }
298 
299     /**
300      * Determines if we need to add the {@link Connection#EXTRA_ANSWERING_DROPS_FG_CALL} extra to
301      * the incoming connection. This is set if the ongoing calls don't support hold.
302      */
maybeAddAnsweringCallDropsFg(Call activeCall, Call incomingCall)303     public void maybeAddAnsweringCallDropsFg(Call activeCall, Call incomingCall) {
304         if (mIsCallSequencingEnabled) {
305             mSequencingController.maybeAddAnsweringCallDropsFg(activeCall, incomingCall);
306         } else {
307             mCallsManager.maybeAddAnsweringCallDropsFgOld(activeCall, incomingCall);
308         }
309     }
310 
311     /**
312      * Tries to see if there are any ongoing calls on another phone account when an MMI code is
313      * detected to determine whether it should be allowed. For DSDA purposes, we will not allow any
314      * MMI codes when there's a call on a different phone account.
315      * @param call The call to ignore and the associated phone account to exclude when getting the
316      *             total call count.
317      * @return {@code true} if the MMI code should be allowed, {@code false} otherwise.
318      */
shouldAllowMmiCode(Call call)319     public boolean shouldAllowMmiCode(Call call) {
320         return !mIsCallSequencingEnabled || !mSequencingController.hasMmiCodeRestriction(call);
321     }
322 
323     /**
324      * Processes the simultaneous call type for the ongoing calls that are being tracked in
325      * {@link CallsManager}. The current call's simultaneous call type will be overridden only if
326      * it's current type priority is lower than the one being set.
327      * @param calls The list of the currently tracked calls.
328      */
processSimultaneousCallTypes(Collection<Call> calls)329     public void processSimultaneousCallTypes(Collection<Call> calls) {
330         // Metrics should only be tracked when call sequencing flag is enabled.
331         if (!mIsCallSequencingEnabled) {
332             return;
333         }
334         // Device should have simultaneous calling supported.
335         boolean isSimultaneousCallingSupported = mCallsManager.isDsdaCallingPossible();
336         int type;
337         // Go through the available calls' phone accounts to determine how many different ones
338         // are being used.
339         Set<PhoneAccountHandle> handles = new HashSet<>();
340         for (Call call : calls) {
341             if (call.getTargetPhoneAccount() != null) {
342                 handles.add(call.getTargetPhoneAccount());
343             }
344             // No need to proceed further given that we already know there is more than 1 phone
345             // account being used.
346             if (handles.size() > 1) {
347                 break;
348             }
349         }
350         type = handles.size() > 1
351                 ? (isSimultaneousCallingSupported ? Call.CALL_DIRECTION_DUAL_DIFF_ACCOUNT
352                         : Call.CALL_SIMULTANEOUS_DISABLED_DIFF_ACCOUNT)
353                 : (isSimultaneousCallingSupported ? Call.CALL_DIRECTION_DUAL_SAME_ACCOUNT
354                         : Call.CALL_SIMULTANEOUS_DISABLED_SAME_ACCOUNT);
355 
356         Log.i(this, "processSimultaneousCallTypes: the calculated simultaneous call type for "
357                 + "the tracked calls is [%d]", type);
358         calls.forEach(c -> {
359             // If the current call's simultaneous call type priority is lower than the one being
360             // set, then let the override occur. Otherwise, ignore it.
361             if (c.getSimultaneousType() < type) {
362                 Log.i(this, "processSimultaneousCallTypes: overriding simultaneous call type for "
363                         + "call (%s). Previous value: %d", c.getId(), c.getSimultaneousType());
364                 c.setSimultaneousType(type);
365             }
366         });
367     }
368 
369     /**
370      * Upon a call resume failure, we will auto-unhold the foreground call that was held. Note that
371      * this should only apply for calls across phone accounts as the ImsPhoneCallTracker handles
372      * this for a single phone.
373      * @param callResumeFailed The call that failed to resume.
374      * @param callToUnhold The fg call that was held.
375      */
handleCallResumeFailed(Call callResumeFailed, Call callToUnhold)376     public void handleCallResumeFailed(Call callResumeFailed, Call callToUnhold) {
377         if (mIsCallSequencingEnabled && !mSequencingController.arePhoneAccountsSame(
378                 callResumeFailed, callToUnhold)) {
379             unholdCall(callToUnhold);
380         }
381     }
382 
getHandler()383     public Handler getHandler() {
384         return mHandler;
385     }
386 }
387