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

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

Preset: Separated preset control UI in hearing device dialog

Flag: com.android.settingslib.flags.hearing_devices_separated_preset_control
Bug: 417628947
Test: atest HearingDevicesDialogDelegateTest
Test: atest PresetLayoutTest
Test: atest PresetSpinnerTest
Change-Id: I4666e1fc304c86d4da4b1a8428e821275c0a3d64
parent f715385b
Loading
Loading
Loading
Loading
+46 −9
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static android.bluetooth.BluetoothHapClient.PRESET_INDEX_UNAVAILABLE;
import static android.bluetooth.BluetoothProfile.STATE_CONNECTED;

import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_LEFT;
import static com.android.settingslib.bluetooth.hearingdevices.ui.ExpandableControlUi.SIDE_UNIFIED;
import static com.android.systemui.accessibility.hearingaid.HearingDevicesDialogDelegate.LIVE_CAPTION_INTENT;

import static com.google.common.truth.Truth.assertThat;
@@ -45,6 +46,7 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.provider.Settings;
import android.testing.TestableLooper;
@@ -65,6 +67,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.settingslib.bluetooth.VolumeControlProfile;
import com.android.settingslib.flags.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.bluetooth.qsdialog.DeviceItem;
@@ -290,6 +293,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
    }

    @Test
    @DisableFlags(Flags.FLAG_HEARING_DEVICES_SEPARATED_PRESET_CONTROL)
    public void showDialog_noPreset_presetLayoutGone() {
        when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(new ArrayList<>());
        when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(PRESET_INDEX_UNAVAILABLE);
@@ -302,7 +306,8 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
    }

    @Test
    public void showDialog_presetExist_presetSelected() {
    @EnableFlags(Flags.FLAG_HEARING_DEVICES_SEPARATED_PRESET_CONTROL)
    public void showDialog_presetExist_newPresetLayoutVisible() {
        BluetoothHapPresetInfo info = getTestPresetInfo();
        when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(List.of(info));
        when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(TEST_PRESET_INDEX);
@@ -310,10 +315,22 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
        setUpDeviceDialogWithoutPairNewDeviceButton();
        showDialogAndProcessAllTasks();

        ViewGroup presetLayout = getPresetLayout(mDialog);
        PresetLayout presetLayout = getNewPresetLayout(mDialog);
        assertThat(presetLayout.getVisibility()).isEqualTo(View.VISIBLE);
        Spinner spinner = getPresetSpinner(mDialog);
        assertThat(spinner.getSelectedItemPosition()).isEqualTo(0);
        assertThat(presetLayout.getControlValue(SIDE_UNIFIED)).isEqualTo(TEST_PRESET_INDEX);
    }

    @Test
    @EnableFlags(Flags.FLAG_HEARING_DEVICES_SEPARATED_PRESET_CONTROL)
    public void showDialog_noPreset_newPresetLayoutGone() {
        when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(new ArrayList<>());
        when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(PRESET_INDEX_UNAVAILABLE);

        setUpDeviceDialogWithoutPairNewDeviceButton();
        showDialogAndProcessAllTasks();

        PresetLayout presetLayout = getNewPresetLayout(mDialog);
        assertThat(presetLayout.getVisibility()).isEqualTo(View.GONE);
    }

    @Test
@@ -357,24 +374,40 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
    }

    @Test
    @DisableFlags(Flags.FLAG_HEARING_DEVICES_SEPARATED_PRESET_CONTROL)
    public void onActiveDeviceChanged_presetExist_presetSelected() {
        setUpDeviceDialogWithoutPairNewDeviceButton();
        showDialogAndProcessAllTasks();
        BluetoothHapPresetInfo info = getTestPresetInfo();
        when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(List.of(info));
        when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(TEST_PRESET_INDEX);

        Spinner spinner = getPresetSpinner(mDialog);
        assertThat(spinner.getSelectedItemPosition()).isEqualTo(-1);
        setUpDeviceDialogWithoutPairNewDeviceButton();
        showDialogAndProcessAllTasks();

        mDialogDelegate.onActiveDeviceChanged(mCachedDevice, BluetoothProfile.LE_AUDIO);
        mExecutor.runAllReady();

        ViewGroup presetLayout = getPresetLayout(mDialog);
        assertThat(presetLayout.getVisibility()).isEqualTo(View.VISIBLE);
        Spinner spinner = getPresetSpinner(mDialog);
        assertThat(spinner.getSelectedItemPosition()).isEqualTo(0);
    }

    @Test
    @EnableFlags(Flags.FLAG_HEARING_DEVICES_SEPARATED_PRESET_CONTROL)
    public void onActiveDeviceChanged_presetExist_newPresetLayoutVisible() {
        BluetoothHapPresetInfo info = getTestPresetInfo();
        when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(List.of(info));
        when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(TEST_PRESET_INDEX);
        setUpDeviceDialogWithoutPairNewDeviceButton();
        showDialogAndProcessAllTasks();

        mDialogDelegate.onActiveDeviceChanged(mCachedDevice, BluetoothProfile.LE_AUDIO);
        mExecutor.runAllReady();

        PresetLayout presetLayout = getNewPresetLayout(mDialog);
        assertThat(presetLayout.getVisibility()).isEqualTo(View.VISIBLE);
        assertThat(presetLayout.getControlValue(SIDE_UNIFIED)).isEqualTo(TEST_PRESET_INDEX);
    }

    private void setUpDeviceDialogWithPairNewDeviceButton() {
        setUpDeviceDialog(/* showPairNewDevice= */ true);
    }
@@ -436,6 +469,10 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
        return dialog.requireViewById(R.id.preset_layout);
    }

    private PresetLayout getNewPresetLayout(SystemUIDialog dialog) {
        return dialog.requireViewById(R.id.preset_layout_new);
    }

    private ViewGroup getAmbientLayout(SystemUIDialog dialog) {
        return dialog.requireViewById(R.id.ambient_layout);
    }
+91 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.accessibility.hearingaid

import android.content.Context
import android.view.View.GONE
import android.view.View.VISIBLE
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_LEFT
import com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_RIGHT
import com.android.settingslib.bluetooth.hearingdevices.ui.ExpandableControlUi.Companion.SIDE_UNIFIED
import com.android.settingslib.bluetooth.hearingdevices.ui.PresetUi
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock

/** Tests for [PresetLayout]. */
@RunWith(AndroidJUnit4::class)
@SmallTest
class PresetLayoutTest : SysuiTestCase() {

    private val context: Context = ApplicationProvider.getApplicationContext()
    private val mockPresetUiListener = mock<PresetUi.PresetUiListener>()
    private lateinit var presetLayout: PresetLayout

    @Before
    fun setUp() {
        presetLayout = PresetLayout(context)
        presetLayout.setListener(mockPresetUiListener)
    }

    @Test
    fun setupControls_assertControlsNotNull() {
        presetLayout.setupControls(setOf(SIDE_LEFT, SIDE_RIGHT))

        val controls: Map<Int, PresetSpinner> = presetLayout.getControls()
        assertThat(controls[SIDE_UNIFIED]).isNotNull()
        assertThat(controls[SIDE_LEFT]).isNotNull()
        assertThat(controls[SIDE_RIGHT]).isNotNull()
    }

    @Test
    fun setControlExpanded_assertControlUiCorrect() {
        presetLayout.setupControls(setOf(SIDE_LEFT, SIDE_RIGHT))

        presetLayout.setControlExpanded(true)
        assertControlUiCorrect()

        presetLayout.setControlExpanded(false)
        assertControlUiCorrect()
    }

    private fun assertControlUiCorrect() {
        val expanded: Boolean = presetLayout.isControlExpanded()
        val controls: Map<Int, PresetSpinner> = presetLayout.getControls()
        if (expanded) {
            assertThat(controls[SIDE_UNIFIED]).isNotNull()
            assertThat(controls[SIDE_UNIFIED]!!.visibility).isEqualTo(GONE)
            assertThat(controls[SIDE_LEFT]).isNotNull()
            assertThat(controls[SIDE_LEFT]!!.visibility).isEqualTo(VISIBLE)
            assertThat(controls[SIDE_RIGHT]).isNotNull()
            assertThat(controls[SIDE_RIGHT]!!.visibility).isEqualTo(VISIBLE)
        } else {
            assertThat(controls[SIDE_UNIFIED]).isNotNull()
            assertThat(controls[SIDE_UNIFIED]!!.visibility).isEqualTo(VISIBLE)
            assertThat(controls[SIDE_LEFT]).isNotNull()
            assertThat(controls[SIDE_LEFT]!!.visibility).isEqualTo(GONE)
            assertThat(controls[SIDE_RIGHT]).isNotNull()
            assertThat(controls[SIDE_RIGHT]!!.visibility).isEqualTo(GONE)
        }
    }
}
+93 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.accessibility.hearingaid

import android.bluetooth.BluetoothHapPresetInfo
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import org.junit.Before
import org.junit.runner.RunWith
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock

@RunWith(AndroidJUnit4::class)
@SmallTest
class PresetSpinnerTest : SysuiTestCase() {

    private val context: Context = ApplicationProvider.getApplicationContext()
    private lateinit var spinnerAdapter: HearingDevicesSpinnerAdapter
    private lateinit var spinner: PresetSpinner

    @Before
    fun setUp() {
        spinnerAdapter = HearingDevicesSpinnerAdapter(context)
        spinner = PresetSpinner(context, spinnerAdapter = spinnerAdapter)
    }

    @Test
    fun setTitle_titleCorrect() {
        val testTitle = "test"
        spinner.setTitle(testTitle)

        assertThat(spinner.getTitle()).isEqualTo(testTitle)
    }

    @Test
    fun setList_setToAdapter() {
        val testInfos =
            listOf(
                createMockPresetInfo(1, "test_preset_1"),
                createMockPresetInfo(2, "test_preset_2"),
                createMockPresetInfo(3, "test_preset_3"),
            )

        spinner.setList(testInfos)

        for (i in testInfos.indices) {
            assertThat(spinnerAdapter.getItem(i)).isEqualTo(testInfos[i].name)
        }
    }

    @Test
    fun setValue_setToAdapter() {
        val testInfos =
            listOf(
                createMockPresetInfo(1, "test_preset_1"),
                createMockPresetInfo(2, "test_preset_2"),
                createMockPresetInfo(3, "test_preset_3"),
            )

        val testSelectedPositin = 1
        val testSelectedPresetIndex = testInfos[testSelectedPositin].index
        spinner.setList(testInfos)
        spinner.setValue(testSelectedPresetIndex)

        assertThat(spinnerAdapter.selected).isEqualTo(testSelectedPositin)
        assertThat(spinner.getValue()).isEqualTo(testSelectedPresetIndex)
    }

    private fun createMockPresetInfo(index: Int, name: String): BluetoothHapPresetInfo =
        mock<BluetoothHapPresetInfo> {
            on { this.index } doReturn index
            on { this.name } doReturn name
        }
}
+20 −1
Original line number Diff line number Diff line
@@ -91,13 +91,32 @@
            style="@style/HearingDeviceDialog.Spinner"/>
    </LinearLayout>

    <com.android.systemui.accessibility.hearingaid.PresetLayout
        android:id="@+id/preset_layout_new"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@id/ambient_layout"
        android:layout_marginStart="@dimen/bluetooth_dialog_layout_margin"
        android:layout_marginEnd="@dimen/bluetooth_dialog_layout_margin"
        android:orientation="vertical"
        android:visibility="gone" />

    <androidx.constraintlayout.widget.Barrier
        android:id="@+id/preset_layout_barrier"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:barrierDirection="bottom"
        app:constraint_referenced_ids="preset_layout_new, preset_layout" />

    <LinearLayout
        android:id="@+id/input_routing_layout"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@id/preset_layout"
        app:layout_constraintTop_toBottomOf="@id/preset_layout_barrier"
        android:layout_marginTop="@dimen/hearing_devices_layout_margin"
        android:layout_marginStart="@dimen/bluetooth_dialog_layout_margin"
        android:layout_marginEnd="@dimen/bluetooth_dialog_layout_margin"
+4 −0
Original line number Diff line number Diff line
@@ -1157,6 +1157,10 @@
    <string name="hearing_devices_presets_error">Couldn\'t update preset</string>
    <!-- QuickSettings: Title for hearing aids presets. Preset is a set of hearing aid settings. User can apply different settings in different environments (e.g. Outdoor, Restaurant, Home) [CHAR LIMIT=40]-->
    <string name="hearing_devices_preset_label">Preset</string>
    <!-- QuickSettings: Title for hearing aids presets of left side device. Preset is a set of hearing aid settings. User can apply different settings in different environments (e.g. Outdoor, Restaurant, Home) [CHAR LIMIT=40]-->
    <string name="hearing_devices_preset_label_left">Left Preset</string>
    <!-- QuickSettings: Title for hearing aids presets of right side device. Preset is a set of hearing aid settings. User can apply different settings in different environments (e.g. Outdoor, Restaurant, Home) [CHAR LIMIT=40]-->
    <string name="hearing_devices_preset_label_right">Right Preset</string>
    <!-- QuickSettings: Title for hearing aids input routing control in hearing device dialog. [CHAR LIMIT=40]-->
    <string name="hearing_devices_input_routing_label">Default microphone for calls</string>
    <!-- QuickSettings: Option for hearing aids input routing control in hearing device dialog. It will alter input routing for calls for hearing aid. [CHAR LIMIT=40]-->
Loading