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