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.
Related
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.
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);
}
}
I have tried to show the dialog box while the user giving the wrong username or password, using the below code.
private void showAlert(String title, String msg) {
customDialog = new Dialog(LoginActivity.this,
android.R.style.Theme_Dialog);
customDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
customDialog.setContentView(R.layout.custom_alert_dialog);
tvTitle = (TextView) customDialog
.findViewById(R.id.dialog_title);
tvMsg = (TextView) customDialog
.findViewById(R.id.dialog_message);
btnNeutral = (Button) customDialog
.findViewById(R.id.closeAlert);
tvMsg.setText(msg);
tvTitle.setText(title);
tvMsg.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
tvMsg.setFocusable(true);
btnNeutral.setText("Close");
btnNeutral.setVisibility(View.VISIBLE);
btnNeutral.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
customDialog.dismiss();
}
});
customDialog.show();
tvMsg.requestFocus();
}
The code working fine but my concern is, when i am trying to use the android talkback. It reads only the title of the dialog box. The talkback needs to read the content(message) of the dialog box instead of title. Can anyone help me to do this?
First, announcing just the title of a new dialog is very standard. Doing otherwise would probably be counter productive in terms of accessibility. This sounds to me like an accessibility requirement from someone motivated to do good, that doesn't really understand the needs of users with disabilities. Shoving focus around arbitrarily is usually bad. Let the operating system do what it wants with focus, it is what Assistive Technology (TalkBack) users will be accustomed to.
This said there are two overarching issues with your code. First, when you say focus, you mean accessibility focus.
tvMsg.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
tvMsg.setFocusable(true);
tvMsg.requestFocus();
All of these lines are referring to keyboard, or input focus, none of which are particularly meaningful for a TextView. These are only meaningful for active elements like Buttons and EditText boxes. Will this work if you do it correctly, yes. But, it comes with awkward side effects, like a TextView being added to Tab ordering, which is awkward for Keyboard only users, because TextViews don't have focus highlights, so Focus navigation disappears. What you really want is the following event type:
AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED
Now, for the second point. You're doing all of this before your view actually renders. Replace this line:
tvMsg.requestFocus();
With this line:
tvMsg.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
Delete the other lines mentioned above, and you should be golden. Though, again, my ultimate recommendation would just be dropping all of this, and removing those three lines outright, and forgetting about this. Let the operating system do its thing!
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."
I need a listener which is called every time the selection in an EditText changes.
I googled around but I couldn't find anything useful for API level 7.
I'm writing a Text Editor and I want the bold/italic/underlined button appear selected every time the user selects bold/italic/underlined text.
Pretty old question, but someone might still need this, so here's my solution : since the text selection accomplished with long press on the text, I simply used the following :
editText.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View view) {
// do whatever you need to do on text selection
}
});
This allows for custom behavior on text selection and doesn't prevent the user from copying/pasting.
The better way to do it would be to extend the EditText and then based upon how you would want to manage the changing text, you could override one of the 2 methods to work out your customized behavior.
If you want the selection changed then you could use the onSelectionChanged() method and implement your code there.
In case you want to implement something when the text changes in your editor then you could use, onTextChanged().