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

Commit 0e4d90ae authored by Poompatai Puntitpong's avatar Poompatai Puntitpong
Browse files

Add additional HFP AT commands that return device information to enhance IOP with remote devices

Test: atest HeadsetStateMachineTest
Test: manual
Tag: #feature
Bug: 273426857
Change-Id: I02404d0675299ad94bebdbd34d188f6679eba1ca
parent 965b0a31
Loading
Loading
Loading
Loading
+81 −1
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.bluetooth.hfp;

import static android.Manifest.permission.BLUETOOTH_CONNECT;

import static com.android.modules.utils.build.SdkLevel.isAtLeastU;

import android.annotation.RequiresPermission;
@@ -31,6 +32,7 @@ import android.bluetooth.BluetoothStatusCodes;
import android.bluetooth.hfp.BluetoothHfpProtoEnums;
import android.content.Intent;
import android.media.AudioManager;
import android.os.Build;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
@@ -179,6 +181,18 @@ public class HeadsetStateMachine extends StateMachine {
        VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.put(
                BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV,
                BluetoothAssignedNumbers.APPLE);
        VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.put(
                BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_CGMI,
                BluetoothAssignedNumbers.GOOGLE);
        VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.put(
                BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_CGMR,
                BluetoothAssignedNumbers.GOOGLE);
        VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.put(
                BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_CGMM,
                BluetoothAssignedNumbers.GOOGLE);
        VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID.put(
                BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_CGSN,
                BluetoothAssignedNumbers.GOOGLE);
    }

    private HeadsetStateMachine(BluetoothDevice device, Looper looper,
@@ -1993,7 +2007,32 @@ public class HeadsetStateMachine extends StateMachine {
    void processVendorSpecificAt(String atString, BluetoothDevice device) {
        log("processVendorSpecificAt - atString = " + atString);

        // Currently we accept only SET type commands.
        // Currently we accept only SET type commands, except the 4 AT commands
        // which requests the device's information: +CGMI, +CGMM, +CGMR and +CGSN, which we
        // responds to right away without any further processing.
        boolean isIopInfoRequestAt = true;
        switch (atString) {
            case BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_CGMI:
                processAtCgmi(device);
                break;
            case BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_CGMM:
                processAtCgmm(device);
                break;
            case BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_CGMR:
                processAtCgmr(device);
                break;
            case BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_CGSN:
                processAtCgsn(device);
                break;
            default:
                isIopInfoRequestAt = false;
        }
        if (isIopInfoRequestAt) {
            mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_OK, 0);
            return;
        }

        // Check if the command is a SET type command.
        int indexOfEqual = atString.indexOf("=");
        if (indexOfEqual == -1) {
            Log.w(TAG, "processVendorSpecificAt: command type error in " + atString);
@@ -2184,6 +2223,47 @@ public class HeadsetStateMachine extends StateMachine {
        mNativeInterface.atResponseString(device, "+XAPL=iPhone," + String.valueOf(2));
    }

    /**
     * Process AT+CGMI AT command
     *
     * @param device Remote device that has sent this command
     */
    @VisibleForTesting
    void processAtCgmi(BluetoothDevice device) {
        mNativeInterface.atResponseString(device, Build.MANUFACTURER);
    }

    /**
     * Process AT+CGMM AT command
     *
     * @param device Remote device that has sent this command
     */
    @VisibleForTesting
    void processAtCgmm(BluetoothDevice device) {
        mNativeInterface.atResponseString(device, Build.MODEL);
    }

    /**
     * Process AT+CGMR AT command
     *
     * @param device Remote device that has sent this command
     */
    @VisibleForTesting
    void processAtCgmr(BluetoothDevice device) {
        mNativeInterface.atResponseString(
                device, String.format("%s (%s)", Build.VERSION.RELEASE, Build.VERSION.INCREMENTAL));
    }

    /**
     * Process AT+CGSN AT command
     *
     * @param device Remote device that has sent this command
     */
    @VisibleForTesting
    void processAtCgsn(BluetoothDevice device) {
        mNativeInterface.atResponseString(device, Build.getSerial());
    }

    @VisibleForTesting
    void processUnknownAt(String atString, BluetoothDevice device) {
        if (device == null) {
+45 −1
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import android.content.ServiceConnection;
import android.database.Cursor;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.HandlerThread;
@@ -1374,7 +1375,7 @@ public class HeadsetStateMachineTest {
    }

    @Test
    public void testProcessVendorSpecificAt_withNoEqualSignCommand() {
    public void testProcessVendorSpecificAt_withNonExceptedNoEqualSignCommand() {
        String atString = "invalid_command";

        mHeadsetStateMachine.processVendorSpecificAt(atString, mTestDevice);
@@ -1413,6 +1414,49 @@ public class HeadsetStateMachineTest {
        verify(mNativeInterface).atResponseCode(mTestDevice, HeadsetHalConstants.AT_RESPONSE_OK, 0);
    }

    @Test
    public void testProcessVendorSpecificAt_withExceptedNoEqualSignCommandCGMI() {
        String atString = BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_CGMI;

        mHeadsetStateMachine.processVendorSpecificAt(atString, mTestDevice);

        verify(mNativeInterface).atResponseString(mTestDevice, Build.MANUFACTURER);
        verify(mNativeInterface).atResponseCode(mTestDevice, HeadsetHalConstants.AT_RESPONSE_OK, 0);
    }

    @Test
    public void testProcessVendorSpecificAt_withExceptedNoEqualSignCommandCGMM() {
        String atString = BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_CGMM;

        mHeadsetStateMachine.processVendorSpecificAt(atString, mTestDevice);

        verify(mNativeInterface).atResponseString(mTestDevice, Build.MODEL);
        verify(mNativeInterface).atResponseCode(mTestDevice, HeadsetHalConstants.AT_RESPONSE_OK, 0);
    }

    @Test
    public void testProcessVendorSpecificAt_withExceptedNoEqualSignCommandCGMR() {
        String atString = BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_CGMR;

        mHeadsetStateMachine.processVendorSpecificAt(atString, mTestDevice);

        verify(mNativeInterface)
                .atResponseString(
                        mTestDevice,
                        String.format("%s (%s)", Build.VERSION.RELEASE, Build.VERSION.INCREMENTAL));
        verify(mNativeInterface).atResponseCode(mTestDevice, HeadsetHalConstants.AT_RESPONSE_OK, 0);
    }

    @Test
    public void testProcessVendorSpecificAt_withExceptedNoEqualSignCommandCGSN() {
        String atString = BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_CGSN;

        mHeadsetStateMachine.processVendorSpecificAt(atString, mTestDevice);

        verify(mNativeInterface).atResponseString(mTestDevice, Build.getSerial());
        verify(mNativeInterface).atResponseCode(mTestDevice, HeadsetHalConstants.AT_RESPONSE_OK, 0);
    }

    @Test
    public void testProcessVolumeEvent_withVolumeTypeMic() {
        when(mHeadsetService.getActiveDevice()).thenReturn(mTestDevice);
+29 −0
Original line number Diff line number Diff line
@@ -270,6 +270,35 @@ public final class BluetoothHeadset implements BluetoothProfile {
     */
    public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT_BATTERY_LEVEL = "BATTERY";

    /**
     * A vendor-specific AT command that asks for the information about device manufacturer.
     *
     * @hide
     */
    public static final String VENDOR_SPECIFIC_HEADSET_EVENT_CGMI = "+CGMI";

    /**
     * A vendor-specific AT command that asks for the information about the model of the device.
     *
     * @hide
     */
    public static final String VENDOR_SPECIFIC_HEADSET_EVENT_CGMM = "+CGMM";

    /**
     * A vendor-specific AT command that asks for the revision information, for Android we will
     * return the OS version and build number.
     *
     * @hide
     */
    public static final String VENDOR_SPECIFIC_HEADSET_EVENT_CGMR = "+CGMR";

    /**
     * A vendor-specific AT command that asks for the device's serial number.
     *
     * @hide
     */
    public static final String VENDOR_SPECIFIC_HEADSET_EVENT_CGSN = "+CGSN";

    /**
     * Headset state when SCO audio is not connected.
     * This state can be one of