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

Commit ad77b619 authored by Angela Wang's avatar Angela Wang
Browse files

Add "mute" related operations in AmbientVolumeController

Flag: com.android.settingslib.flags.hearing_devices_ambient_volume_control
Bug: 357878944
Test: atest AmbientVolumeControllerTest
Change-Id: I44431bf375f0df89405b36e1193eb2966bf5efd7
parent 4b683601
Loading
Loading
Loading
Loading
+93 −5
Original line number Diff line number Diff line
@@ -16,6 +16,10 @@

package com.android.settingslib.bluetooth;

import static android.bluetooth.AudioInputControl.MUTE_DISABLED;
import static android.bluetooth.AudioInputControl.MUTE_MUTED;
import static android.bluetooth.AudioInputControl.MUTE_NOT_MUTED;

import static com.android.settingslib.bluetooth.HearingDeviceLocalDataManager.Data.INVALID_VOLUME;

import android.bluetooth.AudioInputControl;
@@ -171,7 +175,8 @@ public class AmbientVolumeController implements LocalBluetoothProfileManager.Ser
            return null;
        }
        int gainSetting = getAmbient(device);
        return new RemoteAmbientState(gainSetting);
        int mute = getMute(device);
        return new RemoteAmbientState(gainSetting, mute);
    }

    /**
@@ -190,7 +195,9 @@ public class AmbientVolumeController implements LocalBluetoothProfileManager.Ser
        if (!ambientControls.isEmpty()) {
            synchronized (mDeviceAmbientStateMap) {
                value = ambientControls.getFirst().getGainSetting();
                RemoteAmbientState updatedState = new RemoteAmbientState(value);
                RemoteAmbientState state = mDeviceAmbientStateMap.getOrDefault(device,
                        new RemoteAmbientState(INVALID_VOLUME, MUTE_DISABLED));
                RemoteAmbientState updatedState = new RemoteAmbientState(value, state.mute);
                mDeviceAmbientStateMap.put(device, updatedState);
            }
        }
@@ -208,9 +215,55 @@ public class AmbientVolumeController implements LocalBluetoothProfileManager.Ser
            Log.d(TAG, "setAmbient, value:" + value + ", device:" + device);
        }
        List<AudioInputControl> ambientControls = getAmbientControls(device);
        if (!ambientControls.isEmpty()) {
        ambientControls.forEach(control -> control.setGainSetting(value));
    }

    /**
     * Gets the mute state from first ambient control point of the remote device and
     * stores it in cached {@link RemoteAmbientState}. The value will be one of
     * {@link AudioInputControl.Mute}.
     *
     * When any audio input point receives {@link AmbientCallback#onMuteChanged(int)} callback,
     * only the changed value which is different from the value stored in the cached state will
     * be notified to the {@link AmbientVolumeControlCallback} of this controller.
     *
     * @param device the remote device
     */
    public int getMute(@NonNull BluetoothDevice device) {
        List<AudioInputControl> ambientControls = getAmbientControls(device);
        int value = MUTE_DISABLED;
        if (!ambientControls.isEmpty()) {
            synchronized (mDeviceAmbientStateMap) {
                value = ambientControls.getFirst().getMute();
                RemoteAmbientState state = mDeviceAmbientStateMap.getOrDefault(device,
                        new RemoteAmbientState(INVALID_VOLUME, MUTE_DISABLED));
                RemoteAmbientState updatedState = new RemoteAmbientState(state.gainSetting, value);
                mDeviceAmbientStateMap.put(device, updatedState);
            }
        }
        return value;
    }

    /**
     * Sets the mute state to all ambient control points of the remote device.
     *
     * @param device the remote device
     * @param muted the mute state to be updated
     */
    public void setMuted(@NonNull BluetoothDevice device, boolean muted) {
        if (DEBUG) {
            Log.d(TAG, "setMuted, muted:" + muted + ", device:" + device);
        }
        List<AudioInputControl> ambientControls = getAmbientControls(device);
        ambientControls.forEach(control -> {
            try {
                control.setMute(muted ? MUTE_MUTED : MUTE_NOT_MUTED);
            } catch (IllegalStateException e) {
                // Sometimes remote will throw this exception due to initialization not done
                // yet. Catch it to prevent crashes on UI.
                Log.w(TAG, "Remote mute state is currently disabled.");
            }
        });
    }

    /**
@@ -275,6 +328,16 @@ public class AmbientVolumeController implements LocalBluetoothProfileManager.Ser
        default void onAmbientChanged(@NonNull BluetoothDevice device, int gainSettings) {
        }

        /**
         * This method is called when one of the remote device's ambient control point's mute
         * state is changed.
         *
         * @param device the remote device
         * @param mute the new mute state
         */
        default void onMuteChanged(@NonNull BluetoothDevice device, int mute) {
        }

        /**
         * This method is called when any command to the remote device's ambient control point
         * is failed.
@@ -319,9 +382,34 @@ public class AmbientVolumeController implements LocalBluetoothProfileManager.Ser
                mCallback.onCommandFailed(mDevice);
            }
        }

        @Override
        public void onMuteChanged(int mute) {
            if (mCallback != null) {
                synchronized (mDeviceAmbientStateMap) {
                    RemoteAmbientState previousState = mDeviceAmbientStateMap.get(mDevice);
                    if (previousState.mute != mute) {
                        mCallback.onMuteChanged(mDevice, mute);
                    }
                }
            }
        }

    public record RemoteAmbientState(int gainSetting) {
        @Override
        public void onSetMuteFailed() {
            Log.w(TAG, "onSetMuteFailed, device=" + mDevice);
            if (mCallback != null) {
                mCallback.onCommandFailed(mDevice);
            }
        }
    }

    public record RemoteAmbientState(int gainSetting, int mute) {
        public boolean isMutable() {
            return mute != MUTE_DISABLED;
        }
        public boolean isMuted() {
            return mute == MUTE_MUTED;
        }
    }
}
+58 −0
Original line number Diff line number Diff line
@@ -170,6 +170,37 @@ public class AmbientVolumeControllerTest {
        }
    }

    @Test
    public void getMute_verifyGetOnFirstControl() {
        List<AudioInputControl> controls = prepareValidAmbientControls();

        mVolumeController.getMute(mDevice);

        verify(controls.getFirst()).getMute();
    }

    @Test
    public void setMuted_true_verifySetOnAllControls() {
        List<AudioInputControl> controls = prepareValidAmbientControls();

        mVolumeController.setMuted(mDevice, true);

        for (AudioInputControl control : controls) {
            verify(control).setMute(AudioInputControl.MUTE_MUTED);
        }
    }

    @Test
    public void setMuted_false_verifySetOnAllControls() {
        List<AudioInputControl> controls = prepareValidAmbientControls();

        mVolumeController.setMuted(mDevice, false);

        for (AudioInputControl control : controls) {
            verify(control).setMute(AudioInputControl.MUTE_NOT_MUTED);
        }
    }

    @Test
    public void ambientCallback_onGainSettingChanged_verifyCallbackIsCalledWhenStateChange() {
        AmbientVolumeController.AmbientCallback ambientCallback =
@@ -198,6 +229,33 @@ public class AmbientVolumeControllerTest {
        verify(mCallback).onCommandFailed(mDevice);
    }

    @Test
    public void ambientCallback_onMuteChanged_verifyCallbackIsCalledWhenStateChange() {
        AmbientVolumeController.AmbientCallback ambientCallback =
                mVolumeController.new AmbientCallback(mDevice, mCallback);
        final int testMute = 0;
        List<AudioInputControl> controls = prepareValidAmbientControls();
        when(controls.getFirst().getMute()).thenReturn(testMute);

        mVolumeController.refreshAmbientState(mDevice);
        ambientCallback.onMuteChanged(testMute);
        verify(mCallback, never()).onMuteChanged(mDevice, testMute);

        final int updatedTestMute = 1;
        ambientCallback.onMuteChanged(updatedTestMute);
        verify(mCallback).onMuteChanged(mDevice, updatedTestMute);
    }

    @Test
    public void ambientCallback_onSetMuteFailed_verifyCallbackIsCalled() {
        AmbientVolumeController.AmbientCallback ambientCallback =
                mVolumeController.new AmbientCallback(mDevice, mCallback);

        ambientCallback.onSetMuteFailed();

        verify(mCallback).onCommandFailed(mDevice);
    }

    private List<AudioInputControl> prepareValidAmbientControls() {
        List<AudioInputControl> controls = new ArrayList<>();
        final int controlsCount = 2;