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();