Android talkback; how to disable announcing cell position and cell count? - android

When I have a recycler view with 12 items, when accessibility focus goes onto cell number 3, for example, talkback announces "Double tap to activate. Item 3 of 12".
I want to keep the action but stop it from announcing the item position and item count. How can I do this? I have tried to assign a delegate to the recycler view but not sure what to override in the delegate.
How can I do this?

So I figured it out. An AccessibilityNodeInfo has a method called SetCollectionInfo(). CollectionInfo has properties like rowCont and columnCount.
I simply set the info to null.
Note, the below is xamarin:
private class TabLayoutTabAccessibilityDelegate: View.AccessibilityDelegate
{
public override void OnInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info)
{
base.OnInitializeAccessibilityNodeInfo(host, info);
info.SetCollectionInfo(null);
}
}

This is a functionality that should not be disabled: it lets people using Talkback know that they are in a list, how long is the list, which element is currently focused (are there more elements above/below).

Related

AutoCompleteTextView item selection programmatically

I have an AutoCompleteTextView that is filled with cities from an sqlite database that calls an AsyncTask on item click, recently I added an option to detect my location using the gps, so the problem is I can detect the city (i.e Beirut) and set the text for the AutoCompleteTextView but the thing is that the dropdown filter opens showing Beirut (which is correct) but I still need to click on the list item to invoke the listener, how to do so programmatically
How to:
Enter the Activity (DONE)
Detect location (DONE)
set text of text view (DONE)
show textview dropdown list(DONE)
choose the item that will be returned, since it will only return one city (NOT DONE)
To be clear, Tano's solution is sufficient to answer this question. But, in case others run into the same use case I did, here's some more background that may potentially help you...
I had been running into this issue specifically while trying to make a non-editable Material Exposed Dropdown Menu and set it's initial value programmatically. The documentation to create this type of "dropdown" can be found in the Exposed Dropdown Menus section here, which suggests a mechanism using TextInputLayout and AutocompleteTextView (even if you don't want autocomplete functionality).
Failed Solution 1:
At first glance setListSelection() and getListSelection() seemed like they might do the trick. But after many trials, I learned that they may not be sufficient because they only work when the list popup isShowing(). So for example, if you simply want to set the initial selection without having to show the list popup first, this will not work.
Failed Solution 2:
Then I tried setText() which showed the proper text in my textbox. Yay! But wait! When I clicked on the text view, only a subset of options in the list popup were shown for some reason. Why was that? The key thing to keep in mind here is that since this is an autocomplete textview, it by default filters out options based off of the text in the textview. This might not be apparent, especially if you're solely using this control for the sake of making a simple non-editable dropdown selector.
Solution:
This brings us to our actual solution (suggested by Tano)... setText() with filter as false will turn off the filtering capabilities AND it will not change the contents of your list popup.
autoCompleteTextView.setText(myText, false);
I was facing a similar problem and this solved my issue. Important is to call setText(<text>, <filter boolean>) in order not to filter with the given text set the second parameter with false. The text will be got from the dropdown adapter.
Solution snippet:
automCompleteTextView.setText(automCompleteTextView.getAdapter().getItem(position).toString(), false);
A solution were you don't need to change your API level.
automCompleteTextView.setAdapter(adapter);
// set default selection, filtering is active so all items is not visible in drop-down menu
automCompleteTextView.setText(automCompleteTextView.getAdapter().getItem(0).toString());
// change filtering for the adapter so all items can be visible in drop-down menu
adapter.getFilter().filter(null);
one-liner for the same job but requires higher API level
automCompleteTextView.setText(automCompleteTextView.getAdapter().getItem(0).toString(), false);
I figure out after dig into the AutoCompleteTextView code on android source code:
fun AutoCompleteTextView.selectItem(text: String, position: Int = 0) {
this.setText(text)
this.showDropDown()
this.setSelection(position)
this.listSelection = position
this.performCompletion()
}
autoComplete.setListSelection(position);
I have used autoCompleteTextView.setText(myText, false); solution as well, however it sometimes failed. I mean it was actively filtering results so, when user clicks there was only 1 item at dropdown.
In addition I also needed this to work on custom objects as well, and this is my my solution:
binding.hourEditText.configureDropDownMenu(viewModel.hours) { it.hourString() }
.subscribe {
// Do whatever you need when on click.
}
.addTo(disposables)
fun <T> AutoCompleteTextView.configureDropDownMenu(list: List<T>, toString: ((T) -> String)? = null): Observable<T> {
keyListener = null
val textItems = toString?.let(list::map) ?: list.map { it.toString() }
setAdapter(NonFilterArrayAdapter(context!!, android.R.layout.simple_spinner_dropdown_item, textItems))
return itemClickEvents().map {
list[it.position]
}
}
private class NonFilterArrayAdapter<T>(context: Context, #LayoutRes resource: Int, objects: List<T>) : ArrayAdapter<T>(context, resource, objects) {
override fun getFilter() = NonFilter()
private class NonFilter : Filter() {
override fun performFiltering(constraint: CharSequence?) = FilterResults()
override fun publishResults(constraint: CharSequence?, results: FilterResults?) = Unit
}
}
Note: This also contains a bit of Rx, but it can be removed easily.
Try with adding below after setText() in AutoCompleteTextview:-
autoCompleteTV.setSelection(position);
Updated:
This will work in Spinner and AutoCompleteTextView which has dropdown feature, but it will not work with EditText.
Here you can check docs for AbsSpinner in this link:
https://developer.android.com/reference/android/widget/AbsSpinner.html#setSelection(int)
The problem is that you are setting a text and the AutoCompleteTextView is only showing words that match with that text. A non elegant way of solving this is to set an high threshold (at least the max length of the names of the cities) to force Android to show you all the values of your list (this threshold is the number of characters that the field must have to search similarities).
Using the Nilton Vasques solution it can be so:
with(autoComplete) {
setAdapter(this#YourFragment.adapter)
setText(itemText)
showDropDown()
listSelection = if (itemIndex > 0) itemIndex - 1 else 0 // Because AutoCompleteTextView shows the next row.
performCompletion()
}
Notice, that it will show a drop-down list, otherwise listSelection won't work. If you call dismissDropDown(), the item won't be selected. If you don't want to show the drop-down list, you can use setOnTouchListener to capture opening the list, but it hardly will help (you should resolve a filtering problem).
setOnTouchListener { _, event ->
if (event.action == MotionEvent.ACTION_DOWN) {
showDropDown()
listSelection = if (itemIndex > 0) itemIndex - 1 else 0
performCompletion()
requestFocus()
}
false
}

How to prevent Spinner announcement when initialized

Background
The fragment creates it's views and then starts a network operation. When the network operation is completed, various types of subviews are created and added to the fragment's view, based on the results of the network operation.
Problem
If a Spinner is added by the fragment, it's initial value is announced by TalkBack. This is very undesirable because the Spinner is usually buried deep within the form.
Failed Solutions
I've tried:
wrapping addView with setImportantForAccessibility
not setting the initial Spinner value (not an acceptable solution in any case) but the initial value is still announced when added
setting the Spinner's contentDescription to non-breaking space before adding and restoring it in onAttachedToWindow *
Question
Spinner is created and dynamically added, after the fragment is initially created. How can I prevent TalkBack from announcing the initial value of the Spinner?
Here's a workaround that I did in my project which worked as expected i.e. Spinner's content is only read out by TalkBack when it's in focus.
I subclassed the AppCompatSpinner class and overrode its onInitializeAccessibilityEvent function as follows:
#Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) {
super.onInitializeAccessibilityEvent(event);
}
}
1) set important for accessibility to false to all spinners in your form in onCreate().
spinner.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
2) setOnHoverListener to each spinner and inside it enable the accessibility again and send accessibility events to announce them properly.
spinner.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
spinner.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
spinner.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
spinner.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
3) setOnTouchListener to each spinner to perform click after double tap.
spinner.performClick();

How to listen to Talkback's focus-change in Android?

I'm working on improving the accessibility within my app.
I have pretty complicated layout with cards. Each card has some clickable objects inside it, but it also has the global click-listener.
When I enable Talkback, select the card (not something inside it!), double-clicking (to open the card), the card gets the touch-event in the middle of the card.
As a result, nested object got click event and react respectively.
The question is how to determine, which item is in TalkBack's focus (green-rectangle-thing for me)? The idea is to disable inside touch-listeners, if card itself is in focus.
API level I want to support is 16 (Android 4.1+)
Thanks!
I think what would work best for you, is to override the accessibility delegate of layout view, listening for accessibility focus events. When focus is added to a card, remove listeners, when focus leaves your cards re attach your listeners. Attach this delegate to your layout view, and you should be able to watch as various views within your layout obtain and give up accessibility focus.
class MyAccessibilityDelegate extends View.AccessibilityDelegate {
#Override
public boolean onRequestSendAccessibilityEvent(ViewGroup viewGroup, View child, AccessibilityEvent event) {
if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) {
//Do stuff in here! Maybe also do different stuff when focus is cleared!
}
return super.onRequestSendAccessibilityEvent(viewGroup, child, event);
}
}
The apis for this were added in API level 14 so you should be good to go!

listview.pointToPosition providing INVALID value when should not

I am getting information from my application in production, but when I am trying to reproduce the problem I am not able to do it. What I am looking for is any idea (as I am blocked) of how can I try to reproduce the error.
Basically I am having an activities with a listview, toolbar, edittext and admob advertisement.
My listview is composed of a relativealayout containing a textview and one imageview.
With the adapter I am attaching to the textview an OnLongClickListener that starts a drag operation.
To the Listview itself, I am adding an OnDragListener.
#TargetApi(11)
public class myDragEventListener implements View.OnDragListener {
// This is the method that the system calls when it dispatches a drag event to the
// listener.
public boolean onDrag(View v, DragEvent event) {
// Defines a variable to store the action type for the incoming event
final int action = event.getAction();
// Handles each of the expected events
switch(action) {
case DragEvent.ACTION_DRAG_STARTED:
return true;
case DragEvent.ACTION_DROP:
//We detect which is the item where the drop happened on.
int itemPosition = listShop.pointToPosition((int)event.getX(), (int)event.getY());
// An unknown action type was received.
default:
return true;
}
}
}
So, basically we initiate the drag on the textview from the listview item and the drop ends on the listview itself.
This code works properly, but I received from production reports that sometimes the value of itemPosition to be -1. To avoid an exception I can add a simple check, but I am worried about the user experience.
So, what I would like to avoid is a bad user experience with the application not responding properly, and we know is happening due to reports. The problem is that we are not able to reproduce.
Trying to reproduce this error we tried:
Longclick on textview and drop on imageview. (provides correct itemPosition)
Longclick on textivew and drop outside the listview (drag listener not called)
Longclick on textivew and drop at the edge of listview (works ok).
Does somebody has a suggestion of how this "-1" could be reproduced? Theoretically it should not happen.... is the drop (whatever the drag that has been initiated) activates the OnDragListener, that means that the position where the drop occurs is a position of the listview. How can the DragListener be called in a listview in an incorrect position?
Any idea what could be happening?
Ok, I found the problem. My listview covers almost all the screen, and sometimes is feed with items in the list that are not covering the complete list up to the end. When an item is dragged and dropped in the space where there is no item in the list, provide that value.
The problem is that my testing mobile is very small compared to other devices, and that mobile was always having the screen fully covered by items of the list, so any drag/drop operation was always captured.

talkback is announcing hidden views

I am adding accessibility to an app and I am using google's talkback for testing accessibility and I am unable to block certain views from being selected.
Besides removing all listeners and focusability, is there any better way to disable views from obtaining focus when they are hidden on the screen... ie having a "drawer" open and disabling the selection of items in the content container? It seems like there should be a cleaner solution to this or maybe a fix is required on the talkback team's side.
Thanks
Look into AccessibilityDelegateCompat available in support-v4 library - Link.
Create an instance of AccessibilityDelegateCompat and override the following method:
#Override
public void onInitializeAccessibilityNodeInfo(View host,
AccessibilityNodeInfoCompat info) {
// Check if 'host' is visible or not before calling the super method
if (host.getVisibility() != View.INVISIBLE) {
super.onInitializeAccessibilityNodeInfo(host, info);
}
}
Finally, use static method ViewCompat.setAccessibilityDelegate(View, AccessibilityDelegateCompat) for the views that are invisible.

Categories

Resources