5

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.

Levon Petrosyan
  • 8,815
  • 8
  • 54
  • 65

4 Answers4

4

By inspecting the source code of:

TextInputLayout class

https://github.com/material-components/material-components-android/blob/master/lib/java/com/google/android/material/textfield/TextInputLayout.java

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));
Brainnovo
  • 1,749
  • 2
  • 12
  • 17
  • I wonder what is the intention of this code, to only announce TalkBacks own "Password...", or to announce our Hint? Because using this the only that happens is that the hint is ignored, funny enough if you don't put any code at all in onInitializeAccessibilityNodeInfo beside calling super() the same happens, Password is announced and our hint is ignored (tested 19 times). – David Nov 28 '19 at 18:09
  • Consider this specific case: you want to set the hint on the TextInputLayout (for the reasons @Levon Petrosyan stated above) with an TextInputEditText input type of "textPassword. While navigating TalkBack announces (TextInputEditText inputType based keyword: password in this case) + TextInputLayout hint + editbox. Suppose that the hint is set to password then the announcement is password password editbox, which is not desired (one of the words "password" is not needed and need to be removed). The easiest approach here is to remove the second one (hint). This code does that. – Brainnovo Nov 29 '19 at 05:52
  • Regarding what happens if you remove the code in onInitializeAccessibilityNodeInfo() and only keep super(), everything goes back to default. Open TextInputLayout class (link above), scroll to the end (AccessibilityDelegate class). – Brainnovo Nov 29 '19 at 06:01
  • It seems that I wasn't understood, "everything goes back to default", that's the point, this is not being the case! if I don't override onInitializeAccessibilityNodeInfo despite having code inside it or not "password, hint, double tap..." is announced. As soon as I override onInitializeAccessibilityNodeInfo() with ONLY calling super(), nothing more, then TalkBack announces "password, double tap...", so the hint is ignored despite I didn't wrote info.setHintText(""); – David Nov 29 '19 at 15:38
  • 1
    Please check out: [https://github.com/Brainnovo/CustomTextInputLayoutAccessibilityDelegate](https://github.com/Brainnovo/CustomTextInputLayoutAccessibilityDelegate) – Brainnovo Nov 30 '19 at 06:18
0

I think it is reading your TextInputLayout hint. The layout shouldn't need a hint, move that to your TextInputEditText. You can also use the field importantForAccessability="false" on your top layout on the layout if for some reason you need a hint there.

<android.support.design.widget.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <android.support.design.widget.TextInputEditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="Password" />

        </android.support.design.widget.TextInputLayout>
Richard Dapice
  • 838
  • 5
  • 10
0

It works for me. Use hint text in EditText hint.

<android.support.design.widget.TextInputLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/_10"
        android:layout_marginLeft="@dimen/_10"
        android:layout_marginRight="@dimen/_10"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent">
        <EditText
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:hint="Address Line 1"/>
     </android.support.design.widget.TextInputLayout>
Projit Roy
  • 19
  • 4
0

We can try giving a custom role description for the AccessibilityNode. We can check if we are showing masked password using info.isPassword property. Then build roleDescription accordingly.

class MyAccessibilityDelegate(val layout: TextInputLayout) : TextInputLayout.AccessibilityDelegate(layout) {

    override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) {
        super.onInitializeAccessibilityNodeInfo(host, info)
        val editText = layout.editText
        val content = editText?.text
        info.text = content
        info.roleDescription = if(!info.isPassword) "password edit text" else "edit text"
        info.hintText = ""
        info.isShowingHintText = false
        editText?.setSelection(content?.length ?: 0)
    }
}
Jossy Paul
  • 1,267
  • 14
  • 26