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