From 9753899a97020f3402b9f409f3d133690ace03c4 Mon Sep 17 00:00:00 2001 From: Joanne Chung Date: Thu, 13 May 2021 23:02:12 +0800 Subject: [PATCH] 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 --- .../translation/ViewTranslationRequest.java | 23 ++++++++++++++++--- core/java/android/widget/TextView.java | 4 ++++ .../widget/TextViewTranslationCallback.java | 17 ++++++++++++++ 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/core/java/android/view/translation/ViewTranslationRequest.java b/core/java/android/view/translation/ViewTranslationRequest.java index 00f6ef2232e25..a41749a2bc506 100644 --- a/core/java/android/view/translation/ViewTranslationRequest.java +++ b/core/java/android/view/translation/ViewTranslationRequest.java @@ -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 mTranslationRequestValues\npublic @android.annotation.NonNull android.view.translation.TranslationRequestValue getValue(java.lang.String)\npublic @android.annotation.NonNull java.util.Set getKeys()\npublic @android.annotation.NonNull android.view.autofill.AutofillId getAutofillId()\nprivate static java.util.Map 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 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)\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 mTranslationRequestValues\npublic @android.annotation.NonNull android.view.translation.TranslationRequestValue getValue(java.lang.String)\npublic @android.annotation.NonNull java.util.Set getKeys()\npublic @android.annotation.NonNull android.view.autofill.AutofillId getAutofillId()\nprivate static java.util.Map 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 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)\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() {} diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 1a37b595287d1..e0238b9bce133 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -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()); } diff --git a/core/java/android/widget/TextViewTranslationCallback.java b/core/java/android/widget/TextViewTranslationCallback.java index 0376061155216..92c9142749df3 100644 --- a/core/java/android/widget/TextViewTranslationCallback.java +++ b/core/java/android/widget/TextViewTranslationCallback.java @@ -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