Integrate connectionServiceFocusManager into call flow

This changed allows multiple calls existed at the same time by
mataining the ConnectionService focus.

design doc: go/android-telecom-3p-enhancements
Bug: 69651192
Test: unit test

Change-Id: Ifcf64664f61d1b792daa1cab504d9045f2dbc975
Merged-In: Ifcf64664f61d1b792daa1cab504d9045f2dbc975
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index c6bde65..c301aac 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -1762,7 +1762,8 @@
     /**
      * Puts the call on hold if it is currently active.
      */
-    void hold() {
+    @VisibleForTesting
+    public void hold() {
         if (mState == CallState.ACTIVE) {
             if (mConnectionService != null) {
                 mConnectionService.hold(this);
@@ -1777,7 +1778,8 @@
     /**
      * Releases the call from hold if it is currently active.
      */
-    void unhold() {
+    @VisibleForTesting
+    public void unhold() {
         if (mState == CallState.ON_HOLD) {
             if (mConnectionService != null) {
                 mConnectionService.unhold(this);
@@ -2781,6 +2783,10 @@
         return mOriginalConnectionId;
     }
 
+    ConnectionServiceFocusManager getConnectionServiceFocusManager() {
+        return mCallsManager.getConnectionServiceFocusManager();
+    }
+
     /**
      * Determines if a {@link Call}'s capabilities bitmask indicates that video is supported either
      * remotely or locally.
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 114fec9..5640bce 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -122,6 +122,11 @@
         void onDisconnectedTonePlaying(boolean isTonePlaying);
     }
 
+    /** Interface used to define the action which is executed delay under some condition. */
+    interface PendingAction {
+        void performAction();
+    }
+
     private static final String TAG = "CallsManager";
 
     /**
@@ -262,10 +267,27 @@
     private final ClockProxy mClockProxy;
     private final Set<Call> mLocallyDisconnectingCalls = new HashSet<>();
     private final Set<Call> mPendingCallsToDisconnect = new HashSet<>();
+    private final ConnectionServiceFocusManager mConnectionSvrFocusMgr;
     /* Handler tied to thread in which CallManager was initialized. */
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final EmergencyCallHelper mEmergencyCallHelper;
 
+    private final ConnectionServiceFocusManager.CallsManagerRequester mRequester =
+            new ConnectionServiceFocusManager.CallsManagerRequester() {
+                @Override
+                public void releaseConnectionService(
+                        ConnectionServiceFocusManager.ConnectionServiceFocus connectionService) {
+                    mCalls.stream()
+                            .filter(c -> c.getConnectionServiceWrapper().equals(connectionService))
+                            .forEach(c -> c.disconnect());
+                }
+
+                @Override
+                public void setCallsManagerListener(CallsManagerListener listener) {
+                    mListeners.add(listener);
+                }
+            };
+
     private boolean mCanAddCall = true;
 
     private TelephonyManager.MultiSimVariants mRadioSimVariants = null;
@@ -301,6 +323,8 @@
             HeadsetMediaButtonFactory headsetMediaButtonFactory,
             ProximitySensorManagerFactory proximitySensorManagerFactory,
             InCallWakeLockControllerFactory inCallWakeLockControllerFactory,
+            ConnectionServiceFocusManager.ConnectionServiceFocusManagerFactory
+                    connectionServiceFocusManagerFactory,
             CallAudioManager.AudioServiceFactory audioServiceFactory,
             BluetoothRouteManager bluetoothManager,
             WiredHeadsetManager wiredHeadsetManager,
@@ -368,6 +392,9 @@
                         mContext.getSystemService(Context.AUDIO_SERVICE)),
                 playerFactory, mRinger, new RingbackPlayer(playerFactory), mDtmfLocalTonePlayer);
 
+        mConnectionSvrFocusMgr = connectionServiceFocusManagerFactory.create(
+                mRequester, Looper.getMainLooper());
+
         mHeadsetMediaButton = headsetMediaButtonFactory.create(context, this, mLock);
         mTtyManager = new TtyManager(context, mWiredHeadsetManager);
         mProximitySensorManager = proximitySensorManagerFactory.create(context, this);
@@ -862,23 +889,22 @@
             if (call.isSelfManaged()) {
                 // Self managed calls will always be voip audio mode.
                 call.setIsVoipAudioMode(true);
-            } else {
-                // Incoming call is not self-managed, so we need to set extras on it to indicate
-                // whether answering will cause a background self-managed call to drop.
-                if (hasSelfManagedCalls()) {
-                    Bundle dropCallExtras = new Bundle();
-                    dropCallExtras.putBoolean(Connection.EXTRA_ANSWERING_DROPS_FG_CALL, true);
+            }
 
-                    // Include the name of the app which will drop the call.
-                    Call foregroundCall = getForegroundCall();
-                    if (foregroundCall != null) {
-                        CharSequence droppedApp = foregroundCall.getTargetPhoneAccountLabel();
-                        dropCallExtras.putCharSequence(
-                                Connection.EXTRA_ANSWERING_DROPS_FG_CALL_APP_NAME, droppedApp);
-                        Log.i(this, "Incoming managed call will drop %s call.", droppedApp);
-                    }
-                    call.putExtras(Call.SOURCE_CONNECTION_SERVICE, dropCallExtras);
+            Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
+
+            if (activeCall != null && !canHold(activeCall)) {
+                Bundle dropCallExtras = new Bundle();
+                dropCallExtras.putBoolean(Connection.EXTRA_ANSWERING_DROPS_FG_CALL, true);
+
+                // Include the name of the app which will drop the call.
+                if (activeCall != null) {
+                    CharSequence droppedApp = activeCall.getTargetPhoneAccountLabel();
+                    dropCallExtras.putCharSequence(
+                            Connection.EXTRA_ANSWERING_DROPS_FG_CALL_APP_NAME, droppedApp);
+                    Log.i(this, "Incoming managed call will drop %s call.", droppedApp);
                 }
+                call.putExtras(Call.SOURCE_CONNECTION_SERVICE, dropCallExtras);
             }
 
             if (extras.getBoolean(PhoneAccount.EXTRA_ALWAYS_USE_VOIP_AUDIO_MODE)) {
@@ -1038,7 +1064,8 @@
      * @param initiatingUser {@link UserHandle} of user that place the outgoing call.
      * @param originalIntent
      */
-    Call startOutgoingCall(Uri handle, PhoneAccountHandle phoneAccountHandle, Bundle extras,
+    @VisibleForTesting
+    public Call startOutgoingCall(Uri handle, PhoneAccountHandle phoneAccountHandle, Bundle extras,
             UserHandle initiatingUser, Intent originalIntent) {
         boolean isReusedCall = true;
         Call call = reuseOutgoingCall(handle);
@@ -1127,22 +1154,28 @@
         // a call, or cancel this call altogether. If a call is being reused, then it has already
         // passed the makeRoomForOutgoingCall check once and will fail the second time due to the
         // call transitioning into the CONNECTING state.
-        if (!isSelfManaged && !isPotentialInCallMMICode && (!isReusedCall &&
-                !makeRoomForOutgoingCall(call, call.isEmergencyCall()))) {
-            // just cancel at this point.
-            Log.i(this, "No remaining room for outgoing call: %s", call);
-            if (mCalls.contains(call)) {
-                // This call can already exist if it is a reused call,
-                // See {@link #reuseOutgoingCall}.
-                call.disconnect();
+        if (!isPotentialInCallMMICode && (!isReusedCall
+                && !makeRoomForOutgoingCall(call, call.isEmergencyCall()))) {
+            Call foregroundCall = getForegroundCall();
+            Log.d(this, "No more room for outgoing call %s ", call);
+            if (foregroundCall.isSelfManaged()) {
+                // If the ongoing call is a self-managed call, then prompt the user to ask if they'd
+                // like to disconnect their ongoing call and place the outgoing call.
+                call.setOriginalCallIntent(originalIntent);
+                startCallConfirmation(call);
+            } else {
+                // If the ongoing call is a managed call, we will prevent the outgoing call from
+                // dialing.
+                notifyCreateConnectionFailed(call.getTargetPhoneAccount(), call);
             }
             return null;
         }
 
+        // The outgoing call can be placed, go forward.
+
         boolean needsAccountSelection =
                 phoneAccountHandle == null && potentialPhoneAccounts.size() > 1
                         && !call.isEmergencyCall() && !isSelfManaged;
-
         if (needsAccountSelection) {
             // This is the state where the user is expected to select an account
             call.setState(CallState.SELECT_PHONE_ACCOUNT, "needs account selection");
@@ -1165,6 +1198,7 @@
             call.setState(
                     CallState.CONNECTING,
                     phoneAccountHandle == null ? "no-handle" : phoneAccountHandle.toString());
+
             if (extras != null
                     && extras.getBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT, false)) {
                 if (accountToUse != null
@@ -1175,15 +1209,9 @@
         }
         setIntentExtrasAndStartTime(call, extras);
 
-        if ((isPotentialMMICode(handle) || isPotentialInCallMMICode)
-                && !needsAccountSelection) {
+        if ((isPotentialMMICode(handle) || isPotentialInCallMMICode) && !needsAccountSelection) {
             // Do not add the call if it is a potential MMI code.
             call.addListener(this);
-        } else if (!isSelfManaged && hasSelfManagedCalls() && !call.isEmergencyCall()) {
-            // Adding a managed call and there are ongoing self-managed call(s).
-            call.setOriginalCallIntent(originalIntent);
-            startCallConfirmation(call);
-            return null;
         } else if (!mCalls.contains(call)) {
             // We check if mCalls already contains the call because we could potentially be reusing
             // a call which was previously added (See {@link #reuseOutgoingCall}).
@@ -1334,12 +1362,13 @@
             // Otherwise the connection will be initiated when the account is set by the user.
             if (call.isSelfManaged() && !isOutgoingCallPermitted) {
                 notifyCreateConnectionFailed(call.getTargetPhoneAccount(), call);
-            } else if (!call.isSelfManaged() && hasSelfManagedCalls() && !call.isEmergencyCall()) {
-                markCallDisconnectedDueToSelfManagedCall(call);
             } else {
                 if (call.isEmergencyCall()) {
-                    // Disconnect all self-managed calls to make priority for emergency call.
-                    disconnectSelfManagedCalls();
+                    // Disconnect calls from other ConnectionServices other than the one the
+                    // emergency call targets.
+                    // Except, do not disconnect calls from the Connection Manager's
+                    // ConnectionService.
+                    disconnectCallsHaveDifferentConnectionService(call);
                 }
 
                 call.startCreateConnection(mPhoneAccountRegistrar);
@@ -1378,60 +1407,22 @@
         if (!mCalls.contains(call)) {
             Log.i(this, "Request to answer a non-existent call %s", call);
         } else {
-            Call foregroundCall = getForegroundCall();
-            // If the foreground call is not the ringing call and it is currently isActive() or
-            // STATE_DIALING, put it on hold before answering the call.
-            if (foregroundCall != null && foregroundCall != call &&
-                    (foregroundCall.isActive() ||
-                     foregroundCall.getState() == CallState.DIALING ||
-                     foregroundCall.getState() == CallState.PULLING)) {
-                if (!foregroundCall.getTargetPhoneAccount().equals(
-                                call.getTargetPhoneAccount()) &&
-                        ((call.isSelfManaged() != foregroundCall.isSelfManaged()) ||
-                         call.isSelfManaged())) {
-                    // The foreground call is from another connection service, and either:
-                    // 1. FG call's managed state doesn't match that of the incoming call.
-                    //    E.g. Incoming is self-managed and FG is managed, or incoming is managed
-                    //    and foreground is self-managed.
-                    // 2. The incoming call is self-managed.
-                    //    E.g. The incoming call is
-                    Log.i(this, "Answering call from %s CS; disconnecting calls from %s CS.",
-                            foregroundCall.isSelfManaged() ? "selfMg" : "mg",
-                            call.isSelfManaged() ? "selfMg" : "mg");
-                    disconnectOtherCalls(call.getTargetPhoneAccount());
-                } else if (0 == (foregroundCall.getConnectionCapabilities()
-                        & Connection.CAPABILITY_HOLD)) {
-                    // This call does not support hold.  If it is from a different connection
-                    // service, then disconnect it, otherwise allow the connection service to
-                    // figure out the right states.
-                    if (foregroundCall.getConnectionService() != call.getConnectionService()) {
-                        foregroundCall.disconnect();
-                    }
+            // Hold or disconnect the active call and request call focus for the incoming call.
+            Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
+            Log.d(this, "Incoming call = %s Ongoing call %s", call, activeCall);
+            if (activeCall != null) {
+                if (canHold(activeCall)) {
+                    Log.d(this, "answerCall hold active call");
+                    activeCall.hold();
                 } else {
-                    Call heldCall = getHeldCall();
-                    if (heldCall != null) {
-                        Log.i(this, "Disconnecting held call %s before holding active call.",
-                                heldCall);
-                        heldCall.disconnect();
-                    }
-
-                    foregroundCall.hold();
+                    Log.d(this, "answerCall disconnect active call");
+                    activeCall.disconnect();
                 }
-                // TODO: Wait until we get confirmation of the active call being
-                // on-hold before answering the new call.
-                // TODO: Import logic from CallManager.acceptCall()
             }
 
-            for (CallsManagerListener listener : mListeners) {
-                listener.onIncomingCallAnswered(call);
-            }
-
-            // We do not update the UI until we get confirmation of the answer() through
-            // {@link #markCallAsActive}.
-            call.answer(videoState);
-            if (isSpeakerphoneAutoEnabledForVideoCalls(videoState)) {
-                call.setStartWithSpeakerphoneOn(true);
-            }
+            mConnectionSvrFocusMgr.requestFocus(
+                    call,
+                    new RequestCallback(new ActionAnswerCall(call, videoState)));
         }
     }
 
@@ -1601,20 +1592,19 @@
         if (!mCalls.contains(call)) {
             Log.w(this, "Unknown call (%s) asked to be removed from hold", call);
         } else {
-            boolean otherCallHeld = false;
-            Log.d(this, "unholding call: (%s)", call);
-            for (Call c : mCalls) {
-                // Only attempt to hold parent calls and not the individual children.
-                if (c != null && c.isAlive() && c != call && c.getParentCall() == null) {
-                    otherCallHeld = true;
-                    Log.addEvent(c, LogUtils.Events.SWAP);
-                    c.hold();
+           Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
+            if (activeCall != null) {
+                if (canHold(activeCall)) {
+                    activeCall.hold();
+                    Log.addEvent(activeCall, LogUtils.Events.SWAP);
+                    Log.addEvent(call, LogUtils.Events.SWAP);
+                } else {
+                    activeCall.disconnect();
                 }
             }
-            if (otherCallHeld) {
-                Log.addEvent(call, LogUtils.Events.SWAP);
-            }
-            call.unhold();
+            mConnectionSvrFocusMgr.requestFocus(
+                    call,
+                    new RequestCallback(new ActionUnHoldCall(call)));
         }
     }
 
@@ -1822,8 +1812,20 @@
     }
 
     void markCallAsActive(Call call) {
-        setCallState(call, CallState.ACTIVE, "active set explicitly");
-        maybeMoveToSpeakerPhone(call);
+        if (call.isSelfManaged()) {
+            // backward compatibility, the self-managed connection service will set the call state
+            // to active directly. We should request the call focus for self-managed call before
+            // the state change
+            mConnectionSvrFocusMgr.requestFocus(
+                    call,
+                    new RequestCallback(new ActionSetCallState(
+                            call,
+                            CallState.ACTIVE,
+                            "active set explicitly for self-managed")));
+        } else {
+            setCallState(call, CallState.ACTIVE, "active set explicitly");
+            maybeMoveToSpeakerPhone(call);
+        }
     }
 
     @VisibleForTesting
@@ -2545,6 +2547,11 @@
         return (int) callsStream.count();
     }
 
+    private boolean hasMaximumLiveCalls(Call exceptCall) {
+        return MAXIMUM_LIVE_CALLS <= getNumCallsWithState(CALL_FILTER_ALL,
+                exceptCall, null /* phoneAccountHandle*/, LIVE_CALL_STATES);
+    }
+
     private boolean hasMaximumManagedLiveCalls(Call exceptCall) {
         return MAXIMUM_LIVE_CALLS <= getNumCallsWithState(false /* isSelfManaged */,
                 exceptCall, null /* phoneAccountHandle */, LIVE_CALL_STATES);
@@ -2572,6 +2579,11 @@
                 phoneAccountHandle, CallState.RINGING);
     }
 
+    private boolean hasMaximumOutgoingCalls(Call exceptCall) {
+        return MAXIMUM_LIVE_CALLS <= getNumCallsWithState(CALL_FILTER_ALL,
+                exceptCall, null /* phoneAccountHandle */, OUTGOING_CALL_STATES);
+    }
+
     private boolean hasMaximumManagedOutgoingCalls(Call exceptCall) {
         return MAXIMUM_OUTGOING_CALLS <= getNumCallsWithState(false /* isSelfManaged */, exceptCall,
                 null /* phoneAccountHandle */, OUTGOING_CALL_STATES);
@@ -2659,7 +2671,7 @@
     }
 
     private boolean makeRoomForOutgoingCall(Call call, boolean isEmergency) {
-        if (hasMaximumManagedLiveCalls(call)) {
+        if (hasMaximumLiveCalls(call)) {
             // NOTE: If the amount of live calls changes beyond 1, this logic will probably
             // have to change.
             Call liveCall = getFirstCallWithState(LIVE_CALL_STATES);
@@ -2673,7 +2685,7 @@
                 return true;
             }
 
-            if (hasMaximumManagedOutgoingCalls(call)) {
+            if (hasMaximumOutgoingCalls(call)) {
                 Call outgoingCall = getFirstCallWithState(OUTGOING_CALL_STATES);
                 if (isEmergency && !outgoingCall.isEmergencyCall()) {
                     // Disconnect the current outgoing call if it's not an emergency call. If the
@@ -2695,21 +2707,14 @@
                 return false;
             }
 
-            if (hasMaximumManagedHoldingCalls(call)) {
-                // There is no more room for any more calls, unless it's an emergency.
-                if (isEmergency) {
-                    // Kill the current active call, this is easier then trying to disconnect a
-                    // holding call and hold an active call.
-                    call.getAnalytics().setCallIsAdditional(true);
-                    liveCall.getAnalytics().setCallIsInterrupted(true);
-                    liveCall.disconnect();
-                    return true;
-                }
-                return false;  // No more room!
+            // Disconnected the live call if the outgoing call is an emergency call.
+            if (isEmergency && !canHold(liveCall)) {
+                call.getAnalytics().setCallIsAdditional(true);
+                liveCall.getAnalytics().setCallIsInterrupted(true);
+                liveCall.disconnect();
+                return true;
             }
 
-            // We have room for at least one more holding call at this point.
-
             // TODO: Remove once b/23035408 has been corrected.
             // If the live call is a conference, it will not have a target phone account set.  This
             // means the check to see if the live call has the same target phone account as the new
@@ -2745,7 +2750,7 @@
             }
 
             // Try to hold the live call before attempting the new outgoing call.
-            if (liveCall.can(Connection.CAPABILITY_HOLD)) {
+            if (canHold(liveCall)) {
                 Log.i(this, "makeRoomForOutgoingCall: holding live call.");
                 call.getAnalytics().setCallIsAdditional(true);
                 liveCall.getAnalytics().setCallIsInterrupted(true);
@@ -2990,13 +2995,10 @@
                     !hasMaximumManagedLiveCalls(excludeCall) &&
                     !hasMaximumManagedHoldingCalls(excludeCall);
         } else {
-            // Only permit outgoing calls if there is no ongoing emergency calls and all other calls
-            // are associated with the current PhoneAccountHandle.
-            return !hasEmergencyCall() && (
-                    (excludeCall != null && excludeCall.getHandoverSourceCall() != null) || (
-                            !hasMaximumSelfManagedCalls(excludeCall, phoneAccountHandle)
-                                    && !hasCallsForOtherPhoneAccount(phoneAccountHandle)
-                                    && !hasManagedCalls()));
+            // Only permit self-managed outgoing calls if there is no ongoing emergency calls and
+            // the ongoing call can be held.
+            Call foregroundCall = getForegroundCall();
+            return !hasEmergencyCall() && (foregroundCall == null || canHold(foregroundCall));
         }
     }
 
@@ -3082,22 +3084,18 @@
         mPendingCall = call;
 
         // Figure out the name of the app in charge of the self-managed call(s).
-        Call selfManagedCall = mCalls.stream()
-                .filter(c -> c.isSelfManaged())
-                .findFirst()
-                .orElse(null);
-        CharSequence ongoingAppName = "";
-        if (selfManagedCall != null) {
-            ongoingAppName = selfManagedCall.getTargetPhoneAccountLabel();
-        }
-        Log.i(this, "startCallConfirmation: callId=%s, ongoingApp=%s", call.getId(),
-                ongoingAppName);
+        Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
+        if (activeCall != null) {
+            CharSequence ongoingAppName = activeCall.getTargetPhoneAccountLabel();
+            Log.i(this, "startCallConfirmation: callId=%s, ongoingApp=%s", call.getId(),
+                    ongoingAppName);
 
-        Intent confirmIntent = new Intent(mContext, ConfirmCallDialogActivity.class);
-        confirmIntent.putExtra(ConfirmCallDialogActivity.EXTRA_OUTGOING_CALL_ID, call.getId());
-        confirmIntent.putExtra(ConfirmCallDialogActivity.EXTRA_ONGOING_APP_NAME, ongoingAppName);
-        confirmIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        mContext.startActivityAsUser(confirmIntent, UserHandle.CURRENT);
+            Intent confirmIntent = new Intent(mContext, ConfirmCallDialogActivity.class);
+            confirmIntent.putExtra(ConfirmCallDialogActivity.EXTRA_OUTGOING_CALL_ID, call.getId());
+            confirmIntent.putExtra(ConfirmCallDialogActivity.EXTRA_ONGOING_APP_NAME, ongoingAppName);
+            confirmIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            mContext.startActivityAsUser(confirmIntent, UserHandle.CURRENT);
+        }
     }
 
     /**
@@ -3119,6 +3117,14 @@
         mCallAudioManager.switchBaseline();
     }
 
+    private void disconnectCallsHaveDifferentConnectionService(Call exceptCall) {
+        mCalls.stream().filter(c ->
+                c.getConnectionService() != exceptCall.getConnectionService()
+                        && c.getConnectionManagerPhoneAccount()
+                        != exceptCall.getConnectionManagerPhoneAccount())
+                .forEach(c -> c.disconnect());
+    }
+
     /**
      * Dumps the state of the {@link CallsManager}.
      *
@@ -3591,4 +3597,87 @@
 
         call.startCreateConnection(mPhoneAccountRegistrar);
     }
+
+    ConnectionServiceFocusManager getConnectionServiceFocusManager() {
+        return mConnectionSvrFocusMgr;
+    }
+
+    private boolean canHold(Call call) {
+        return call.can(Connection.CAPABILITY_HOLD);
+    }
+
+    private final class ActionSetCallState implements PendingAction {
+
+        private final Call mCall;
+        private final int mState;
+        private final String mTag;
+
+        ActionSetCallState(Call call, int state, String tag) {
+            mCall = call;
+            mState = state;
+            mTag = tag;
+        }
+
+        @Override
+        public void performAction() {
+            Log.d(this, "perform set call state for %s, state = %s", mCall, mState);
+            setCallState(mCall, mState, mTag);
+        }
+    }
+
+    private final class ActionUnHoldCall implements PendingAction {
+        private final Call mCall;
+
+        ActionUnHoldCall(Call call) {
+            mCall = call;
+        }
+
+        @Override
+        public void performAction() {
+            Log.d(this, "perform unhold call for %s", mCall);
+            mCall.unhold();
+        }
+    }
+
+    private final class ActionAnswerCall implements PendingAction {
+        private final Call mCall;
+        private final int mVideoState;
+
+        ActionAnswerCall(Call call, int videoState) {
+            mCall = call;
+            mVideoState = videoState;
+        }
+
+        @Override
+        public void performAction() {
+            Log.d(this, "perform answer call for %s, videoState = %d", mCall, mVideoState);
+            for (CallsManagerListener listener : mListeners) {
+                listener.onIncomingCallAnswered(mCall);
+            }
+
+            // We do not update the UI until we get confirmation of the answer() through
+            // {@link #markCallAsActive}.
+            mCall.answer(mVideoState);
+            if (isSpeakerphoneAutoEnabledForVideoCalls(mVideoState)) {
+                mCall.setStartWithSpeakerphoneOn(true);
+            }
+        }
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public static final class RequestCallback implements
+            ConnectionServiceFocusManager.RequestFocusCallback {
+        private PendingAction mPendingAction;
+
+        RequestCallback(PendingAction pendingAction) {
+            mPendingAction = pendingAction;
+        }
+
+        @Override
+        public void onRequestFocusDone(ConnectionServiceFocusManager.CallFocus call) {
+            if (mPendingAction != null) {
+                mPendingAction.performAction();
+            }
+        }
+    }
 }
diff --git a/src/com/android/server/telecom/ConnectionServiceFocusManager.java b/src/com/android/server/telecom/ConnectionServiceFocusManager.java
index 60fbbd1..e213968 100644
--- a/src/com/android/server/telecom/ConnectionServiceFocusManager.java
+++ b/src/com/android/server/telecom/ConnectionServiceFocusManager.java
@@ -20,6 +20,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.telecom.Log;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
@@ -28,8 +29,12 @@
 import java.util.stream.Collectors;
 
 public class ConnectionServiceFocusManager {
+    private static final String TAG = "ConnectionSvrFocusMgr";
 
-    private static final String TAG = "ConnectionServiceFocusManager";
+    /** Factory interface used to create the {@link ConnectionServiceFocusManager} instance. */
+    public interface ConnectionServiceFocusManagerFactory {
+        ConnectionServiceFocusManager create(CallsManagerRequester requester, Looper looper);
+    }
 
     /**
      * Interface used by ConnectionServiceFocusManager to communicate with
@@ -190,18 +195,22 @@
 
     private final ConnectionServiceFocusListener mConnectionServiceFocusListener =
             new ConnectionServiceFocusListener() {
-        @Override
-        public void onConnectionServiceReleased(ConnectionServiceFocus connectionServiceFocus) {
-            mEventHandler.obtainMessage(MSG_RELEASE_CONNECTION_FOCUS, connectionServiceFocus)
-                    .sendToTarget();
-        }
+                @Override
+                public void onConnectionServiceReleased(
+                        ConnectionServiceFocus connectionServiceFocus) {
+                    mEventHandler
+                            .obtainMessage(MSG_RELEASE_CONNECTION_FOCUS, connectionServiceFocus)
+                            .sendToTarget();
+                }
 
-        @Override
-        public void onConnectionServiceDeath(ConnectionServiceFocus connectionServiceFocus) {
-            mEventHandler.obtainMessage(MSG_CONNECTION_SERVICE_DEATH, connectionServiceFocus)
-                    .sendToTarget();
-        }
-    };
+                @Override
+                public void onConnectionServiceDeath(
+                        ConnectionServiceFocus connectionServiceFocus) {
+                    mEventHandler
+                            .obtainMessage(MSG_CONNECTION_SERVICE_DEATH, connectionServiceFocus)
+                            .sendToTarget();
+                }
+            };
 
     private ConnectionServiceFocus mCurrentFocus;
     private CallFocus mCurrentFocusCall;
@@ -257,6 +266,7 @@
                 connSvrFocus.connectionServiceFocusGained();
             }
             mCurrentFocus = connSvrFocus;
+            Log.d(this, "updateConnectionServiceFocus connSvr = %s", connSvrFocus);
         }
     }
 
@@ -276,10 +286,13 @@
             for (CallFocus call : calls) {
                 if (call.getState() == PRIORITY_FOCUS_CALL_STATE[i]) {
                     mCurrentFocusCall = call;
+                    Log.d(this, "updateCurrentFocusCall %s", mCurrentFocusCall);
                     return;
                 }
             }
         }
+
+        Log.d(this, "updateCurrentFocusCall = null");
     }
 
     private void onRequestFocusDone(FocusRequest focusRequest) {
@@ -289,6 +302,7 @@
     }
 
     private void handleRequestFocus(FocusRequest focusRequest) {
+        Log.d(this, "handleRequestFocus req = %s", focusRequest);
         if (mCurrentFocus == null
                 || mCurrentFocus.equals(focusRequest.call.getConnectionServiceWrapper())) {
             updateConnectionServiceFocus(focusRequest.call.getConnectionServiceWrapper());
@@ -304,6 +318,7 @@
     }
 
     private void handleReleasedFocus(ConnectionServiceFocus connectionServiceFocus) {
+        Log.d(this, "handleReleasedFocus connSvr = %s", connectionServiceFocus);
         // The ConnectionService can call onConnectionServiceFocusReleased even if it's not the
         // current focus connection service, nothing will be changed in this case.
         if (Objects.equals(mCurrentFocus, connectionServiceFocus)) {
@@ -322,6 +337,7 @@
     }
 
     private void handleReleasedFocusTimeout(FocusRequest focusRequest) {
+        Log.d(this, "handleReleasedFocusTimeout req = %s", focusRequest);
         mCallsManagerRequester.releaseConnectionService(mCurrentFocus);
         updateConnectionServiceFocus(focusRequest.call.getConnectionServiceWrapper());
         updateCurrentFocusCall();
@@ -330,6 +346,7 @@
     }
 
     private void handleConnectionServiceDeath(ConnectionServiceFocus connectionServiceFocus) {
+        Log.d(this, "handleConnectionServiceDeath %s", connectionServiceFocus);
         if (Objects.equals(connectionServiceFocus, mCurrentFocus)) {
             updateConnectionServiceFocus(null);
             updateCurrentFocusCall();
@@ -337,6 +354,7 @@
     }
 
     private void handleAddedCall(CallFocus call) {
+        Log.d(this, "handleAddedCall %s", call);
         if (!mCalls.contains(call)) {
             mCalls.add(call);
         }
@@ -346,6 +364,7 @@
     }
 
     private void handleRemovedCall(CallFocus call) {
+        Log.d(this, "handleRemovedCall %s", call);
         mCalls.remove(call);
         if (call.equals(mCurrentFocusCall)) {
             updateCurrentFocusCall();
@@ -353,6 +372,11 @@
     }
 
     private void handleCallStateChanged(CallFocus call, int oldState, int newState) {
+        Log.d(this,
+                "handleCallStateChanged %s, oldState = %d, newState = %d",
+                call,
+                oldState,
+                newState);
         if (mCalls.contains(call)
                 && Objects.equals(mCurrentFocus, call.getConnectionServiceWrapper())) {
             updateCurrentFocusCall();
@@ -401,4 +425,4 @@
             this.callback = callback;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index dce085c..2ebbb54 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -859,8 +859,17 @@
         @Override
         public void onConnectionServiceFocusReleased(Session.Info sessionInfo)
                 throws RemoteException {
-            // TODO(mpq): This method is added to avoid the compiled error. Add the real
-            // implementation once ag/3273964 done.
+            Log.startSession(sessionInfo, "CSW.oCSFR");
+            long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    mConnSvrFocusListener.onConnectionServiceReleased(
+                            ConnectionServiceWrapper.this);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+                Log.endSession();
+            }
         }
     }
 
@@ -1421,12 +1430,41 @@
         // Immediately response to the Telecom that it has released the call resources.
         // TODO(mpq): Change back to the default implementation once b/69651192 done.
         if (mConnSvrFocusListener != null) {
-            mConnSvrFocusListener.onConnectionServiceReleased(this);
+            mConnSvrFocusListener.onConnectionServiceReleased(ConnectionServiceWrapper.this);
         }
+        BindCallback callback = new BindCallback() {
+            @Override
+            public void onSuccess() {
+                try {
+                    mServiceInterface.connectionServiceFocusLost(Log.getExternalSession());
+                } catch (RemoteException ignored) {
+                    Log.d(this, "failed to inform the focus lost event");
+                }
+            }
+
+            @Override
+            public void onFailure() {}
+        };
+        mBinder.bind(callback, null /* null call */);
     }
 
     @Override
-    public void connectionServiceFocusGained() {}
+    public void connectionServiceFocusGained() {
+        BindCallback callback = new BindCallback() {
+            @Override
+            public void onSuccess() {
+                try {
+                    mServiceInterface.connectionServiceFocusGained(Log.getExternalSession());
+                } catch (RemoteException ignored) {
+                    Log.d(this, "failed to inform the focus gained event");
+                }
+            }
+
+            @Override
+            public void onFailure() {}
+        };
+        mBinder.bind(callback, null /* null call */);
+    }
 
     @Override
     public void setConnectionServiceFocusListener(
diff --git a/src/com/android/server/telecom/CreateConnectionProcessor.java b/src/com/android/server/telecom/CreateConnectionProcessor.java
index be9e72f..cc77824 100644
--- a/src/com/android/server/telecom/CreateConnectionProcessor.java
+++ b/src/com/android/server/telecom/CreateConnectionProcessor.java
@@ -214,7 +214,19 @@
                 mCall.setConnectionService(mService);
                 setTimeoutIfNeeded(mService, attempt);
 
-                mService.createConnection(mCall, this);
+                // Start to create the connection after the ConnectionService of the call has gained
+                // the focus.
+                mCall.getConnectionServiceFocusManager().requestFocus(
+                        mCall,
+                        new CallsManager.RequestCallback(new CallsManager.PendingAction() {
+                            @Override
+                            public void performAction() {
+                                Log.d(this, "perform create connection");
+                                mService.createConnection(
+                                        mCall,
+                                        CreateConnectionProcessor.this);
+                            }
+                        }));
             }
         } else {
             Log.v(this, "attemptNextPhoneAccount, no more accounts, failing");
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index e74c25c..18b24f2 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -185,6 +185,8 @@
             AudioServiceFactory audioServiceFactory,
             BluetoothPhoneServiceImplFactory
                     bluetoothPhoneServiceImplFactory,
+            ConnectionServiceFocusManager.ConnectionServiceFocusManagerFactory
+                    connectionServiceFocusManagerFactory,
             Timeouts.Adapter timeoutsAdapter,
             AsyncRingtonePlayer asyncRingtonePlayer,
             PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
@@ -257,6 +259,7 @@
                 headsetMediaButtonFactory,
                 proximitySensorManagerFactory,
                 inCallWakeLockControllerFactory,
+                connectionServiceFocusManagerFactory,
                 audioServiceFactory,
                 bluetoothRouteManager,
                 wiredHeadsetManager,
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index 19dd404..a1c70fa 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -23,6 +23,7 @@
 import android.media.IAudioService;
 import android.media.ToneGenerator;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.PowerManager;
 import android.os.ServiceManager;
 import android.os.SystemClock;
@@ -35,6 +36,7 @@
 import com.android.server.telecom.CallerInfoAsyncQueryFactory;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.ClockProxy;
+import com.android.server.telecom.ConnectionServiceFocusManager;
 import com.android.server.telecom.DefaultDialerCache;
 import com.android.server.telecom.HeadsetMediaButton;
 import com.android.server.telecom.HeadsetMediaButtonFactory;
@@ -97,7 +99,9 @@
                             },
                             new CallerInfoAsyncQueryFactory() {
                                 @Override
-                                public CallerInfoAsyncQuery startQuery(int token, Context context,
+                                public CallerInfoAsyncQuery startQuery(
+                                        int token,
+                                        Context context,
                                         String number,
                                         CallerInfoAsyncQuery.OnQueryCompleteListener listener,
                                         Object cookie) {
@@ -133,7 +137,7 @@
                             new InCallWakeLockControllerFactory() {
                                 @Override
                                 public InCallWakeLockController create(Context context,
-                                        CallsManager callsManager) {
+                                                                       CallsManager callsManager) {
                                     return new InCallWakeLockController(
                                             new TelecomWakeLock(context,
                                                     PowerManager.FULL_WAKE_LOCK,
@@ -159,6 +163,15 @@
                                             phoneAccountRegistrar);
                                 }
                             },
+                            new ConnectionServiceFocusManager
+                                    .ConnectionServiceFocusManagerFactory() {
+                                @Override
+                                public ConnectionServiceFocusManager create(
+                                        ConnectionServiceFocusManager.CallsManagerRequester requester,
+                                        Looper looper) {
+                                    return new ConnectionServiceFocusManager(requester, looper);
+                                }
+                            },
                             new Timeouts.Adapter(),
                             new AsyncRingtonePlayer(),
                             new PhoneNumberUtilsAdapterImpl(),
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index bf9decb..818e575 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -23,18 +23,27 @@
 import static org.mockito.ArgumentMatchers.anyChar;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.ComponentName;
+import android.content.Context;
 import android.net.Uri;
 import android.os.SystemClock;
+import android.telecom.Connection;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
+import android.telecom.VideoProfile;
 import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.MediumTest;
 
+import android.test.suitebuilder.annotation.SmallTest;
 import com.android.server.telecom.AsyncRingtonePlayer;
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallAudioManager;
@@ -42,7 +51,8 @@
 import com.android.server.telecom.CallerInfoAsyncQueryFactory;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.ClockProxy;
-import com.android.server.telecom.ConnectionServiceWrapper;
+import com.android.server.telecom.ConnectionServiceFocusManager;
+import com.android.server.telecom.ConnectionServiceFocusManager.ConnectionServiceFocusManagerFactory;
 import com.android.server.telecom.ContactsAsyncHelper;
 import com.android.server.telecom.DefaultDialerCache;
 import com.android.server.telecom.EmergencyCallHelper;
@@ -68,6 +78,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Matchers;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
@@ -113,6 +125,7 @@
     @Mock private ProximitySensorManager mProximitySensorManager;
     @Mock private ProximitySensorManagerFactory mProximitySensorManagerFactory;
     @Mock private InCallWakeLockController mInCallWakeLockController;
+    @Mock private ConnectionServiceFocusManagerFactory mConnSvrFocusManagerFactory;
     @Mock private InCallWakeLockControllerFactory mInCallWakeLockControllerFactory;
     @Mock private CallAudioManager.AudioServiceFactory mAudioServiceFactory;
     @Mock private BluetoothRouteManager mBluetoothRouteManager;
@@ -127,6 +140,7 @@
     @Mock private ClockProxy mClockProxy;
     @Mock private InCallControllerFactory mInCallControllerFactory;
     @Mock private InCallController mInCallController;
+    @Mock private ConnectionServiceFocusManager mConnectionSvrFocusMgr;
     private CallsManager mCallsManager;
 
     @Override
@@ -144,6 +158,7 @@
                 any())).thenReturn(mInCallController);
         when(mClockProxy.currentTimeMillis()).thenReturn(System.currentTimeMillis());
         when(mClockProxy.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime());
+        when(mConnSvrFocusManagerFactory.create(any(), any())).thenReturn(mConnectionSvrFocusMgr);
         mCallsManager = new CallsManager(
                 mComponentContextFixture.getTestDouble().getApplicationContext(),
                 mLock,
@@ -154,6 +169,7 @@
                 mHeadsetMediaButtonFactory,
                 mProximitySensorManagerFactory,
                 mInCallWakeLockControllerFactory,
+                mConnSvrFocusManagerFactory,
                 mAudioServiceFactory,
                 mBluetoothRouteManager,
                 mWiredHeadsetManager,
@@ -395,6 +411,106 @@
         verify(callSpy).stopDtmfTone();
     }
 
+    @SmallTest
+    @Test
+    public void testUnholdCallWhenOngoingCallCanBeHeld() {
+        // GIVEN a CallsManager with ongoing call, and this call can be held
+        Call ongoingCall = addSpyCall();
+        doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
+        when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
+
+        // and a held call
+        Call heldCall = addSpyCall();
+
+        // WHEN unhold the held call
+        mCallsManager.unholdCall(heldCall);
+
+        // THEN the ongoing call is held, and the focus request for incoming call is sent
+        verify(ongoingCall).hold();
+        verifyFocusRequestAndExecuteCallback(heldCall);
+
+        // and held call is unhold now
+        verify(heldCall).unhold();
+    }
+
+    @SmallTest
+    @Test
+    public void testUnholdCallWhenOngoingCallCanNotBeHeld() {
+        // GIVEN a CallsManager with ongoing call, and this call can not be held
+        Call ongoingCall = addSpyCall();
+        doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
+        when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
+
+        // and a held call
+        Call heldCall = addSpyCall();
+
+        // WHEN unhold the held call
+        mCallsManager.unholdCall(heldCall);
+
+        // THEN the ongoing call is disconnected, and the focus request for incoming call is sent
+        verify(ongoingCall).disconnect();
+        verifyFocusRequestAndExecuteCallback(heldCall);
+
+        // and held call is unhold now
+        verify(heldCall).unhold();
+    }
+
+    @SmallTest
+    @Test
+    public void testAnswerCallWhenOngoingCallCanBeHeld() {
+        // GIVEN a CallsManager with ongoing call, and this call can be held
+        Call ongoingCall = addSpyCall();
+        doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
+        when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
+
+        // WHEN answer an incoming call
+        Call incomingCall = addSpyCall();
+        mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);
+
+        // THEN the ongoing call is held and the focus request for incoming call is sent
+        verify(ongoingCall).hold();
+        verifyFocusRequestAndExecuteCallback(incomingCall);
+
+        // and the incoming call is answered.
+        verify(incomingCall).answer(VideoProfile.STATE_AUDIO_ONLY);
+    }
+
+    @SmallTest
+    @Test
+    public void testAnswerCallWhenOngoingCallCanNotBeHeld() {
+        // GIVEN a CallsManager with ongoing call, and this call can not be held
+        Call ongoingCall = addSpyCall();
+        doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
+        when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
+
+        // WHEN answer an incoming call
+        Call incomingCall = addSpyCall();
+        mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);
+
+        // THEN the ongoing call is disconnected and the focus request for incoming call is sent
+        verify(ongoingCall).disconnect();
+        verifyFocusRequestAndExecuteCallback(incomingCall);
+
+        // and the incoming call is answered.
+        verify(incomingCall).answer(VideoProfile.STATE_AUDIO_ONLY);
+    }
+
+    @SmallTest
+    @Test
+    public void testAnswerCallWhenNoOngoingCallExisted() {
+        // GIVEN a CallsManager with no ongoing call.
+
+        // WHEN answer an incoming call
+        Call incomingCall = addSpyCall();
+        mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);
+
+        // THEN the focus request for incoming call is sent
+        verifyFocusRequestAndExecuteCallback(incomingCall);
+
+        // and the incoming call is answered.
+        verify(incomingCall).answer(VideoProfile.STATE_AUDIO_ONLY);
+    }
+
     private Call addSpyCall() {
         Call ongoingCall = new Call("1", /* callId */
                 mComponentContextFixture.getTestDouble(),
@@ -414,10 +530,26 @@
                 mClockProxy);
         ongoingCall.setState(CallState.ACTIVE, "just cuz");
         Call callSpy = Mockito.spy(ongoingCall);
+
+        // Mocks some methods to not call the real method.
+        doNothing().when(callSpy).unhold();
+        doNothing().when(callSpy).hold();
+        doNothing().when(callSpy).disconnect();
+        doNothing().when(callSpy).answer(Matchers.anyInt());
+        doNothing().when(callSpy).setStartWithSpeakerphoneOn(Matchers.anyBoolean());
+
         mCallsManager.addCall(callSpy);
         return callSpy;
     }
 
+    private void verifyFocusRequestAndExecuteCallback(Call call) {
+        ArgumentCaptor<CallsManager.RequestCallback> captor =
+                ArgumentCaptor.forClass(CallsManager.RequestCallback.class);
+        verify(mConnectionSvrFocusMgr).requestFocus(eq(call), captor.capture());
+        CallsManager.RequestCallback callback = captor.getValue();
+        callback.onRequestFocusDone(call);
+    }
+
     private void setupMsimAccounts() {
         TelephonyManager mockTelephonyManager = mComponentContextFixture.getTelephonyManager();
         when(mockTelephonyManager.getMultiSimConfiguration()).thenReturn(
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index f369049..b26143d 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -72,6 +72,7 @@
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.CallsManagerListenerBase;
 import com.android.server.telecom.ClockProxy;
+import com.android.server.telecom.ConnectionServiceFocusManager;
 import com.android.server.telecom.DefaultDialerCache;
 import com.android.server.telecom.HeadsetMediaButton;
 import com.android.server.telecom.HeadsetMediaButtonFactory;
@@ -446,6 +447,14 @@
                         return mBluetoothPhoneServiceImpl;
                     }
                 },
+                new ConnectionServiceFocusManager.ConnectionServiceFocusManagerFactory() {
+                    @Override
+                    public ConnectionServiceFocusManager create(
+                            ConnectionServiceFocusManager.CallsManagerRequester requester,
+                            Looper looper) {
+                        return new ConnectionServiceFocusManager(requester, looper);
+                    }
+                },
                 mTimeoutsAdapter,
                 mAsyncRingtonePlayer,
                 mPhoneNumberUtilsAdapter,