Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 70f17d9f authored by Ahaan Ugale's avatar Ahaan Ugale Committed by Android (Google) Code Review
Browse files

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

parents 80320a92 a5fa9bc0
Loading
Loading
Loading
Loading
+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();
    }
}
+69 −0
Original line number Diff line number Diff line
@@ -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
    }
}
+120 −0
Original line number Diff line number Diff line
@@ -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);
    }
}