Add content description to volume title in settings.

Adds content description that is announced by talkback when a11y focus
is on volume preference. This improves talkback announcement when view changes.

Fixes: 285529113
Bug: 285455826
Fixes: 285487766
Test: atest VolumeSeekBarPreferenceControllerTest
Test: atest VolumeSeekBarPreferenceTest
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:499f965c1c79cea14377c37028133a9f8717693d)
Merged-In: Ibe80b4b1d489dc058df1cc79c96b034d5ddc6e56
Change-Id: Ibe80b4b1d489dc058df1cc79c96b034d5ddc6e56
This commit is contained in:
Michael Mikhail
2023-06-21 16:58:20 +00:00
committed by Android Build Coastguard Worker
parent 84e2fc0e79
commit 2b49d000b6
9 changed files with 164 additions and 23 deletions

View File

@@ -7151,6 +7151,18 @@
<!-- Sound: Title for the option managing notification volume. [CHAR LIMIT=30] -->
<string name="notification_volume_option_title">Notification volume</string>
<!-- Sound: Content description of ring volume title in silent mode. [CHAR LIMIT=NONE BACKUP_MESSAGE_ID=8994620163934249882] -->
<string name="ringer_content_description_silent_mode">Ringer silent</string>
<!-- Sound: Content description of ring volume title in vibrate mode. [CHAR LIMIT=NONE BACKUP_MESSAGE_ID=6261841170896561364] -->
<string name="ringer_content_description_vibrate_mode">Ringer vibrate</string>
<!-- Sound: Content description of notification volume title in vibrate mode. [CHAR LIMIT=NONE] -->
<string name="notification_volume_content_description_vibrate_mode">Notification volume muted, notifications will vibrate</string>
<!-- Sound: Content description of volume title in silent mode [CHAR LIMIT=NONE] -->
<string name="volume_content_description_silent_mode"> <xliff:g id="volume type" example="notification volume">%1$s</xliff:g> muted</string>
<!-- Sound: Summary for when notification volume is disabled. [CHAR LIMIT=100] -->
<string name="notification_volume_disabled_summary">Unavailable because ring is muted</string>

View File

@@ -52,6 +52,7 @@ public class MediaVolumePreferenceController extends VolumeSeekBarPreferenceCont
public MediaVolumePreferenceController(Context context) {
super(context, KEY_MEDIA_VOLUME);
mVolumePreferenceListener = this::updateContentDescription;
}
@Override
@@ -109,6 +110,18 @@ public class MediaVolumePreferenceController extends VolumeSeekBarPreferenceCont
return false;
}
private void updateContentDescription() {
if (mPreference != null) {
if (mPreference.isMuted()) {
mPreference.updateContentDescription(
mContext.getString(R.string.volume_content_description_silent_mode,
mPreference.getTitle()));
} else {
mPreference.updateContentDescription(mPreference.getTitle());
}
}
}
@Override
public SliceAction getSliceEndItem(Context context) {
if (!isSupportEndItem()) {

View File

@@ -75,6 +75,7 @@ public class NotificationVolumePreferenceController extends
updateEffectsSuppressor();
selectPreferenceIconState();
updateContentDescription();
updateEnabledState();
}
@@ -120,23 +121,32 @@ public class NotificationVolumePreferenceController extends
}
@Override
protected void selectPreferenceIconState() {
protected int getEffectiveRingerMode() {
if (mVibrator == null && mRingerMode == AudioManager.RINGER_MODE_VIBRATE) {
return AudioManager.RINGER_MODE_SILENT;
} else if (mRingerMode == AudioManager.RINGER_MODE_NORMAL) {
if (mHelper.getStreamVolume(AudioManager.STREAM_NOTIFICATION) == 0) {
// Ring is in normal, but notification is in silent.
return AudioManager.RINGER_MODE_SILENT;
}
}
return mRingerMode;
}
@Override
protected void updateContentDescription() {
if (mPreference != null) {
if (mVibrator != null && mRingerMode == AudioManager.RINGER_MODE_VIBRATE) {
mMuteIcon = mVibrateIconId;
mPreference.showIcon(mVibrateIconId);
} else if (mRingerMode == AudioManager.RINGER_MODE_SILENT
|| mVibrator == null && mRingerMode == AudioManager.RINGER_MODE_VIBRATE) {
mMuteIcon = mSilentIconId;
mPreference.showIcon(mSilentIconId);
} else { // ringmode normal: could be that we are still silent
if (mHelper.getStreamVolume(AudioManager.STREAM_NOTIFICATION) == 0) {
// ring is in normal, but notification is in silent
mMuteIcon = mSilentIconId;
mPreference.showIcon(mSilentIconId);
} else {
mPreference.showIcon(mNormalIconId);
}
int ringerMode = getEffectiveRingerMode();
if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) {
mPreference.updateContentDescription(
mContext.getString(
R.string.notification_volume_content_description_vibrate_mode));
} else if (ringerMode == AudioManager.RINGER_MODE_SILENT) {
mPreference.updateContentDescription(
mContext.getString(R.string.volume_content_description_silent_mode,
mPreference.getTitle()));
} else {
mPreference.updateContentDescription(mPreference.getTitle());
}
}
}
@@ -169,6 +179,7 @@ public class NotificationVolumePreferenceController extends
break;
case NOTIFICATION_VOLUME_CHANGED:
selectPreferenceIconState();
updateContentDescription();
updateEnabledState();
break;
}

View File

@@ -26,6 +26,7 @@ import android.os.Vibrator;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import java.util.Objects;
@@ -54,6 +55,7 @@ public abstract class RingerModeAffectedVolumePreferenceController extends
if (mVibrator != null && !mVibrator.hasVibrator()) {
mVibrator = null;
}
mVolumePreferenceListener = this::updateContentDescription;
}
protected void updateEffectsSuppressor() {
@@ -123,6 +125,7 @@ public abstract class RingerModeAffectedVolumePreferenceController extends
}
mRingerMode = ringerMode;
selectPreferenceIconState();
updateContentDescription();
return true;
}
@@ -131,10 +134,11 @@ public abstract class RingerModeAffectedVolumePreferenceController extends
*/
protected void selectPreferenceIconState() {
if (mPreference != null) {
if (mRingerMode == AudioManager.RINGER_MODE_NORMAL) {
int ringerMode = getEffectiveRingerMode();
if (ringerMode == AudioManager.RINGER_MODE_NORMAL) {
mPreference.showIcon(mNormalIconId);
} else {
if (mRingerMode == AudioManager.RINGER_MODE_VIBRATE && mVibrator != null) {
if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) {
mMuteIcon = mVibrateIconId;
} else {
mMuteIcon = mSilentIconId;
@@ -144,6 +148,28 @@ public abstract class RingerModeAffectedVolumePreferenceController extends
}
}
protected int getEffectiveRingerMode() {
if (mVibrator == null && mRingerMode == AudioManager.RINGER_MODE_VIBRATE) {
return AudioManager.RINGER_MODE_SILENT;
}
return mRingerMode;
}
protected void updateContentDescription() {
if (mPreference != null) {
int ringerMode = getEffectiveRingerMode();
if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) {
mPreference.updateContentDescription(
mContext.getString(R.string.ringer_content_description_vibrate_mode));
} else if (ringerMode == AudioManager.RINGER_MODE_SILENT) {
mPreference.updateContentDescription(
mContext.getString(R.string.ringer_content_description_silent_mode));
} else {
mPreference.updateContentDescription(mPreference.getTitle());
}
}
}
protected abstract boolean hintsMatch(int hints);
}

View File

@@ -65,6 +65,7 @@ public class SeparateRingVolumePreferenceController extends
mReceiver.register(true);
updateEffectsSuppressor();
selectPreferenceIconState();
updateContentDescription();
if (mPreference != null) {
mPreference.setVisible(getAvailabilityStatus() == AVAILABLE);

View File

@@ -47,10 +47,13 @@ public class VolumeSeekBarPreference extends SeekBarPreference {
protected SeekBar mSeekBar;
private int mStream;
private SeekBarVolumizer mVolumizer;
@VisibleForTesting
SeekBarVolumizer mVolumizer;
private Callback mCallback;
private Listener mListener;
private ImageView mIconView;
private TextView mSuppressionTextView;
private TextView mTitle;
private String mSuppressionText;
private boolean mMuted;
private boolean mZenMuted;
@@ -98,6 +101,10 @@ public class VolumeSeekBarPreference extends SeekBarPreference {
mCallback = callback;
}
public void setListener(Listener listener) {
mListener = listener;
}
public void onActivityResume() {
if (mStopped) {
init();
@@ -118,6 +125,7 @@ public class VolumeSeekBarPreference extends SeekBarPreference {
mSeekBar = (SeekBar) view.findViewById(com.android.internal.R.id.seekbar);
mIconView = (ImageView) view.findViewById(com.android.internal.R.id.icon);
mSuppressionTextView = (TextView) view.findViewById(R.id.suppression_text);
mTitle = (TextView) view.findViewById(com.android.internal.R.id.title);
init();
}
@@ -142,6 +150,9 @@ public class VolumeSeekBarPreference extends SeekBarPreference {
mMuted = muted;
mZenMuted = zenMuted;
updateIconView();
if (mListener != null) {
mListener.onUpdateMuteState();
}
}
@Override
public void onStartTrackingTouch(SeekBarVolumizer sbv) {
@@ -165,6 +176,9 @@ public class VolumeSeekBarPreference extends SeekBarPreference {
mVolumizer.setSeekBar(mSeekBar);
updateIconView();
updateSuppressionText();
if (mListener != null) {
mListener.onUpdateMuteState();
}
if (!isEnabled()) {
mSeekBar.setEnabled(false);
mVolumizer.stop();
@@ -175,7 +189,7 @@ public class VolumeSeekBarPreference extends SeekBarPreference {
if (mIconView == null) return;
if (mIconResId != 0) {
mIconView.setImageResource(mIconResId);
} else if (mMuteIconResId != 0 && mMuted && !mZenMuted) {
} else if (mMuteIconResId != 0 && isMuted()) {
mIconView.setImageResource(mMuteIconResId);
} else {
mIconView.setImageDrawable(getIcon());
@@ -208,6 +222,10 @@ public class VolumeSeekBarPreference extends SeekBarPreference {
updateSuppressionText();
}
protected boolean isMuted() {
return mMuted && !mZenMuted;
}
protected void updateSuppressionText() {
if (mSuppressionTextView != null && mSeekBar != null) {
mSuppressionTextView.setText(mSuppressionText);
@@ -216,6 +234,14 @@ public class VolumeSeekBarPreference extends SeekBarPreference {
}
}
/**
* Update content description of title to improve talkback announcements.
*/
protected void updateContentDescription(CharSequence contentDescription) {
if (mTitle == null) return;
mTitle.setContentDescription(contentDescription);
}
public interface Callback {
void onSampleStarting(SeekBarVolumizer sbv);
void onStreamValueChanged(int stream, int progress);
@@ -225,4 +251,15 @@ public class VolumeSeekBarPreference extends SeekBarPreference {
*/
void onStartTrackingTouch(SeekBarVolumizer sbv);
}
/**
* Listener to view updates in volumeSeekbarPreference.
*/
public interface Listener {
/**
* Listener to mute state updates.
*/
void onUpdateMuteState();
}
}

View File

@@ -36,6 +36,7 @@ public abstract class VolumeSeekBarPreferenceController extends
protected VolumeSeekBarPreference mPreference;
protected VolumeSeekBarPreference.Callback mVolumePreferenceCallback;
protected AudioHelper mHelper;
protected VolumeSeekBarPreference.Listener mVolumePreferenceListener;
public VolumeSeekBarPreferenceController(Context context, String key) {
super(context, key);
@@ -62,6 +63,7 @@ public abstract class VolumeSeekBarPreferenceController extends
protected void setupVolPreference(PreferenceScreen screen) {
mPreference = screen.findPreference(getPreferenceKey());
mPreference.setCallback(mVolumePreferenceCallback);
mPreference.setListener(mVolumePreferenceListener);
mPreference.setStream(getAudioStream());
mPreference.setMuteIcon(getMuteIcon());
}

View File

@@ -49,6 +49,8 @@ public class VolumeSeekBarPreferenceControllerTest {
@Mock
private VolumeSeekBarPreference.Callback mCallback;
@Mock
private VolumeSeekBarPreference.Listener mListener;
@Mock
private AudioHelper mHelper;
private VolumeSeekBarPreferenceControllerTestable mController;
@@ -59,7 +61,7 @@ public class VolumeSeekBarPreferenceControllerTest {
when(mScreen.findPreference(nullable(String.class))).thenReturn(mPreference);
when(mPreference.getKey()).thenReturn("key");
mController = new VolumeSeekBarPreferenceControllerTestable(mContext, mCallback, true,
mPreference.getKey());
mPreference.getKey(), mListener);
mController.setAudioHelper(mHelper);
}
@@ -70,18 +72,20 @@ public class VolumeSeekBarPreferenceControllerTest {
verify(mPreference).setCallback(mCallback);
verify(mPreference).setStream(VolumeSeekBarPreferenceControllerTestable.AUDIO_STREAM);
verify(mPreference).setMuteIcon(VolumeSeekBarPreferenceControllerTestable.MUTE_ICON);
verify(mPreference).setListener(mListener);
}
@Test
public void displayPreference_notAvailable_shouldNotUpdatePreference() {
mController = new VolumeSeekBarPreferenceControllerTestable(mContext, mCallback, false,
mPreference.getKey());
mPreference.getKey(), mListener);
mController.displayPreference(mScreen);
verify(mPreference, never()).setCallback(any(VolumeSeekBarPreference.Callback.class));
verify(mPreference, never()).setStream(anyInt());
verify(mPreference, never()).setMuteIcon(anyInt());
verify(mPreference, never()).setListener(mListener);
}
@Test
@@ -157,10 +161,12 @@ public class VolumeSeekBarPreferenceControllerTest {
private boolean mAvailable;
VolumeSeekBarPreferenceControllerTestable(Context context,
VolumeSeekBarPreference.Callback callback, boolean available, String key) {
VolumeSeekBarPreference.Callback callback, boolean available, String key,
VolumeSeekBarPreference.Listener listener) {
super(context, key);
setCallback(callback);
mAvailable = available;
mVolumePreferenceListener = listener;
}
@Override

View File

@@ -18,11 +18,14 @@ package com.android.settings.notification;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.media.AudioManager;
import android.preference.SeekBarVolumizer;
import android.widget.SeekBar;
import org.junit.Before;
import org.junit.Test;
@@ -34,18 +37,28 @@ import org.robolectric.RobolectricTestRunner;
@RunWith(RobolectricTestRunner.class)
public class VolumeSeekBarPreferenceTest {
private static final CharSequence CONTENT_DESCRIPTION = "TEST";
@Mock
private AudioManager mAudioManager;
@Mock
private VolumeSeekBarPreference mPreference;
@Mock
private Context mContext;
@Mock
private SeekBar mSeekBar;
@Mock
private SeekBarVolumizer mVolumizer;
private VolumeSeekBarPreference.Listener mListener;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mContext.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mAudioManager);
doCallRealMethod().when(mPreference).updateContentDescription(CONTENT_DESCRIPTION);
mPreference.mSeekBar = mSeekBar;
mPreference.mAudioManager = mAudioManager;
mPreference.mVolumizer = mVolumizer;
mListener = () -> mPreference.updateContentDescription(CONTENT_DESCRIPTION);
}
@Test
@@ -65,4 +78,24 @@ public class VolumeSeekBarPreferenceTest {
verify(mPreference).setMin(min);
verify(mPreference).setProgress(progress);
}
@Test
public void init_listenerIsCalled() {
doCallRealMethod().when(mPreference).setListener(mListener);
doCallRealMethod().when(mPreference).init();
mPreference.setListener(mListener);
mPreference.init();
verify(mPreference).updateContentDescription(CONTENT_DESCRIPTION);
}
@Test
public void init_listenerNotSet_noException() {
doCallRealMethod().when(mPreference).init();
mPreference.init();
verify(mPreference, never()).updateContentDescription(CONTENT_DESCRIPTION);
}
}