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

Commit 5462e6c9 authored by Jakub Rotkiewicz's avatar Jakub Rotkiewicz Committed by William Escande
Browse files

HeadsetClientStateMachine: AM/HF conversion symmetric

Assure that volume converions from AM to HF and back are symmetric.

Bug: 339590522
Bug: 340482648
Flag: com.android.bluetooth.flags.headset_client_am_hf_volume_symmetric
Test: atest HeadsetClientStateMachineTest
Change-Id: Id8f1c64b5cb23b90112adf1131fdf82cd9128b51
parent 7926c5c5
Loading
Loading
Loading
Loading
+19 −6
Original line number Diff line number Diff line
@@ -68,6 +68,7 @@ import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.MetricsLogger;
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.flags.Flags;
import com.android.bluetooth.hfp.HeadsetService;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IState;
@@ -134,8 +135,8 @@ public class HeadsetClientStateMachine extends StateMachine {
    static final int CONNECTING_TIMEOUT_MS = 10000;  // 10s
    private static final int ROUTING_DELAY_MS = 250;

    private static final int MAX_HFP_SCO_VOICE_CALL_VOLUME = 15; // HFP 1.5 spec.
    private static final int MIN_HFP_SCO_VOICE_CALL_VOLUME = 1; // HFP 1.5 spec.
    @VisibleForTesting static final int MAX_HFP_SCO_VOICE_CALL_VOLUME = 15; // HFP 1.5 spec.
    @VisibleForTesting static final int MIN_HFP_SCO_VOICE_CALL_VOLUME = 1; // HFP 1.5 spec.

    static final int HF_ORIGINATED_CALL_ID = -1;
    private static final long OUTGOING_TIMEOUT_MILLI = 10 * 1000; // 10 seconds
@@ -995,8 +996,14 @@ public class HeadsetClientStateMachine extends StateMachine {
    static int hfToAmVol(int hfVol) {
        int amRange = sMaxAmVcVol - sMinAmVcVol;
        int hfRange = MAX_HFP_SCO_VOICE_CALL_VOLUME - MIN_HFP_SCO_VOICE_CALL_VOLUME;
        int amVol = 0;
        if (Flags.headsetClientAmHfVolumeSymmetric()) {
            amVol = (int) Math.round((hfVol - MIN_HFP_SCO_VOICE_CALL_VOLUME)
                                      * ((double) amRange / hfRange)) + sMinAmVcVol;
        } else {
            int amOffset = (amRange * (hfVol - MIN_HFP_SCO_VOICE_CALL_VOLUME)) / hfRange;
        int amVol = sMinAmVcVol + amOffset;
            amVol = sMinAmVcVol + amOffset;
        }
        Log.d(TAG, "HF -> AM " + hfVol + " " + amVol);
        return amVol;
    }
@@ -1004,8 +1011,14 @@ public class HeadsetClientStateMachine extends StateMachine {
    static int amToHfVol(int amVol) {
        int amRange = (sMaxAmVcVol > sMinAmVcVol) ? (sMaxAmVcVol - sMinAmVcVol) : 1;
        int hfRange = MAX_HFP_SCO_VOICE_CALL_VOLUME - MIN_HFP_SCO_VOICE_CALL_VOLUME;
        int hfVol = 0;
        if (Flags.headsetClientAmHfVolumeSymmetric()) {
            hfVol = (int) Math.round((amVol - sMinAmVcVol) * ((double) hfRange / amRange))
                            + MIN_HFP_SCO_VOICE_CALL_VOLUME;
        } else {
            int hfOffset = (hfRange * (amVol - sMinAmVcVol)) / amRange;
        int hfVol = MIN_HFP_SCO_VOICE_CALL_VOLUME + hfOffset;
            hfVol = MIN_HFP_SCO_VOICE_CALL_VOLUME + hfOffset;
        }
        Log.d(TAG, "AM -> HF " + amVol + " " + hfVol);
        return hfVol;
    }
+98 −0
Original line number Diff line number Diff line
@@ -22,6 +22,8 @@ import static android.content.pm.PackageManager.FEATURE_WATCH;
import static com.android.bluetooth.hfpclient.HeadsetClientStateMachine.AT_OK;
import static com.android.bluetooth.hfpclient.HeadsetClientStateMachine.ENTER_PRIVATE_MODE;
import static com.android.bluetooth.hfpclient.HeadsetClientStateMachine.EXPLICIT_CALL_TRANSFER;
import static com.android.bluetooth.hfpclient.HeadsetClientStateMachine.MAX_HFP_SCO_VOICE_CALL_VOLUME;
import static com.android.bluetooth.hfpclient.HeadsetClientStateMachine.MIN_HFP_SCO_VOICE_CALL_VOLUME;
import static com.android.bluetooth.hfpclient.HeadsetClientStateMachine.VOICE_RECOGNITION_START;
import static com.android.bluetooth.hfpclient.HeadsetClientStateMachine.VOICE_RECOGNITION_STOP;

@@ -47,6 +49,8 @@ import android.os.Bundle;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.Pair;

import androidx.test.InstrumentationRegistry;
@@ -61,6 +65,7 @@ import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.RemoteDevices;
import com.android.bluetooth.flags.Flags;
import com.android.bluetooth.hfp.HeadsetService;

import org.hamcrest.core.AllOf;
@@ -78,7 +83,9 @@ import org.mockito.hamcrest.MockitoHamcrest;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
@@ -95,6 +102,8 @@ public class HeadsetClientStateMachineTest {

    @Rule public MockitoRule mockitoRule = MockitoJUnit.rule();

    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();

    @Mock private AdapterService mAdapterService;
    @Mock private Resources mMockHfpResources;
    @Mock private HeadsetService mHeadsetService;
@@ -157,6 +166,95 @@ public class HeadsetClientStateMachineTest {
        verifyNoMoreInteractions(mHeadsetService);
    }

    private void quitHeadsetClientStateMachine() {
        if (mHeadsetClientStateMachine != null) {
            TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
            mHeadsetClientStateMachine.allowConnect = null;
            mHeadsetClientStateMachine.doQuit();
            TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
            mHeadsetClientStateMachine = null;
        }
    }

    private void startHeadsetClientStateMachine() {
        if (mHeadsetClientStateMachine == null) {
            mHeadsetClientStateMachine =
                    new TestHeadsetClientStateMachine(
                            mHeadsetClientService,
                            mHeadsetService,
                            mHandlerThread.getLooper(),
                            mNativeInterface);
            mHeadsetClientStateMachine.start();
            TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
        }
    }

    /**
     * Test AM to HF volume symmetric. The test takes the AM volume range 1-10 and HF 1-15. All the
     * AM values are mapped with corresponding HF values. After all collected, the test converts
     * back HF values and checks if they match AM. This proves that the conversion is symmetric.
     */
    @Test
    @EnableFlags(Flags.FLAG_HEADSET_CLIENT_AM_HF_VOLUME_SYMMETRIC)
    public void testAmHfVolumeSymmetric_AmLowerRange() {
        int amMin = 1;
        int amMax = 10;
        Map<Integer, Integer> amToHfMap = new HashMap<>();

        Assert.assertTrue(amMax < MAX_HFP_SCO_VOICE_CALL_VOLUME);

        quitHeadsetClientStateMachine();

        when(mAudioManager.getStreamMaxVolume(anyInt())).thenReturn(amMax);
        when(mAudioManager.getStreamMinVolume(anyInt())).thenReturn(amMin);

        startHeadsetClientStateMachine();

        for (int i = amMin; i <= amMax; i++) {
            // Collect AM to HF conversion
            amToHfMap.put(i, HeadsetClientStateMachine.amToHfVol(i));
        }

        for (Map.Entry entry : amToHfMap.entrySet()) {
            // Convert back from collected HF to AM and check if equal the saved AM value
            Assert.assertEquals(
                    HeadsetClientStateMachine.hfToAmVol((int) entry.getValue()), entry.getKey());
        }
    }

    /**
     * Test HF to AM volume symmetric. The test takes the AM volume range 1-20 and HF 1-15. All the
     * HF values are mapped with corresponding AM values. After all collected, the test converts
     * back AM values and checks if they match HF. This proves that the conversion is symmetric.
     */
    @Test
    @EnableFlags(Flags.FLAG_HEADSET_CLIENT_AM_HF_VOLUME_SYMMETRIC)
    public void testAmHfVolumeSymmetric_HfLowerRange() {
        int amMin = 1;
        int amMax = 20;
        Map<Integer, Integer> hfToAmMap = new HashMap<>();

        Assert.assertTrue(amMax > MAX_HFP_SCO_VOICE_CALL_VOLUME);

        quitHeadsetClientStateMachine();

        when(mAudioManager.getStreamMaxVolume(anyInt())).thenReturn(amMax);
        when(mAudioManager.getStreamMinVolume(anyInt())).thenReturn(amMin);

        startHeadsetClientStateMachine();

        for (int i = MIN_HFP_SCO_VOICE_CALL_VOLUME; i <= MAX_HFP_SCO_VOICE_CALL_VOLUME; i++) {
            // Collect HF to AM conversion
            hfToAmMap.put(i, HeadsetClientStateMachine.hfToAmVol(i));
        }

        for (Map.Entry entry : hfToAmMap.entrySet()) {
            // Convert back from collected AM to HF and check if equal the saved HF value
            Assert.assertEquals(
                    HeadsetClientStateMachine.amToHfVol((int) entry.getValue()), entry.getKey());
        }
    }

    /**
     * Test that default state is disconnected
     */