Merge "Add initial View's APIs for auto translation"
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user