Loading core/java/android/text/method/TranslationTransformationMethod.java 0 → 100644 +93 −0 Original line number Diff line number Diff line /* * 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(); } } core/java/android/view/View.java +69 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 } } core/java/android/widget/TextView.java +120 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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); } } Loading
core/java/android/text/method/TranslationTransformationMethod.java 0 → 100644 +93 −0 Original line number Diff line number Diff line /* * 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(); } }
core/java/android/view/View.java +69 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 } }
core/java/android/widget/TextView.java +120 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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); } }