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