Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 775f4cb5 authored by Jack He's avatar Jack He
Browse files

HSP: Correctly hang up call when key is pressed

* When ringing, key pressed event should answer call
* When in call and audio is not active, key pressed event should connect
  audio to headset by setting it as active device
* When in call and audio is active, key pressed event should hang up
  call
* When call state is idle and audio is active, key pressed event should
  disconnect audio
* When idle and audio is not active, key pressed event should dial
  outgoing call using the last dialed number if exists

Bug: 74234576
Test: HeadsetStateMachineTest
Change-Id: Idc0925f1b4e77128e00a50927d0f8442792e5a8e
(cherry picked from commit 405293b65f0f5c0ef739d7ebf95e4db653595ddc)
parent e8aa95a6
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -119,11 +119,13 @@ public class AtPhonebook {
                Calls.TYPE + "=" + Calls.OUTGOING_TYPE, null,
                Calls.DEFAULT_SORT_ORDER + " LIMIT 1");
        if (cursor == null) {
            Log.w(TAG, "getLastDialledNumber, cursor is null");
            return null;
        }

        if (cursor.getCount() < 1) {
            cursor.close();
            Log.w(TAG, "getLastDialledNumber, cursor.getCount is 0");
            return null;
        }
        cursor.moveToNext();
+9 −2
Original line number Diff line number Diff line
@@ -1274,7 +1274,8 @@ public class HeadsetService extends ProfileService {
     * @param dialNumber number to dial
     * @return true on successful dial out
     */
    boolean dialOutgoingCall(BluetoothDevice fromDevice, String dialNumber) {
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public boolean dialOutgoingCall(BluetoothDevice fromDevice, String dialNumber) {
        synchronized (mStateMachines) {
            Log.i(TAG, "dialOutgoingCall: from " + fromDevice);
            if (!isOnStateMachineThread()) {
@@ -1306,7 +1307,13 @@ public class HeadsetService extends ProfileService {
        }
    }

    boolean hasDeviceInitiatedDialingOut() {
    /**
     * Check if any connected headset has started dialing calls
     *
     * @return true if some device has started dialing calls
     */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public boolean hasDeviceInitiatedDialingOut() {
        synchronized (mStateMachines) {
            return mDialingOutTimeoutEvent != null;
        }
+10 −9
Original line number Diff line number Diff line
@@ -1826,20 +1826,21 @@ public class HeadsetStateMachine extends StateMachine {

    // HSP +CKPD command
    private void processKeyPressed(BluetoothDevice device) {
        final HeadsetPhoneState phoneState = mSystemInterface.getHeadsetPhoneState();
        if (phoneState.getCallState() == HeadsetHalConstants.CALL_STATE_INCOMING) {
        if (mSystemInterface.isRinging()) {
            mSystemInterface.answerCall(device);
        } else if (phoneState.getNumActiveCall() > 0) {
            if (getAudioState() != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
                mHeadsetService.setActiveDevice(mDevice);
                mSystemInterface.getAudioManager().setParameters("A2dpSuspended=true");
                if (!mNativeInterface.connectAudio(mDevice)) {
                    mSystemInterface.getAudioManager().setParameters("A2dpSuspended=false");
                    Log.w(TAG, "processKeyPressed: failed to connectAudio to " + mDevice);
        } else if (mSystemInterface.isInCall()) {
            if (getAudioState() == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
                // Should connect audio as well
                if (!mHeadsetService.setActiveDevice(mDevice)) {
                    Log.w(TAG, "processKeyPressed, failed to set active device to " + mDevice);
                }
            } else {
                mSystemInterface.hangupCall(device);
            }
        } else if (getAudioState() != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
            if (!mNativeInterface.disconnectAudio(mDevice)) {
                Log.w(TAG, "processKeyPressed, failed to disconnect audio from " + mDevice);
            }
        } else {
            // We have already replied OK to this HSP command, no feedback is needed
            if (mHeadsetService.hasDeviceInitiatedDialingOut()) {
+100 −0
Original line number Diff line number Diff line
@@ -25,13 +25,18 @@ import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.database.Cursor;
import android.media.AudioManager;
import android.net.Uri;
import android.os.HandlerThread;
import android.os.UserHandle;
import android.provider.CallLog;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.MediumTest;
import android.support.test.runner.AndroidJUnit4;
import android.telephony.PhoneStateListener;
import android.test.mock.MockContentProvider;
import android.test.mock.MockContentResolver;

import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;
@@ -57,6 +62,7 @@ public class HeadsetStateMachineTest {
    private static final int CONNECT_TIMEOUT_TEST_MILLIS = 1000;
    private static final int CONNECT_TIMEOUT_TEST_WAIT_MILLIS = CONNECT_TIMEOUT_TEST_MILLIS * 3 / 2;
    private static final int ASYNC_CALL_TIMEOUT_MILLIS = 250;
    private static final String TEST_PHONE_NUMBER = "1234567890";
    private Context mTargetContext;
    private BluetoothAdapter mAdapter;
    private HandlerThread mHandlerThread;
@@ -69,6 +75,7 @@ public class HeadsetStateMachineTest {
    @Mock private HeadsetSystemInterface mSystemInterface;
    @Mock private AudioManager mAudioManager;
    @Mock private HeadsetPhoneState mPhoneState;
    private MockContentResolver mMockContentResolver;
    private HeadsetNativeInterface mNativeInterface;

    @Before
@@ -94,6 +101,8 @@ public class HeadsetStateMachineTest {
        doReturn(true).when(mNativeInterface).connectAudio(mTestDevice);
        doReturn(true).when(mNativeInterface).disconnectAudio(mTestDevice);
        // Stub headset service
        mMockContentResolver = new MockContentResolver();
        when(mHeadsetService.getContentResolver()).thenReturn(mMockContentResolver);
        doReturn(BluetoothDevice.BOND_BONDED).when(mAdapterService)
                .getBondState(any(BluetoothDevice.class));
        when(mHeadsetService.bindService(any(Intent.class), any(ServiceConnection.class), anyInt()))
@@ -840,6 +849,97 @@ public class HeadsetStateMachineTest {
                PhoneStateListener.LISTEN_NONE);
    }

    /**
     * A test to verify that we correctly handles key pressed event from a HSP headset
     */
    @Test
    public void testKeyPressedEventWhenIdleAndAudioOff_dialCall() {
        setUpConnectedState();
        Cursor cursor = mock(Cursor.class);
        when(cursor.getCount()).thenReturn(1);
        when(cursor.moveToNext()).thenReturn(true);
        int magicNumber = 42;
        when(cursor.getColumnIndexOrThrow(CallLog.Calls.NUMBER)).thenReturn(magicNumber);
        when(cursor.getString(magicNumber)).thenReturn(TEST_PHONE_NUMBER);
        MockContentProvider mockContentProvider = new MockContentProvider() {
            @Override
            public Cursor query(Uri uri, String[] projection, String selection,
                    String[] selectionArgs, String sortOrder) {
                if (uri == null || !uri.equals(CallLog.Calls.CONTENT_URI)) {
                    return null;
                }
                if (projection == null || (projection.length == 0) || !projection[0].equals(
                        CallLog.Calls.NUMBER)) {
                    return null;
                }
                if (selection == null || !selection.equals(
                        CallLog.Calls.TYPE + "=" + CallLog.Calls.OUTGOING_TYPE)) {
                    return null;
                }
                if (selectionArgs != null) {
                    return null;
                }
                if (sortOrder == null || !sortOrder.equals(
                        CallLog.Calls.DEFAULT_SORT_ORDER + " LIMIT 1")) {
                    return null;
                }
                return cursor;
            }
        };
        mMockContentResolver.addProvider(CallLog.AUTHORITY, mockContentProvider);
        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_KEY_PRESSED, mTestDevice));
        verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).dialOutgoingCall(mTestDevice,
                TEST_PHONE_NUMBER);
    }

    /**
     * A test to verify that we correctly handles key pressed event from a HSP headset
     */
    @Test
    public void testKeyPressedEventDuringRinging_answerCall() {
        setUpConnectedState();
        when(mSystemInterface.isRinging()).thenReturn(true);
        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_KEY_PRESSED, mTestDevice));
        verify(mSystemInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).answerCall(mTestDevice);
    }

    /**
     * A test to verify that we correctly handles key pressed event from a HSP headset
     */
    @Test
    public void testKeyPressedEventInCallButAudioOff_setActiveDevice() {
        setUpConnectedState();
        when(mSystemInterface.isInCall()).thenReturn(true);
        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_KEY_PRESSED, mTestDevice));
        verify(mHeadsetService, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).setActiveDevice(mTestDevice);
    }

    /**
     * A test to verify that we correctly handles key pressed event from a HSP headset
     */
    @Test
    public void testKeyPressedEventInCallAndAudioOn_hangupCall() {
        setUpAudioOnState();
        when(mSystemInterface.isInCall()).thenReturn(true);
        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_KEY_PRESSED, mTestDevice));
        verify(mSystemInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).hangupCall(mTestDevice);
    }

    /**
     * A test to verify that we correctly handles key pressed event from a HSP headset
     */
    @Test
    public void testKeyPressedEventWhenIdleAndAudioOn_disconnectAudio() {
        setUpAudioOnState();
        mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_KEY_PRESSED, mTestDevice));
        verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).disconnectAudio(mTestDevice);
    }

    /**
     * Setup Connecting State
     * @return number of times mHeadsetService.sendBroadcastAsUser() has been invoked