I have the following piece of code in my layout
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/tilPassword"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Password"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tilUserName"
app:passwordToggleEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/tiePassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeOptions="actionDone"
android:inputType="textPassword"
android:selectAllOnFocus="true"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
While navigating to TextInputLayout TalkBack announces : "password password edit box"
Desired announcement: "password edit box"
If I remove either android:hint="Password"
or android:inputType="textPassword"
it works as expected.
Notes about setting the hint
The hint should be set on TextInputLayout, rather than the TextInputEditText or EditText. If a hint is specified on the child EditText in XML, the TextInputLayout might still work correctly; TextInputLayout will use the EditText’s hint as its floating label. However, future calls to modify the hint will not update TextInputLayout’s hint. To avoid unintended behavior, call setHint() and getHint() on TextInputLayout, instead of on EditText.
By inspecting the source code of:
TextInputLayout class
one can find that:
TextInputLayout Accessibility info is provided through the following public class:
public static class AccessibilityDelegate extends AccessibilityDelegateCompat {
private final TextInputLayout layout;
public AccessibilityDelegate(TextInputLayout layout) {
this.layout = layout;
}
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
super.onInitializeAccessibilityNodeInfo(host, info);
EditText editText = layout.getEditText();
CharSequence text = (editText != null) ? editText.getText() : null;
CharSequence hintText = layout.getHint();
CharSequence errorText = layout.getError();
CharSequence counterDesc = layout.getCounterOverflowDescription();
boolean showingText = !TextUtils.isEmpty(text);
boolean hasHint = !TextUtils.isEmpty(hintText);
boolean showingError = !TextUtils.isEmpty(errorText);
boolean contentInvalid = showingError || !TextUtils.isEmpty(counterDesc);
if (showingText) {
info.setText(text);
} else if (hasHint) {
info.setText(hintText);
}
if (hasHint) {
info.setHintText(hintText);
info.setShowingHintText(!showingText && hasHint);
}
if (contentInvalid) {
info.setError(showingError ? errorText : counterDesc);
info.setContentInvalid(true);
}
}
}
and that its is applied to TextInputLayout by calling the following public method:
public void setTextInputAccessibilityDelegate(TextInputLayout.AccessibilityDelegate delegate) {
if (editText != null) {
ViewCompat.setAccessibilityDelegate(editText, delegate);
}
}
so, one can extend:
TextInputLayout.AccessibilityDelegate class and override onInitializeAccessibilityNodeInfo() to announce only what is needed. For example in your case, you can do the following:
private class CustomTextInputLayoutAccessibilityDelegate extends TextInputLayout.AccessibilityDelegate{
private final TextInputLayout layout;
public CustomTextInputLayoutAccessibilityDelegate(TextInputLayout layout) {
super(layout);
this.layout = layout;
}
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
super.onInitializeAccessibilityNodeInfo(host, info);
EditText editText = layout.getEditText();
CharSequence text = (editText != null) ? editText.getText() : null;
CharSequence hintText = layout.getHint();
CharSequence errorText = layout.getError();
//CharSequence counterDesc = layout.getCounterOverflowDescription();
boolean showingText = !TextUtils.isEmpty(text);
boolean hasHint = !TextUtils.isEmpty(hintText);
//boolean showingError = !TextUtils.isEmpty(errorText);
//boolean contentInvalid = showingError || !TextUtils.isEmpty(counterDesc);
if (showingText) {
info.setText(text);
} else if (hasHint) {
info.setText("");
}
if (hasHint) {
info.setHintText("");
info.setShowingHintText(!showingText && hasHint);
}
//if (contentInvalid) {
// info.setError(showingError ? errorText : counterDesc);
// info.setContentInvalid(true);
//}
}
}
and then call:
tilPassword.setTextInputAccessibilityDelegate(new CustomTextInputLayoutAccessibilityDelegate(tilPassword));