Implement Call handover initiation side APIs.
Bug: 65415068
Test: Manual
Design doc:
https://docs.google.com/document/d/1qY3oAzjff_4A1ttYb_CGrE_OwTRmXMG_KGsIuPT1ey8/edit#
Change-Id: I9f67f63c2ed0d20dfd4a96c8c33f5e2d8ed11406
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 35ba93a..c516274 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -134,7 +134,8 @@
void onRttInitiationFailure(Call call, int reason);
void onRemoteRttRequest(Call call, int requestId);
void onHandoverRequested(Call call, PhoneAccountHandle handoverTo, int videoState,
- Bundle extras);
+ Bundle extras, boolean isLegacy);
+ void onHandoverFailed(Call call, int error);
}
public abstract static class ListenerBase implements Listener {
@@ -208,7 +209,9 @@
public void onRemoteRttRequest(Call call, int requestId) {}
@Override
public void onHandoverRequested(Call call, PhoneAccountHandle handoverTo, int videoState,
- Bundle extras) {}
+ Bundle extras, boolean isLegacy) {}
+ @Override
+ public void onHandoverFailed(Call call, int error) {}
}
private final CallerInfoLookupHelper.OnQueryCompleteListener mCallerInfoQueryListener =
@@ -482,13 +485,13 @@
/**
* When a call handover has been initiated via {@link #requestHandover(PhoneAccountHandle,
- * int, Bundle)}, contains the call which this call is being handed over to.
+ * int, Bundle, boolean)}, contains the call which this call is being handed over to.
*/
private Call mHandoverDestinationCall = null;
/**
* When a call handover has been initiated via {@link #requestHandover(PhoneAccountHandle,
- * int, Bundle)}, contains the call which this call is being handed over from.
+ * int, Bundle, boolean)}, contains the call which this call is being handed over from.
*/
private Call mHandoverSourceCall = null;
@@ -2048,7 +2051,7 @@
if (handoverExtras instanceof Bundle) {
handoverExtrasBundle = (Bundle) handoverExtras;
}
- requestHandover(phoneAccountHandle, videoState, handoverExtrasBundle);
+ requestHandover(phoneAccountHandle, videoState, handoverExtrasBundle, true);
} else {
Log.addEvent(this, LogUtils.Events.CALL_EVENT, event);
mConnectionService.sendCallEvent(this, event, extras);
@@ -2067,7 +2070,7 @@
* @param extras Extra information to be passed to ConnectionService
*/
public void handoverTo(PhoneAccountHandle destAcct, int videoState, Bundle extras) {
- // TODO: Call requestHandover(destAcct, videoState, extras);
+ requestHandover(destAcct, videoState, extras, false);
}
/**
@@ -2739,6 +2742,12 @@
}
}
+ public void onHandoverFailed(int handoverError) {
+ for (Listener l : mListeners) {
+ l.onHandoverFailed(this, handoverError);
+ }
+ }
+
public void setOriginalConnectionId(String originalConnectionId) {
mOriginalConnectionId = originalConnectionId;
}
@@ -2788,9 +2797,9 @@
* {@link android.telecom.InCallService}.
*/
private void requestHandover(PhoneAccountHandle handoverToHandle, int videoState,
- Bundle extras) {
+ Bundle extras, boolean isLegacy) {
for (Listener l : mListeners) {
- l.onHandoverRequested(this, handoverToHandle, videoState, extras);
+ l.onHandoverRequested(this, handoverToHandle, videoState, extras, isLegacy);
}
}
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 34f299d..cfa8138 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -729,8 +729,12 @@
*/
@Override
public void onHandoverRequested(Call call, PhoneAccountHandle handoverTo, int videoState,
- Bundle extras) {
- requestHandover(call, handoverTo, videoState, extras);
+ Bundle extras, boolean isLegacy) {
+ if (isLegacy) {
+ requestHandoverViaEvents(call, handoverTo, videoState, extras);
+ } else {
+ requestHandover(call, handoverTo, videoState, extras);
+ }
}
@VisibleForTesting
@@ -3207,8 +3211,9 @@
* @param initiatingExtras Extras associated with the handover, to be passed to the handover
* {@link android.telecom.ConnectionService}.
*/
- private void requestHandover(Call handoverFromCall, PhoneAccountHandle handoverToHandle,
- int videoState, Bundle initiatingExtras) {
+ private void requestHandoverViaEvents(Call handoverFromCall,
+ PhoneAccountHandle handoverToHandle,
+ int videoState, Bundle initiatingExtras) {
boolean isHandoverFromSupported = isHandoverFromPhoneAccountSupported(
handoverFromCall.getTargetPhoneAccount());
@@ -3246,6 +3251,132 @@
}
/**
+ * Called in response to a {@link Call} receiving a {@link Call#handoverTo(PhoneAccountHandle,
+ * int, Bundle)} indicating the {@link android.telecom.InCallService} has requested a
+ * handover to another {@link android.telecom.ConnectionService}.
+ *
+ * We will explicitly disallow a handover when there is an emergency call present.
+ *
+ * @param handoverFromCall The {@link Call} to be handed over.
+ * @param handoverToHandle The {@link PhoneAccountHandle} to hand over the call to.
+ * @param videoState The desired video state of {@link Call} after handover.
+ * @param initiatingExtras Extras associated with the handover, to be passed to the handover
+ * {@link android.telecom.ConnectionService}.
+ */
+ private void requestHandover(Call handoverFromCall, PhoneAccountHandle handoverToHandle,
+ int videoState, Bundle extras) {
+
+ // Send an error back if there are any ongoing emergency calls.
+ if (hasEmergencyCall()) {
+ handoverFromCall.onHandoverFailed(
+ android.telecom.Call.Callback.HANDOVER_FAILURE_ONGOING_EMERG_CALL);
+ return;
+ }
+
+ // If source and destination phone accounts don't support handover, send an error back.
+ boolean isHandoverFromSupported = isHandoverFromPhoneAccountSupported(
+ handoverFromCall.getTargetPhoneAccount());
+ boolean isHandoverToSupported = isHandoverToPhoneAccountSupported(handoverToHandle);
+ if (!isHandoverFromSupported || !isHandoverToSupported) {
+ handoverFromCall.onHandoverFailed(
+ android.telecom.Call.Callback.HANDOVER_FAILURE_DEST_NOT_SUPPORTED);
+ return;
+ }
+
+ Log.addEvent(handoverFromCall, LogUtils.Events.HANDOVER_REQUEST, handoverToHandle);
+
+ // Create a new instance of Call
+ PhoneAccount account =
+ mPhoneAccountRegistrar.getPhoneAccount(handoverToHandle, getCurrentUserHandle());
+ boolean isSelfManaged = account != null && account.isSelfManaged();
+
+ Call call = new Call(getNextCallId(), mContext,
+ this, mLock, mConnectionServiceRepository,
+ mContactsAsyncHelper, mCallerInfoAsyncQueryFactory, mPhoneNumberUtilsAdapter,
+ handoverFromCall.getHandle(), null,
+ null, null,
+ Call.CALL_DIRECTION_OUTGOING, false,
+ false, mClockProxy);
+ call.initAnalytics();
+
+ // Set self-managed and voipAudioMode if destination is self-managed CS
+ call.setIsSelfManaged(isSelfManaged);
+ if (isSelfManaged) {
+ call.setIsVoipAudioMode(true);
+ }
+ call.setInitiatingUser(getCurrentUserHandle());
+
+
+
+ // Ensure we don't try to place an outgoing call with video if video is not
+ // supported.
+ if (VideoProfile.isVideo(videoState) && account != null &&
+ !account.hasCapabilities(PhoneAccount.CAPABILITY_VIDEO_CALLING)) {
+ call.setVideoState(VideoProfile.STATE_AUDIO_ONLY);
+ } else {
+ call.setVideoState(videoState);
+ }
+
+ // Set target phone account to destAcct.
+ call.setTargetPhoneAccount(handoverToHandle);
+
+ if (account != null && account.getExtras() != null && account.getExtras()
+ .getBoolean(PhoneAccount.EXTRA_ALWAYS_USE_VOIP_AUDIO_MODE)) {
+ Log.d(this, "requestHandover: defaulting to voip mode for call %s",
+ call.getId());
+ call.setIsVoipAudioMode(true);
+ }
+
+ // Set call state to connecting
+ call.setState(
+ CallState.CONNECTING,
+ handoverToHandle == null ? "no-handle" : handoverToHandle.toString());
+ setIntentExtrasAndStartTime(call, extras);
+
+ // Add call to call tracker
+ if (!mCalls.contains(call)) {
+ addCall(call);
+ }
+
+ Log.addEvent(handoverFromCall, LogUtils.Events.START_HANDOVER,
+ "handOverFrom=%s, handOverTo=%s", handoverFromCall.getId(), call.getId());
+
+ handoverFromCall.setHandoverDestinationCall(call);
+ handoverFromCall.setHandoverState(HandoverState.HANDOVER_FROM_STARTED);
+ call.setHandoverState(HandoverState.HANDOVER_TO_STARTED);
+ call.setHandoverSourceCall(handoverFromCall);
+ call.setNewOutgoingCallIntentBroadcastIsDone();
+
+ // Auto-enable speakerphone if the originating intent specified to do so, if the call
+ // is a video call, of if using speaker when docked
+ final boolean useSpeakerWhenDocked = mContext.getResources().getBoolean(
+ R.bool.use_speaker_when_docked);
+ final boolean useSpeakerForDock = isSpeakerphoneEnabledForDock();
+ final boolean useSpeakerForVideoCall = isSpeakerphoneAutoEnabledForVideoCalls(videoState);
+ call.setStartWithSpeakerphoneOn(false || useSpeakerForVideoCall
+ || (useSpeakerWhenDocked && useSpeakerForDock));
+ call.setVideoState(videoState);
+
+ final boolean isOutgoingCallPermitted = isOutgoingCallPermitted(call,
+ call.getTargetPhoneAccount());
+
+ // If the account has been set, proceed to place the outgoing call.
+ 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();
+ }
+
+ call.startCreateConnection(mPhoneAccountRegistrar);
+ }
+
+ }
+
+ /**
* Determines if handover from the specified {@link PhoneAccountHandle} is supported.
*
* @param from The {@link PhoneAccountHandle} the handover originates from.
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 58f489c..cf3b6ce 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -677,6 +677,11 @@
}
@Override
+ public void onHandoverFailed(Call call, int error) {
+ notifyHandoverFailed(call, error);
+ }
+
+ @Override
public void onRttInitiationFailure(Call call, int reason) {
notifyRttInitiationFailure(call, reason);
updateCall(call, false, true);
@@ -1020,6 +1025,18 @@
});
}
}
+
+ private void notifyHandoverFailed(Call call, int error) {
+ if (!mInCallServices.isEmpty()) {
+ for (IInCallService inCallService : mInCallServices.values()) {
+ try {
+ inCallService.onHandoverFailed(mCallIdMapper.getCallId(call), error);
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+ }
+
/**
* Unbinds an existing bound connection to the in-call app.
*/
diff --git a/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java b/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
index f851162..b7338c6 100644
--- a/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
@@ -138,6 +138,10 @@
public IInterface queryLocalInterface(String descriptor) {
return this;
}
+
+ @Override
+ public void onHandoverFailed(String callId, int error) {}
+
}
private IInCallService.Stub mInCallServiceFake = new FakeInCallService();