Merge "Add initial View's APIs for auto translation"

This commit is contained in:
Ahaan Ugale
2021-01-25 21:08:43 +00:00
committed by Android (Google) Code Review
3 changed files with 282 additions and 0 deletions

View File

@@ -0,0 +1,93 @@
/*
* Copyright (C) 2020 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.text.method;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Rect;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.translation.TranslationRequest;
import android.widget.TextView;
import java.util.regex.Pattern;
/**
* Transforms source text into an translated string.
*
* @hide
*/
public class TranslationTransformationMethod implements TransformationMethod2 {
private static final String TAG = "TranslationTransformationMethod";
private static final Pattern PATTERN_WHITESPACE = Pattern.compile("\\s+");
@NonNull
private TranslationRequest mTranslationRequest;
@Nullable
private TransformationMethod mOriginalTranslationMethod;
private boolean mAllowLengthChanges;
/**
* @param request the translated result from translation service.
* @param method the {@link TextView}'s original {@link TransformationMethod}
*/
public TranslationTransformationMethod(@NonNull TranslationRequest request,
@Nullable TransformationMethod method) {
mTranslationRequest = request;
mOriginalTranslationMethod = method;
}
/**
* Returns the {@link TextView}'s original {@link TransformationMethod}. This can be used to
* restore to show if the user pauses or finish the ui translation.
*/
public TransformationMethod getOriginalTransformationMethod() {
return mOriginalTranslationMethod;
}
@Override
public CharSequence getTransformation(CharSequence source, View view) {
if (!mAllowLengthChanges) {
Log.w(TAG, "Caller did not enable length changes; not transforming to translated text");
return source;
}
CharSequence translatedText = mTranslationRequest.getTranslationText();
if (TextUtils.isEmpty(translatedText) || isWhitespace(translatedText.toString())) {
return source;
} else {
// TODO(b/174283799): apply the spans to the text
return translatedText;
}
}
@Override
public void onFocusChanged(View view, CharSequence sourceText,
boolean focused, int direction,
Rect previouslyFocusedRect) {
// do nothing
}
@Override
public void setLengthChangesAllowed(boolean allowLengthChanges) {
mAllowLengthChanges = allowLengthChanges;
}
private boolean isWhitespace(String text) {
return PATTERN_WHITESPACE.matcher(text.substring(0, text.length())).matches();
}
}

View File

@@ -139,6 +139,7 @@ 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.TranslationRequest;
import android.widget.Checkable;
import android.widget.FrameLayout;
import android.widget.ScrollBarDrawable;
@@ -30630,4 +30631,72 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mPrivateFlags4 &= ~PFLAG4_DETACHED;
}
}
/**
* Returns a {@link TranslationRequest} to the {@link onStartUiTranslation} which represents
* the content to be translated.
*
* <p>The default implementation does nothing and return null.</p>
*
* @hide
*
* @return the {@link TranslationRequest} which contains the information to be translated.
*/
@Nullable
//TODO(b/178046780): initial version for demo. Will mark public when the design is reviewed.
public TranslationRequest onCreateTranslationRequest() {
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.
*
* @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 onRestoreUiTranslation() {
// no-op
}
/**
* 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
*
* <p> The default implementation does nothing.</p>
*
* @param request 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 TranslationRequest request) {
// no-op
}
}

View File

@@ -129,6 +129,7 @@ import android.text.method.TextKeyListener;
import android.text.method.TimeKeyListener;
import android.text.method.TransformationMethod;
import android.text.method.TransformationMethod2;
import android.text.method.TranslationTransformationMethod;
import android.text.method.WordIterator;
import android.text.style.CharacterStyle;
import android.text.style.ClickableSpan;
@@ -193,6 +194,7 @@ import android.view.textclassifier.TextClassifier;
import android.view.textclassifier.TextLinks;
import android.view.textservice.SpellCheckerSubtype;
import android.view.textservice.TextServicesManager;
import android.view.translation.TranslationRequest;
import android.widget.RemoteViews.RemoteView;
import com.android.internal.annotations.VisibleForTesting;
@@ -732,6 +734,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private MovementMethod mMovement;
private TransformationMethod mTransformation;
private TranslationTransformationMethod mTranslationTransformation;
@UnsupportedAppUsage
private boolean mAllowTransformationLengthChange;
@UnsupportedAppUsage
@@ -13814,4 +13817,121 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
Log.d(LOG_TAG, location + ": " + String.format(msgFormat, msgArgs));
}
}
/**
* Provides a {@link TranslationRequest} that represents the content to be translated via
* translation service.
*
* <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.
*
* @hide
*/
@Nullable
@Override
public TranslationRequest onCreateTranslationRequest() {
if (mText == null || mText.length() == 0) {
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()) {
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 informatopn
TranslationRequest request =
new TranslationRequest.Builder()
.setAutofillId(getAutofillId())
.setTranslationText(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());
}
}
/**
* 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.
*
* @hide
*/
@Override
public void onRestoreUiTranslation() {
if (mTranslationTransformation != null) {
setTransformationMethod(mTranslationTransformation);
} else {
Log.w(LOG_TAG, "onResumeTranslatedText(): 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;
}
}
/**
* 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 TranslationRequest data) {
// Show the translated text.
TransformationMethod originalTranslationMethod = mTranslationTransformation != null
? mTranslationTransformation.getOriginalTransformationMethod() : mTransformation;
mTranslationTransformation =
new TranslationTransformationMethod(data, originalTranslationMethod);
// TODO(b/178353965): well-handle setTransformationMethod.
setTransformationMethod(mTranslationTransformation);
}
}