Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9753899a authored by Joanne Chung's avatar Joanne Chung
Browse files

Translate TextView content description if exist

Screen reader(Talkback) will read content description first if the
developers set. We only translate TextView text, this may cause the
screen reader will not read the translated text if the developers set
content description.

To fix the issue, we also send the content description to translate.
When the translated text is shown, we also set content description
with translated content description and reset to original content
description if show original text.

Bug: 187134784
Test: atest CtsTranslationTestCases
Change-Id: I6986384260627a0539780b7293d47666c442d852
parent 0a35d1d6
Loading
Loading
Loading
Loading
+20 −3
Original line number Diff line number Diff line
@@ -36,7 +36,7 @@ import java.util.Set;
 * be used by {@link android.service.translation.TranslationService}.
 */
@DataClass(genBuilder = false, genToString = true, genEqualsHashCode = true, genGetters = false,
        genHiddenConstructor = true)
        genHiddenConstructor = true, genHiddenConstDefs = true)
public final class ViewTranslationRequest implements Parcelable {

    /**
@@ -45,6 +45,14 @@ public final class ViewTranslationRequest implements Parcelable {
     */
    public static final String ID_TEXT = "android:text";

    /**
     * Constant id for the default view content description to be translated. This is used by
     * {@link Builder#setValue(String, TranslationRequestValue)}.
     *
     * @hide
     */
    public static final String ID_CONTENT_DESCRIPTION = "android:content_description";

    /**
     * The {@link AutofillId} of the view associated with this request.
     */
@@ -191,6 +199,15 @@ public final class ViewTranslationRequest implements Parcelable {
    //@formatter:off


    /** @hide */
    @android.annotation.StringDef(prefix = "ID_", value = {
        ID_TEXT,
        ID_CONTENT_DESCRIPTION
    })
    @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
    @DataClass.Generated.Member
    public @interface Id {}

    /**
     * Creates a new ViewTranslationRequest.
     *
@@ -303,10 +320,10 @@ public final class ViewTranslationRequest implements Parcelable {
    };

    @DataClass.Generated(
            time = 1620259482911L,
            time = 1621230365943L,
            codegenVersion = "1.0.23",
            sourceFile = "frameworks/base/core/java/android/view/translation/ViewTranslationRequest.java",
            inputSignatures = "public static final  java.lang.String ID_TEXT\nprivate final @android.annotation.NonNull android.view.autofill.AutofillId mAutofillId\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"translationRequestValue\") java.util.Map<java.lang.String,android.view.translation.TranslationRequestValue> mTranslationRequestValues\npublic @android.annotation.NonNull android.view.translation.TranslationRequestValue getValue(java.lang.String)\npublic @android.annotation.NonNull java.util.Set<java.lang.String> getKeys()\npublic @android.annotation.NonNull android.view.autofill.AutofillId getAutofillId()\nprivate static  java.util.Map<java.lang.String,android.view.translation.TranslationRequestValue> defaultTranslationRequestValues()\nclass ViewTranslationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate @android.annotation.NonNull android.view.autofill.AutofillId mAutofillId\nprivate @android.annotation.NonNull java.util.Map<java.lang.String,android.view.translation.TranslationRequestValue> mTranslationRequestValues\nprivate  long mBuilderFieldsSet\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.view.translation.ViewTranslationRequest.Builder setValue(java.lang.String,android.view.translation.TranslationRequestValue)\npublic @android.annotation.NonNull android.view.translation.ViewTranslationRequest build()\n  android.view.translation.ViewTranslationRequest.Builder setTranslationRequestValues(java.util.Map<java.lang.String,android.view.translation.TranslationRequestValue>)\nprivate  void checkNotUsed()\nclass Builder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=false, genToString=true, genEqualsHashCode=true, genGetters=false, genHiddenConstructor=true)")
            inputSignatures = "public static final  java.lang.String ID_TEXT\npublic static final  java.lang.String ID_CONTENT_DESCRIPTION\nprivate final @android.annotation.NonNull android.view.autofill.AutofillId mAutofillId\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"translationRequestValue\") java.util.Map<java.lang.String,android.view.translation.TranslationRequestValue> mTranslationRequestValues\npublic @android.annotation.NonNull android.view.translation.TranslationRequestValue getValue(java.lang.String)\npublic @android.annotation.NonNull java.util.Set<java.lang.String> getKeys()\npublic @android.annotation.NonNull android.view.autofill.AutofillId getAutofillId()\nprivate static  java.util.Map<java.lang.String,android.view.translation.TranslationRequestValue> defaultTranslationRequestValues()\nclass ViewTranslationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate @android.annotation.NonNull android.view.autofill.AutofillId mAutofillId\nprivate @android.annotation.NonNull java.util.Map<java.lang.String,android.view.translation.TranslationRequestValue> mTranslationRequestValues\nprivate  long mBuilderFieldsSet\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.view.translation.ViewTranslationRequest.Builder setValue(java.lang.String,android.view.translation.TranslationRequestValue)\npublic @android.annotation.NonNull android.view.translation.ViewTranslationRequest build()\n  android.view.translation.ViewTranslationRequest.Builder setTranslationRequestValues(java.util.Map<java.lang.String,android.view.translation.TranslationRequestValue>)\nprivate  void checkNotUsed()\nclass Builder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=false, genToString=true, genEqualsHashCode=true, genGetters=false, genHiddenConstructor=true, genHiddenConstDefs=true)")
    @Deprecated
    private void __metadata() {}

+4 −0
Original line number Diff line number Diff line
@@ -13924,6 +13924,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
            // TODO(b/176488462): apply the view's important for translation
            requestBuilder.setValue(ViewTranslationRequest.ID_TEXT,
                    TranslationRequestValue.forText(mText));
            if (!TextUtils.isEmpty(getContentDescription())) {
                requestBuilder.setValue(ViewTranslationRequest.ID_CONTENT_DESCRIPTION,
                        TranslationRequestValue.forText(getContentDescription()));
            }
        }
        requestsCollector.accept(requestBuilder.build());
    }
+17 −0
Original line number Diff line number Diff line
@@ -19,11 +19,13 @@ package android.widget;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Build;
import android.text.TextUtils;
import android.text.method.TranslationTransformationMethod;
import android.util.Log;
import android.view.View;
import android.view.translation.UiTranslationManager;
import android.view.translation.ViewTranslationCallback;
import android.view.translation.ViewTranslationRequest;
import android.view.translation.ViewTranslationResponse;

/**
@@ -46,6 +48,8 @@ public class TextViewTranslationCallback implements ViewTranslationCallback {
    private boolean mIsTextPaddingEnabled = false;
    private CharSequence mPaddedText;

    private CharSequence mContentDescription;

    /**
     * Invoked by the platform when receiving the successful {@link ViewTranslationResponse} for the
     * view that provides the translatable information by {@link View#createTranslationRequest} and
@@ -86,6 +90,15 @@ public class TextViewTranslationCallback implements ViewTranslationCallback {
        }
        if (mTranslationTransformation != null) {
            ((TextView) view).setTransformationMethod(mTranslationTransformation);
            ViewTranslationResponse response = view.getViewTranslationResponse();
            if (response.getKeys().contains(ViewTranslationRequest.ID_CONTENT_DESCRIPTION)) {
                CharSequence translatedContentDescription =
                        response.getValue(ViewTranslationRequest.ID_CONTENT_DESCRIPTION).getText();
                if (!TextUtils.isEmpty(translatedContentDescription)) {
                    mContentDescription = view.getContentDescription();
                    view.setContentDescription(translatedContentDescription);
                }
            }
        } else {
            if (DEBUG) {
                // TODO(b/182433547): remove before S release
@@ -111,6 +124,9 @@ public class TextViewTranslationCallback implements ViewTranslationCallback {
        if (mTranslationTransformation != null) {
            ((TextView) view).setTransformationMethod(
                    mTranslationTransformation.getOriginalTransformationMethod());
            if (!TextUtils.isEmpty(mContentDescription)) {
                view.setContentDescription(mContentDescription);
            }
        } else {
            if (DEBUG) {
                // TODO(b/182433547): remove before S release
@@ -131,6 +147,7 @@ public class TextViewTranslationCallback implements ViewTranslationCallback {
            onHideTranslation(view);
            clearTranslationTransformation();
            mPaddedText = null;
            mContentDescription = null;
        } else {
            if (DEBUG) {
                // TODO(b/182433547): remove before S release