From 0691b100ebf20b6294d14b737d2a43324f55f464 Mon Sep 17 00:00:00 2001 From: Nicolas Pomepuy <nicolas@videolabs.io> Date: Wed, 1 Jun 2022 09:23:34 +0200 Subject: [PATCH] Accessibility: improve talkback for the audio player --- .../resources/src/main/res/values/strings.xml | 9 ++- .../res/layout-land/audio_player.xml | 15 +++- .../layout-land/cover_media_switcher_item.xml | 3 + .../cover_media_switcher_item.xml | 2 + .../vlc-android/res/layout/audio_player.xml | 10 ++- .../res/layout/cover_media_switcher_item.xml | 3 + .../vlc-android/res/layout/playlist_item.xml | 4 + .../vlc/gui/AudioPlayerContainerActivity.kt | 8 ++ .../org/videolan/vlc/gui/audio/AudioPlayer.kt | 77 +++++++++++++------ 9 files changed, 101 insertions(+), 30 deletions(-) diff --git a/application/resources/src/main/res/values/strings.xml b/application/resources/src/main/res/values/strings.xml index e29f7236fb..eaea750f07 100644 --- a/application/resources/src/main/res/values/strings.xml +++ b/application/resources/src/main/res/values/strings.xml @@ -1014,10 +1014,17 @@ <string name="talkback_decrease_speed">Decrease speed</string> <string name="talkback_subtitle_history">Subtitle history</string> <string name="talkback_language_selection">%s selected languages</string> + <string name="talkback_audio_player_opened">Audio player opened</string> + <string name="talkback_audio_player_collapsed">Audio player collapsed</string> + <string name="talkback_audio_player_closed">Audio player closed</string> + <string name="talkback_audio_player">Audio player. Currently playing: %s</string> + <string name="talkback_out_of">%s out of %s</string> + <string name="talkback_track_index">Track %s out of %s</string> <string name="downloaded">Downloaded</string> <string name="not_downloaded">Not downloaded</string> <string name="downloading">Downloading</string> - <string name="talkback_out_of">%s out of %s</string> + <string name="talkback_action_rewind">Rewind %s seconds</string> + <string name="talkback_action_forward">Seek forward %s seconds</string> </resources> diff --git a/application/vlc-android/res/layout-land/audio_player.xml b/application/vlc-android/res/layout-land/audio_player.xml index e39fbaed9d..00751a1773 100644 --- a/application/vlc-android/res/layout-land/audio_player.xml +++ b/application/vlc-android/res/layout-land/audio_player.xml @@ -31,6 +31,7 @@ android:background="?attr/background_default_darker" android:clickable="true" android:focusable="true" + android:importantForAccessibility="no" android:keyboardNavigationCluster="true" tools:targetApi="o"> @@ -88,6 +89,7 @@ android:layoutDirection="ltr" android:maxHeight="4dp" android:minHeight="4dp" + android:importantForAccessibility="no" android:paddingLeft="0px" android:paddingRight="0px" android:progressDrawable="?attr/progress_mini_player" @@ -433,6 +435,7 @@ android:background="?attr/audio_list_background" android:clipToPadding="false" android:paddingBottom="@dimen/listview_bottom_padding" + android:importantForAccessibility="no" app:layout_constraintBottom_toBottomOf="@id/timeline" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@+id/header" @@ -462,6 +465,7 @@ android:textColor="?attr/font_default" android:ellipsize="marquee" android:marqueeRepeatLimit="1" + android:importantForAccessibility="no" android:singleLine="true" android:textSize="24sp" app:layout_constraintVertical_chainStyle="packed" @@ -482,6 +486,7 @@ app:layout_goneMarginBottom="16dp" android:ellipsize="marquee" android:marqueeRepeatLimit="1" + android:importantForAccessibility="no" android:singleLine="true" android:maxLines="1" android:textColor="?attr/font_audio_light" @@ -498,6 +503,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="24dp" + android:importantForAccessibility="no" android:layout_marginEnd="24dp" android:layout_marginBottom="16dp" android:maxLines="1" @@ -519,7 +525,6 @@ android:layout_marginEnd="8dp" android:background="?attr/actionBarItemBackground" android:clickable="true" - android:contentDescription="@string/air_action_rewind" android:focusable="true" android:padding="8dp" android:onClick="@{fragment::onJumpBack}" @@ -536,6 +541,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="?attr/player_icon_color" + android:importantForAccessibility="no" android:textSize="8dp" android:textStyle="bold" app:layout_constraintBottom_toBottomOf="@+id/audio_rewind_10" @@ -552,7 +558,6 @@ android:layout_marginStart="8dp" android:background="?attr/actionBarItemBackground" android:clickable="true" - android:contentDescription="@string/air_action_forward" android:focusable="true" android:padding="8dp" android:onClick="@{fragment::onJumpForward}" @@ -569,6 +574,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="?attr/player_icon_color" + android:importantForAccessibility="no" android:textSize="8dp" android:textStyle="bold" app:layout_constraintBottom_toBottomOf="@+id/audio_forward_10" @@ -590,6 +596,7 @@ android:ellipsize="middle" android:maxLines="1" android:paddingStart="16dp" + android:accessibilityLiveRegion="none" android:paddingTop="8dp" android:paddingEnd="16dp" android:paddingBottom="8dp" @@ -628,13 +635,14 @@ android:elevation="4dp" android:focusable="true" android:onClick="@{fragment::onTimeLabelClick}" + android:importantForAccessibility="no" android:text="@string/time_0" android:textColor="?attr/font_default" android:textSize="12sp" app:layout_constraintBottom_toTopOf="@+id/timeline" app:layout_constraintStart_toStartOf="@+id/timeline" /> - <SeekBar + <org.videolan.vlc.gui.view.AccessibleSeekBar android:id="@+id/timeline" android:layout_width="0dp" android:layout_height="wrap_content" @@ -658,6 +666,7 @@ android:layout_gravity="center|end" android:layout_marginEnd="8dp" android:contentDescription="@string/length" + android:importantForAccessibility="no" android:elevation="4dp" android:text="@string/time_0" android:textColor="?attr/font_default" diff --git a/application/vlc-android/res/layout-land/cover_media_switcher_item.xml b/application/vlc-android/res/layout-land/cover_media_switcher_item.xml index 4e3a9fa24c..ee7729814d 100644 --- a/application/vlc-android/res/layout-land/cover_media_switcher_item.xml +++ b/application/vlc-android/res/layout-land/cover_media_switcher_item.xml @@ -69,6 +69,7 @@ android:fontFamily="sans-serif-light" android:maxLines="1" android:singleLine="true" + android:importantForAccessibility="no" android:textColor="?attr/font_default" android:textSize="24sp" app:layout_constrainedWidth="true" @@ -85,6 +86,7 @@ android:layout_marginStart="24dp" android:layout_marginTop="8dp" android:layout_marginEnd="24dp" + android:importantForAccessibility="no" android:maxLines="1" android:singleLine="true" android:textColor="?attr/font_audio_light" @@ -106,6 +108,7 @@ android:layout_marginBottom="32dp" android:maxLines="1" android:textColor="?attr/font_audio_light" + android:importantForAccessibility="no" android:textSize="12sp" android:visibility="gone" app:layout_constrainedWidth="true" diff --git a/application/vlc-android/res/layout-large-land/cover_media_switcher_item.xml b/application/vlc-android/res/layout-large-land/cover_media_switcher_item.xml index 94988791e5..74c959c307 100644 --- a/application/vlc-android/res/layout-large-land/cover_media_switcher_item.xml +++ b/application/vlc-android/res/layout-large-land/cover_media_switcher_item.xml @@ -70,6 +70,7 @@ android:layout_marginTop="24dp" android:layout_marginEnd="24dp" android:fontFamily="sans-serif-light" + android:importantForAccessibility="no" android:maxLines="1" android:textColor="?attr/font_default" android:textSize="24sp" @@ -90,6 +91,7 @@ android:layout_marginEnd="24dp" android:maxLines="1" android:textColor="?attr/font_audio_light" + android:importantForAccessibility="no" android:textSize="14sp" android:visibility="gone" app:layout_constrainedWidth="true" diff --git a/application/vlc-android/res/layout/audio_player.xml b/application/vlc-android/res/layout/audio_player.xml index 16ff57e1c7..fe12d64a52 100644 --- a/application/vlc-android/res/layout/audio_player.xml +++ b/application/vlc-android/res/layout/audio_player.xml @@ -31,6 +31,7 @@ android:background="?attr/bottom_navigation_background" android:clickable="true" android:focusable="true" + android:importantForAccessibility="no" android:keyboardNavigationCluster="true" tools:targetApi="o"> @@ -80,6 +81,7 @@ android:maxHeight="4dp" android:minHeight="4dp" android:paddingLeft="0px" + android:importantForAccessibility="no" android:paddingRight="0px" android:progressDrawable="?attr/progress_mini_player" app:layout_constraintLeft_toLeftOf="parent" @@ -421,6 +423,7 @@ android:clipToPadding="false" android:maxWidth="800dp" android:paddingBottom="68dp" + android:importantForAccessibility="no" app:layout_constraintBottom_toTopOf="@+id/songs_list_guide" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" @@ -443,6 +446,7 @@ android:background="?attr/audio_chip_background" android:elevation="4dp" android:paddingStart="16dp" + android:accessibilityLiveRegion="none" android:paddingTop="8dp" android:paddingEnd="16dp" android:paddingBottom="8dp" @@ -476,7 +480,6 @@ android:layout_marginBottom="24dp" android:background="?attr/actionBarItemBackground" android:clickable="true" - android:contentDescription="@string/air_action_rewind" android:focusable="true" android:padding="8dp" android:visibility="gone" @@ -511,7 +514,6 @@ android:layout_marginStart="8dp" android:background="?attr/actionBarItemBackground" android:clickable="true" - android:contentDescription="@string/air_action_forward" android:focusable="true" android:padding="8dp" android:visibility="gone" @@ -550,6 +552,7 @@ android:clickable="true" android:elevation="4dp" android:focusable="true" + android:importantForAccessibility="no" android:onClick="@{fragment::onTimeLabelClick}" android:text="@string/time_0" android:textColor="?attr/font_default" @@ -567,7 +570,7 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@id/timeline" /> - <SeekBar + <org.videolan.vlc.gui.view.AccessibleSeekBar android:id="@+id/timeline" android:layout_width="0dp" android:layout_height="wrap_content" @@ -591,6 +594,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center|end" + android:importantForAccessibility="no" android:layout_marginEnd="@dimen/default_margin" android:background="?attr/selectableItemBackgroundBorderless" android:contentDescription="@string/length" diff --git a/application/vlc-android/res/layout/cover_media_switcher_item.xml b/application/vlc-android/res/layout/cover_media_switcher_item.xml index 809f5a562f..cc1c338673 100644 --- a/application/vlc-android/res/layout/cover_media_switcher_item.xml +++ b/application/vlc-android/res/layout/cover_media_switcher_item.xml @@ -68,6 +68,7 @@ android:layout_marginTop="24dp" android:layout_marginEnd="24dp" android:fontFamily="sans-serif-light" + android:importantForAccessibility="no" android:maxLines="1" android:singleLine="true" android:textColor="?attr/font_default" @@ -87,6 +88,7 @@ android:layout_marginTop="8dp" android:layout_marginEnd="24dp" android:maxLines="1" + android:importantForAccessibility="no" android:singleLine="true" android:textColor="?attr/font_audio_light" android:textSize="14sp" @@ -106,6 +108,7 @@ android:layout_marginTop="8dp" android:layout_marginEnd="24dp" android:layout_marginBottom="32dp" + android:importantForAccessibility="no" android:maxLines="1" android:singleLine="true" android:textColor="?attr/font_audio_light" diff --git a/application/vlc-android/res/layout/playlist_item.xml b/application/vlc-android/res/layout/playlist_item.xml index 02ecc91ac1..426dda9fe4 100644 --- a/application/vlc-android/res/layout/playlist_item.xml +++ b/application/vlc-android/res/layout/playlist_item.xml @@ -58,6 +58,7 @@ android:orientation="vertical" android:paddingTop="4dp" android:paddingBottom="4dp" + app:mediaContentDescription="@{media}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/item_move_up" app:layout_constraintStart_toStartOf="parent" @@ -108,6 +109,7 @@ android:singleLine="true" android:text="@{media.title}" android:textColor="?attr/font_default" + android:importantForAccessibility="no" android:textSize="16sp" app:ellipsizeMode="@{true}" app:layout_constraintBottom_toTopOf="@+id/audio_item_subtitle" @@ -129,6 +131,7 @@ android:ellipsize="middle" android:fontFamily="sans-serif" android:paddingLeft="8dp" + android:importantForAccessibility="no" android:paddingRight="8dp" android:singleLine="true" android:text="@{subTitle}" @@ -150,6 +153,7 @@ android:layout_centerVertical="true" android:layout_gravity="center" android:layout_marginRight="4dp" + android:contentDescription="@string/more_actions" android:padding="8dp" android:background="?attr/selectableItemBackgroundBorderless" android:onClick="@{holder::onMoreClick}" diff --git a/application/vlc-android/src/org/videolan/vlc/gui/AudioPlayerContainerActivity.kt b/application/vlc-android/src/org/videolan/vlc/gui/AudioPlayerContainerActivity.kt index 3fa5aa17b0..4c7679b305 100644 --- a/application/vlc-android/src/org/videolan/vlc/gui/AudioPlayerContainerActivity.kt +++ b/application/vlc-android/src/org/videolan/vlc/gui/AudioPlayerContainerActivity.kt @@ -62,6 +62,7 @@ import org.videolan.vlc.gui.helpers.* import org.videolan.vlc.gui.helpers.UiTools.isTablet import org.videolan.vlc.interfaces.IRefreshable import org.videolan.vlc.media.PlaylistManager +import org.videolan.vlc.util.isTalkbackIsEnabled import kotlin.math.max import kotlin.math.min @@ -246,6 +247,13 @@ open class AudioPlayerContainerActivity : BaseActivity(), KeycodeListener { updateFragmentMargins(newState) applyMarginToProgressBar(playerBehavior.peekHeight) setContentBottomPadding() + if (isTalkbackIsEnabled()) { + when (playerBehavior.state) { + STATE_EXPANDED -> audioPlayerContainer.announceForAccessibility(getString(R.string.talkback_audio_player_opened)) + STATE_COLLAPSED -> audioPlayerContainer.announceForAccessibility(getString(R.string.talkback_audio_player_collapsed)) + STATE_HIDDEN -> audioPlayerContainer.announceForAccessibility(getString(R.string.talkback_audio_player_closed)) + } + } } }) showTipViewIfNeeded(R.id.audio_player_tips, PREF_AUDIOPLAYER_TIPS_SHOWN) diff --git a/application/vlc-android/src/org/videolan/vlc/gui/audio/AudioPlayer.kt b/application/vlc-android/src/org/videolan/vlc/gui/audio/AudioPlayer.kt index bfae0de879..314deabd88 100644 --- a/application/vlc-android/src/org/videolan/vlc/gui/audio/AudioPlayer.kt +++ b/application/vlc-android/src/org/videolan/vlc/gui/audio/AudioPlayer.kt @@ -66,11 +66,8 @@ import org.videolan.vlc.gui.dialogs.CtxActionReceiver import org.videolan.vlc.gui.dialogs.PlaybackSpeedDialog import org.videolan.vlc.gui.dialogs.SleepTimerDialog import org.videolan.vlc.gui.dialogs.showContext +import org.videolan.vlc.gui.helpers.* import org.videolan.vlc.gui.helpers.AudioUtil.setRingtone -import org.videolan.vlc.gui.helpers.BookmarkListDelegate -import org.videolan.vlc.gui.helpers.PlayerOptionsDelegate -import org.videolan.vlc.gui.helpers.SwipeDragItemTouchHelperCallback -import org.videolan.vlc.gui.helpers.UiTools import org.videolan.vlc.gui.helpers.UiTools.addToPlaylist import org.videolan.vlc.gui.helpers.UiTools.isTablet import org.videolan.vlc.gui.video.VideoPlayerActivity @@ -319,6 +316,10 @@ class AudioPlayer : Fragment(), PlaylistAdapter.IPlayer, TextWatcher, IAudioPlay updateRepeatMode() binding.audioMediaSwitcher.updateMedia(playlistModel.service) binding.coverMediaSwitcher.updateMedia(playlistModel.service) + playlistModel.service?.currentMediaWrapper?.let { + binding.audioMediaSwitcher.contentDescription = getString(R.string.talkback_audio_player,TalkbackUtil.getAudioTrack(requireActivity(), it)) + binding.trackInfoContainer?.contentDescription = getString(R.string.talkback_audio_player,TalkbackUtil.getAudioTrack(requireActivity(), it)) + } val chapter = playlistModel.service?.getCurrentChapter(true) binding.songTitle?.text = if (!chapter.isNullOrEmpty()) chapter else playlistModel.title @@ -331,6 +332,8 @@ class AudioPlayer : Fragment(), PlaylistAdapter.IPlayer, TextWatcher, IAudioPlay binding.audioRewindText.text = "${Settings.audioJumpDelay}" binding.audioForwardText.text = "${Settings.audioJumpDelay}" + binding.audioForward10.contentDescription = getString(R.string.talkback_action_forward, Settings.audioJumpDelay.toString()) + binding.audioRewind10.contentDescription = getString(R.string.talkback_action_rewind, Settings.audioJumpDelay.toString()) updateBackground() } @@ -398,6 +401,12 @@ class AudioPlayer : Fragment(), PlaylistAdapter.IPlayer, TextWatcher, IAudioPlay previousRepeatType = repeatType } + /** + * Updates the text views in the player with the current progress + * It includes the time, the length and the progress pill text and content description + * + * @param progress the progress to be displayed + */ private fun updateProgress(progress: PlaybackProgress) { if (playlistModel.currentMediaPosition == -1) return binding.length.text = progress.lengthText @@ -413,42 +422,63 @@ class AudioPlayer : Fragment(), PlaylistAdapter.IPlayer, TextWatcher, IAudioPlay } lifecycleScope.launchWhenStarted { - val text = withContext(Dispatchers.Default) { - val medias = playlistModel.medias ?: return@withContext "" + val text:Pair<String, String> = withContext(Dispatchers.Default) { + val medias = playlistModel.medias ?: return@withContext Pair("", "") withContext(Dispatchers.Main) { if (!shouldHidePlayProgress()) binding.audioPlayProgress.setVisible() else binding.audioPlayProgress.setGone() } - if (playlistModel.currentMediaPosition == -1) return@withContext "" - val elapsedTracksTime = playlistModel.previousTotalTime ?: return@withContext "" + if (playlistModel.currentMediaPosition == -1) return@withContext Pair("", "") + val elapsedTracksTime = playlistModel.previousTotalTime ?: return@withContext Pair("", "") val progressTime = elapsedTracksTime + progress.time val totalTime = playlistModel.getTotalTime() val progressTimeText = Tools.millisToString( - if (showRemainingTime && totalTime > 0) totalTime - progressTime else progressTime, - false, - true, - false + if (showRemainingTime && totalTime > 0) totalTime - progressTime else progressTime, + false, + true, + false ) val totalTimeText = Tools.millisToString(totalTime, false, false, false) + val totalTimeDescription = TalkbackUtil.millisToString(requireActivity(), totalTime) + val progressTimeDescription = TalkbackUtil.millisToString(requireActivity(), if (showRemainingTime && totalTime > 0) totalTime - progressTime else progressTime) val currentProgressText = if (progressTimeText.isNullOrEmpty()) "0:00" else progressTimeText val textTrack = getString(R.string.track_index, "${playlistModel.currentMediaPosition + 1} / ${medias.size}") + val textTrackDescription = getString(R.string.talkback_track_index, "${playlistModel.currentMediaPosition + 1}", "${medias.size}") val textProgress = if (audioPlayProgressMode) { val endsAt = System.currentTimeMillis() + totalTime - progressTime if ((lastEndsAt - endsAt).absoluteValue > 1) lastEndsAt = endsAt getString( - R.string.audio_queue_progress_finished, - getTimeInstance(java.text.DateFormat.MEDIUM).format(lastEndsAt) + R.string.audio_queue_progress_finished, + getTimeInstance(java.text.DateFormat.MEDIUM).format(lastEndsAt) ) - } else if (showRemainingTime && totalTime > 0) getString( - R.string.audio_queue_progress_remaining, - currentProgressText - ) - else getString( - R.string.audio_queue_progress, - if (totalTimeText.isNullOrEmpty()) currentProgressText else "$currentProgressText / $totalTimeText" + } else + if (showRemainingTime && totalTime > 0) getString( + R.string.audio_queue_progress_remaining, + currentProgressText + ) + else getString( + R.string.audio_queue_progress, + if (totalTimeText.isNullOrEmpty()) currentProgressText else "$currentProgressText / $totalTimeText" + ) + val textDescription = if (audioPlayProgressMode) { + val endsAt = System.currentTimeMillis() + totalTime - progressTime + if ((lastEndsAt - endsAt).absoluteValue > 1) lastEndsAt = endsAt + getString( + R.string.audio_queue_progress_finished, + getTimeInstance(java.text.DateFormat.MEDIUM).format(lastEndsAt) + ) + } else + if (showRemainingTime && totalTime > 0) getString( + R.string.audio_queue_progress_remaining, + progressTimeDescription + ) + else getString( + R.string.audio_queue_progress, + if (totalTimeText.isNullOrEmpty()) progressTimeDescription else getString(R.string.talkback_out_of, progressTimeDescription, totalTimeDescription) ) - "$textTrack • $textProgress" + Pair("$textTrack ${TextUtils.separator} $textProgress", "$textTrackDescription. $textDescription") } - binding.audioPlayProgress.text = text + binding.audioPlayProgress.text = text.first + binding.audioPlayProgress.contentDescription = text.second } } @@ -743,6 +773,7 @@ class AudioPlayer : Fragment(), PlaylistAdapter.IPlayer, TextWatcher, IAudioPlay playlistModel.setTime(progress.toLong(), true) binding.time.text = Tools.millisToString(if (showRemainingTime) progress - playlistModel.length else progress.toLong()) binding.headerTime.text = Tools.millisToString(progress.toLong()) + binding.timeline.forceAccessibilityUpdate() } } } -- GitLab