[Android] [PATCH] Accessibility improvements for audio/video lists
aryan
aryanchoudharyk2008 at gmail.com
Sun Jan 18 09:05:40 UTC 2026
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 | 126 ++++++++++++++++
.../vlc/gui/browser/BaseBrowserAdapter.kt | 127 ++++++++++++++++
.../vlc/gui/video/VideoGridFragment.kt | 4 +
.../vlc/gui/video/VideoListAdapter.kt | 136 ++++++++++++++++++
5 files changed, 417 insertions(+)
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..e46a8d4f4 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
@@ -314,6 +332,7 @@ open class AudioBrowserAdapter @JvmOverloads constructor(
}
}
binding.imageWidth = listImageWidth
+ binding.itemMore.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
}
override fun selectView(selected: Boolean) {
@@ -355,6 +374,7 @@ open class AudioBrowserAdapter @JvmOverloads constructor(
}
binding.imageWidth = cardSize
binding.container.layoutParams.width = cardSize
+ binding.itemMore.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
}
@@ -384,6 +404,112 @@ open class AudioBrowserAdapter @JvmOverloads constructor(
@TargetApi(Build.VERSION_CODES.M)
abstract inner class AbstractMediaItemViewHolder<T : ViewDataBinding>(binding: T) : SelectorViewHolder<T>(binding), MarqueeViewHolder {
+ init {
+ ViewCompat.setAccessibilityDelegate(itemView, object : AccessibilityDelegateCompat() {
+ override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) {
+ super.onInitializeAccessibilityNodeInfo(host, info)
+ val position = layoutPosition
+ if (!isPositionValid(position)) return
+ val item = getItem(position) ?: return
+ 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 position = layoutPosition
+ if (!isPositionValid(position)) return super.performAccessibilityAction(host, action, args)
+ val item = getItem(position) ?: return super.performAccessibilityAction(host, action, args)
+
+ 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) {
+ 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..e949db439 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 {
@@ -275,6 +290,118 @@ open class BaseBrowserAdapter(val browserContainer: BrowserContainer<MediaLibrar
bindingContainer.banIcon.onFocusChangeListener = focusChangeListener
bindingContainer.container.onFocusChangeListener = focusChangeListener
+ bindingContainer.moreIcon.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
+
+ ViewCompat.setAccessibilityDelegate(itemView, object : AccessibilityDelegateCompat() {
+ override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) {
+ super.onInitializeAccessibilityNodeInfo(host, info)
+ val position = layoutPosition
+ if (position < 0 || position >= dataset.size) return
+ val item = dataset[position]
+ 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 position = layoutPosition
+ if (position < 0 || position >= dataset.size) return super.performAccessibilityAction(host, action, args)
+ val item = dataset[position]
+
+ 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) {
+ (browserContainer as CtxActionReceiver).onCtxAction(position, option)
+ return true
+ }
+
+ return super.performAccessibilityAction(host, action, args)
+ }
+ })
}
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..59c18e2be 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"
@@ -201,11 +212,136 @@ class VideoListAdapter(private var isSeenMediaMarkerVisible: Boolean, private va
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)
true
}
+ ViewCompat.setAccessibilityDelegate(itemView, object : AccessibilityDelegateCompat() {
+ override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) {
+ super.onInitializeAccessibilityNodeInfo(host, info)
+ val position = layoutPosition
+ if (!isPositionValid(position)) return
+ val item = getItem(position) ?: return
+ 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 position = layoutPosition
+ if (!isPositionValid(position)) return super.performAccessibilityAction(host, action, args)
+ val item = getItem(position) ?: return super.performAccessibilityAction(host, action, args)
+
+ 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) {
+ eventsChannel.trySend(VideoCtxAction(position, option))
+ return true
+ }
+
+ return super.performAccessibilityAction(host, action, args)
+ }
+ })
}
fun onImageClick(@Suppress("UNUSED_PARAMETER") v: View) {
--
2.52.0.windows.1
More information about the Android
mailing list