summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt10
-rw-r--r--core/java/android/view/View.java78
-rw-r--r--core/java/android/view/translation/UiTranslationController.java109
-rw-r--r--core/java/android/view/translation/ViewTranslationCallback.java51
-rw-r--r--core/java/android/widget/TextView.java175
-rw-r--r--core/java/android/widget/TextViewTranslationCallback.java120
6 files changed, 368 insertions, 175 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index 2e2147f808df..935cf7067043 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -48364,6 +48364,7 @@ package android.view {
method protected int computeVerticalScrollRange();
method public android.view.accessibility.AccessibilityNodeInfo createAccessibilityNodeInfo();
method public void createContextMenu(android.view.ContextMenu);
+ method @Nullable public android.view.translation.ViewTranslationRequest createTranslationRequest(@NonNull int[]);
method @Deprecated public void destroyDrawingCache();
method public android.view.WindowInsets dispatchApplyWindowInsets(android.view.WindowInsets);
method public boolean dispatchCapturedPointerEvent(android.view.MotionEvent);
@@ -48584,6 +48585,7 @@ package android.view {
method @Nullable public android.graphics.drawable.Drawable getVerticalScrollbarThumbDrawable();
method @Nullable public android.graphics.drawable.Drawable getVerticalScrollbarTrackDrawable();
method public int getVerticalScrollbarWidth();
+ method @Nullable public android.view.translation.ViewTranslationCallback getViewTranslationCallback();
method public android.view.ViewTreeObserver getViewTreeObserver();
method public int getVisibility();
method public final int getWidth();
@@ -48727,6 +48729,7 @@ package android.view {
method public void onStartTemporaryDetach();
method public boolean onTouchEvent(android.view.MotionEvent);
method public boolean onTrackballEvent(android.view.MotionEvent);
+ method public void onTranslationResponse(@NonNull android.view.translation.ViewTranslationResponse);
method @CallSuper public void onVisibilityAggregated(boolean);
method protected void onVisibilityChanged(@NonNull android.view.View, int);
method public void onWindowFocusChanged(boolean);
@@ -48935,6 +48938,7 @@ package android.view {
method public void setVerticalScrollbarPosition(int);
method public void setVerticalScrollbarThumbDrawable(@Nullable android.graphics.drawable.Drawable);
method public void setVerticalScrollbarTrackDrawable(@Nullable android.graphics.drawable.Drawable);
+ method public void setViewTranslationCallback(@NonNull android.view.translation.ViewTranslationCallback);
method public void setVisibility(int);
method @Deprecated public void setWillNotCacheDrawing(boolean);
method public void setWillNotDraw(boolean);
@@ -52757,6 +52761,12 @@ package android.view.translation {
method public void onStarted(@NonNull String, @NonNull String);
}
+ @UiThread public interface ViewTranslationCallback {
+ method public boolean onClearTranslation(@NonNull android.view.View);
+ method public boolean onHideTranslation(@NonNull android.view.View);
+ method public boolean onShowTranslation(@NonNull android.view.View);
+ }
+
public final class ViewTranslationRequest implements android.os.Parcelable {
method public int describeContents();
method @NonNull public android.view.autofill.AutofillId getAutofillId();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index e573056ddbaa..a03e9e352c60 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -151,6 +151,8 @@ import android.view.inputmethod.InputConnection;
import android.view.inspector.InspectableProperty;
import android.view.inspector.InspectableProperty.EnumEntry;
import android.view.inspector.InspectableProperty.FlagEntry;
+import android.view.translation.TranslationSpec.DataFormat;
+import android.view.translation.ViewTranslationCallback;
import android.view.translation.ViewTranslationRequest;
import android.view.translation.ViewTranslationResponse;
import android.widget.Checkable;
@@ -5253,6 +5255,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
@Nullable
private String[] mOnReceiveContentMimeTypes;
+ @Nullable
+ private ViewTranslationCallback mViewTranslationCallback;
+
/**
* Simple constructor to use when creating a view from code.
*
@@ -30717,71 +30722,62 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
}
+ //TODO(b/1960696): update javadoc when dispatchRequestTranslation is ready.
/**
- * Returns a {@link ViewTranslationRequest} to the {@link onStartUiTranslation} which represents
- * the content to be translated.
- *
- * <p>The default implementation does nothing and return null.</p>
+ * Returns a {@link ViewTranslationRequest} which represents the content to be translated.
*
- * @hide
+ * <p>The default implementation does nothing and returns null.</p>
*
- * @return the {@link ViewTranslationRequest} which contains the information to be translated.
+ * @param supportedFormats the supported translation formats. For now, the only possible value
+ * is the {@link android.view.translation.TranslationSpec#DATA_FORMAT_TEXT}.
+ * @return the {@link ViewTranslationRequest} which contains the information to be translated or
+ * {@code null} if this View doesn't support translation.
+ * The {@link AutofillId} must be set on the returned value.
*/
@Nullable
- //TODO(b/178046780): initial version for demo. Will mark public when the design is reviewed.
- public ViewTranslationRequest onCreateTranslationRequest() {
+ public ViewTranslationRequest createTranslationRequest(
+ @NonNull @DataFormat int[] supportedFormats) {
return null;
}
/**
- * Called when the user wants to show the original text instead of the translated text.
- *
- * @hide
- *
- * <p> The default implementation does nothing.
- */
- //TODO(b/178046780): initial version for demo. Will mark public when the design is reviewed.
- public void onPauseUiTranslation() {
- // no-op
- }
-
- /**
- * User can switch back to show the original text, this method called when the user wants to
- * re-show the translated text again.
+ * Returns a {@link ViewTranslationCallback} that is used to display/hide the translated
+ * information. If the View supports displaying translated content, it should implement
+ * {@link ViewTranslationCallback}.
*
- * @hide
+ * <p>The default implementation returns null if developers don't set the customized
+ * {@link ViewTranslationCallback} by {@link #setViewTranslationCallback} </p>
*
- * <p> The default implementation does nothing.</p>
+ * @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.
*/
- //TODO(b/178046780): initial version for demo. Will mark public when the design is reviewed.
- public void onRestoreUiTranslation() {
- // no-op
+ @Nullable
+ public ViewTranslationCallback getViewTranslationCallback() {
+ return mViewTranslationCallback;
}
/**
- * Called when the user finish the Ui translation and no longer to show the translated text.
+ * Sets a {@link ViewTranslationCallback} that is used to display/hide the translated
+ * information. Developers can provide the customized implementation for show/hide translated
+ * information.
*
- * @hide
- *
- * <p> The default implementation does nothing.</p>
+ * @param callback a {@link ViewTranslationCallback} that is used to control how to display the
+ * translated information
*/
- //TODO(b/178046780): initial version for demo. Will mark public when the design is reviewed.
- public void onFinishUiTranslation() {
- // no-op
+ public void setViewTranslationCallback(@NonNull ViewTranslationCallback callback) {
+ mViewTranslationCallback = callback;
}
/**
- * Called when the request from {@link onStartUiTranslation} is completed by the translation
- * service so that the translation result can be shown.
- *
- * @hide
+ * Called when the content from {@link #createTranslationRequest} had been translated by the
+ * TranslationService.
*
* <p> The default implementation does nothing.</p>
*
- * @param response the translated information which can be shown in the view.
+ * @param response a {@link ViewTranslationResponse} that contains the translated information
+ * which can be shown in the view.
*/
- //TODO(b/178046780): initial version for demo. Will mark public when the design is reviewed.
- public void onTranslationComplete(@NonNull ViewTranslationResponse response) {
+ public void onTranslationResponse(@NonNull ViewTranslationResponse response) {
// no-op
}
diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java
index d79ecca1426e..15d01ae6a8fc 100644
--- a/core/java/android/view/translation/UiTranslationController.java
+++ b/core/java/android/view/translation/UiTranslationController.java
@@ -46,7 +46,7 @@ import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
-import java.util.function.Consumer;
+import java.util.function.BiConsumer;
/**
* A controller to manage the ui translation requests for the {@link Activity}.
@@ -77,6 +77,7 @@ public class UiTranslationController {
private final HandlerThread mWorkerThread;
@NonNull
private final Handler mWorkerHandler;
+ private int mCurrentState;
public UiTranslationController(Activity activity, Context context) {
mActivity = activity;
@@ -101,6 +102,9 @@ public class UiTranslationController {
}
Log.i(TAG, "updateUiTranslationState state: " + stateToString(state)
+ (DEBUG ? ", views: " + views : ""));
+ synchronized (mLock) {
+ mCurrentState = state;
+ }
switch (state) {
case STATE_UI_TRANSLATION_STARTED:
final Pair<TranslationSpec, TranslationSpec> specs =
@@ -114,14 +118,14 @@ public class UiTranslationController {
}
break;
case STATE_UI_TRANSLATION_PAUSED:
- runForEachView(View::onPauseUiTranslation);
+ runForEachView((view, callback) -> callback.onHideTranslation(view));
break;
case STATE_UI_TRANSLATION_RESUMED:
- runForEachView(View::onRestoreUiTranslation);
+ runForEachView((view, callback) -> callback.onShowTranslation(view));
break;
case STATE_UI_TRANSLATION_FINISHED:
destroyTranslators();
- runForEachView(View::onFinishUiTranslation);
+ runForEachView((view, callback) -> callback.onClearTranslation(view));
synchronized (mLock) {
mViews.clear();
}
@@ -232,11 +236,16 @@ public class UiTranslationController {
}
final SparseArray<ViewTranslationResponse> translatedResult =
response.getViewTranslationResponses();
+ // TODO(b/177960696): handle virtual views. Check the result if the AutofillId is virtual
+ // AutofillId?
onTranslationCompleted(translatedResult);
}
private void onTranslationCompleted(SparseArray<ViewTranslationResponse> translatedResult) {
if (!mActivity.isResumed()) {
+ if (DEBUG) {
+ Log.v(TAG, "onTranslationCompleted: Activity is not resumed.");
+ }
return;
}
final int resultCount = translatedResult.size();
@@ -244,6 +253,11 @@ public class UiTranslationController {
Log.v(TAG, "onTranslationCompleted: receive " + resultCount + " responses.");
}
synchronized (mLock) {
+ if (mCurrentState == STATE_UI_TRANSLATION_FINISHED) {
+ Log.w(TAG, "onTranslationCompleted: the translation state is finished now. "
+ + "Skip to show the translated text.");
+ return;
+ }
for (int i = 0; i < resultCount; i++) {
final ViewTranslationResponse response = translatedResult.get(i);
final AutofillId autofillId = response.getAutofillId();
@@ -256,18 +270,28 @@ public class UiTranslationController {
+ " may be gone.");
continue;
}
- mActivity.runOnUiThread(() -> view.onTranslationComplete(response));
+ mActivity.runOnUiThread(() -> {
+ if (view.getViewTranslationCallback() == null) {
+ if (DEBUG) {
+ Log.d(TAG, view + " doesn't support showing translation because of "
+ + "null ViewTranslationCallback.");
+ }
+ return;
+ }
+ view.onTranslationResponse(response);
+ view.getViewTranslationCallback().onShowTranslation(view);
+ });
}
}
}
/**
- * Called when there is an ui translation request comes to request view translation.
+ * Creates a Translator for the given source and target translation specs and start the ui
+ * translation when the Translator is created successfully.
*/
@WorkerThread
private void createTranslatorAndStart(TranslationSpec sourceSpec, TranslationSpec destSpec,
List<AutofillId> views) {
- // Create Translator
final Translator translator = createTranslatorIfNeeded(sourceSpec, destSpec);
if (translator == null) {
Log.w(TAG, "Can not create Translator for sourceSpec:" + sourceSpec + " destSpec:"
@@ -295,31 +319,51 @@ public class UiTranslationController {
*/
private void onUiTranslationStarted(Translator translator, List<AutofillId> views) {
synchronized (mLock) {
- // Find Views collect the translation data
- final ArrayList<ViewTranslationRequest> requests = new ArrayList<>();
+ // TODO(b/177960696): handle virtual views. Need to check the requested view list is
+ // virtual AutofillId or not
+ findViewsAndCollectViewTranslationRequest(translator, views);
+ }
+ }
+
+ /**
+ * If the translation requested views are not virtual view, we need to traverse the tree to
+ * find the views and get the View's ViewTranslationRequest.
+ */
+ private void findViewsAndCollectViewTranslationRequest(Translator translator,
+ List<AutofillId> views) {
+ // Find Views collect the translation data
+ final ArrayList<ViewTranslationRequest> requests = new ArrayList<>();
final ArrayList<View> foundViews = new ArrayList<>();
- findViewsTraversalByAutofillIds(views, foundViews);
- for (int i = 0; i < foundViews.size(); i++) {
- final View view = foundViews.get(i);
- final int currentCount = i;
- mActivity.runOnUiThread(() -> {
- final ViewTranslationRequest request = view.onCreateTranslationRequest();
- if (request != null
- && request.getKeys().size() > 0) {
- requests.add(request);
- }
- if (currentCount == (foundViews.size() - 1)) {
- Log.v(TAG, "onUiTranslationStarted: collect " + requests.size()
- + " requests.");
- mWorkerHandler.sendMessage(PooledLambda.obtainMessage(
- UiTranslationController::sendTranslationRequest,
- UiTranslationController.this, translator, requests));
- }
- });
- }
+ findViewsTraversalByAutofillIds(views, foundViews);
+ final int[] supportedFormats = getSupportedFormatsLocked();
+ for (int i = 0; i < foundViews.size(); i++) {
+ final View view = foundViews.get(i);
+ final int currentCount = i;
+ mActivity.runOnUiThread(() -> {
+ final ViewTranslationRequest request =
+ view.createTranslationRequest(supportedFormats);
+ // TODO(b/177960696): handle null case, the developers may want to handle the
+ // translation, call dispatchRequestTranslation() instead.
+ if (request != null
+ && request.getKeys().size() > 0) {
+ requests.add(request);
+ }
+ if (currentCount == (foundViews.size() - 1)) {
+ Log.v(TAG, "onUiTranslationStarted: collect " + requests.size()
+ + " requests.");
+ mWorkerHandler.sendMessage(PooledLambda.obtainMessage(
+ UiTranslationController::sendTranslationRequest,
+ UiTranslationController.this, translator, requests));
+ }
+ });
}
}
+ private int[] getSupportedFormatsLocked() {
+ // We only support text now
+ return new int[] {TranslationSpec.DATA_FORMAT_TEXT};
+ }
+
private void findViewsTraversalByAutofillIds(List<AutofillId> sourceViewIds,
ArrayList<View> foundViews) {
final ArrayList<ViewRootImpl> roots =
@@ -356,20 +400,21 @@ public class UiTranslationController {
}
}
- private void runForEachView(Consumer<View> action) {
+ private void runForEachView(BiConsumer<View, ViewTranslationCallback> action) {
synchronized (mLock) {
final ArrayMap<AutofillId, WeakReference<View>> views = new ArrayMap<>(mViews);
mActivity.runOnUiThread(() -> {
final int viewCounts = views.size();
for (int i = 0; i < viewCounts; i++) {
final View view = views.valueAt(i).get();
- if (view == null) {
+ if (view == null || view.getViewTranslationCallback() == null) {
if (DEBUG) {
- Log.d(TAG, "View was gone for autofillid = " + views.keyAt(i));
+ Log.d(TAG, "View was gone or ViewTranslationCallback for autofillid "
+ + "= " + views.keyAt(i));
}
continue;
}
- action.accept(view);
+ action.accept(view, view.getViewTranslationCallback());
}
});
}
diff --git a/core/java/android/view/translation/ViewTranslationCallback.java b/core/java/android/view/translation/ViewTranslationCallback.java
new file mode 100644
index 000000000000..c8959847d64b
--- /dev/null
+++ b/core/java/android/view/translation/ViewTranslationCallback.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.translation;
+
+import android.annotation.NonNull;
+import android.annotation.UiThread;
+import android.view.View;
+
+/**
+ * Callback for handling the translated information show or hide in the {@link View}. See {@link
+ * View#onTranslationResponse} for how to get the translated information.
+ */
+@UiThread
+public interface ViewTranslationCallback {
+ /**
+ * Called when the translated text is ready to show or if the user has requested to reshow the
+ * translated content after hiding it. This may be called before the translation results are
+ * returned through the {@link View#onTranslationResponse}.
+ *
+ * @return {@code true} if the View handles showing the translation.
+ */
+ boolean onShowTranslation(@NonNull View view);
+ /**
+ * Called when the user wants to show the original text instead of the translated text. This
+ * may be called before the translation results are returned through the
+ * {@link View#onTranslationResponse}.
+ *
+ * @return {@code true} if the View handles hiding the translation.
+ */
+ boolean onHideTranslation(@NonNull View view);
+ /**
+ * Called when the user finish the Ui translation and no longer to show the translated text.
+ *
+ * @return {@code true} if the View handles clearing the translation.
+ */
+ boolean onClearTranslation(@NonNull View view);
+}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 940a3c9cccdf..6733c0d3a8e1 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -195,7 +195,9 @@ import android.view.textclassifier.TextLinks;
import android.view.textservice.SpellCheckerSubtype;
import android.view.textservice.TextServicesManager;
import android.view.translation.TranslationRequestValue;
+import android.view.translation.TranslationSpec;
import android.view.translation.UiTranslationController;
+import android.view.translation.ViewTranslationCallback;
import android.view.translation.ViewTranslationRequest;
import android.view.translation.ViewTranslationResponse;
import android.widget.RemoteViews.RemoteView;
@@ -203,6 +205,7 @@ import android.widget.RemoteViews.RemoteView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastMath;
import com.android.internal.util.Preconditions;
import com.android.internal.widget.EditableInputConnection;
@@ -737,7 +740,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private MovementMethod mMovement;
private TransformationMethod mTransformation;
- private TranslationTransformationMethod mTranslationTransformation;
+ private TextViewTranslationCallback mDefaultTranslationCallback;
@UnsupportedAppUsage
private boolean mAllowTransformationLengthChange;
@UnsupportedAppUsage
@@ -13857,136 +13860,104 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
- * Provides a {@link ViewTranslationRequest} that represents the content to be translated via
- * translation service.
+ * Returns a {@link ViewTranslationRequest} which represents the content to be translated.
*
- * <p>NOTE: When overriding the method, it should not translate the password. We also suggest
- * that not translating the text is selectable or editable. We use the transformation method to
- * implement showing the translated text. The TextView does not support the transformation
- * method text length change. If the text is selectable or editable, it will crash while
- * selecting the text. To support it, it needs broader changes to text APIs, we only allow to
- * translate non selectable and editable text now.
+ * <p>NOTE: When overriding the method, it should not translate the password. If the subclass
+ * uses {@link TransformationMethod} to display the translated result, it's also not recommend
+ * to translate text is selectable or editable.
*
- * @hide
+ * @param supportedFormats the supported translation format. The value could be {@link
+ * android.view.translation.TranslationSpec#DATA_FORMAT_TEXT}.
+ * @return the {@link ViewTranslationRequest} which contains the information to be translated.
*/
@Nullable
@Override
- public ViewTranslationRequest onCreateTranslationRequest() {
- if (mText == null || mText.length() == 0) {
+ public ViewTranslationRequest createTranslationRequest(@NonNull int[] supportedFormats) {
+ if (supportedFormats == null || supportedFormats.length == 0) {
// TODO(b/182433547): remove before S release
if (UiTranslationController.DEBUG) {
- Log.w(LOG_TAG, "Cannot create translation request for the empty text.");
+ Log.w(LOG_TAG, "Do not provide the support translation formats.");
}
return null;
}
- // Not translate password, editable text and not important for translation
- // TODO(b/177214256): support selectable text translation. It needs to broader changes to
- // text selection apis, not support in S.
- boolean isPassword = isAnyPasswordInputType() || hasPasswordTransformationMethod();
- if (isTextEditable() || isPassword || isTextSelectable()) {
- // TODO(b/182433547): remove before S release
- if (UiTranslationController.DEBUG) {
- Log.w(LOG_TAG, "Cannot create translation request. editable = " + isTextEditable()
- + ", isPassword = " + isPassword + ", selectable = " + isTextSelectable());
+ ViewTranslationRequest.Builder requestBuilder =
+ new ViewTranslationRequest.Builder(getAutofillId());
+ // Support Text translation
+ if (ArrayUtils.contains(supportedFormats, TranslationSpec.DATA_FORMAT_TEXT)) {
+ if (mText == null || mText.length() == 0) {
+ // TODO(b/182433547): remove before S release
+ if (UiTranslationController.DEBUG) {
+ Log.w(LOG_TAG, "Cannot create translation request for the empty text.");
+ }
+ return null;
}
- return null;
- }
- // TODO(b/176488462): apply the view's important for translation property
- // TODO(b/174283799): remove the spans from the mText and save the spans information
- // TODO: use fixed ids for request texts.
- ViewTranslationRequest request =
- new ViewTranslationRequest.Builder(getAutofillId())
- .setValue(ViewTranslationRequest.ID_TEXT,
- TranslationRequestValue.forText(mText))
- .build();
- return request;
- }
-
- /**
- * Provides the implementation that pauses the ongoing Ui translation, it will show the original
- * text instead of the translated text and restore the original transformation method.
- *
- * <p>NOTE: If this method is overridden, other translation related methods such as
- * {@link onRestoreUiTranslation}, {@link onFinishUiTranslation}, {@link onTranslationComplete}
- * should also be overridden.
- *
- * @hide
- */
- @Override
- public void onPauseUiTranslation() {
- // Restore to original text content.
- if (mTranslationTransformation != null) {
- setTransformationMethod(mTranslationTransformation.getOriginalTransformationMethod());
- } else {
- // TODO(b/182433547): remove before S release
- Log.w(LOG_TAG, "onPauseUiTranslation(): no translated text.");
+ boolean isPassword = isAnyPasswordInputType() || hasPasswordTransformationMethod();
+ // TODO(b/177214256): support selectable text translation.
+ // We use the TransformationMethod to implement showing the translated text. The
+ // TextView does not support the text length change for TransformationMethod. If the
+ // text is selectable or editable, it will crash while selecting the text. To support
+ // it, it needs broader changes to text APIs, we only allow to translate non selectable
+ // and editable text in S.
+ if (isTextEditable() || isPassword || isTextSelectable()) {
+ // TODO(b/182433547): remove before S release
+ if (UiTranslationController.DEBUG) {
+ Log.w(LOG_TAG, "Cannot create translation request. editable = "
+ + isTextEditable() + ", isPassword = " + isPassword + ", selectable = "
+ + isTextSelectable());
+ }
+ return null;
+ }
+ // TODO(b/176488462): apply the view's important for translation
+ requestBuilder.setValue(ViewTranslationRequest.ID_TEXT,
+ TranslationRequestValue.forText(mText));
}
+ return requestBuilder.build();
}
/**
- * Provides the implementation that restoes the paused Ui translation, it will show the
- * translated text again if the text had been translated. This method will replace the current
- * tansformation method with {@link TranslationTransformationMethod}.
- *
- * <p>NOTE: If this method is overridden, other translation related methods such as
- * {@link onPauseUiTranslation}, {@link onFinishUiTranslation}, {@link onTranslationComplete}
- * should also be overridden.
+ * 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.
*
- * @hide
+ * @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.
*/
+ @Nullable
@Override
- public void onRestoreUiTranslation() {
- if (mTranslationTransformation != null) {
- setTransformationMethod(mTranslationTransformation);
- } else {
- // TODO(b/182433547): remove before S release
- Log.w(LOG_TAG, "onRestoreUiTranslation(): no translated text.");
- }
+ public ViewTranslationCallback getViewTranslationCallback() {
+ return getDefaultViewTranslationCallback();
}
- /**
- * Provides the implementation that finishes the current Ui translation and it's no longer to
- * show the translated text. This method restores the original transformation method and resets
- * the saved {@link TranslationTransformationMethod}.
- *
- * <p>NOTE: If this method is overridden, other translation related methods such as
- * {@link onPauseUiTranslation}, {@link onRestoreUiTranslation}, {@link onTranslationComplete}
- * should also be overridden.
- *
- * @hide
- */
- @Override
- public void onFinishUiTranslation() {
- // Restore to original text content and clear TranslationTransformation
- if (mTranslationTransformation != null) {
- setTransformationMethod(mTranslationTransformation.getOriginalTransformationMethod());
- mTranslationTransformation = null;
- } else {
- // TODO(b/182433547): remove before S release
- Log.w(LOG_TAG, "onFinishUiTranslation(): no translated text.");
+ private ViewTranslationCallback getDefaultViewTranslationCallback() {
+ if (mDefaultTranslationCallback == null) {
+ mDefaultTranslationCallback = new TextViewTranslationCallback();
}
+ return mDefaultTranslationCallback;
}
/**
- * Default {@link TextView} implementation after the translation request is done by the
- * translation service, it's ok to show the translated text. This method will save the original
- * transformation method and replace the current transformation method with
- * {@link TranslationTransformationMethod}.
*
- * <p>NOTE: If this method is overridden, other translation related methods such as
- * {@link onPauseUiTranslation}, {@link onRestoreUiTranslation}, {@link onFinishUiTranslation}
- * should also be overridden.
+ * Called when the content from {@link #createTranslationRequest} had been translated by the
+ * TranslationService. The default implementation will replace the current
+ * {@link TransformationMethod} to transform the original text to the translated text display.
*
- * @hide
+ * @param response a {@link ViewTranslationResponse} that contains the translated information
+ * which can be shown in the view.
*/
@Override
- public void onTranslationComplete(@NonNull ViewTranslationResponse response) {
- // Show the translated text.
- TransformationMethod originalTranslationMethod = mTranslationTransformation != null
- ? mTranslationTransformation.getOriginalTransformationMethod() : mTransformation;
- mTranslationTransformation =
+ public void onTranslationResponse(@NonNull ViewTranslationResponse 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.
- setTransformationMethod(mTranslationTransformation);
+ callback.setTranslationTransformation(newTranslationMethod);
}
}
diff --git a/core/java/android/widget/TextViewTranslationCallback.java b/core/java/android/widget/TextViewTranslationCallback.java
new file mode 100644
index 000000000000..296d93c88554
--- /dev/null
+++ b/core/java/android/widget/TextViewTranslationCallback.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import android.annotation.NonNull;
+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.ViewTranslationResponse;
+
+/**
+ * Default implementation for {@link ViewTranslationCallback} for {@link TextView} components.
+ * This class handles how to display the translated information for {@link TextView}.
+ *
+ * @hide
+ */
+public class TextViewTranslationCallback implements ViewTranslationCallback {
+
+ private static final String TAG = "TextViewTranslationCallback";
+
+ private static final boolean DEBUG = Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG);
+
+ private TranslationTransformationMethod mTranslationTransformation;
+
+ /**
+ * Invoked by the platform when receiving the successful {@link ViewTranslationResponse} for the
+ * view that provides the translatable information by {@link View#createTranslationRequest} and
+ * sent by the platform.
+ */
+ void setTranslationTransformation(TranslationTransformationMethod method) {
+ if (method == null) {
+ if (DEBUG) {
+ Log.w(TAG, "setTranslationTransformation: should not set null "
+ + "TranslationTransformationMethod");
+ }
+ return;
+ }
+ mTranslationTransformation = method;
+ }
+
+ TranslationTransformationMethod getTranslationTransformation() {
+ return mTranslationTransformation;
+ }
+
+ private void clearTranslationTransformation() {
+ if (DEBUG) {
+ Log.v(TAG, "clearTranslationTransformation: " + mTranslationTransformation);
+ }
+ mTranslationTransformation = null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onShowTranslation(@NonNull View view) {
+ if (mTranslationTransformation != null) {
+ ((TextView) view).setTransformationMethod(mTranslationTransformation);
+ } else {
+ if (DEBUG) {
+ // TODO(b/182433547): remove before S release
+ Log.w(TAG, "onShowTranslation(): no translated text.");
+ }
+ }
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onHideTranslation(@NonNull View view) {
+ // Restore to original text content.
+ if (mTranslationTransformation != null) {
+ ((TextView) view).setTransformationMethod(
+ mTranslationTransformation.getOriginalTransformationMethod());
+ } else {
+ if (DEBUG) {
+ // TODO(b/182433547): remove before S release
+ Log.w(TAG, "onHideTranslation(): no translated text.");
+ }
+ }
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onClearTranslation(@NonNull View view) {
+ // Restore to original text content and clear TranslationTransformation
+ if (mTranslationTransformation != null) {
+ ((TextView) view).setTransformationMethod(
+ mTranslationTransformation.getOriginalTransformationMethod());
+ clearTranslationTransformation();
+ } else {
+ if (DEBUG) {
+ // TODO(b/182433547): remove before S release
+ Log.w(TAG, "onClearTranslation(): no translated text.");
+ }
+ }
+ return true;
+ }
+}