From 0771fd8a9e534548535df5747e739330b3e5f653 Mon Sep 17 00:00:00 2001 From: Daniel Norman Date: Tue, 2 Jul 2024 23:25:16 +0000 Subject: [PATCH] Uses a custom state description for the Audio Balance seek bar. This makes the seek bar more informative for a user of a screen reader or other accessibility service. See context in b/319575109#comment7. Bug: 319575109 Flag: com.android.settings.accessibility.audio_balance_state_description Test: Use TalkBack to observe the state description while moving the seek bar. Test: atest BalanceSeekBarTest Change-Id: I7be15b26116f83854efe69d6b0560edde946daf5 --- .../accessibility/accessibility_flags.aconfig | 10 +++ res/values/strings.xml | 4 ++ .../accessibility/BalanceSeekBar.java | 23 +++++++ .../accessibility/BalanceSeekBarTest.java | 61 +++++++++++++++++++ 4 files changed, 98 insertions(+) diff --git a/aconfig/accessibility/accessibility_flags.aconfig b/aconfig/accessibility/accessibility_flags.aconfig index 2c92547474d..3ed618bd6e0 100644 --- a/aconfig/accessibility/accessibility_flags.aconfig +++ b/aconfig/accessibility/accessibility_flags.aconfig @@ -10,6 +10,16 @@ flag { bug: "332974327" } +flag { + name: "audio_balance_state_description" + namespace: "accessibility" + description: "Provides a more valuable state description to the audio balance slider." + bug: "319575109" + metadata { + purpose: PURPOSE_BUGFIX + } +} + flag { name: "edit_shortcuts_in_full_screen" namespace: "accessibility" diff --git a/res/values/strings.xml b/res/values/strings.xml index a4908560b45..d940e4a928f 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -13451,6 +13451,10 @@ Off On + + Audio %1$d%% left, %2$d%% right + + Audio %1$d%% right, %2$d%% left Your device name is visible to apps you installed. It may also be seen by other people when you connect to Bluetooth devices, connect to a Wi-Fi network or set up a Wi-Fi hotspot. diff --git a/src/com/android/settings/accessibility/BalanceSeekBar.java b/src/com/android/settings/accessibility/BalanceSeekBar.java index 19301ae9ea6..7441d6fe9e2 100644 --- a/src/com/android/settings/accessibility/BalanceSeekBar.java +++ b/src/com/android/settings/accessibility/BalanceSeekBar.java @@ -20,6 +20,7 @@ import static android.view.HapticFeedbackConstants.CLOCK_TICK; import static com.android.settings.Utils.isNightMode; +import android.annotation.StringRes; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Resources; @@ -86,6 +87,14 @@ public class BalanceSeekBar extends SeekBar { Settings.System.putFloatForUser(mContext.getContentResolver(), Settings.System.MASTER_BALANCE, balance, UserHandle.USER_CURRENT); } + final int max = getMax(); + if (Flags.audioBalanceStateDescription() && max > 0) { + seekBar.setStateDescription(createStateDescription(mContext, + R.string.audio_seek_bar_state_left_first, + R.string.audio_seek_bar_state_right_first, + progress, + max)); + } // If fromUser is false, the call is a set from the framework on creation or on // internal update. The progress may be zero, ignore (don't change system settings). @@ -161,5 +170,19 @@ public class BalanceSeekBar extends SeekBar { canvas.restore(); super.onDraw(canvas); } + + private static CharSequence createStateDescription(Context context, + @StringRes int resIdLeftFirst, @StringRes int resIdRightFirst, + int progress, float max) { + final boolean isLayoutRtl = context.getResources().getConfiguration().getLayoutDirection() + == LAYOUT_DIRECTION_RTL; + final int rightPercent = (int) (100 * (progress / max)); + final int leftPercent = 100 - rightPercent; + if (rightPercent > leftPercent || (rightPercent == leftPercent && isLayoutRtl)) { + return context.getString(resIdRightFirst, rightPercent, leftPercent); + } else { + return context.getString(resIdLeftFirst, leftPercent, rightPercent); + } + } } diff --git a/tests/robotests/src/com/android/settings/accessibility/BalanceSeekBarTest.java b/tests/robotests/src/com/android/settings/accessibility/BalanceSeekBarTest.java index ce2a5712434..d74794f0363 100644 --- a/tests/robotests/src/com/android/settings/accessibility/BalanceSeekBarTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/BalanceSeekBarTest.java @@ -28,23 +28,32 @@ import static org.mockito.Mockito.verify; import static org.robolectric.Shadows.shadowOf; import android.content.Context; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.AttributeSet; import android.widget.SeekBar; +import com.android.settings.R; import com.android.settings.testutils.shadow.ShadowSystemSettings; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +import java.util.Locale; + @RunWith(RobolectricTestRunner.class) @Config(shadows = { ShadowSystemSettings.class, }) public class BalanceSeekBarTest { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + // Fix the maximum process value to 200 for testing the BalanceSeekBar. // It affects the SeekBar value of center(100) and snapThreshold(200 * SNAP_TO_PERCENTAGE). private static final int MAX_PROGRESS_VALUE = 200; @@ -143,6 +152,58 @@ public class BalanceSeekBarTest { assertThat(mSeekBar.getProgress()).isEqualTo(progressWithoutThreshold); } + @Test + @EnableFlags(Flags.FLAG_AUDIO_BALANCE_STATE_DESCRIPTION) + public void onProgressChanged_getStateDescription_centered_leftFirst() { + // Seek bar centered + int progress = (int) (0.50f * MAX_PROGRESS_VALUE); + mSeekBar.setMax(MAX_PROGRESS_VALUE); + + mProxySeekBarListener.onProgressChanged(mSeekBar, progress, true); + + assertThat(mSeekBar.getStateDescription()).isEqualTo( + mContext.getString(R.string.audio_seek_bar_state_left_first, 50, 50)); + } + + @Test + @EnableFlags(Flags.FLAG_AUDIO_BALANCE_STATE_DESCRIPTION) + public void onProgressChanged_getStateDescription_centered_rtl_rightFirst() { + // RTL layout + mContext.getResources().getConfiguration().setLayoutDirection(new Locale("iw", "IL")); + // Seek bar centered + int progress = (int) (0.50f * MAX_PROGRESS_VALUE); + mSeekBar.setMax(MAX_PROGRESS_VALUE); + + mProxySeekBarListener.onProgressChanged(mSeekBar, progress, true); + + assertThat(mSeekBar.getStateDescription()).isEqualTo( + mContext.getString(R.string.audio_seek_bar_state_right_first, 50, 50)); + } + + @Test + @EnableFlags(Flags.FLAG_AUDIO_BALANCE_STATE_DESCRIPTION) + public void onProgressChanged_getStateDescription_25percent_leftFirst() { + // Seek bar 3/4th toward the left + int progress = (int) (0.25f * MAX_PROGRESS_VALUE); + mSeekBar.setMax(MAX_PROGRESS_VALUE); + mProxySeekBarListener.onProgressChanged(mSeekBar, progress, true); + + assertThat(mSeekBar.getStateDescription()).isEqualTo( + mContext.getString(R.string.audio_seek_bar_state_left_first, 75, 25)); + } + + @Test + @EnableFlags(Flags.FLAG_AUDIO_BALANCE_STATE_DESCRIPTION) + public void onProgressChanged_getStateDescription_75percent_rightFirst() { + // Seek bar 3/4th toward the right + int progress = (int) (0.75f * MAX_PROGRESS_VALUE); + mSeekBar.setMax(MAX_PROGRESS_VALUE); + mProxySeekBarListener.onProgressChanged(mSeekBar, progress, true); + + assertThat(mSeekBar.getStateDescription()).isEqualTo( + mContext.getString(R.string.audio_seek_bar_state_right_first, 75, 25)); + } + // method to get the center from BalanceSeekBar for testing setMax(). private int getBalanceSeekBarCenter(BalanceSeekBar seekBar) { return seekBar.getMax() / 2;