[Android] [PATCH v2] Accessibility: Add custom actions to lists (play, Favorite, etc.)
Aryan Choudhary
aryanchoudharyk2008 at gmail.com
Thu Feb 12 13:03:18 UTC 2026
just want to check in on any changes i need to make
On Sun, 18 Jan 2026 at 15:43, aryan <aryanchoudharyk2008 at gmail.com> wrote:
> Changes in v2:
> - Moved AccessibilityDelegate to onBindViewHolder to fix actions
> disappearing on scroll.
>
> Hello VideoLAN Android Team,
> I am submitting a patch to improve the TalkBack experience in the media
> browser and player lists.
> The Problem: Currently, list items require excessive swiping to access
> common actions (like "More options"), and the interface does not expose
> quick actions like "Remove from favorites" or "Seek" directly to
> accessibility services.
> The Fix: This patch implements AccessibilityDelegateCompat to:
> 1.
> Add Custom Accessibility Actions (Seek, Favorite, Play Next, etc.)
> directly to the list items.
> 2.
> Hide the redundant "More" button from TalkBack focus
> (IMPORTANT_FOR_ACCESSIBILITY_NO) to reduce clutter.
> 3.
> Dynamically update action labels (e.g., swapping "Add to favorites" with
> "Remove from favorites" based on state).
> I am submitting this via the mailing list as I am unable to create an
> account on code.videolan.org at the moment.
> Best regards, Aryan
>
> ---
> .../resources/src/main/res/values/ids.xml | 24 +++
> .../vlc/gui/audio/AudioBrowserAdapter.kt | 127 +++++++++++++++
> .../vlc/gui/browser/BaseBrowserAdapter.kt | 132 ++++++++++++++++
> .../vlc/gui/video/VideoGridFragment.kt | 4 +
> .../vlc/gui/video/VideoListAdapter.kt | 146 +++++++++++++++++-
> 5 files changed, 432 insertions(+), 1 deletion(-)
>
> diff --git a/application/resources/src/main/res/values/ids.xml
> b/application/resources/src/main/res/values/ids.xml
> index 5ff0ec18e..59ad7b7ab 100644
> --- a/application/resources/src/main/res/values/ids.xml
> +++ b/application/resources/src/main/res/values/ids.xml
> @@ -25,4 +25,28 @@
> <resources>
> <item type="id" name="sort" />
> <item type="id" name="sort_desc" />
> + <item type="id" name="accessibility_action_append" />
> + <item type="id" name="accessibility_action_play_next" />
> + <item type="id" name="accessibility_action_delete" />
> + <item type="id" name="accessibility_action_add_to_playlist" />
> + <item type="id" name="accessibility_action_info" />
> + <item type="id" name="accessibility_action_play_all" />
> + <item type="id" name="accessibility_action_share" />
> + <item type="id" name="accessibility_action_set_ringtone" />
> + <item type="id" name="accessibility_action_download_subtitles" />
> + <item type="id" name="accessibility_action_play_as_audio" />
> + <item type="id" name="accessibility_action_add_favorite" />
> + <item type="id" name="accessibility_action_remove_favorite" />
> + <item type="id" name="accessibility_action_mark_played" />
> + <item type="id" name="accessibility_action_mark_unplayed" />
> + <item type="id" name="accessibility_action_add_shortcut" />
> + <item type="id" name="accessibility_action_go_to_folder" />
> + <item type="id" name="accessibility_action_add_group" />
> + <item type="id" name="accessibility_action_group_similar" />
> + <item type="id" name="accessibility_action_rename" />
> + <item type="id" name="accessibility_action_play_from_start" />
> + <item type="id" name="accessibility_action_go_to_album" />
> + <item type="id" name="accessibility_action_go_to_artist" />
> + <item type="id" name="accessibility_action_ban_folder" />
> + <item type="id" name="accessibility_action_open_context_menu" />
> </resources>
> \ No newline at end of file
> diff --git
> a/application/vlc-android/src/org/videolan/vlc/gui/audio/AudioBrowserAdapter.kt
> b/application/vlc-android/src/org/videolan/vlc/gui/audio/AudioBrowserAdapter.kt
> index a8be20509..33572698e 100644
> ---
> a/application/vlc-android/src/org/videolan/vlc/gui/audio/AudioBrowserAdapter.kt
> +++
> b/application/vlc-android/src/org/videolan/vlc/gui/audio/AudioBrowserAdapter.kt
> @@ -41,9 +41,11 @@ import androidx.paging.PagedListAdapter
> import androidx.recyclerview.widget.DiffUtil
> import androidx.recyclerview.widget.RecyclerView
> import org.videolan.libvlc.util.AndroidUtil
> +import org.videolan.medialibrary.interfaces.media.Album
> import org.videolan.medialibrary.interfaces.media.Artist
> import org.videolan.medialibrary.interfaces.media.Genre
> import org.videolan.medialibrary.interfaces.media.MediaWrapper
> +import org.videolan.medialibrary.interfaces.media.Playlist
> import org.videolan.medialibrary.media.MediaLibraryItem
> import org.videolan.medialibrary.media.MediaLibraryItem.FLAG_SELECTED
> import org.videolan.resources.AppContextProvider
> @@ -73,6 +75,22 @@ import org.videolan.vlc.util.isOTG
> import org.videolan.vlc.util.isSD
> import org.videolan.vlc.util.isSchemeSMB
> import org.videolan.vlc.viewmodels.PlaylistModel
> +import android.os.Bundle
> +import androidx.core.view.AccessibilityDelegateCompat
> +import androidx.core.view.ViewCompat
> +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
> +import androidx.appcompat.app.AppCompatActivity
> +import androidx.fragment.app.FragmentActivity
> +import org.videolan.vlc.media.MediaUtils
> +import org.videolan.vlc.gui.helpers.UiTools
> +import org.videolan.vlc.gui.helpers.UiTools.addToPlaylist
> +import org.videolan.vlc.gui.helpers.UiTools.showMediaInfo
> +import org.videolan.vlc.util.share
> +import androidx.lifecycle.lifecycleScope
> +import org.videolan.vlc.util.ContextOption
> +import org.videolan.vlc.gui.dialogs.CtxActionReceiver
> +import org.videolan.vlc.media.getAll
> +import org.videolan.tools.retrieveParent
>
> private const val SHOW_IN_LIST = -1
>
> @@ -172,6 +190,7 @@ open class AudioBrowserAdapter @JvmOverloads
> constructor(
> if (position >= itemCount) return
> val item = getItem(position)
> holder.setItem(item)
> + holder.updateAccessibilityDelegate(item)
> if (item is Artist) item.description =
> holder.binding.root.context.resources.getQuantityString(R.plurals.albums_quantity,
> item.albumsCount, item.albumsCount)
> if (item is Genre) item.description =
> holder.binding.root.context.resources.getQuantityString(R.plurals.track_quantity,
> item.tracksCount, item.tracksCount)
> val isSelected = multiSelectHelper.isSelected(position)
> @@ -314,6 +333,7 @@ open class AudioBrowserAdapter @JvmOverloads
> constructor(
> }
> }
> binding.imageWidth = listImageWidth
> + binding.itemMore.importantForAccessibility =
> View.IMPORTANT_FOR_ACCESSIBILITY_NO
> }
>
> override fun selectView(selected: Boolean) {
> @@ -355,6 +375,7 @@ open class AudioBrowserAdapter @JvmOverloads
> constructor(
> }
> binding.imageWidth = cardSize
> binding.container.layoutParams.width = cardSize
> + binding.itemMore.importantForAccessibility =
> View.IMPORTANT_FOR_ACCESSIBILITY_NO
>
> }
>
> @@ -384,6 +405,112 @@ open class AudioBrowserAdapter @JvmOverloads
> constructor(
> @TargetApi(Build.VERSION_CODES.M)
> abstract inner class AbstractMediaItemViewHolder<T :
> ViewDataBinding>(binding: T) : SelectorViewHolder<T>(binding),
> MarqueeViewHolder {
>
> + fun updateAccessibilityDelegate(item: MediaLibraryItem?) {
> + if (item == null) {
> + ViewCompat.setAccessibilityDelegate(itemView, null)
> + return
> + }
> + ViewCompat.setAccessibilityDelegate(itemView, object :
> AccessibilityDelegateCompat() {
> + override fun onInitializeAccessibilityNodeInfo(host:
> View, info: AccessibilityNodeInfoCompat) {
> + super.onInitializeAccessibilityNodeInfo(host, info)
> + val context = itemView.context
> +
> + if (item.isFavorite) {
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_remove_favorite,
> context.getString(R.string.favorites_remove)))
> + } else {
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_add_favorite,
> context.getString(R.string.favorites_add)))
> + }
> +
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_append,
> context.getString(R.string.append)))
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_play_next,
> context.getString(R.string.insert_next)))
> +
> + if (item is MediaWrapper || item is Playlist) {
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_add_shortcut,
> context.getString(R.string.create_shortcut)))
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_rename,
> context.getString(R.string.rename)))
> + }
> +
> + if (item is MediaWrapper) {
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_play_all,
> context.getString(R.string.play_all)))
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_info,
> context.getString(R.string.info)))
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_share,
> context.getString(R.string.share)))
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_add_to_playlist,
> context.getString(R.string.add_to_playlist)))
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_delete,
> context.getString(R.string.delete)))
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_set_ringtone,
> context.getString(R.string.set_song)))
> + if (item.uri.retrieveParent() != null) {
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_go_to_folder,
> context.getString(R.string.go_to_folder)))
> + }
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_go_to_album,
> context.getString(R.string.go_to_album)))
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_go_to_artist,
> context.getString(R.string.go_to_artist)))
> + } else if (item is Album || item is Artist || item is
> Genre || item is Playlist) {
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_play_all,
> context.getString(R.string.play_all)))
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_add_to_playlist,
> context.getString(R.string.add_to_playlist)))
> + if (item !is Genre) {
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_delete,
> context.getString(R.string.delete)))
> + }
> + if (item is Album) {
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_go_to_artist,
> context.getString(R.string.go_to_artist)))
> + }
> + }
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_open_context_menu,
> context.getString(R.string.more_actions)))
> + }
> +
> + override fun performAccessibilityAction(host: View,
> action: Int, args: Bundle?): Boolean {
> + val option = when (action) {
> + R.id.accessibility_action_append ->
> ContextOption.CTX_APPEND
> + R.id.accessibility_action_play_next ->
> ContextOption.CTX_PLAY_NEXT
> + R.id.accessibility_action_info ->
> ContextOption.CTX_INFORMATION
> + R.id.accessibility_action_share ->
> ContextOption.CTX_SHARE
> + R.id.accessibility_action_add_to_playlist ->
> ContextOption.CTX_ADD_TO_PLAYLIST
> + R.id.accessibility_action_delete ->
> ContextOption.CTX_DELETE
> + R.id.accessibility_action_set_ringtone ->
> ContextOption.CTX_SET_RINGTONE
> + R.id.accessibility_action_play_all ->
> ContextOption.CTX_PLAY_ALL
> + R.id.accessibility_action_add_favorite ->
> ContextOption.CTX_FAV_ADD
> + R.id.accessibility_action_remove_favorite ->
> ContextOption.CTX_FAV_REMOVE
> + R.id.accessibility_action_add_shortcut ->
> ContextOption.CTX_ADD_SHORTCUT
> + R.id.accessibility_action_go_to_folder ->
> ContextOption.CTX_GO_TO_FOLDER
> + R.id.accessibility_action_rename ->
> ContextOption.CTX_RENAME
> + R.id.accessibility_action_go_to_album ->
> ContextOption.CTX_GO_TO_ALBUM
> + R.id.accessibility_action_go_to_artist ->
> ContextOption.CTX_GO_TO_ARTIST
> + R.id.accessibility_action_open_context_menu -> {
> + onMoreClick(host)
> + return true
> + }
> + else -> null
> + }
> +
> + if (option != null && eventsHandler is
> CtxActionReceiver) {
> + val position = layoutPosition
> + if (isPositionValid(position)) {
> + eventsHandler.onCtxAction(position, option)
> + }
> + return true
> + }
> +
> + return super.performAccessibilityAction(host, action,
> args)
> + }
> + })
> + }
> +
> val canBeReordered: Boolean
> get() = reorderable && !stopReorder
>
> diff --git
> a/application/vlc-android/src/org/videolan/vlc/gui/browser/BaseBrowserAdapter.kt
> b/application/vlc-android/src/org/videolan/vlc/gui/browser/BaseBrowserAdapter.kt
> index 96e4803e1..33ae5aa35 100644
> ---
> a/application/vlc-android/src/org/videolan/vlc/gui/browser/BaseBrowserAdapter.kt
> +++
> b/application/vlc-android/src/org/videolan/vlc/gui/browser/BaseBrowserAdapter.kt
> @@ -55,6 +55,21 @@ import org.videolan.vlc.gui.view.MiniVisualizer
> import org.videolan.vlc.util.LifecycleAwareScheduler
> import org.videolan.vlc.util.getDescriptionSpan
> import org.videolan.vlc.viewmodels.PlaylistModel
> +import android.os.Bundle
> +import androidx.core.view.AccessibilityDelegateCompat
> +import androidx.core.view.ViewCompat
> +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
> +import androidx.appcompat.app.AppCompatActivity
> +import androidx.fragment.app.FragmentActivity
> +import org.videolan.vlc.media.MediaUtils
> +import org.videolan.vlc.gui.helpers.UiTools
> +import org.videolan.vlc.gui.helpers.UiTools.addToPlaylist
> +import org.videolan.vlc.gui.helpers.UiTools.showMediaInfo
> +import org.videolan.vlc.util.share
> +import androidx.lifecycle.lifecycleScope
> +import org.videolan.vlc.util.ContextOption
> +import org.videolan.vlc.gui.dialogs.CtxActionReceiver
> +import org.videolan.tools.retrieveParent
>
> const val UPDATE_PROGRESS = "update_progress"
> open class BaseBrowserAdapter(val browserContainer:
> BrowserContainer<MediaLibraryItem>, var sort:Int =
> Medialibrary.SORT_FILENAME, var asc:Boolean = true, val forMain:Boolean =
> true) : DiffUtilAdapter<MediaLibraryItem,
> BaseBrowserAdapter.ViewHolder<ViewDataBinding>>(),
> MultiSelectAdapter<MediaLibraryItem>, FastScroller.SeparatedAdapter {
> @@ -155,6 +170,9 @@ open class BaseBrowserAdapter(val browserContainer:
> BrowserContainer<MediaLibrar
> }
>
> override fun onBindViewHolder(holder: ViewHolder<ViewDataBinding>,
> position: Int) {
> + if (holder is MediaViewHolder) {
> + holder.updateAccessibilityDelegate(dataset[position])
> + }
> val viewType = getItemViewType(position)
> if (viewType == TYPE_MEDIA) {
> onBindMediaViewHolder(holder as MediaViewHolder, position)
> @@ -258,6 +276,119 @@ open class BaseBrowserAdapter(val browserContainer:
> BrowserContainer<MediaLibrar
> override val titleView: TextView = bindingContainer.title
> var job : Job? = null
>
> + fun updateAccessibilityDelegate(item: MediaLibraryItem?) {
> + if (item == null) {
> + ViewCompat.setAccessibilityDelegate(itemView, null)
> + return
> + }
> + ViewCompat.setAccessibilityDelegate(itemView, object :
> AccessibilityDelegateCompat() {
> + override fun onInitializeAccessibilityNodeInfo(host:
> View, info: AccessibilityNodeInfoCompat) {
> + super.onInitializeAccessibilityNodeInfo(host, info)
> + val context = itemView.context
> +
> + if (item.isFavorite) {
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_remove_favorite,
> context.getString(R.string.favorites_remove)))
> + } else {
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_add_favorite,
> context.getString(R.string.favorites_add)))
> + }
> +
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_append,
> context.getString(R.string.append)))
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_play_next,
> context.getString(R.string.insert_next)))
> +
> + if (item is MediaWrapper) {
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_add_shortcut,
> context.getString(R.string.create_shortcut)))
> + if (item.uri.retrieveParent() != null) {
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_go_to_folder,
> context.getString(R.string.go_to_folder)))
> + }
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_rename,
> context.getString(R.string.rename)))
> + }
> +
> + if (item is MediaWrapper) {
> + if (item.type != MediaWrapper.TYPE_DIR) {
> + if (item.playCount > 0) {
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> +
> R.id.accessibility_action_mark_unplayed,
> context.getString(R.string.mark_as_not_played)))
> + } else {
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> +
> R.id.accessibility_action_mark_played,
> context.getString(R.string.mark_as_played)))
> + }
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_play_all,
> context.getString(R.string.play_all)))
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_info,
> context.getString(R.string.info)))
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_share,
> context.getString(R.string.share)))
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> +
> R.id.accessibility_action_add_to_playlist,
> context.getString(R.string.add_to_playlist)))
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_delete,
> context.getString(R.string.delete)))
> + } else {
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_play_all,
> context.getString(R.string.play_all)))
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> +
> R.id.accessibility_action_add_to_playlist,
> context.getString(R.string.add_to_playlist)))
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_ban_folder,
> context.getString(R.string.ban_folder)))
> + }
> + if (item.type == MediaWrapper.TYPE_VIDEO) {
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> +
> R.id.accessibility_action_download_subtitles,
> context.getString(R.string.download_subtitles)))
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_play_as_audio,
> context.getString(R.string.play_as_audio)))
> + }
> + } else if (item is Storage) {
> + // Storage actions if any
> + }
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_open_context_menu,
> context.getString(R.string.more_actions)))
> + }
> +
> + override fun performAccessibilityAction(host: View,
> action: Int, args: Bundle?): Boolean {
> + val option = when (action) {
> + R.id.accessibility_action_add_favorite ->
> ContextOption.CTX_FAV_ADD
> + R.id.accessibility_action_remove_favorite ->
> ContextOption.CTX_FAV_REMOVE
> + R.id.accessibility_action_mark_played ->
> ContextOption.CTX_MARK_AS_PLAYED
> + R.id.accessibility_action_mark_unplayed ->
> ContextOption.CTX_MARK_AS_UNPLAYED
> + R.id.accessibility_action_append ->
> ContextOption.CTX_APPEND
> + R.id.accessibility_action_play_next ->
> ContextOption.CTX_PLAY_NEXT
> + R.id.accessibility_action_info ->
> ContextOption.CTX_INFORMATION
> + R.id.accessibility_action_share ->
> ContextOption.CTX_SHARE
> + R.id.accessibility_action_add_to_playlist ->
> ContextOption.CTX_ADD_TO_PLAYLIST
> + R.id.accessibility_action_delete ->
> ContextOption.CTX_DELETE
> + R.id.accessibility_action_download_subtitles ->
> ContextOption.CTX_DOWNLOAD_SUBTITLES
> + R.id.accessibility_action_play_as_audio ->
> ContextOption.CTX_PLAY_AS_AUDIO
> + R.id.accessibility_action_play_all ->
> ContextOption.CTX_PLAY_ALL
> + R.id.accessibility_action_add_shortcut ->
> ContextOption.CTX_ADD_SHORTCUT
> + R.id.accessibility_action_go_to_folder ->
> ContextOption.CTX_GO_TO_FOLDER
> + R.id.accessibility_action_rename ->
> ContextOption.CTX_RENAME
> + R.id.accessibility_action_ban_folder ->
> ContextOption.CTX_BAN_FOLDER
> + R.id.accessibility_action_open_context_menu -> {
> + onMoreClick(host)
> + return true
> + }
> + else -> null
> + }
> +
> + if (option != null && browserContainer is
> CtxActionReceiver) {
> + val position = layoutPosition
> + if (position >= 0 && position < dataset.size) {
> + (browserContainer as
> CtxActionReceiver).onCtxAction(position, option)
> + }
> + return true
> + }
> +
> + return super.performAccessibilityAction(host, action,
> args)
> + }
> + })
> + }
> +
> init {
> bindingContainer.setHolder(this)
> if (AndroidUtil.isMarshMallowOrLater)
> itemView.setOnContextClickListener { v ->
> @@ -275,6 +406,7 @@ open class BaseBrowserAdapter(val browserContainer:
> BrowserContainer<MediaLibrar
>
> bindingContainer.banIcon.onFocusChangeListener =
> focusChangeListener
> bindingContainer.container.onFocusChangeListener =
> focusChangeListener
> + bindingContainer.moreIcon.importantForAccessibility =
> View.IMPORTANT_FOR_ACCESSIBILITY_NO
> }
>
> override fun selectView(selected: Boolean) {
> diff --git
> a/application/vlc-android/src/org/videolan/vlc/gui/video/VideoGridFragment.kt
> b/application/vlc-android/src/org/videolan/vlc/gui/video/VideoGridFragment.kt
> index 99092d919..8a856b977 100644
> ---
> a/application/vlc-android/src/org/videolan/vlc/gui/video/VideoGridFragment.kt
> +++
> b/application/vlc-android/src/org/videolan/vlc/gui/video/VideoGridFragment.kt
> @@ -746,6 +746,9 @@ class VideoGridFragment :
> MediaBrowserFragment<VideosViewModel>(), SwipeRefreshL
> onLongClick(position)
> }
> }
> + is VideoCtxAction -> {
> + onCtxAction(position, option)
> + }
> }
> }
>
> @@ -806,3 +809,4 @@ class VideoClick(val position: Int, val item:
> MediaLibraryItem) : VideoAction()
> class VideoLongClick(val position: Int, val item: MediaLibraryItem) :
> VideoAction()
> class VideoCtxClick(val position: Int, val item: MediaLibraryItem) :
> VideoAction()
> class VideoImageClick(val position: Int, val item: MediaLibraryItem) :
> VideoAction()
> +class VideoCtxAction(val position: Int, val option: ContextOption) :
> VideoAction()
> diff --git
> a/application/vlc-android/src/org/videolan/vlc/gui/video/VideoListAdapter.kt
> b/application/vlc-android/src/org/videolan/vlc/gui/video/VideoListAdapter.kt
> index 9e3a48292..1cb0602bb 100644
> ---
> a/application/vlc-android/src/org/videolan/vlc/gui/video/VideoListAdapter.kt
> +++
> b/application/vlc-android/src/org/videolan/vlc/gui/video/VideoListAdapter.kt
> @@ -53,6 +53,17 @@ import org.videolan.vlc.media.isOTG
> import org.videolan.vlc.media.isSD
> import org.videolan.vlc.util.*
> import org.videolan.vlc.viewmodels.mobile.VideoGroupingType
> +import android.os.Bundle
> +import androidx.core.view.AccessibilityDelegateCompat
> +import androidx.core.view.ViewCompat
> +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
> +import androidx.appcompat.app.AppCompatActivity
> +import androidx.fragment.app.FragmentActivity
> +import org.videolan.vlc.media.MediaUtils
> +import org.videolan.vlc.gui.helpers.UiTools.addToPlaylist
> +import org.videolan.vlc.gui.helpers.UiTools.showMediaInfo
> +import androidx.lifecycle.lifecycleScope
> +import org.videolan.tools.retrieveParent
>
> private const val TAG = "VLC/VideoListAdapter"
>
> @@ -88,7 +99,12 @@ class VideoListAdapter(private var
> isSeenMediaMarkerVisible: Boolean, private va
> }
>
> override fun onBindViewHolder(holder: ViewHolder, position: Int) {
> - val item = getItem(position) ?: return
> + val item = getItem(position)
> + if (item == null) {
> + holder.updateAccessibilityDelegate(null)
> + return
> + }
> + holder.updateAccessibilityDelegate(item)
> holder.binding.setVariable(BR.scaleType,
> ImageView.ScaleType.CENTER_CROP)
> fillView(holder, item)
> holder.binding.setVariable(BR.media, item)
> @@ -198,9 +214,137 @@ class VideoListAdapter(private var
> isSeenMediaMarkerVisible: Boolean, private va
> val title : TextView = itemView.findViewById(R.id.ml_item_title)
> val more : ImageView = itemView.findViewById(R.id.item_more)
>
> + fun updateAccessibilityDelegate(item: MediaLibraryItem?) {
> + if (item == null) {
> + ViewCompat.setAccessibilityDelegate(itemView, null)
> + return
> + }
> + ViewCompat.setAccessibilityDelegate(itemView, object :
> AccessibilityDelegateCompat() {
> + override fun onInitializeAccessibilityNodeInfo(host:
> View, info: AccessibilityNodeInfoCompat) {
> + super.onInitializeAccessibilityNodeInfo(host, info)
> + val context = itemView.context
> +
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_append,
> context.getString(R.string.append)))
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_play_next,
> context.getString(R.string.insert_next)))
> +
> + if (item.isFavorite) {
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_remove_favorite,
> context.getString(R.string.favorites_remove)))
> + } else {
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_add_favorite,
> context.getString(R.string.favorites_add)))
> + }
> +
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_add_shortcut,
> context.getString(R.string.create_shortcut)))
> +
> + if (item is MediaWrapper) {
> + if (item.seen > 0) {
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_mark_unplayed,
> context.getString(R.string.mark_as_not_played)))
> + } else {
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_mark_played,
> context.getString(R.string.mark_as_played)))
> + }
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_play_all,
> context.getString(R.string.play_all)))
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_play_as_audio,
> context.getString(R.string.play_as_audio)))
> + if (item.time > 0L) {
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> +
> R.id.accessibility_action_play_from_start,
> context.getString(R.string.play_from_start)))
> + }
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_info,
> context.getString(R.string.info)))
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_share,
> context.getString(R.string.share)))
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_add_to_playlist,
> context.getString(R.string.add_to_playlist)))
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_delete,
> context.getString(R.string.delete)))
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_rename,
> context.getString(R.string.rename)))
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_download_subtitles,
> context.getString(R.string.download_subtitles)))
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_add_group,
> context.getString(R.string.add_to_group)))
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_group_similar,
> context.getString(R.string.group_similar)))
> + if (item.uri.retrieveParent() != null) {
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_go_to_folder,
> context.getString(R.string.go_to_folder)))
> + }
> + } else if (item is VideoGroup || item is Folder) {
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_mark_unplayed,
> context.getString(R.string.mark_all_as_not_played)))
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_mark_played,
> context.getString(R.string.mark_all_as_played)))
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_play_all,
> context.getString(R.string.play_all)))
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_add_to_playlist,
> context.getString(R.string.add_to_playlist)))
> + if (item is Folder) {
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_ban_folder,
> context.getString(R.string.ban_folder)))
> + } else {
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_rename,
> context.getString(R.string.rename_group)))
> + }
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_delete,
> context.getString(R.string.delete)))
> + }
> +
> info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(
> + R.id.accessibility_action_open_context_menu,
> context.getString(R.string.more_actions)))
> + }
> +
> + override fun performAccessibilityAction(host: View,
> action: Int, args: Bundle?): Boolean {
> + val option = when (action) {
> + R.id.accessibility_action_append ->
> ContextOption.CTX_APPEND
> + R.id.accessibility_action_play_next ->
> ContextOption.CTX_PLAY_NEXT
> + R.id.accessibility_action_info ->
> ContextOption.CTX_INFORMATION
> + R.id.accessibility_action_share ->
> ContextOption.CTX_SHARE
> + R.id.accessibility_action_add_to_playlist ->
> ContextOption.CTX_ADD_TO_PLAYLIST
> + R.id.accessibility_action_delete ->
> ContextOption.CTX_DELETE
> + R.id.accessibility_action_download_subtitles ->
> ContextOption.CTX_DOWNLOAD_SUBTITLES
> + R.id.accessibility_action_play_as_audio ->
> ContextOption.CTX_PLAY_AS_AUDIO
> + R.id.accessibility_action_play_from_start ->
> ContextOption.CTX_PLAY_FROM_START
> + R.id.accessibility_action_play_all ->
> ContextOption.CTX_PLAY_ALL
> + R.id.accessibility_action_add_favorite ->
> ContextOption.CTX_FAV_ADD
> + R.id.accessibility_action_remove_favorite ->
> ContextOption.CTX_FAV_REMOVE
> + R.id.accessibility_action_add_shortcut ->
> ContextOption.CTX_ADD_SHORTCUT
> + R.id.accessibility_action_add_group ->
> ContextOption.CTX_ADD_GROUP
> + R.id.accessibility_action_group_similar ->
> ContextOption.CTX_GROUP_SIMILAR
> + R.id.accessibility_action_go_to_folder ->
> ContextOption.CTX_GO_TO_FOLDER
> + R.id.accessibility_action_rename -> if (item is
> VideoGroup) ContextOption.CTX_RENAME_GROUP else ContextOption.CTX_RENAME
> + R.id.accessibility_action_ban_folder ->
> ContextOption.CTX_BAN_FOLDER
> + R.id.accessibility_action_mark_played -> if (item
> is MediaWrapper || item.itemType == MediaLibraryItem.TYPE_MEDIA)
> ContextOption.CTX_MARK_AS_PLAYED else ContextOption.CTX_MARK_ALL_AS_PLAYED
> + R.id.accessibility_action_mark_unplayed -> if
> (item is MediaWrapper || item.itemType == MediaLibraryItem.TYPE_MEDIA)
> ContextOption.CTX_MARK_AS_UNPLAYED else
> ContextOption.CTX_MARK_ALL_AS_UNPLAYED
> + R.id.accessibility_action_open_context_menu -> {
> + onMoreClick(host)
> + return true
> + }
> + else -> null
> + }
> +
> + if (option != null) {
> + val position = layoutPosition
> + if (isPositionValid(position)) {
> +
> eventsChannel.trySend(VideoCtxAction(position, option))
> + }
> + return true
> + }
> +
> + return super.performAccessibilityAction(host, action,
> args)
> + }
> + })
> + }
> +
> init {
> binding.setVariable(BR.holder, this)
> binding.setVariable(BR.cover,
> UiTools.getDefaultVideoDrawable(itemView.context))
> + more.importantForAccessibility =
> View.IMPORTANT_FOR_ACCESSIBILITY_NO
> if (AndroidUtil.isMarshMallowOrLater)
> itemView.setOnContextClickListener { v ->
> onMoreClick(v)
> --
> 2.52.0.windows.1
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mailman.videolan.org/pipermail/android/attachments/20260212/fa6aa4b2/attachment-0001.htm>
More information about the Android
mailing list