Android override accessibility "double tap to activate" - android

I have a refresh button on my screen which TalkBack should announce as "Refresh button, refreshes the page". It shouldn't announce "double tap to activate".
I'm using
contentDescription="refresh"
and a custom accessibility delegate.
Below is the custom accessibility delegate:
class CustomClickAccessibilityDelegate(private val clickDescription: String) : View AccessibilityDelegate {
override fun onInitialiseAccessibilityNodeInfo(host: View?, info: AccessibilityNodeInfo?) {
super.onInitialiseAccessibilityNodeInfo(host, info)
val customClick = AccessibilityNodeInfo.AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK, clickDescription)
info?.addAction(customClick)
}
}
With contentDescription="refresh", and the custom accessibility delegate taking "refreshes the page", announcement reads "refresh button. Double tap to refreshes the page" while I need it to announce "refresh button, refreshes the page". Is there a way to prevent TalkBack from saying "double tap to".

To achieve something close to this, you want to replace the action description of the component:
/**
* renames the "activate" portion of the announcement from "double tap to activate"
* #param descriptionResourceId: The string resource - should be translated
*
* example usage:
* val refreshButton = findViewById<Button>(R.id.refresh_button)
* refreshButton.renameClickAction(R.string.action_refresh)
*
* reference: https://developer.android.com/guide/topics/ui/accessibility/principles#understandable-actions
*/
fun Button.renameClickAction(#StringRes descriptionResourceId: Int) {
ViewCompat.replaceAccessibilityAction(
this,
AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
// Announcement read by TalkBack to surface this action
getText(descriptionResourceId),
null
)
}
I made it an extension function, but the documentation demonstrates that this is not necessary. It is worth noting that screen reader users who use different languages may be left out as a result.
It's worth pointing out that the "double tap to" portion is part of TalkBack, and indicates the gestures that are available to users (like if there are addition actions, state, etc) - If you want to take that away you are moving away from a consistent experience for users, and it may be difficult for them to use your app. Other portions of the documentation also infer that we should be adding more actions, not removing them
It is important to allow users of accessibility services to easily perform all user flows within your app. For example, if a user can swipe on an item in a list, this action can also be exposed to Accessibility services so users have an alternative way to complete the same user flow.
By removing the announcement for "Double tap to ..." how will folks know that there are actions available? The main WCAG regression you would be introducing is that of Success Criterion 3.2.4: Consistent navigation:
Components that have the same functionality within a set of [Activities] are identified consistently.
What makes this button so unique that users should not know that they can activate it the same way as other buttons? Perhaps I am misunderstanding the reason for removing this requirement, but from the question it seems a little odd to require it's removal.

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.

Unexplained Snapping Behaviour In A Programmatically Scrolled "Lazy List""

I stumbled upon this issue while trying to solve a case. To summarise, the patient needed complete control over the 'speed' of the scroll. Now, Android's sensible defaults have the item-scroll follow the user's finger at all times, but apparently an un-shared condition/requirement of this patient lead them to want the user scrolling further than the item actually scrolls. So, I suggested, as originally proposed by David in his solution, to disable the user's direct control over the lazy list, and manually scroll it by observing events from a touch detection callback, like the ones provided by the pointerInput(...) Modifier. Hence, the implementation:
#Composable
fun LazyStack(sdd: Int = 1) { // Scroll-Distance-Divisor
val lazyStackState = rememberLazyListState()
val lazyStackScope = rememberCoroutineScope()
LazyColumn(
modifier = Modifier.pointerInput(Unit) {
detectVerticalDragGestures { _, dragAmount ->
lazyStackScope.launch {
lazyStackState.scrollBy(-dragAmount / sdd)
}
}
},
state = lazyStackState,
userScrollEnabled = false
) {
items((0..500).map { LoremIpsum(randomInt(5)) }) {
Text(text = it.values.joinToString(" "))
}
}
}
Now, the treatment actually worked, except a .. tiny side-effect, apparently.
You see, the list actually scrolls perfectly as intended for the most part. For example, if I pass the sdd parameter as 2, the scroll distance is reduced to half its original value. But, the side-effect is "snapping".
Apparently while scrolling smoothly through the list, there are certain "points" on both sides of which, the list "snaps", WHILE THE USER HAS THIER INPUT POINTER IN USE (a finger, a mouse, or any other pointers, are actively "holding" the list). Now, this is a point, which, if you cross in either direction, the list snaps, and apparently by the exact same value every single time.
The list above uses Lorem Ipsum as a convention, but perhaps a numbered list would help the diagnose the problem.
So, a synopsis of the symptoms:
There seem to be certain "points" in the list, occurring at inconsistent intervals/positions throughout the list/screen which tend to make the list instantly scroll through a lot of pixels. Once discovered, the points are consistently present and can be used to re-produce the symptom unless recomposed. The value of the "snap" is apparently not returned to be a massive float when continuously logging the value returned by the scrollBy method. This makes the issue untraceable by regular diagnostic techniques, which is why we, the diagnosticians are here.
You have the history, you have the symptoms. Let the differential diagnosis begin.
The snapping behavior is caused by incorrectly used randomInt(5), within a composable. List generation should be inside the viewmodel then there is no regeneration of the list during scrolling and everything works perfectly.
Also using detectVerticalDragGestures does not work as smoothly as my original suggestion of using modfiier.scrollable(...) but maybe that's a desired effect.

google sheet custom menu function not visible in google sheet on android

I created in google sheets a custom menu linked to a custom function that pops up a dialog window with a youtube video in it. All this i did on my PC in a browser.
I now installed google sheets on my android phone, and shared the sheet myself (using a second gmail account). I now notice that the custom menu doesn't appear in google sheets app on android.
I am wondering, does custom menus and dialogs work in google sheets on android. Am i doing something incorrect in relation to permissions -- i.e. are there any permissions i need to assign to other users for them to see and use custom menu items and related functions. If yes, how can i make the correct settings.
thank you,
Dan
i used a drop-down list/combo - populated using data validation. this is supported on mobile device. then i use the onEdit(e) event to trigger the code... the first step is to identify what's been edited (which cell) and then act accordingly... an example follows where the drop-down would contain two items "Do Task 1" and "Do Task 2"... the event is triggered when any cell on the sheet is changed... the process then identifies if the cell change is that of the drop-down (cell value set to a global variable 'FunctionsCell') if it is the drop-down that triggered the event, it then gets the value (see the 'getValue()' part of the code) and then inspects it's value and then executes the code accordingly (see '// do something part')
var FunctionsCell = "B2" // global
function onEdit(e) {
var editCell = e.range.getA1Notation()
switch (editCell) {
case FunctionsCell:
{
var functionType = SpreadsheetApp.getActiveSheet().getRange(FunctionsCell).getValue()
switch(functionType) {
case "Do Task 1": {
// do something
break
}
case "Do Task 2": {
// do something
break
}
}
}
}
}

Announce a TextView with Talkback

I am developing a app using google maps api, and it is suppose to be used by people that can't see, so the acessibility is important.
How can I make a TextView that already as a text and a contentDescription to be announced as soon as the activity starts and without the user having to touch it?
The other point of my question is that I have a bus route and I want to be notified in certain points with a description, and my problem is that the talkback works properly, but if I touch the googlemap or anywhere else in there activity, the talkback stops saying the description. (This description shows up in a Toast)
What you outline in your first point about reading text off as soon as your activity stars is undesired behavior. Let TalkBack users explore your app to find this information. If you must post an announcement, what you're looking for is Accessibility Announcement events, which allow you to send an arbitrary text announcement to the Assistive Technology layer.
if (AccessibilityManager.getInstance(context).isEnabled()) {
AccessibilityEvent event = AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_ANNOUNCEMENT);
onInitializeAccessibilityEvent(event);
event.getText().add("some text");
event.setContentDescription(null);
yourContentView.requestSendAccessibilityEvent(this, event);
}
What you outline in your second point is desired behavior. Imagine if a TalkBack user could not interrupt various announcements, how long they could have to wait if they accidentally focused a few paragraphs of text in a row, to hear the simple name of something like a button? That would be a very frustrating user experience.
Same answer as above, with the latest code updates:
private void sendAccessibiltyEvent(#NonNull String message) {
AccessibilityManager accessibilityManager = (AccessibilityManager)requireActivity().getApplicationContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
if (accessibilityManager.isEnabled()) {
AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_ANNOUNCEMENT);
event.getText().add(message);
accessibilityManager.sendAccessibilityEvent(event);
}
}

disabling view announcement when focused (Talkback Enabled)

I'm using a custom dynamic contentDescription for my textview, so it has been implemented in my java class and not in my xml file.
private void enableButton(TextView textView, boolean enabled) {
if (textView == null) {
return;
}
if (!enabled) {
textView.setContentDescription(textView.getResources().getString(R.string.install_disabled));
} else {
textView.setContentDescription(textView.getResources().getString(R.string.install));
}
textView.setEnabled(enabled);
}
After I'm enabling my textview to be clickable, and when talkback is enabled, focusing on my textview is announcing the state of my textview which is "disabled". Is there a way to not announce that state?
I do not want to set the accessibility to be not important because I still want my dynamic contentDescription to be recited when talkback users focus on the textview.
Suggestion:
I believe the culprit is the "setEnabled" method that is somehow triggering and announcing the state of the textview, but I'm still not able to stop it from reciting that last.
My first answer is: LEAVE IT ALONE! The "disabled" announcement tells a TalkBack user that there is a user interface control there, that under some circumstances can be interacted with, but is not currently active. Given your description, this is exactly what you have. To remove the announcement is actually going to make things WORSE from an accessibility perspective, the explanations for why this is the case are covered in WCAG 1.3.1.
Definitions:
Button = android.widget.Button
button = a user interface component that does something when you click it.
Text = a user interface component that conveys information, but is not active
Long story short, the fact that the control is ever in a state that it can be active and "not disabled" is significant information on its own, and SHOULD be shared with the user. ESPECIALLY since you're using a "TextView" to implement this. This isn't a super uncommon practice, but one of the ways TalkBack calculates roles (button, link, image, text, etc) is by looking at the Class/Type of object. So, when it sees a TextView, it is going to assume Text, unless you inform it otherwise. Now, since you have added click listeners to your TextView (or Gesture Recognizers, or whatever) TalkBack may be able to figure out that the thing you're dealing with is actually a "button", and it may not. REGARDLESS, the fact that this "button" (lower case B!) is not active is important state to share with the user, and communicates to them the fact that they can somehow enable it and come back and interact with it later. This is immensely important information! Imagine if every button/link on a WebPage looked exactly like plane text? How would you know what to interact with?
Now, I will show you the different pieces of this puzzle, as information, but I really do encourage you to leave the announcement alone. This is coming from someone who routinely speaks at Accessibility conferences on Native Android development, PLEASE LEAVE THIS ANNOUNCEMENT IN. To not do so shows a misunderstanding of how users with sight impairments want to perceive controls within your application, and the information that is important to them.
The setEnabled() function on a TextView corresponds directly with the isEnabled() property of AccessibilityNodeInfo.
In order to implement the behavior you want, you want the AccessibilityNodeInfo representation of your TextView to be different from that of the actual representation of your TextView. In order to do this you can use AccessibilityDelegate, I'm actually not sure which callback you want to use. In one of these the node is likely to be "sealed" and in one of them it might not be sealed yet. You obviously want the one where the node is not yet sealed! Regardless the general approach is:
textView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
#Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
// Let the default implementation populate the info.
super.onInitializeAccessibilityNodeInfo(host, info);
// Override this particular property
info.setEnabled(whateverYouThinkItShouldBe);
}
});
Use setClickable(false) to replace setEnabled(false) will solve this problem.

Categories

Resources