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

Commit 4f01d815 authored by Joanne Chung's avatar Joanne Chung Committed by Automerger Merge Worker
Browse files

Merge "Update View translation APIs." into sc-dev am: b6a9530d

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/13874869

Change-Id: I9b5f21a62051f5aa9fd393d5204df42237a52fa2
parents ba8d6d9d b6a9530d
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -48364,6 +48364,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);
@@ -48584,6 +48585,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();
@@ -48727,6 +48729,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);
@@ -48935,6 +48938,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);
@@ -52757,6 +52761,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