[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