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

Commit 4657c0a5 authored by Jack He's avatar Jack He
Browse files

HeadsetService: Manage voice recognition for multiple devices

* Handle voice recognition in HeadsetService so that we can manage
  multiple connected HFP devices. Implement the following policies to
  manage relations among multiple HFP devices:
  - If voice recognition is initiated by HF, the HF will be set as
    active device and following startVoiceRecognition(device) callback will
    be routed to the initiating HFP regardless of the input argument
  - If startVoiceRecognition(device) is called without a pending request
    from HF, the requested device will be set as active device and audio
    will be routed to that device
* Added unit tests to verify voice recognition function behavior in
  Multi-HFP which can be:
  - Started by HF and stopped by HF
  - Started by HF and stopped by AG
  - Started by AG and stopped by HF
  - Started by AG and stopped by AG
* Fixed logic in HeadsetStateMachine to match the expected behavior

Bug: 35793101
Test: runtest -j40 bluetooth
Change-Id: Ibbabcf7a5eb9355a1f35fb9ff287f7345cdcd372
(cherry picked from commit 2e5b99c2e33a27bed7acfdef46c1e2c95b04f591)
parent ee100a3f
Loading
Loading
Loading
Loading
+301 −69

File changed.

Preview size limit exceeded, changes collapsed.

+43 −154
Original line number Original line Diff line number Diff line
@@ -20,15 +20,10 @@ import android.bluetooth.BluetoothAssignedNumbers;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothProfile;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.Intent;
import android.media.AudioManager;
import android.media.AudioManager;
import android.os.IDeviceIdleController;
import android.os.Looper;
import android.os.Looper;
import android.os.Message;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserHandle;
import android.support.annotation.VisibleForTesting;
import android.support.annotation.VisibleForTesting;
@@ -98,14 +93,13 @@ public class HeadsetStateMachine extends StateMachine {
    static final int SEND_VENDOR_SPECIFIC_RESULT_CODE = 12;
    static final int SEND_VENDOR_SPECIFIC_RESULT_CODE = 12;
    static final int SEND_BSIR = 13;
    static final int SEND_BSIR = 13;
    static final int DIALING_OUT_RESULT = 14;
    static final int DIALING_OUT_RESULT = 14;
    static final int VOICE_RECOGNITION_RESULT = 15;


    static final int STACK_EVENT = 101;
    static final int STACK_EVENT = 101;
    private static final int START_VR_TIMEOUT = 103;
    private static final int CLCC_RSP_TIMEOUT = 104;
    private static final int CLCC_RSP_TIMEOUT = 104;


    private static final int CONNECT_TIMEOUT = 201;
    private static final int CONNECT_TIMEOUT = 201;


    private static final int START_VR_TIMEOUT_MS = 5000;
    private static final int CLCC_RSP_TIMEOUT_MS = 5000;
    private static final int CLCC_RSP_TIMEOUT_MS = 5000;
    // NOTE: the value is not "final" - it is modified in the unit tests
    // NOTE: the value is not "final" - it is modified in the unit tests
    @VisibleForTesting static int sConnectTimeoutMs = 30000;
    @VisibleForTesting static int sConnectTimeoutMs = 30000;
@@ -132,8 +126,6 @@ public class HeadsetStateMachine extends StateMachine {
    private final HeadsetSystemInterface mSystemInterface;
    private final HeadsetSystemInterface mSystemInterface;


    // Runtime states
    // Runtime states
    private boolean mVoiceRecognitionStarted;
    private boolean mWaitingForVoiceRecognition;
    private int mSpeakerVolume;
    private int mSpeakerVolume;
    private int mMicVolume;
    private int mMicVolume;
    private HeadsetAgIndicatorEnableState mAgIndicatorEnableState;
    private HeadsetAgIndicatorEnableState mAgIndicatorEnableState;
@@ -148,8 +140,6 @@ public class HeadsetStateMachine extends StateMachine {


    // Keys are AT commands, and values are the company IDs.
    // Keys are AT commands, and values are the company IDs.
    private static final Map<String, Integer> VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID;
    private static final Map<String, Integer> VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID;
    // Intent that get sent during voice recognition events.
    private static final Intent VOICE_COMMAND_INTENT;


    static {
    static {
        VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID = new HashMap<>();
        VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID = new HashMap<>();
@@ -165,8 +155,6 @@ public class HeadsetStateMachine extends StateMachine {
        VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.put(
        VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.put(
                BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV,
                BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV,
                BluetoothAssignedNumbers.APPLE);
                BluetoothAssignedNumbers.APPLE);
        VOICE_COMMAND_INTENT = new Intent(Intent.ACTION_VOICE_COMMAND);
        VOICE_COMMAND_INTENT.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    }
    }


    private HeadsetStateMachine(BluetoothDevice device, Looper looper,
    private HeadsetStateMachine(BluetoothDevice device, Looper looper,
@@ -229,8 +217,6 @@ public class HeadsetStateMachine extends StateMachine {
        ProfileService.println(sb, "  mPrevState: " + mPrevState);
        ProfileService.println(sb, "  mPrevState: " + mPrevState);
        ProfileService.println(sb, "  mConnectionState: " + getConnectionState());
        ProfileService.println(sb, "  mConnectionState: " + getConnectionState());
        ProfileService.println(sb, "  mAudioState: " + getAudioState());
        ProfileService.println(sb, "  mAudioState: " + getAudioState());
        ProfileService.println(sb, "  mVoiceRecognitionStarted: " + mVoiceRecognitionStarted);
        ProfileService.println(sb, "  mWaitingForVoiceRecognition: " + mWaitingForVoiceRecognition);
        ProfileService.println(sb, "  mNeedDialingOutReply: " + mNeedDialingOutReply);
        ProfileService.println(sb, "  mNeedDialingOutReply: " + mNeedDialingOutReply);
        ProfileService.println(sb, "  mSpeakerVolume: " + mSpeakerVolume);
        ProfileService.println(sb, "  mSpeakerVolume: " + mSpeakerVolume);
        ProfileService.println(sb, "  mMicVolume: " + mMicVolume);
        ProfileService.println(sb, "  mMicVolume: " + mMicVolume);
@@ -442,8 +428,6 @@ public class HeadsetStateMachine extends StateMachine {
            mConnectingTimestampMs = Long.MIN_VALUE;
            mConnectingTimestampMs = Long.MIN_VALUE;
            mPhonebook.resetAtState();
            mPhonebook.resetAtState();
            updateAgIndicatorEnableState(null);
            updateAgIndicatorEnableState(null);
            mVoiceRecognitionStarted = false;
            mWaitingForVoiceRecognition = false;
            mNeedDialingOutReply = false;
            mNeedDialingOutReply = false;
            mAudioParams.clear();
            mAudioParams.clear();
            broadcastStateTransitions();
            broadcastStateTransitions();
@@ -617,7 +601,7 @@ public class HeadsetStateMachine extends StateMachine {
                        case HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED:
                        case HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED:
                            stateLogW("Unexpected VR event, device=" + event.device + ", state="
                            stateLogW("Unexpected VR event, device=" + event.device + ", state="
                                    + event.valueInt);
                                    + event.valueInt);
                            processVrEvent(event.valueInt, event.device);
                            processVrEvent(event.valueInt);
                            break;
                            break;
                        case HeadsetStackEvent.EVENT_TYPE_DIAL_CALL:
                        case HeadsetStackEvent.EVENT_TYPE_DIAL_CALL:
                            stateLogW("Unexpected dial event, device=" + event.device);
                            stateLogW("Unexpected dial event, device=" + event.device);
@@ -826,7 +810,10 @@ public class HeadsetStateMachine extends StateMachine {
                                + " is not currentDevice");
                                + " is not currentDevice");
                        break;
                        break;
                    }
                    }
                    processLocalVrEvent(HeadsetHalConstants.VR_STATE_STARTED);
                    if (!mNativeInterface.startVoiceRecognition(mDevice)) {
                        stateLogW("Failed to start voice recognition");
                        break;
                    }
                    break;
                    break;
                }
                }
                case VOICE_RECOGNITION_STOP: {
                case VOICE_RECOGNITION_STOP: {
@@ -836,7 +823,10 @@ public class HeadsetStateMachine extends StateMachine {
                                + " is not currentDevice");
                                + " is not currentDevice");
                        break;
                        break;
                    }
                    }
                    processLocalVrEvent(HeadsetHalConstants.VR_STATE_STOPPED);
                    if (!mNativeInterface.stopVoiceRecognition(mDevice)) {
                        stateLogW("Failed to stop voice recognition");
                        break;
                    }
                    break;
                    break;
                }
                }
                case CALL_STATE_CHANGED: {
                case CALL_STATE_CHANGED: {
@@ -869,31 +859,29 @@ public class HeadsetStateMachine extends StateMachine {
                case SEND_BSIR:
                case SEND_BSIR:
                    mNativeInterface.sendBsir(mDevice, message.arg1 == 1);
                    mNativeInterface.sendBsir(mDevice, message.arg1 == 1);
                    break;
                    break;
                case DIALING_OUT_RESULT: {
                case VOICE_RECOGNITION_RESULT: {
                    BluetoothDevice device = (BluetoothDevice) message.obj;
                    BluetoothDevice device = (BluetoothDevice) message.obj;
                    if (!mDevice.equals(device)) {
                    if (!mDevice.equals(device)) {
                        stateLogW("DIALING_OUT_TIMEOUT failed " + device + " is not currentDevice");
                        stateLogW("VOICE_RECOGNITION_RESULT failed " + device
                                + " is not currentDevice");
                        break;
                        break;
                    }
                    }
                    if (mNeedDialingOutReply) {
                        mNeedDialingOutReply = false;
                    mNativeInterface.atResponseCode(mDevice,
                    mNativeInterface.atResponseCode(mDevice,
                            message.arg1 == 1 ? HeadsetHalConstants.AT_RESPONSE_OK
                            message.arg1 == 1 ? HeadsetHalConstants.AT_RESPONSE_OK
                                    : HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
                                    : HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
                    }
                }
                    break;
                    break;
                case START_VR_TIMEOUT: {
                }
                case DIALING_OUT_RESULT: {
                    BluetoothDevice device = (BluetoothDevice) message.obj;
                    BluetoothDevice device = (BluetoothDevice) message.obj;
                    if (!mDevice.equals(device)) {
                    if (!mDevice.equals(device)) {
                        stateLogW("START_VR_TIMEOUT failed " + device + " is not currentDevice");
                        stateLogW("DIALING_OUT_RESULT failed " + device + " is not currentDevice");
                        break;
                        break;
                    }
                    }
                    if (mWaitingForVoiceRecognition) {
                    if (mNeedDialingOutReply) {
                        mWaitingForVoiceRecognition = false;
                        mNeedDialingOutReply = false;
                        stateLogE("Timeout waiting for voice recognition to start");
                        mNativeInterface.atResponseCode(mDevice,
                        mNativeInterface.atResponseCode(device,
                                message.arg1 == 1 ? HeadsetHalConstants.AT_RESPONSE_OK
                                HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
                                        : HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
                    }
                    }
                }
                }
                break;
                break;
@@ -916,7 +904,7 @@ public class HeadsetStateMachine extends StateMachine {
                            processAudioEvent(event.valueInt);
                            processAudioEvent(event.valueInt);
                            break;
                            break;
                        case HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED:
                        case HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED:
                            processVrEvent(event.valueInt, event.device);
                            processVrEvent(event.valueInt);
                            break;
                            break;
                        case HeadsetStackEvent.EVENT_TYPE_ANSWER_CALL:
                        case HeadsetStackEvent.EVENT_TYPE_ANSWER_CALL:
                            mSystemInterface.answerCall(event.device);
                            mSystemInterface.answerCall(event.device);
@@ -1433,119 +1421,6 @@ public class HeadsetStateMachine extends StateMachine {
        return state.getAudioStateInt();
        return state.getAudioStateInt();
    }
    }


    private void processVrEvent(int state, BluetoothDevice device) {
        Log.d(TAG, "processVrEvent: state=" + state + " mVoiceRecognitionStarted: "
                + mVoiceRecognitionStarted + " mWaitingforVoiceRecognition: "
                + mWaitingForVoiceRecognition + " isInCall: " + mSystemInterface.isInCall());
        if (state == HeadsetHalConstants.VR_STATE_STARTED) {
            if (!mHeadsetService.isVirtualCallStarted() && !mSystemInterface.isInCall()) {
                IDeviceIdleController dic = IDeviceIdleController.Stub.asInterface(
                        ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
                if (dic != null) {
                    try {
                        dic.exitIdle("voice-command");
                    } catch (RemoteException e) {
                    }
                }
                try {
                    mHeadsetService.startActivity(VOICE_COMMAND_INTENT);
                } catch (ActivityNotFoundException e) {
                    mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR,
                            0);
                    return;
                }
                expectVoiceRecognition(device);
            } else {
                // send error response if call is ongoing
                mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
            }
        } else if (state == HeadsetHalConstants.VR_STATE_STOPPED) {
            if (mVoiceRecognitionStarted || mWaitingForVoiceRecognition) {
                mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_OK, 0);
                mVoiceRecognitionStarted = false;
                mWaitingForVoiceRecognition = false;
                if (!mSystemInterface.isInCall() && (getAudioState()
                        != BluetoothHeadset.STATE_AUDIO_DISCONNECTED)) {
                    mNativeInterface.disconnectAudio(mDevice);
                    mSystemInterface.getAudioManager().setParameters("A2dpSuspended=false");
                }
            } else {
                mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
            }
        } else {
            Log.e(TAG, "Bad Voice Recognition state: " + state);
        }
    }

    private void processLocalVrEvent(int state) {
        if (state == HeadsetHalConstants.VR_STATE_STARTED) {
            boolean needAudio = true;
            if (mVoiceRecognitionStarted || mSystemInterface.isInCall()) {
                Log.e(TAG, "Voice recognition started when call is active. isInCall:"
                        + mSystemInterface.isInCall() + " mVoiceRecognitionStarted: "
                        + mVoiceRecognitionStarted);
                return;
            }
            mVoiceRecognitionStarted = true;

            if (mWaitingForVoiceRecognition) {
                if (!hasMessages(START_VR_TIMEOUT)) {
                    return;
                }
                Log.d(TAG, "Voice recognition started successfully");
                mWaitingForVoiceRecognition = false;
                mNativeInterface.atResponseCode(mDevice, HeadsetHalConstants.AT_RESPONSE_OK, 0);
                removeMessages(START_VR_TIMEOUT);
            } else {
                Log.d(TAG, "Voice recognition started locally");
                needAudio = mNativeInterface.startVoiceRecognition(mDevice);
            }

            if (needAudio && getAudioState() == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
                Log.d(TAG, "Initiating audio connection for Voice Recognition");
                // At this stage, we need to be sure that AVDTP is not streaming. This is needed
                // to be compliant with the AV+HFP Whitepaper as we cannot have A2DP in
                // streaming state while a SCO connection is established.
                // This is needed for VoiceDial scenario alone and not for
                // incoming call/outgoing call scenarios as the phone enters MODE_RINGTONE
                // or MODE_IN_CALL which shall automatically suspend the AVDTP stream if needed.
                // Whereas for VoiceDial we want to activate the SCO connection but we are still
                // in MODE_NORMAL and hence the need to explicitly suspend the A2DP stream
                mSystemInterface.getAudioManager().setParameters("A2dpSuspended=true");
                if (!mNativeInterface.connectAudio(mDevice)) {
                    mSystemInterface.getAudioManager().setParameters("A2dpSuspended=false");
                    Log.w(TAG, "processLocalVrEvent: failed connectAudio to " + mDevice);
                }
            }

            if (mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
                mSystemInterface.getVoiceRecognitionWakeLock().release();
            }
        } else {
            Log.d(TAG, "Voice Recognition stopped. mVoiceRecognitionStarted: "
                    + mVoiceRecognitionStarted + " mWaitingForVoiceRecognition: "
                    + mWaitingForVoiceRecognition);
            if (mVoiceRecognitionStarted || mWaitingForVoiceRecognition) {
                mVoiceRecognitionStarted = false;
                mWaitingForVoiceRecognition = false;

                if (mNativeInterface.stopVoiceRecognition(mDevice) && !mSystemInterface.isInCall()
                        && getAudioState() != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
                    mNativeInterface.disconnectAudio(mDevice);
                }
            }
        }
    }

    private synchronized void expectVoiceRecognition(BluetoothDevice device) {
        mWaitingForVoiceRecognition = true;
        mHeadsetService.setActiveDevice(device);
        sendMessageDelayed(START_VR_TIMEOUT, device, START_VR_TIMEOUT_MS);
        if (!mSystemInterface.getVoiceRecognitionWakeLock().isHeld()) {
            mSystemInterface.getVoiceRecognitionWakeLock().acquire(START_VR_TIMEOUT_MS);
        }
    }

    public long getConnectingTimestampMs() {
    public long getConnectingTimestampMs() {
        return mConnectingTimestampMs;
        return mConnectingTimestampMs;
    }
    }
@@ -1562,10 +1437,8 @@ public class HeadsetStateMachine extends StateMachine {
        // assert: all elements of args are Serializable
        // assert: all elements of args are Serializable
        intent.putExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS, arguments);
        intent.putExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS, arguments);
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);

        intent.addCategory(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY + "."
        intent.addCategory(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY + "."
                + Integer.toString(companyId));
                + Integer.toString(companyId));

        mHeadsetService.sendBroadcastAsUser(intent, UserHandle.ALL, HeadsetService.BLUETOOTH_PERM);
        mHeadsetService.sendBroadcastAsUser(intent, UserHandle.ALL, HeadsetService.BLUETOOTH_PERM);
    }
    }


@@ -1665,6 +1538,22 @@ public class HeadsetStateMachine extends StateMachine {
        mNeedDialingOutReply = true;
        mNeedDialingOutReply = true;
    }
    }


    private void processVrEvent(int state) {
        if (state == HeadsetHalConstants.VR_STATE_STARTED) {
            if (!mHeadsetService.startVoiceRecognitionByHeadset(mDevice)) {
                mNativeInterface.atResponseCode(mDevice, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
            }
        } else if (state == HeadsetHalConstants.VR_STATE_STOPPED) {
            if (mHeadsetService.stopVoiceRecognitionByHeadset(mDevice)) {
                mNativeInterface.atResponseCode(mDevice, HeadsetHalConstants.AT_RESPONSE_OK, 0);
            } else {
                mNativeInterface.atResponseCode(mDevice, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
            }
        } else {
            mNativeInterface.atResponseCode(mDevice, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
        }
    }

    private void processVolumeEvent(int volumeType, int volume) {
    private void processVolumeEvent(int volumeType, int volume) {
        // Only current active device can change SCO volume
        // Only current active device can change SCO volume
        if (!mDevice.equals(mHeadsetService.getActiveDevice())) {
        if (!mDevice.equals(mHeadsetService.getActiveDevice())) {
@@ -2160,16 +2049,16 @@ public class HeadsetStateMachine extends StateMachine {
                return "SEND_VENDOR_SPECIFIC_RESULT_CODE";
                return "SEND_VENDOR_SPECIFIC_RESULT_CODE";
            case STACK_EVENT:
            case STACK_EVENT:
                return "STACK_EVENT";
                return "STACK_EVENT";
            case VOICE_RECOGNITION_RESULT:
                return "VOICE_RECOGNITION_RESULT";
            case DIALING_OUT_RESULT:
            case DIALING_OUT_RESULT:
                return "DIALING_OUT_RESULT";
                return "DIALING_OUT_RESULT";
            case START_VR_TIMEOUT:
                return "START_VR_TIMEOUT";
            case CLCC_RSP_TIMEOUT:
            case CLCC_RSP_TIMEOUT:
                return "CLCC_RSP_TIMEOUT";
                return "CLCC_RSP_TIMEOUT";
            case CONNECT_TIMEOUT:
            case CONNECT_TIMEOUT:
                return "CONNECT_TIMEOUT";
                return "CONNECT_TIMEOUT";
            default:
            default:
                return "UNKNOWN";
                return "UNKNOWN(" + what + ")";
        }
        }
    }
    }
}
}
+36 −0
Original line number Original line Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.bluetooth.hfp;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.IBluetoothHeadsetPhone;
import android.bluetooth.IBluetoothHeadsetPhone;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.ComponentName;
import android.content.Context;
import android.content.Context;
import android.content.Intent;
import android.content.Intent;
@@ -352,4 +353,39 @@ public class HeadsetSystemInterface {
        return !isInCall() && !isRinging();
        return !isInCall() && !isRinging();
    }
    }


    /**
     * Activate voice recognition on Android system
     *
     * @return true if activation succeeds, caller should wait for
     * {@link BluetoothHeadset#startVoiceRecognition(BluetoothDevice)} callback that will then
     * trigger {@link HeadsetService#startVoiceRecognition(BluetoothDevice)}, false if failed to
     * activate
     */
    @VisibleForTesting
    public boolean activateVoiceRecognition() {
        Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        try {
            mHeadsetService.startActivity(intent);
        } catch (ActivityNotFoundException e) {
            Log.e(TAG, "activateVoiceRecognition, failed due to activity not found for " + intent);
            return false;
        }
        return true;
    }

    /**
     * Deactivate voice recognition on Android system
     *
     * @return true if activation succeeds, caller should wait for
     * {@link BluetoothHeadset#stopVoiceRecognition(BluetoothDevice)} callback that will then
     * trigger {@link HeadsetService#stopVoiceRecognition(BluetoothDevice)}, false if failed to
     * activate
     */
    @VisibleForTesting
    public boolean deactivateVoiceRecognition() {
        // TODO: need a method to deactivate voice recognition on Android
        return true;
    }

}
}
+575 −67

File changed.

Preview size limit exceeded, changes collapsed.

+1 −0
Original line number Original line Diff line number Diff line
@@ -117,6 +117,7 @@ public class HeadsetServiceTest {
        doNothing().when(mSystemInterface).stop();
        doNothing().when(mSystemInterface).stop();
        when(mSystemInterface.getHeadsetPhoneState()).thenReturn(mPhoneState);
        when(mSystemInterface.getHeadsetPhoneState()).thenReturn(mPhoneState);
        when(mSystemInterface.getAudioManager()).thenReturn(mAudioManager);
        when(mSystemInterface.getAudioManager()).thenReturn(mAudioManager);
        when(mSystemInterface.isCallIdle()).thenReturn(true);
        // Mock methods in HeadsetNativeInterface
        // Mock methods in HeadsetNativeInterface
        mNativeInterface = spy(HeadsetNativeInterface.getInstance());
        mNativeInterface = spy(HeadsetNativeInterface.getInstance());
        doNothing().when(mNativeInterface).init(anyInt(), anyBoolean());
        doNothing().when(mNativeInterface).init(anyInt(), anyBoolean());