Android accessibility: how to change the search bar readed string - android

as I wrote in the title, the Android Talkback actually reads the hint text of my search bars, but I need to change that behavior to read a custom string (different from the hint). I tried to set the contentDescription but it didn't work (the talkback still reads the hint text).
Do you have any advice?

You can set an AccessibilityDelegate on a view to override things like how it describes itself in general:
ViewCompat.setAccessibilityDelegate(yourView, object : AccessibilityDelegateCompat(){
override fun onInitializeAccessibilityNodeInfo(host: View?, info: AccessibilityNodeInfoCompat?) {
super.onInitializeAccessibilityNodeInfo(host, info)
info?.let { it.text = "whatever" }
}
})
or you can override onPopulateAccessibilityEvent (which handles triggered events like AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED) and use event.getText(), which gives you a List<CharSequence> you can add your description to. It really depends on what you're doing and how dynamic it needs to be.
I haven't found a good overview of all this stuff unfortunately, otherwise I'd link you to it. I think the delegates are what you need to look into though

Related

How to set an android's view accessibility flag of "button" to "true"?

I have a ConstraintLayout with an onClickListener so users can tap anywhere on the layout for it to perform its onClickListener action.
The problem is, Android does not flag this item as a button. It will say "double tap to activate" but our accessibility team has flagged this as incorrect because screen-reader users need to know the item is a "button" (from the Android tag) to know an item is actionable.
In the past, my work-around was to change views to be a button that looks exactly alike. However, this is a lot more difficult in this case because it's a ConstraintView.
Does anyone know how to set Accessibility's 'button' flag to 'true' on a ConstraintView? Or on any view?
Simple fix worked like a charm:
fun View.setAccessibilityRole(role: String = "button") {
ViewCompat.setAccessibilityDelegate(this, object :
AccessibilityDelegateCompat() {
override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) {
super.onInitializeAccessibilityNodeInfo(host, info)
info.roleDescription = role
info.className = role
}
})
}
While the other answer would seem to work, the main issue would be that you are then relying on your app's translation team to translate "button" if the screen reader user uses any language that doesn't have the same word for button.
// example usage
// val view = findViewById(...)
// view.setAccessibilityRole<Button>()
inline fun <reified T: View> View.setAccessibilityRole() {
ViewCompat.setAccessibilityDelegate(
this,
object : AccessibilityDelegateCompat() {
override fun onInitializeAccessibilityNodeInfo(
host: View,
info: AccessibilityNodeInfoCompat
) {
super.onInitializeAccessibilityNodeInfo(host, info)
info.className = T::class.qualifiedName
}
}
)
}
This works for a button, and probably other standard controls like RatingBar, ProgressBar and CheckBox but could probably be refined to be a little safer than what I have done.
A word of caution: There be dragons here. It can walk like a button, talk like a button, but there might be elements that go against the grain that make it inaccessible further and lead you to more regressions, as can be seen by the other answer (translation issue). Other examples are keyboard navigation and highlight, or touch target size, or someone doesn't load text / content description and then wonders why it's not focusable by a screen reader, switch access or voice assistant. You could also end up with focusable controls within your "button" and that could be terribly confusing for certain users, depending on how they use the device (TalkBack does not encompass all assistive tech).
You're always better off extending existing controls for this reason.

Android: How to change the control type for Accessibility

There are some controls in our app which we'd like to update the control type read out by Talkback. For example: A TextView which would better be described as a link, or an ImageView that would better be described as a button.
We could simply update the content description to report out the desired control type, though I am wondering if there is a better way? If there is another way, can it be done both through the view XML and dynamically in the code behind?
Yes, it is possible to change the type. It is called roleDescription. You would change it as follows:
ViewCompat.setAccessibilityDelegate(yourView,
object : AccessibilityDelegateCompat() {
override fun onInitializeAccessibilityNodeInfo(v: View, info: AccessibilityNodeInfoCompat) {
super.onInitializeAccessibilityNodeInfo(v, info)
info.roleDescription = "Button"
}
})
(use string resources and translate the strings to all languages supported by your app)
This cannot be done via XML by default, but you could look into writing your own binding adapter for this.

Android Accessibility onInitializeAccessibilityNodeInfo not called

I created a custom tab view and I am attempting to set up the correct accessibility for this custom view. Within the Fragment's onViewCreated, I am setting the desired roleDescription:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.customViewFoo.also { customViewFoo ->
ViewCompat.setAccessibilityDelegate(customViewFoo,
object : AccessibilityDelegateCompat() {
override fun onInitializeAccessibilityNodeInfo(v: View, info: AccessibilityNodeInfoCompat) {
super.onInitializeAccessibilityNodeInfo(v, info)
info.roleDescription = "Custom View Foo"
}
})
}
}
However, the function onInitializeAccessibilityNodeInfo is not being called. Looking at Android Accessibility — Resolving common Talkback issues in the Roles section, I don't see a difference between my code and her example other than her example is set within an Activity. I also looked at the Make Custom Views More Accessible on the Android site and it says pretty much the same thing.
Thoughts on what I am missing?
Not sure if I'm on time, but this may help someone else. Honestly, I'm not totally sure this is what helped, because I was trying to solve the same problem in a very big project, but it seems so to me.
Try adding this line in your xml view file, you want to set accessible.
android:importantForAccessibility="yes"
Without this property set my application was trying to handle accessibility options via dispatchPopulateAccessibilityEvent method, after adding this line it seems to have started to add the delegate first and than reacting to the event with dispatchPopulateAccessibilityEvent if still needed. But I repeat, that I'm not 100% sure this is the solution.

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
}

Changing text spoken by talkback in Android

I am trying to change the text announced by TalkBack when an ImageView is focused through accessibility.
The Android documentation states that we should create an AccessibilityDelegate, and override onPopulateAccessibilityEvent (I am using the support library because I am also supporting GingerBread)
Thus, my code is the following:
public static void setImageDelegate(View view) {
AccessibilityDelegateCompat delegate = new AccessibilityDelegateCompat() {
#Override
public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
event.getText().add(event.getContentDescription() + ", image");
}
};
ViewCompat.setAccessibilityDelegate(view, delegate);
}
When I call this function on my imageview, the delegate gets set, but the modified text is not being read. It simply reads the original content description. Am I doing something wrong or missing something about the accessibility functions?
Stepping through the code, it seems to be adding the correct text, but still, no change in spoken text.
Note: the above is a contrived example, content description could be used, but I'm trying to figure out why it doesn't work before I try it on custom views.
In ICS and above, TalkBack doesn't use the accessibility event text in most cases. Instead, it checks the text and content description of the AccessibilityNodeInfo exposed by the view. You would need to override onInitializeAccessibilityNodeInfo.
In most cases, though, you would just want to call View.setContentDescription.
In this particular case, you shouldn't set anything since TalkBack handles speaking control types and capabilities. We strongly advise developers against adding descriptions like "button" or "image."

Categories

Resources