diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 1f58fdcbf6d17..0acacb60da3fb 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -3514,6 +3514,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * 11 PFLAG4_SCROLL_CAPTURE_HINT_MASK * 1 PFLAG4_ALLOW_CLICK_WHEN_DISABLED * 1 PFLAG4_DETACHED + * 1 PFLAG4_HAS_TRANSLATION_TRANSIENT_STATE * |-------|-------|-------|-------| */ @@ -3580,6 +3581,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ private static final int PFLAG4_DETACHED = 0x000002000; + /** + * Indicates that the view has transient state because the system is translating it. + */ + private static final int PFLAG4_HAS_TRANSLATION_TRANSIENT_STATE = 0x000004000; + /* End of masks for mPrivateFlags4 */ /** @hide */ @@ -12270,6 +12276,30 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } + /** + * Set the view is tracking translation transient state. This flag is used to check if the view + * need to call setHasTransientState(false) to reset transient state that set when starting + * translation. + * + * @param hasTranslationTransientState true if this view has translation transient state + * @hide + */ + public void setHasTranslationTransientState(boolean hasTranslationTransientState) { + if (hasTranslationTransientState) { + mPrivateFlags4 |= PFLAG4_HAS_TRANSLATION_TRANSIENT_STATE; + } else { + mPrivateFlags4 &= ~PFLAG4_HAS_TRANSLATION_TRANSIENT_STATE; + } + } + + /** + * @hide + */ + public boolean hasTranslationTransientState() { + return (mPrivateFlags4 & PFLAG4_HAS_TRANSLATION_TRANSIENT_STATE) + == PFLAG4_HAS_TRANSLATION_TRANSIENT_STATE; + } + /** * Returns true if this view is currently attached to a window. */ @@ -30839,6 +30869,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Returns a {@link ViewTranslationCallback} that is used to display the translated information + * or {@code null} if this View doesn't support translation. + * * @hide */ @Nullable @@ -30925,7 +30958,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * view or calls {@link View#onVirtualViewTranslationResponses} for view contains virtual * children to build {@link ViewTranslationRequest} if the view should be translated. * The view is marked as having {@link #setHasTransientState(boolean) transient state} so that - * recycling of views doesn't prevent the system from attaching the response to it.

+ * recycling of views doesn't prevent the system from attaching the response to it. Therefore, + * if overriding this method, you should set or reset the transient state.

* * @param viewIds a map for the view's {@link AutofillId} and its virtual child ids or * {@code null} if the view doesn't have virtual child that should be translated. The virtual @@ -30975,10 +31009,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Log.v(CONTENT_CAPTURE_LOG_TAG, "Calling setHasTransientState(true) for " + getAutofillId()); } - // TODO: Add a default ViewTranslationCallback for View that resets this in - // onClearTranslation(). Also update the javadoc for this method to mention - // that. setHasTransientState(true); + setHasTranslationTransientState(true); } } } diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java index f79c329e4a0c0..4b2c343655890 100644 --- a/core/java/android/view/translation/UiTranslationController.java +++ b/core/java/android/view/translation/UiTranslationController.java @@ -43,6 +43,8 @@ import android.view.ViewRootImpl; import android.view.WindowManagerGlobal; import android.view.autofill.AutofillId; import android.view.translation.UiTranslationManager.UiTranslationState; +import android.widget.TextView; +import android.widget.TextViewTranslationCallback; import com.android.internal.util.function.pooled.PooledLambda; @@ -146,7 +148,13 @@ public class UiTranslationController { break; case STATE_UI_TRANSLATION_FINISHED: destroyTranslators(); - runForEachView((view, callback) -> callback.onClearTranslation(view)); + runForEachView((view, callback) -> { + callback.onClearTranslation(view); + if (view.hasTranslationTransientState()) { + view.setHasTransientState(false); + view.setHasTranslationTransientState(false); + } + }); synchronized (mLock) { mViews.clear(); } @@ -381,17 +389,23 @@ public class UiTranslationController { continue; } mActivity.runOnUiThread(() -> { - final ViewTranslationCallback callback = view.getViewTranslationCallback(); + ViewTranslationCallback callback = view.getViewTranslationCallback(); if (callback == null) { - if (DEBUG) { - Log.d(TAG, view + " doesn't support showing translation because of " - + "null ViewTranslationCallback."); + if (view instanceof TextView) { + // developer doesn't provide their override, we set the default TextView + // implememtation. + callback = new TextViewTranslationCallback(); + view.setViewTranslationCallback(callback); + if (mViewsToPadContent.contains(autofillId)) { + callback.enableContentPadding(); + } + } else { + if (DEBUG) { + Log.d(TAG, view + " doesn't support showing translation because of " + + "null ViewTranslationCallback."); + } + return; } - return; - } - - if (mViewsToPadContent.contains(autofillId)) { - callback.enableContentPadding(); } view.onViewTranslationResponse(response); callback.onShowTranslation(view); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index e0238b9bce133..9959510d5ac07 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -740,7 +740,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private MovementMethod mMovement; private TransformationMethod mTransformation; - private TextViewTranslationCallback mDefaultTranslationCallback; @UnsupportedAppUsage private boolean mAllowTransformationLengthChange; @UnsupportedAppUsage @@ -2376,11 +2375,16 @@ 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); + if (mUseTextPaddingForUiTranslation) { + ViewTranslationCallback callback = getViewTranslationCallback(); + if (callback != null && callback instanceof TextViewTranslationCallback) { + TextViewTranslationCallback defaultCallback = + (TextViewTranslationCallback) callback; + if (defaultCallback.isTextPaddingEnabled() + && defaultCallback.isShowingTranslation()) { + return defaultCallback.getPaddedText(mText, mTransformed); + } + } } return mText; } @@ -13932,30 +13936,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener requestsCollector.accept(requestBuilder.build()); } - /** - * Returns a {@link ViewTranslationCallback} that is used to display the translated information. - * The default implementation will use a {@link TransformationMethod} that allow to replace the - * current {@link TransformationMethod} to transform the original text to the translated text - * display. - * - * @return a {@link ViewTranslationCallback} that is used to control how to display the - * translated information or {@code null} if this View doesn't support translation. - * - * @hide - */ - @Nullable - @Override - public ViewTranslationCallback getViewTranslationCallback() { - return getDefaultViewTranslationCallback(); - } - - private ViewTranslationCallback getDefaultViewTranslationCallback() { - if (mDefaultTranslationCallback == null) { - mDefaultTranslationCallback = new TextViewTranslationCallback(); - } - return mDefaultTranslationCallback; - } - /** * * Called when the content from {@link #onCreateViewTranslationRequest} had been translated by @@ -13969,17 +13949,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public void onViewTranslationResponse(@NonNull ViewTranslationResponse response) { // set ViewTranslationResponse super.onViewTranslationResponse(response); - // TODO(b/183467275): Use the overridden ViewTranslationCallback instead of our default - // implementation if the view has overridden getViewTranslationCallback. - TextViewTranslationCallback callback = - (TextViewTranslationCallback) getDefaultViewTranslationCallback(); - TranslationTransformationMethod oldTranslationMethod = - callback.getTranslationTransformation(); - TransformationMethod originalTranslationMethod = oldTranslationMethod != null - ? oldTranslationMethod.getOriginalTransformationMethod() : mTransformation; - TranslationTransformationMethod newTranslationMethod = - new TranslationTransformationMethod(response, originalTranslationMethod); - // TODO(b/178353965): well-handle setTransformationMethod. - callback.setTranslationTransformation(newTranslationMethod); + // TODO(b/178353965): move to ViewTranslationCallback.onShow() + ViewTranslationCallback callback = getViewTranslationCallback(); + if (callback instanceof TextViewTranslationCallback) { + TextViewTranslationCallback textViewDefaultCallback = + (TextViewTranslationCallback) callback; + TranslationTransformationMethod oldTranslationMethod = + textViewDefaultCallback.getTranslationTransformation(); + TransformationMethod originalTranslationMethod = oldTranslationMethod != null + ? oldTranslationMethod.getOriginalTransformationMethod() : mTransformation; + TranslationTransformationMethod newTranslationMethod = + new TranslationTransformationMethod(response, originalTranslationMethod); + // TODO(b/178353965): well-handle setTransformationMethod. + textViewDefaultCallback.setTranslationTransformation(newTranslationMethod); + } } }