Fix issue that the disconnect tone doesn't sound from BTHS

When the call is disconnected, the BTHF state is changed to IDLE and
SCO connection is disconnected before playing disconnect tone. So the
disconnect tone can't be sound from BTHS.

Add DISCONNECTED state for HFP and keep the state until disconnect tone
sounds from BT.

Test: manual - Checked whether the disconnect tone is sound from the
BTHS
Test: auto - Passed BluetoothPhoneServiceTest
Bug: 69645337

Change-Id: I6deca4ccee97b8b1a50c491810d2b75f6741c0e9
diff --git a/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
index 93d4f71..d0fcfa1 100644
--- a/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
+++ b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
@@ -66,6 +66,7 @@
     private static final int CALL_STATE_INCOMING = 4;
     private static final int CALL_STATE_WAITING = 5;
     private static final int CALL_STATE_IDLE = 6;
+    private static final int CALL_STATE_DISCONNECTED = 7;
 
     // match up with bthf_call_state_t of bt_hf.h
     // Terminate all held or set UDUB("busy") to a waiting call
@@ -84,6 +85,7 @@
     private String mRingingAddress = null;
     private int mRingingAddressType = 0;
     private Call mOldHeldCall = null;
+    private boolean mIsDisconnectedTonePlaying = false;
 
     /**
      * Binder implementation of IBluetoothHeadsetPhone. Implements the command interface that the
@@ -387,6 +389,12 @@
             }
             updateHeadsetWithCallState(false /* force */);
         }
+
+        @Override
+        public void onDisconnectedTonePlaying(boolean isTonePlaying) {
+            mIsDisconnectedTonePlaying = isTonePlaying;
+            updateHeadsetWithCallState(false /* force */);
+        }
     };
 
     /**
@@ -816,6 +824,7 @@
         CallsManager callsManager = mCallsManager;
         Call ringingCall = mCallsManager.getRingingCall();
         Call dialingCall = mCallsManager.getOutgoingCall();
+        boolean hasOnlyDisconnectedCalls = mCallsManager.hasOnlyDisconnectedCalls();
 
         //
         // !! WARNING !!
@@ -831,6 +840,9 @@
             bluetoothCallState = CALL_STATE_INCOMING;
         } else if (dialingCall != null) {
             bluetoothCallState = CALL_STATE_ALERTING;
+        } else if (hasOnlyDisconnectedCalls || mIsDisconnectedTonePlaying) {
+            // Keep the DISCONNECTED state until the disconnect tone's playback is done
+            bluetoothCallState = CALL_STATE_DISCONNECTED;
         }
         return bluetoothCallState;
     }
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index bf1ef8f..ab06209 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -56,6 +56,7 @@
 
     private Call mForegroundCall;
     private boolean mIsTonePlaying = false;
+    private boolean mIsDisconnectedTonePlaying = false;
     private InCallTonePlayer mHoldTonePlayer;
 
     public CallAudioManager(CallAudioRouteStateMachine callAudioRouteStateMachine,
@@ -519,6 +520,11 @@
                 isTonePlaying ? CallAudioModeStateMachine.TONE_STARTED_PLAYING
                         : CallAudioModeStateMachine.TONE_STOPPED_PLAYING,
                 makeArgsForModeStateMachine());
+
+        if (!isTonePlaying && mIsDisconnectedTonePlaying) {
+            mCallsManager.onDisconnectedTonePlaying(false);
+            mIsDisconnectedTonePlaying = false;
+        }
     }
 
     private void onCallLeavingState(Call call, int state) {
@@ -699,6 +705,8 @@
 
             if (toneToPlay != InCallTonePlayer.TONE_INVALID) {
                 mPlayerFactory.createPlayer(toneToPlay).startTone();
+                mCallsManager.onDisconnectedTonePlaying(true);
+                mIsDisconnectedTonePlaying = true;
             }
         }
     }
@@ -792,4 +800,4 @@
     public SparseArray<LinkedHashSet<Call>> getCallStateToCalls() {
         return mCallStateToCalls;
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index f9cad9f..35abca1 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -119,6 +119,7 @@
         void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile);
         void onHoldToneRequested(Call call);
         void onExternalCallChanged(Call call, boolean isExternalCall);
+        void onDisconnectedTonePlaying(boolean isTonePlaying);
     }
 
     private static final String TAG = "CallsManager";
@@ -773,7 +774,11 @@
         return false;
     }
 
-    boolean hasOnlyDisconnectedCalls() {
+    @VisibleForTesting
+    public boolean hasOnlyDisconnectedCalls() {
+        if (mCalls.size() == 0) {
+            return false;
+        }
         for (Call call : mCalls) {
             if (!call.isDisconnected()) {
                 return false;
@@ -1788,6 +1793,20 @@
         }
     }
 
+    /**
+     * Called when disconnect tone is started or stopped, including any InCallTone
+     * after disconnected call.
+     *
+     * @param isTonePlaying true if the disconnected tone is started, otherwise the disconnected
+     * tone is stopped.
+     */
+    void onDisconnectedTonePlaying(boolean isTonePlaying) {
+        Log.v(this, "onDisconnectedTonePlaying, %s", isTonePlaying ? "started" : "stopped");
+        for (CallsManagerListener listener : mListeners) {
+            listener.onDisconnectedTonePlaying(isTonePlaying);
+        }
+    }
+
     void markCallAsRinging(Call call) {
         setCallState(call, CallState.RINGING, "ringing set explicitly");
     }
diff --git a/src/com/android/server/telecom/CallsManagerListenerBase.java b/src/com/android/server/telecom/CallsManagerListenerBase.java
index a4c76c1..c0a71eb 100644
--- a/src/com/android/server/telecom/CallsManagerListenerBase.java
+++ b/src/com/android/server/telecom/CallsManagerListenerBase.java
@@ -88,4 +88,8 @@
     @Override
     public void onExternalCallChanged(Call call, boolean isExternalCall) {
     }
+
+    @Override
+    public void onDisconnectedTonePlaying(boolean isTonePlaying) {
+    }
 }
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothPhoneServiceTest.java b/tests/src/com/android/server/telecom/tests/BluetoothPhoneServiceTest.java
index 300415a..885fbc4 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothPhoneServiceTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothPhoneServiceTest.java
@@ -78,6 +78,7 @@
     private static final int CALL_STATE_INCOMING = 4;
     private static final int CALL_STATE_WAITING = 5;
     private static final int CALL_STATE_IDLE = 6;
+    private static final int CALL_STATE_DISCONNECTED = 7;
     // Terminate all held or set UDUB("busy") to a waiting call
     private static final int CHLD_TYPE_RELEASEHELD = 0;
     // Terminate all active calls and accepts a waiting/held call
@@ -110,6 +111,7 @@
         doReturn(null).when(mMockCallsManager).getHeldCall();
         doReturn(null).when(mMockCallsManager).getOutgoingCall();
         doReturn(0).when(mMockCallsManager).getNumHeldCalls();
+        doReturn(false).when(mMockCallsManager).hasOnlyDisconnectedCalls();
         mBluetoothPhoneService = new BluetoothPhoneServiceImpl(mContext, mLock, mMockCallsManager,
                 mock(BluetoothAdapterProxy.class), mMockPhoneAccountRegistrar);
 
@@ -817,6 +819,25 @@
     }
 
     @MediumTest
+    public void testOnCallStateChangedDisconnected() throws Exception {
+        Call disconnectedCall = createDisconnectedCall();
+        doReturn(true).when(mMockCallsManager).hasOnlyDisconnectedCalls();
+        mBluetoothPhoneService.mCallsManagerListener.onCallStateChanged(disconnectedCall,
+                CallState.DISCONNECTING, CallState.DISCONNECTED);
+        verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_DISCONNECTED),
+                eq(""), eq(128));
+
+        doReturn(false).when(mMockCallsManager).hasOnlyDisconnectedCalls();
+        mBluetoothPhoneService.mCallsManagerListener.onDisconnectedTonePlaying(true);
+        verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_DISCONNECTED),
+                eq(""), eq(128));
+
+        mBluetoothPhoneService.mCallsManagerListener.onDisconnectedTonePlaying(false);
+        verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_IDLE),
+                eq(""), eq(128));
+    }
+
+    @MediumTest
     public void testOnCallStateChanged() throws Exception {
         Call ringingCall = createRingingCall();
         when(ringingCall.getHandle()).thenReturn(Uri.parse("tel:555-0000"));
@@ -929,6 +950,12 @@
         return call;
     }
 
+    private Call createDisconnectedCall() {
+        Call call = mock(Call.class);
+        when(mMockCallsManager.getFirstCallWithState(CallState.DISCONNECTED)).thenReturn(call);
+        return call;
+    }
+
     private Call createForegroundCall() {
         Call call = mock(Call.class);
         when(mMockCallsManager.getForegroundCall()).thenReturn(call);