Update View translation APIs.
This change doesn't contain the virtual view part and the API dispatchTranslationRequests, it will be done in another change. Bug: 178046780 Test: manual Test: atest CtsTranslationTestCases CTS-Coverage-Bug: 177960696 Change-Id: I65fe1db19c9dff21c0caca425fbb7d08559e730b
This commit is contained in:
@@ -48273,6 +48273,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);
|
||||
@@ -48493,6 +48494,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();
|
||||
@@ -48636,6 +48638,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);
|
||||
@@ -48844,6 +48847,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);
|
||||
@@ -52665,6 +52669,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();
|
||||
|
||||
@@ -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.
|
||||
* Returns a {@link ViewTranslationRequest} which represents the content to be translated.
|
||||
*
|
||||
* <p>The default implementation does nothing and return null.</p>
|
||||
* <p>The default implementation does nothing and returns null.</p>
|
||||
*
|
||||
* @hide
|
||||
*
|
||||
* @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.
|
||||
* 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.
|
||||
* @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 onPauseUiTranslation() {
|
||||
// no-op
|
||||
@Nullable
|
||||
public ViewTranslationCallback getViewTranslationCallback() {
|
||||
return mViewTranslationCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* User can switch back to show the original text, this method called when the user wants to
|
||||
* re-show the translated text again.
|
||||
* 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 onRestoreUiTranslation() {
|
||||
// no-op
|
||||
public void setViewTranslationCallback(@NonNull ViewTranslationCallback callback) {
|
||||
mViewTranslationCallback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user finish the Ui translation and no longer to show the translated text.
|
||||
*
|
||||
* @hide
|
||||
*
|
||||
* <p> The default implementation does nothing.</p>
|
||||
*/
|
||||
//TODO(b/178046780): initial version for demo. Will mark public when the design is reviewed.
|
||||
public void onFinishUiTranslation() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
|
||||
|
||||
@@ -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<>();
|
||||
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));
|
||||
}
|
||||
});
|
||||
}
|
||||
// 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);
|
||||
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());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
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));
|
||||
}
|
||||
// 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;
|
||||
return requestBuilder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
*
|
||||
* <p>NOTE: If this method is overridden, other translation related methods such as
|
||||
* {@link onRestoreUiTranslation}, {@link onFinishUiTranslation}, {@link onTranslationComplete}
|
||||
* should also be overridden.
|
||||
*
|
||||
* @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 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.");
|
||||
public ViewTranslationCallback getViewTranslationCallback() {
|
||||
return getDefaultViewTranslationCallback();
|
||||
}
|
||||
|
||||
private ViewTranslationCallback getDefaultViewTranslationCallback() {
|
||||
if (mDefaultTranslationCallback == null) {
|
||||
mDefaultTranslationCallback = new TextViewTranslationCallback();
|
||||
}
|
||||
return mDefaultTranslationCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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 onRestoreUiTranslation() {
|
||||
if (mTranslationTransformation != null) {
|
||||
setTransformationMethod(mTranslationTransformation);
|
||||
} else {
|
||||
// TODO(b/182433547): remove before S release
|
||||
Log.w(LOG_TAG, "onRestoreUiTranslation(): no translated text.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
120
core/java/android/widget/TextViewTranslationCallback.java
Normal file
120
core/java/android/widget/TextViewTranslationCallback.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user