IMS: Add support for call deflection feature
am: ad4ebc022b

Change-Id: Ida5bd3ec74128296d993d569e37a4078a9b8d06a
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 420b71d..5a54d93 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -1756,6 +1756,31 @@
     }
 
     /**
+     * Deflects the call if it is ringing.
+     *
+     * @param address address to be deflected to.
+     */
+    @VisibleForTesting
+    public void deflect(Uri address) {
+        // Check to verify that the call is still in the ringing state. A call can change states
+        // between the time the user hits 'deflect' and Telecomm receives the command.
+        if (isRinging("deflect")) {
+            // At this point, we are asking the connection service to deflect but we don't assume
+            // that it will work. Instead, we wait until confirmation from the connection service
+            // that the call is in a non-STATE_RINGING state before changing the UI. See
+            // {@link ConnectionServiceAdapter#setActive} and other set* methods.
+            mVideoStateHistory |= mVideoState;
+            if (mConnectionService != null) {
+                mConnectionService.deflect(this, address);
+            } else {
+                Log.e(this, new NullPointerException(),
+                        "deflect call failed due to null CS callId=%s", getId());
+            }
+            Log.addEvent(this, LogUtils.Events.REQUEST_DEFLECT, Log.pii(address));
+        }
+    }
+
+    /**
      * Rejects the call if it is ringing.
      *
      * @param rejectWithMessage Whether to send a text message as part of the call rejection.
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 82e3787..7ee4ac8 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -1440,6 +1440,20 @@
     }
 
     /**
+     * Instructs Telecom to deflect the specified call. Intended to be invoked by the in-call
+     * app through {@link InCallAdapter} after Telecom notifies it of an incoming call followed by
+     * the user opting to deflect said call.
+     */
+    @VisibleForTesting
+    public void deflectCall(Call call, Uri address) {
+        if (!mCalls.contains(call)) {
+            Log.i(this, "Request to deflect a non-existent call %s", call);
+        } else {
+            call.deflect(address);
+        }
+    }
+
+    /**
      * Determines if the speakerphone should be automatically enabled for the call.  Speakerphone
      * should be enabled if the call is a video call and bluetooth or the wired headset are not in
      * use.
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index 2534bc0..67f3017 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -1229,6 +1229,18 @@
         }
     }
 
+    /** @see IConnectionService#deflect(String, Uri , Session.Info) */
+    void deflect(Call call, Uri address) {
+        final String callId = mCallIdMapper.getCallId(call);
+        if (callId != null && isServiceValid("deflect")) {
+            try {
+                logOutgoing("deflect %s", callId);
+                mServiceInterface.deflect(callId, address, Log.getExternalSession());
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
     /** @see IConnectionService#reject(String, Session.Info) */
     void reject(Call call, boolean rejectWithMessage, String message) {
         final String callId = mCallIdMapper.getCallId(call);
diff --git a/src/com/android/server/telecom/InCallAdapter.java b/src/com/android/server/telecom/InCallAdapter.java
index 7801140..3357d9b 100644
--- a/src/com/android/server/telecom/InCallAdapter.java
+++ b/src/com/android/server/telecom/InCallAdapter.java
@@ -16,6 +16,7 @@
 
 package com.android.server.telecom;
 
+import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
 import android.telecom.Log;
@@ -69,6 +70,29 @@
     }
 
     @Override
+    public void deflectCall(String callId, Uri address) {
+        try {
+            Log.startSession(LogUtils.Sessions.ICA_DEFLECT_CALL, mOwnerComponentName);
+            long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    Log.i(this, "deflectCall - %s, %s ", callId, Log.pii(address));
+                    Call call = mCallIdMapper.getCall(callId);
+                    if (call != null) {
+                        mCallsManager.deflectCall(call, address);
+                    } else {
+                        Log.w(this, "deflectCall, unknown call id: %s", callId);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        } finally {
+            Log.endSession();
+        }
+    }
+
+    @Override
     public void rejectCall(String callId, boolean rejectWithMessage, String textMessage) {
         try {
             Log.startSession(LogUtils.Sessions.ICA_REJECT_CALL, mOwnerComponentName);
diff --git a/src/com/android/server/telecom/LogUtils.java b/src/com/android/server/telecom/LogUtils.java
index 13ffacd..fcf914e 100644
--- a/src/com/android/server/telecom/LogUtils.java
+++ b/src/com/android/server/telecom/LogUtils.java
@@ -33,6 +33,7 @@
 
     public static final class Sessions {
         public static final String ICA_ANSWER_CALL = "ICA.aC";
+        public static final String ICA_DEFLECT_CALL = "ICA.defC";
         public static final String ICA_REJECT_CALL = "ICA.rC";
         public static final String ICA_DISCONNECT_CALL = "ICA.dC";
         public static final String ICA_HOLD_CALL = "ICA.hC";
@@ -71,6 +72,7 @@
         public static final String REQUEST_UNHOLD = "REQUEST_UNHOLD";
         public static final String REQUEST_DISCONNECT = "REQUEST_DISCONNECT";
         public static final String REQUEST_ACCEPT = "REQUEST_ACCEPT";
+        public static final String REQUEST_DEFLECT = "REQUEST_DEFLECT";
         public static final String REQUEST_REJECT = "REQUEST_REJECT";
         public static final String START_DTMF = "START_DTMF";
         public static final String STOP_DTMF = "STOP_DTMF";
diff --git a/src/com/android/server/telecom/ParcelableCallUtils.java b/src/com/android/server/telecom/ParcelableCallUtils.java
index 527e8e6..3c0e756 100644
--- a/src/com/android/server/telecom/ParcelableCallUtils.java
+++ b/src/com/android/server/telecom/ParcelableCallUtils.java
@@ -301,7 +301,10 @@
         android.telecom.Call.Details.CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO,
 
         Connection.CAPABILITY_CAN_PULL_CALL,
-        android.telecom.Call.Details.CAPABILITY_CAN_PULL_CALL
+        android.telecom.Call.Details.CAPABILITY_CAN_PULL_CALL,
+
+        Connection.CAPABILITY_SUPPORT_DEFLECT,
+        android.telecom.Call.Details.CAPABILITY_SUPPORT_DEFLECT
     };
 
     private static int convertConnectionToCallCapabilities(int connectionCapabilities) {
diff --git a/tests/src/com/android/server/telecom/tests/BasicCallTests.java b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
index bdb9157..64abd14 100644
--- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -1026,4 +1026,46 @@
         assertEquals(CallAudioState.ROUTE_EARPIECE,
                 mInCallServiceFixtureX.mCallAudioState.getRoute());
     }
+
+    /**
+     * Tests the {@link Call#deflect} API.  Verifies that if a call is incoming,
+     * and deflect API is called, then request is made to the connection service.
+     *
+     * @throws Exception
+     */
+    @LargeTest
+    @Test
+    public void testDeflectCallWhenIncoming() throws Exception {
+        Uri deflectAddress = Uri.parse("tel:650-555-1214");
+        IdPair ids = startIncomingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
+                mConnectionServiceFixtureA);
+
+        assertEquals(Call.STATE_RINGING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
+        assertEquals(Call.STATE_RINGING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
+        // Attempt to deflect the call and verify the API call makes it through
+        mInCallServiceFixtureX.mInCallAdapter.deflectCall(ids.mCallId, deflectAddress);
+        verify(mConnectionServiceFixtureA.getTestDouble(), timeout(TEST_TIMEOUT))
+                .deflect(eq(ids.mConnectionId), eq(deflectAddress), any());
+        mInCallServiceFixtureX.mInCallAdapter.disconnectCall(ids.mCallId);
+    }
+
+    /**
+     * Tests the {@link Call#deflect} API.  Verifies that if a call is outgoing,
+     * and deflect API is called, then request is not made to the connection service.
+     * Ideally, deflect option should be displayed only if call is incoming/waiting.
+     *
+     * @throws Exception
+     */
+    @LargeTest
+    @Test
+    public void testDeflectCallWhenOutgoing() throws Exception {
+        Uri deflectAddress = Uri.parse("tel:650-555-1214");
+        IdPair ids = startOutgoingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
+                mConnectionServiceFixtureA, Process.myUserHandle());
+        // Attempt to deflect the call and verify the API call does not make it through
+        mInCallServiceFixtureX.mInCallAdapter.deflectCall(ids.mCallId, deflectAddress);
+        verify(mConnectionServiceFixtureA.getTestDouble(), never())
+                .deflect(eq(ids.mConnectionId), eq(deflectAddress), any());
+        mInCallServiceFixtureX.mInCallAdapter.disconnectCall(ids.mCallId);
+    }
 }
diff --git a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
index e0f3cbd..3154b7d 100644
--- a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
@@ -276,6 +276,10 @@
         public void answer(String callId, Session.Info info) throws RemoteException { }
 
         @Override
+        public void deflect(String callId, Uri address, Session.Info info)
+                throws RemoteException { }
+
+        @Override
         public void reject(String callId, Session.Info info) throws RemoteException {
             rejectedCallIds.add(callId);
         }