diff --git a/core/java/android/text/method/TranslationTransformationMethod.java b/core/java/android/text/method/TranslationTransformationMethod.java index 7db999a89fbe7..54c0ffcdbb655 100644 --- a/core/java/android/text/method/TranslationTransformationMethod.java +++ b/core/java/android/text/method/TranslationTransformationMethod.java @@ -78,24 +78,11 @@ public class TranslationTransformationMethod implements TransformationMethod2 { if (TextUtils.isEmpty(translatedText) || isWhitespace(translatedText.toString())) { return source; } else { - // TODO(b/179693024): Remove this once we have the fix to pad the view text instead. - translatedText = ellipsize(translatedText, ((TextView) view).getText().length()); // TODO(b/174283799): apply the spans to the text return translatedText; } } - private static CharSequence ellipsize(CharSequence text, int newLength) { - if (text.length() <= newLength) { - return text; - } - String ellipsis = String.valueOf('\u2026'); - if (newLength == 1) { - return ellipsis; - } - return TextUtils.concat(TextUtils.trimToSize(text, newLength - 1), ellipsis); - } - @Override public void onFocusChanged(View view, CharSequence sourceText, boolean focused, int direction, diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java index c09e8bde74171..00aeede2b2429 100644 --- a/core/java/android/view/translation/UiTranslationController.java +++ b/core/java/android/view/translation/UiTranslationController.java @@ -362,17 +362,20 @@ public class UiTranslationController { continue; } mActivity.runOnUiThread(() -> { - if (view.getViewTranslationCallback() == null) { + final ViewTranslationCallback callback = view.getViewTranslationCallback(); + if (callback == null) { if (DEBUG) { Log.d(TAG, view + " doesn't support showing translation because of " + "null ViewTranslationCallback."); } return; } + + // TODO: Do this for specific views on request only. + callback.enableContentPadding(); + view.onTranslationResponse(response); - if (view.getViewTranslationCallback() != null) { - view.getViewTranslationCallback().onShowTranslation(view); - } + callback.onShowTranslation(view); }); } } diff --git a/core/java/android/view/translation/ViewTranslationCallback.java b/core/java/android/view/translation/ViewTranslationCallback.java index c8959847d64b5..78b4855bc7cf2 100644 --- a/core/java/android/view/translation/ViewTranslationCallback.java +++ b/core/java/android/view/translation/ViewTranslationCallback.java @@ -48,4 +48,16 @@ public interface ViewTranslationCallback { * @return {@code true} if the View handles clearing the translation. */ boolean onClearTranslation(@NonNull View view); + + /** + * Enables padding on the view's original content. + *
+ * This is useful when we do not modify the content directly, rather use a mechanism like + * {@link android.text.method.TransformationMethod}. If the app misbehaves when the displayed + * translation and the underlying content have different sizes, the platform intelligence can + * request that the original content be padded to make the sizes match. + * + * @hide + */ + default void enableContentPadding() {} } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 40c79cf086fce..1953a7687cb9e 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -770,6 +770,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private final boolean mUseInternationalizedInput; // True if fallback fonts that end up getting used should be allowed to affect line spacing. /* package */ boolean mUseFallbackLineSpacing; + // True if the view text can be padded for compat reasons, when the view is translated. + private final boolean mUseTextPaddingForUiTranslation; @ViewDebug.ExportedProperty(category = "text") @UnsupportedAppUsage @@ -1480,6 +1482,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; mUseInternationalizedInput = targetSdkVersion >= VERSION_CODES.O; mUseFallbackLineSpacing = targetSdkVersion >= VERSION_CODES.P; + // TODO(b/179693024): Use a ChangeId instead. + mUseTextPaddingForUiTranslation = targetSdkVersion <= Build.VERSION_CODES.R; if (inputMethod != null) { Class> c; @@ -2372,6 +2376,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @ViewDebug.CapturedViewProperty @InspectableProperty public CharSequence getText() { + if (mUseTextPaddingForUiTranslation + && mDefaultTranslationCallback != null + && mDefaultTranslationCallback.isTextPaddingEnabled() + && mDefaultTranslationCallback.isShowingTranslation()) { + return mDefaultTranslationCallback.getPaddedText(mText, mTransformed); + } return mText; } diff --git a/core/java/android/widget/TextViewTranslationCallback.java b/core/java/android/widget/TextViewTranslationCallback.java index a479b8ac4760f..73f19a7a48c0f 100644 --- a/core/java/android/widget/TextViewTranslationCallback.java +++ b/core/java/android/widget/TextViewTranslationCallback.java @@ -17,6 +17,7 @@ package android.widget; import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Build; import android.text.method.TranslationTransformationMethod; import android.util.Log; @@ -33,7 +34,7 @@ import android.view.translation.ViewTranslationResponse; */ public class TextViewTranslationCallback implements ViewTranslationCallback { - private static final String TAG = "TextViewTranslationCallback"; + private static final String TAG = "TextViewTranslationCb"; // TODO(b/182433547): remove Build.IS_DEBUGGABLE before ship. Enable the logging in debug build // to help the debug during the development phase @@ -41,6 +42,9 @@ public class TextViewTranslationCallback implements ViewTranslationCallback { || Build.IS_DEBUGGABLE; private TranslationTransformationMethod mTranslationTransformation; + private boolean mIsShowingTranslation = false; + private boolean mIsTextPaddingEnabled = false; + private CharSequence mPaddedText; /** * Invoked by the platform when receiving the successful {@link ViewTranslationResponse} for the @@ -74,6 +78,7 @@ public class TextViewTranslationCallback implements ViewTranslationCallback { */ @Override public boolean onShowTranslation(@NonNull View view) { + mIsShowingTranslation = true; if (mTranslationTransformation != null) { ((TextView) view).setTransformationMethod(mTranslationTransformation); } else { @@ -90,6 +95,7 @@ public class TextViewTranslationCallback implements ViewTranslationCallback { */ @Override public boolean onHideTranslation(@NonNull View view) { + mIsShowingTranslation = false; // Restore to original text content. if (mTranslationTransformation != null) { ((TextView) view).setTransformationMethod( @@ -110,9 +116,9 @@ public class TextViewTranslationCallback implements ViewTranslationCallback { public boolean onClearTranslation(@NonNull View view) { // Restore to original text content and clear TranslationTransformation if (mTranslationTransformation != null) { - ((TextView) view).setTransformationMethod( - mTranslationTransformation.getOriginalTransformationMethod()); + onHideTranslation(view); clearTranslationTransformation(); + mPaddedText = null; } else { if (DEBUG) { // TODO(b/182433547): remove before S release @@ -121,4 +127,59 @@ public class TextViewTranslationCallback implements ViewTranslationCallback { } return true; } + + boolean isShowingTranslation() { + return mIsShowingTranslation; + } + + @Override + public void enableContentPadding() { + mIsTextPaddingEnabled = true; + } + + /** + * Returns whether readers of the view text should receive padded text for compatibility + * reasons. The view's original text will be padded to match the length of the translated text. + */ + boolean isTextPaddingEnabled() { + return mIsTextPaddingEnabled; + } + + /** + * Returns the view's original text with padding added. If the translated text isn't longer than + * the original text, returns the original text itself. + * + * @param text the view's original text + * @param translatedText the view's translated text + * @see #isTextPaddingEnabled() + */ + @Nullable + CharSequence getPaddedText(CharSequence text, CharSequence translatedText) { + if (text == null) { + return null; + } + if (mPaddedText == null) { + mPaddedText = computePaddedText(text, translatedText); + } + return mPaddedText; + } + + @NonNull + private CharSequence computePaddedText(CharSequence text, CharSequence translatedText) { + if (translatedText == null) { + return text; + } + int newLength = translatedText.length(); + if (newLength <= text.length()) { + return text; + } + StringBuilder sb = new StringBuilder(newLength); + sb.append(text); + for (int i = text.length(); i < newLength; i++) { + sb.append(COMPAT_PAD_CHARACTER); + } + return sb; + } + + private static final char COMPAT_PAD_CHARACTER = '\u2002'; }