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

Commit a749f2aa authored by Joanne Chung's avatar Joanne Chung
Browse files

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
parent 1363aa95
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -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();
+37 −41
Original line number Diff line number Diff line
@@ -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.
     *
     * <p>The default implementation does nothing and return null.</p>
     * Returns a {@link ViewTranslationRequest} which represents the content to be translated.
     *
     * @hide
     * <p>The default implementation does nothing and returns null.</p>
     *
     * @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.
     *
     * @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.
     * 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.</p>
     * @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 onRestoreUiTranslation() {
        // no-op
    @Nullable
    public ViewTranslationCallback getViewTranslationCallback() {
        return mViewTranslationCallback;
    }
    /**
     * Called when the user finish the Ui translation and no longer to show the translated text.
     * 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 onFinishUiTranslation() {
        // no-op
    public void setViewTranslationCallback(@NonNull ViewTranslationCallback callback) {
        mViewTranslationCallback = callback;
    }
    /**
     * 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
    }
+77 −32
Original line number Diff line number Diff line
@@ -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,15 +319,31 @@ public class UiTranslationController {
     */
    private void onUiTranslationStarted(Translator translator, List<AutofillId> views) {
        synchronized (mLock) {
            // 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.onCreateTranslationRequest();
                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);
@@ -318,6 +358,10 @@ public class UiTranslationController {
            });
        }
    }

    private int[] getSupportedFormatsLocked() {
        // We only support text now
        return new int[] {TranslationSpec.DATA_FORMAT_TEXT};
    }

    private void findViewsTraversalByAutofillIds(List<AutofillId> sourceViewIds,
@@ -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());
                }
            });
        }
+51 −0
Original line number Diff line number Diff line
/*
 * 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);
}
+73 −102

File changed.

Preview size limit exceeded, changes collapsed.

Loading