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

Commit 5dafa9d6 authored by Angela Wang's avatar Angela Wang Committed by Android (Google) Code Review
Browse files

Merge changes from topic "ha-separated-mute" into main

* changes:
  Seperated ambient mute state for different devices
  Seperated ambient mute state for different devices
parents 5a02a939 b388fc17
Loading
Loading
Loading
Loading
+0 −6
Original line number Diff line number Diff line
@@ -405,11 +405,5 @@ public class AmbientVolumeController implements LocalBluetoothProfileManager.Ser
    }

    public record RemoteAmbientState(int gainSetting, int mute) {
        public boolean isMutable() {
            return mute != MUTE_DISABLED;
        }
        public boolean isMuted() {
            return mute == MUTE_MUTED;
        }
    }
}
+18 −12
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.settingslib.bluetooth;
import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_LEFT;
import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_RIGHT;

import android.bluetooth.AudioInputControl;
import android.bluetooth.BluetoothDevice;

import androidx.annotation.NonNull;
@@ -105,20 +106,9 @@ public interface AmbientVolumeUi {
    /** @return if the UI is in expanded mode. */
    boolean isControlExpanded();

    /**
     * Sets if the UI is capable to mute the ambient of the remote device.
     *
     * <p> If the value is {@code false}, it implies the remote device ambient will always be
     * unmute and can not be mute from the UI
     */
    void setMutable(boolean mutable);

    /** @return if the UI is capable to mute the ambient of remote device. */
    boolean isMutable();

    /** Sets if the UI shows mute state. */
    void setMuted(boolean muted);

    /** @return if the UI shows mute state */
    boolean isMuted();

@@ -149,7 +139,7 @@ public interface AmbientVolumeUi {
    void setSliderEnabled(int side, boolean enabled);

    /**
     * Sets the slider value.
     * Sets the slider's value.
     *
     * @param side the side of the slider
     * @param value the ambient value
@@ -165,6 +155,22 @@ public interface AmbientVolumeUi {
     */
    void setSliderRange(int side, int min, int max);

    /**
     * Sets the slider's mute state.
     *
     * @param side the side of the slider
     * @param muteState the mute state, see {@link AudioInputControl.Mute}
     */
    void setSliderMuteState(int side, int muteState);

    /**
     * Gets the slider's mute state.
     *
     * @param side the side of the slider
     * @return the mute state, see {@link AudioInputControl.Mute}
     */
    int getSliderMuteState(int side);

    /** Updates the UI according to current state. */
    void updateLayout();
}
+70 −64
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.settingslib.bluetooth;

import static android.bluetooth.AudioInputControl.MUTE_DISABLED;
import static android.bluetooth.AudioInputControl.MUTE_NOT_MUTED;
import static android.bluetooth.AudioInputControl.MUTE_MUTED;
import static android.bluetooth.BluetoothDevice.BOND_BONDED;
@@ -65,6 +66,7 @@ public class AmbientVolumeUiController implements

    private final Set<CachedBluetoothDevice> mCachedDevices = new ArraySet<>();
    private final BiMap<Integer, BluetoothDevice> mSideToDeviceMap = HashBiMap.create();
    private final Set<Integer> mRangeInitializedSliderSides = new ArraySet<>();
    private CachedBluetoothDevice mCachedDevice;
    private boolean mShowUiWhenLocalDataExist = true;

@@ -173,7 +175,7 @@ public class AmbientVolumeUiController implements
    @Override
    public void onExpandIconClick() {
        mSideToDeviceMap.forEach((s, d) -> {
            if (!mAmbientLayout.isMuted()) {
            if (!isDeviceMuted(d)) {
                // Apply previous collapsed/expanded volume to remote device
                HearingDeviceLocalDataManager.Data data = mLocalDataManager.get(d);
                int volume = mAmbientLayout.isControlExpanded()
@@ -181,8 +183,7 @@ public class AmbientVolumeUiController implements
                mVolumeController.setAmbient(d, volume);
            }
            // Update new value to local data
            mLocalDataManager.updateAmbientControlExpanded(d,
                    mAmbientLayout.isControlExpanded());
            mLocalDataManager.updateAmbientControlExpanded(d, mAmbientLayout.isControlExpanded());
        });
        mLocalDataManager.flush();
    }
@@ -213,15 +214,27 @@ public class AmbientVolumeUiController implements
            }
        };

        boolean performUnmuteAction = false;
        if (side == SIDE_UNIFIED) {
            if (mAmbientLayout.isMuted()) {
            // User drag on the volume slider when muted. Unmute the devices first.
            mAmbientLayout.setMuted(false);

                // User drag on the unified slider when muted. Unmute all devices first.
                mAmbientLayout.setSliderMuteState(side, MUTE_NOT_MUTED);
                for (BluetoothDevice device : mSideToDeviceMap.values()) {
                    mVolumeController.setMuted(device, false);
                }
            // Restore the value before muted
            loadLocalDataToUi();
                performUnmuteAction = true;
            }
        } else {
            final BluetoothDevice device = mSideToDeviceMap.get(side);
            if (isDeviceMuted(device)) {
                // User drag on the slider when muted. Unmute the device first.
                mAmbientLayout.setSliderMuteState(side, MUTE_NOT_MUTED);
                mVolumeController.setMuted(device, false);
                performUnmuteAction = true;

            }
        }
        if (performUnmuteAction) {
            // Delay set ambient on remote device since the immediately sequential command
            // might get failed sometimes
            postDelayedOnMainThread(setAmbientRunnable, 1000L);
@@ -342,21 +355,6 @@ public class AmbientVolumeUiController implements
        mShowUiWhenLocalDataExist = shouldShow;
    }

    /** Updates the ambient sliders according to current state. */
    private void updateSliderUi() {
        boolean isAnySliderEnabled = false;
        for (Map.Entry<Integer, BluetoothDevice> entry : mSideToDeviceMap.entrySet()) {
            final int side = entry.getKey();
            final BluetoothDevice device = entry.getValue();
            final boolean enabled = isDeviceConnectedToVcp(device)
                    && mVolumeController.isAmbientControlAvailable(device);
            isAnySliderEnabled |= enabled;
            mAmbientLayout.setSliderEnabled(side, enabled);
        }
        mAmbientLayout.setSliderEnabled(SIDE_UNIFIED, isAnySliderEnabled);
        mAmbientLayout.updateLayout();
    }

    /** Sets the ambient to the corresponding control slider. */
    private void setVolumeIfValid(int side, int volume) {
        if (volume == INVALID_VOLUME) {
@@ -381,13 +379,12 @@ public class AmbientVolumeUiController implements
        if (DEBUG) {
            Log.d(TAG, "loadLocalDataToUi, data=" + data + ", device=" + device);
        }
        if (isDeviceConnectedToVcp(device) && !mAmbientLayout.isMuted()) {
        if (isDeviceAmbientControlAvailable(device) && !isDeviceMuted(device)) {
            final int side = mSideToDeviceMap.inverse().getOrDefault(device, SIDE_INVALID);
            setVolumeIfValid(side, data.ambient());
            setVolumeIfValid(SIDE_UNIFIED, data.groupAmbient());
        }
        setAmbientControlExpanded(data.ambientControlExpanded());
        updateSliderUi();
    }

    private void loadRemoteDataToUi() {
@@ -400,15 +397,30 @@ public class AmbientVolumeUiController implements
        if (DEBUG) {
            Log.d(TAG, "loadRemoteDataToUi, left=" + leftState + ", right=" + rightState);
        }
        // Update ambient range. This should be done first since the muted state and enabled state
        // will set the value to minimum value
        mSideToDeviceMap.forEach((side, device) -> {
            if (!mRangeInitializedSliderSides.contains(side)) {
                int ambientMax = mVolumeController.getAmbientMax(device);
                int ambientMin = mVolumeController.getAmbientMin(device);
                if (ambientMin != ambientMax) {
                    mAmbientLayout.setSliderRange(side, ambientMin, ambientMax);
                    mAmbientLayout.setSliderRange(SIDE_UNIFIED, ambientMin, ambientMax);
                    mRangeInitializedSliderSides.add(side);
                }
            }
        });

        // Check the remote mute state to decide if we need to expand the control. This should be
        // done before updating ambient value since it'll affect the controls expanded state
        final int leftMuteState = leftState != null ? leftState.mute() : MUTE_DISABLED;
        final int rightMuteState = rightState != null ? rightState.mute() : MUTE_DISABLED;
        if (leftMuteState != MUTE_DISABLED && rightMuteState != MUTE_DISABLED
                && leftMuteState != rightMuteState) {
            // Expand the controls if two devices are mutable but with different mute states
            setAmbientControlExpanded(true);
        }

        // Update ambient volume
        final int leftAmbient = leftState != null ? leftState.gainSetting() : INVALID_VOLUME;
        final int rightAmbient = rightState != null ? rightState.gainSetting() : INVALID_VOLUME;
@@ -416,8 +428,9 @@ public class AmbientVolumeUiController implements
            setVolumeIfValid(SIDE_LEFT, leftAmbient);
            setVolumeIfValid(SIDE_RIGHT, rightAmbient);
        } else {
            if (leftAmbient != rightAmbient && leftAmbient != INVALID_VOLUME
                    && rightAmbient != INVALID_VOLUME) {
            if (leftAmbient != INVALID_VOLUME && rightAmbient != INVALID_VOLUME
                    && leftAmbient != rightAmbient) {
                // Expand the controls if two devices have different ambient values
                setVolumeIfValid(SIDE_LEFT, leftAmbient);
                setVolumeIfValid(SIDE_RIGHT, rightAmbient);
                setAmbientControlExpanded(true);
@@ -429,25 +442,24 @@ public class AmbientVolumeUiController implements
        // Initialize local data between side and group value
        initLocalAmbientDataIfNeeded();

        // Update mute state
        boolean mutable = true;
        boolean muted = true;
        if (isDeviceConnectedToVcp(leftDevice) && leftState != null) {
            mutable &= leftState.isMutable();
            muted &= leftState.isMuted();
        }
        if (isDeviceConnectedToVcp(rightDevice) && rightState != null) {
            mutable &= rightState.isMutable();
            muted &= rightState.isMuted();
        }
        mAmbientLayout.setMutable(mutable);
        mAmbientLayout.setMuted(muted);

        // Ensure remote device mute state is synced
        syncMuteStateIfNeeded(leftDevice, leftState, muted);
        syncMuteStateIfNeeded(rightDevice, rightState, muted);
        // Update slider mute state. This should be done after loading remote ambient into local
        // database since we'll show minimum value of the slider instead of the remote value if the
        // device is muted
        mAmbientLayout.setSliderMuteState(SIDE_LEFT, leftMuteState);
        mAmbientLayout.setSliderMuteState(SIDE_RIGHT, rightMuteState);

        updateSliderUi();
        // Update slider enabled state. This should be done after loading remote ambient into local
        // database since we'll show minimum value of the slider instead of the remote value if the
        // slider is not enabled.
        boolean isAnySliderEnabled = false;
        for (Map.Entry<Integer, BluetoothDevice> entry : mSideToDeviceMap.entrySet()) {
            final int side = entry.getKey();
            final BluetoothDevice device = entry.getValue();
            final boolean enabled = isDeviceAmbientControlAvailable(device);
            isAnySliderEnabled |= enabled;
            mAmbientLayout.setSliderEnabled(side, enabled);
        }
        mAmbientLayout.setSliderEnabled(SIDE_UNIFIED, isAnySliderEnabled);
    }

    private void setAmbientControlExpanded(boolean expanded) {
@@ -469,7 +481,7 @@ public class AmbientVolumeUiController implements
                }
            }
            // Found remote ambient control points
            if (mVolumeController.isAmbientControlAvailable(device)) {
            if (isDeviceAmbientControlAvailable(device)) {
                return true;
            }
        }
@@ -499,19 +511,13 @@ public class AmbientVolumeUiController implements
        mLocalDataManager.flush();
    }

    private void syncMuteStateIfNeeded(@Nullable BluetoothDevice device,
            @Nullable AmbientVolumeController.RemoteAmbientState state, boolean muted) {
        if (isDeviceConnectedToVcp(device) && state != null && state.isMutable()) {
            if (state.isMuted() != muted) {
                mVolumeController.setMuted(device, muted);
            }
        }
    private boolean isDeviceMuted(BluetoothDevice device) {
        final int side = mSideToDeviceMap.inverse().getOrDefault(device, SIDE_INVALID);
        return mAmbientLayout.getSliderMuteState(side) == MUTE_MUTED;
    }

    private boolean isDeviceConnectedToVcp(@Nullable BluetoothDevice device) {
        return device != null && device.isConnected()
                && mProfileManager.getVolumeControlProfile().getConnectionStatus(device)
                == BluetoothProfile.STATE_CONNECTED;
    private boolean isDeviceAmbientControlAvailable(BluetoothDevice device) {
        return device.isConnected() && mVolumeController.isAmbientControlAvailable(device);
    }

    private void postOnMainThread(Runnable runnable) {
+4 −17
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

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 android.bluetooth.BluetoothDevice.BOND_BONDED;
@@ -257,26 +256,14 @@ public class AmbientVolumeUiControllerTest {
    }

    @Test
    public void refresh_oneSideNotMutable_controlNotMutableAndNotMuted() {
        prepareRemoteData(mDevice, 10, MUTE_DISABLED);
        prepareRemoteData(mMemberDevice, 20, MUTE_NOT_MUTED);

        mController.refresh();

        verify(mAmbientLayout).setMutable(false);
        verify(mAmbientLayout).setMuted(false);
    }

    @Test
    public void refresh_oneSideNotMuted_controlNotMutedAndSyncToRemote() {
    public void refresh_leftAndRightDifferentMuteState_expandControl() {
        prepareRemoteData(mDevice, 10, MUTE_MUTED);
        prepareRemoteData(mMemberDevice, 20, MUTE_NOT_MUTED);
        prepareRemoteData(mMemberDevice, 10, MUTE_NOT_MUTED);
        when(mAmbientLayout.isControlExpanded()).thenReturn(false);

        mController.refresh();

        verify(mAmbientLayout).setMutable(true);
        verify(mAmbientLayout).setMuted(false);
        verify(mVolumeController).setMuted(mDevice, false);
        verify(mAmbientLayout).setControlExpanded(true);
    }

    private void prepareDevice(boolean hasMember) {
+57 −33
Original line number Diff line number Diff line
@@ -16,6 +16,9 @@

package com.android.systemui.accessibility.hearingaid;

import static android.bluetooth.AudioInputControl.MUTE_DISABLED;
import static android.bluetooth.AudioInputControl.MUTE_MUTED;
import static android.bluetooth.AudioInputControl.MUTE_NOT_MUTED;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;

@@ -79,7 +82,6 @@ public class AmbientVolumeLayoutTest extends SysuiTestCase {
        mLayout = new AmbientVolumeLayout(mContext);
        mLayout.setListener(mListener);
        mLayout.setControlExpandable(true);
        mLayout.setMutable(true);

        prepareDevices();
        mLayout.setupSliders(mSideToDeviceMap);
@@ -128,36 +130,7 @@ public class AmbientVolumeLayoutTest extends SysuiTestCase {
    }

    @Test
    public void setMutable_mutable_clickOnMuteIconChangeMuteState() {
        mLayout.setMutable(true);
        mLayout.setMuted(false);

        mVolumeIcon.callOnClick();

        assertThat(mLayout.isMuted()).isTrue();
    }

    @Test
    public void setMutable_notMutable_clickOnMuteIconWontChangeMuteState() {
        mLayout.setMutable(false);
        mLayout.setMuted(false);

        mVolumeIcon.callOnClick();

        assertThat(mLayout.isMuted()).isFalse();
    }

    @Test
    public void updateLayout_mute_volumeIconIsCorrect() {
        mLayout.setMuted(true);
        mLayout.updateLayout();

        assertThat(mVolumeIcon.getDrawable().getLevel()).isEqualTo(0);
    }

    @Test
    public void updateLayout_unmuteAndExpanded_volumeIconIsCorrect() {
        mLayout.setMuted(false);
    public void updateLayout_expanded_volumeIconIsCorrect() {
        mLayout.setControlExpanded(true);
        mLayout.updateLayout();

@@ -166,8 +139,7 @@ public class AmbientVolumeLayoutTest extends SysuiTestCase {
    }

    @Test
    public void updateLayout_unmuteAndNotExpanded_volumeIconIsCorrect() {
        mLayout.setMuted(false);
    public void updateLayout_notExpanded_volumeIconIsCorrect() {
        mLayout.setControlExpanded(false);
        mLayout.updateLayout();

@@ -194,6 +166,58 @@ public class AmbientVolumeLayoutTest extends SysuiTestCase {
        assertThat(mVolumeIcon.getDrawable().getLevel()).isEqualTo(expectedLevel);
    }

    @Test
    public void isMutable_bothSideNotMutable_returnFalse() {
        mLayout.setSliderMuteState(SIDE_LEFT, MUTE_DISABLED);
        mLayout.setSliderMuteState(SIDE_RIGHT, MUTE_DISABLED);

        assertThat(mLayout.isMutable()).isFalse();
    }

    @Test
    public void isMutable_oneSideMutable_returnTrue() {
        mLayout.setSliderMuteState(SIDE_LEFT, MUTE_DISABLED);
        mLayout.setSliderMuteState(SIDE_RIGHT, MUTE_NOT_MUTED);

        assertThat(mLayout.isMutable()).isTrue();
    }

    @Test
    public void isMuted_bothSideMuted_returnTrue() {
        mLayout.setSliderMuteState(SIDE_LEFT, MUTE_MUTED);
        mLayout.setSliderMuteState(SIDE_RIGHT, MUTE_MUTED);

        assertThat(mLayout.isMuted()).isTrue();
    }

    @Test
    public void isMuted_oneSideNotMuted_returnFalse() {
        mLayout.setSliderMuteState(SIDE_LEFT, MUTE_MUTED);
        mLayout.setSliderMuteState(SIDE_RIGHT, MUTE_NOT_MUTED);

        assertThat(mLayout.isMuted()).isFalse();
    }

    @Test
    public void setSliderMuteState_muteLeft_volumeIconIsCorrect() {
        mLayout.setControlExpanded(true);
        mLayout.setSliderMuteState(SIDE_LEFT, MUTE_MUTED);
        mLayout.setSliderMuteState(SIDE_RIGHT, MUTE_NOT_MUTED);

        int expectedLevel = calculateVolumeLevel(0, TEST_RIGHT_VOLUME_LEVEL);
        assertThat(mVolumeIcon.getDrawable().getLevel()).isEqualTo(expectedLevel);
    }

    @Test
    public void setSliderMuteState_muteLeftAndRight_volumeIconIsCorrect() {
        mLayout.setControlExpanded(true);
        mLayout.setSliderMuteState(SIDE_LEFT, MUTE_MUTED);
        mLayout.setSliderMuteState(SIDE_RIGHT, MUTE_MUTED);

        int expectedLevel = calculateVolumeLevel(0, 0);
        assertThat(mVolumeIcon.getDrawable().getLevel()).isEqualTo(expectedLevel);
    }

    private int calculateVolumeLevel(int left, int right) {
        return left * 5 + right;
    }
Loading