Optionally allow to end on-going call via call button
Add a configuration flag to enable usage of physical call button to end
an on-going call.
Bug: 172907394
Test: atest CarServiceUnitTest
manual verification
Merged-In: I498e45f87d1a43e9469cbdf1ccefaecba03e9afa
Change-Id: I7f50df0538161933622181b09567483707e72cee
(cherry picked from commit de539cd4dfc65fdbc2d87859aff520c90bdd20b2)
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index d40dea9..c47330d 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -360,4 +360,7 @@
<!-- The ComponentName of the media source that will be selected as the default -->
<string name="config_defaultMediaSource">com.android.bluetooth/com.android.bluetooth.avrcpcontroller.BluetoothMediaBrowserService</string>
+
+ <!-- A configuration flag to enable ending an ongoing call using the physical Call button. -->
+ <bool name="config_callButtonEndsOngoingCall">false</bool>
</resources>
diff --git a/service/src/com/android/car/CarInputService.java b/service/src/com/android/car/CarInputService.java
index 222e760..4a7b394 100644
--- a/service/src/com/android/car/CarInputService.java
+++ b/service/src/com/android/car/CarInputService.java
@@ -72,6 +72,7 @@
import java.util.BitSet;
import java.util.Collections;
import java.util.List;
+import java.util.function.BooleanSupplier;
import java.util.function.IntSupplier;
import java.util.function.Supplier;
@@ -183,6 +184,8 @@
// ComponentName of the RotaryService.
private final String mRotaryServiceComponentName;
+ private final BooleanSupplier mShouldCallButtonEndOngoingCallSupplier;
+
private final Object mLock = new Object();
@GuardedBy("mLock")
@@ -308,7 +311,8 @@
.injectInputEvent(event, INJECT_INPUT_EVENT_MODE_ASYNC),
() -> Calls.getLastOutgoingCall(context),
getDefaultInputComponent(context),
- () -> getViewLongPressDelay(context.getContentResolver()));
+ () -> getViewLongPressDelay(context.getContentResolver()),
+ () -> context.getResources().getBoolean(R.bool.config_callButtonEndsOngoingCall));
}
@VisibleForTesting
@@ -316,7 +320,8 @@
Handler handler, TelecomManager telecomManager, AssistUtils assistUtils,
KeyEventListener mainDisplayHandler, Supplier<String> lastCalledNumberSupplier,
@Nullable ComponentName customInputServiceComponent,
- IntSupplier longPressDelaySupplier) {
+ IntSupplier longPressDelaySupplier,
+ BooleanSupplier shouldCallButtonEndOngoingCallSupplier) {
mContext = context;
mCaptureController = new InputCaptureClientController(context);
mInputHalService = inputHalService;
@@ -335,6 +340,7 @@
new KeyPressTimer(handler, longPressDelaySupplier, this::handleCallLongPress);
mRotaryServiceComponentName = mContext.getString(R.string.rotaryService);
+ mShouldCallButtonEndOngoingCallSupplier = shouldCallButtonEndOngoingCallSupplier;
}
@VisibleForTesting
@@ -580,6 +586,11 @@
return;
}
+ if (mShouldCallButtonEndOngoingCallSupplier.getAsBoolean() && endCall()) {
+ // On-going call ended, nothing more to do.
+ return;
+ }
+
if (dispatchProjectionKeyEvent(
CarProjectionManager.KEY_EVENT_CALL_SHORT_PRESS_KEY_UP)) {
return;
@@ -595,6 +606,10 @@
return;
}
+ if (mShouldCallButtonEndOngoingCallSupplier.getAsBoolean() && endCall()) {
+ return;
+ }
+
if (dispatchProjectionKeyEvent(CarProjectionManager.KEY_EVENT_CALL_LONG_PRESS_KEY_DOWN)) {
return;
}
@@ -643,6 +658,15 @@
return false;
}
+ private boolean endCall() {
+ if (mTelecomManager != null && mTelecomManager.isInCall()) {
+ Log.i(CarLog.TAG_INPUT, "End the call!");
+ mTelecomManager.endCall();
+ return true;
+ }
+ return false;
+ }
+
private boolean isBluetoothVoiceRecognitionEnabled() {
Resources res = mContext.getResources();
return res.getBoolean(R.bool.enableLongPressBluetoothVoiceRecognition);
@@ -711,6 +735,8 @@
writer.println("mCarInputListener: " + mCarInputListener);
}
writer.println("Long-press delay: " + mLongPressDelaySupplier.getAsInt() + "ms");
+ writer.println("Call button ends ongoing call: "
+ + mShouldCallButtonEndOngoingCallSupplier.getAsBoolean());
mCaptureController.dump(writer);
}
diff --git a/tests/carservice_unit_test/src/com/android/car/CarInputServiceTest.java b/tests/carservice_unit_test/src/com/android/car/CarInputServiceTest.java
index cabab88..b038cb6 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarInputServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarInputServiceTest.java
@@ -85,6 +85,7 @@
import org.mockito.junit.MockitoJUnitRunner;
import java.util.BitSet;
+import java.util.function.BooleanSupplier;
import java.util.function.IntSupplier;
import java.util.function.Supplier;
@@ -100,6 +101,7 @@
@Mock CarInputService.KeyEventListener mDefaultMainListener;
@Mock Supplier<String> mLastCallSupplier;
@Mock IntSupplier mLongPressDelaySupplier;
+ @Mock BooleanSupplier mShouldCallButtonEndOngoingCallSupplier;
@Spy Context mContext = ApplicationProvider.getApplicationContext();
@Spy Handler mHandler = new Handler(Looper.getMainLooper());
@@ -152,13 +154,16 @@
mCarUserService = mock(CarUserService.class);
mCarInputService = new CarInputService(mContext, mInputHalService, mCarUserService,
mHandler, mTelecomManager, mAssistUtils, mDefaultMainListener, mLastCallSupplier,
- /* customInputServiceComponent= */ null, mLongPressDelaySupplier);
+ /* customInputServiceComponent= */ null, mLongPressDelaySupplier,
+ mShouldCallButtonEndOngoingCallSupplier);
when(mInputHalService.isKeyInputSupported()).thenReturn(true);
mCarInputService.init();
// Delay Handler callbacks until flushHandler() is called.
doReturn(true).when(mHandler).sendMessageAtTime(any(), anyLong());
+
+ when(mShouldCallButtonEndOngoingCallSupplier.getAsBoolean()).thenReturn(false);
}
@Test
@@ -446,6 +451,27 @@
}
@Test
+ public void callKey_shortPress_duringCall_endCallViaCallButtonOn_endsCall() {
+ when(mShouldCallButtonEndOngoingCallSupplier.getAsBoolean()).thenReturn(true);
+ when(mTelecomManager.isInCall()).thenReturn(true);
+
+ send(Key.DOWN, KeyEvent.KEYCODE_CALL, Display.MAIN);
+ send(Key.UP, KeyEvent.KEYCODE_CALL, Display.MAIN);
+
+ verify(mTelecomManager).endCall();
+ }
+
+ @Test
+ public void callKey_shortPress_duringCall_endCallViaCallButtonOff_doesNotEndCall() {
+ when(mTelecomManager.isInCall()).thenReturn(true);
+
+ send(Key.DOWN, KeyEvent.KEYCODE_CALL, Display.MAIN);
+ send(Key.UP, KeyEvent.KEYCODE_CALL, Display.MAIN);
+
+ verify(mTelecomManager, never()).endCall();
+ }
+
+ @Test
public void callKey_longPress_withoutEventHandler_redialsLastCall() {
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -524,6 +550,27 @@
}
@Test
+ public void callKey_longPress_duringCall_endCallViaCallButtonOn_endsCall() {
+ when(mShouldCallButtonEndOngoingCallSupplier.getAsBoolean()).thenReturn(true);
+ when(mTelecomManager.isInCall()).thenReturn(true);
+
+ send(Key.DOWN, KeyEvent.KEYCODE_CALL, Display.MAIN);
+ flushHandler();
+
+ verify(mTelecomManager).endCall();
+ }
+
+ @Test
+ public void callKey_longPress_duringCall_endCallViaCallButtonOff_doesNotEndCall() {
+ when(mTelecomManager.isInCall()).thenReturn(true);
+
+ send(Key.DOWN, KeyEvent.KEYCODE_CALL, Display.MAIN);
+ flushHandler();
+
+ verify(mTelecomManager, never()).endCall();
+ }
+
+ @Test
public void callKey_keyDown_withEventHandler_triggersEventHandler() {
CarProjectionManager.ProjectionKeyEventHandler eventHandler =
registerProjectionKeyEventHandler(
@@ -608,7 +655,8 @@
mCarInputService = new CarInputService(mMockContext, mInputHalService, mCarUserService,
mHandler, mTelecomManager, mAssistUtils, mDefaultMainListener, mLastCallSupplier,
- /* customInputServiceComponent= */ null, mLongPressDelaySupplier);
+ /* customInputServiceComponent= */ null, mLongPressDelaySupplier,
+ mShouldCallButtonEndOngoingCallSupplier);
mCarInputService.init();
}