Merge "Integrate connectionServiceFocusManager into call flow"
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,