diff options
| -rw-r--r-- | core/api/current.txt | 3 | ||||
| -rw-r--r-- | core/java/android/view/View.java | 47 | ||||
| -rw-r--r-- | core/java/android/view/ViewGroup.java | 25 | ||||
| -rw-r--r-- | core/java/android/view/translation/UiTranslationController.java | 134 | ||||
| -rw-r--r-- | core/java/android/widget/TextView.java | 4 | 
5 files changed, 157 insertions, 56 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index d0a2f0bb8fb2..3773d3b09299 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -48449,7 +48449,6 @@ 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); @@ -48475,6 +48474,7 @@ package android.view {      method public boolean dispatchPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);      method public void dispatchProvideAutofillStructure(@NonNull android.view.ViewStructure, int);      method public void dispatchProvideStructure(android.view.ViewStructure); +    method public void dispatchRequestTranslation(@NonNull java.util.Map<android.view.autofill.AutofillId,long[]>, @NonNull int[], @Nullable android.view.translation.TranslationCapability, @NonNull java.util.List<android.view.translation.ViewTranslationRequest>);      method protected void dispatchRestoreInstanceState(android.util.SparseArray<android.os.Parcelable>);      method protected void dispatchSaveInstanceState(android.util.SparseArray<android.os.Parcelable>);      method public void dispatchScrollCaptureSearch(@NonNull android.graphics.Rect, @NonNull android.graphics.Point, @NonNull java.util.function.Consumer<android.view.ScrollCaptureTarget>); @@ -48770,6 +48770,7 @@ package android.view {      method protected void onCreateContextMenu(android.view.ContextMenu);      method protected int[] onCreateDrawableState(int);      method public android.view.inputmethod.InputConnection onCreateInputConnection(android.view.inputmethod.EditorInfo); +    method @Nullable public android.view.translation.ViewTranslationRequest onCreateTranslationRequest(@NonNull int[]);      method @CallSuper protected void onDetachedFromWindow();      method protected void onDisplayHint(int);      method public boolean onDragEvent(android.view.DragEvent); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index a03e9e352c60..fb528995b2f7 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -151,6 +151,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.TranslationCapability;  import android.view.translation.TranslationSpec.DataFormat;  import android.view.translation.ViewTranslationCallback;  import android.view.translation.ViewTranslationRequest; @@ -30722,7 +30723,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,          }      } -    //TODO(b/1960696): update javadoc when dispatchRequestTranslation is ready.      /**       * Returns a {@link ViewTranslationRequest} which represents the content to be translated.       * @@ -30735,7 +30735,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,       * The {@link AutofillId} must be set on the returned value.       */      @Nullable -    public ViewTranslationRequest createTranslationRequest( +    public ViewTranslationRequest onCreateTranslationRequest(              @NonNull @DataFormat int[] supportedFormats) {          return null;      } @@ -30769,8 +30769,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,      }      /** -     * Called when the content from {@link #createTranslationRequest} had been translated by the -     * TranslationService. +     * Called when the content from {@link View#onCreateTranslationRequest} had been translated by +     * the TranslationService.       *       * <p> The default implementation does nothing.</p>       * @@ -30782,6 +30782,45 @@ public class View implements Drawable.Callback, KeyEvent.Callback,      }      /** +     * Dispatch to collect the {@link ViewTranslationRequest}s for translation purpose by traversing +     * the hierarchy when the app requests ui translation. Typically, this method should only be +     * overridden by subclasses that provide a view hierarchy (such as {@link ViewGroup}). Other +     * classes should override {@link View#onCreateTranslationRequest}. When requested to start the +     * ui translation, the system will call this method to traverse the view hierarchy to call +     * {@link View#onCreateTranslationRequest} to build {@link ViewTranslationRequest}s and create a +     * {@link android.view.translation.Translator} to translate the requests. +     * +     * <p> The default implementation will call {@link View#onCreateTranslationRequest} to build +     * {@link ViewTranslationRequest} if the view should be translated. </p> +     * +     * @param viewIds a map for the view's {@link AutofillId} and its virtual child ids or +     * {@code null} if the view doesn't have virtual child that should be translated. The virtual +     * child ids are the same virtual ids provided by ContentCapture. +     * @param supportedFormats the supported translation formats. For now, the only possible value +     * is the {@link android.view.translation.TranslationSpec#DATA_FORMAT_TEXT}. +     * @param capability a {@link TranslationCapability} that holds translation capability. +     * information, e.g. source spec, target spec. +     * @param requests fill in with {@link ViewTranslationRequest}s for translation purpose. +     */ +    public void dispatchRequestTranslation(@NonNull Map<AutofillId, long[]> viewIds, +            @NonNull @DataFormat int[] supportedFormats, +            @Nullable TranslationCapability capability, +            @NonNull List<ViewTranslationRequest> requests) { +        AutofillId autofillId = getAutofillId(); +        if (viewIds.containsKey(autofillId)) { +            ViewTranslationRequest request = null; +            if (viewIds.get(autofillId) == null) { +                request = onCreateTranslationRequest(supportedFormats); +                if (request != null && request.getKeys().size() > 0) { +                    requests.add(request); +                } +            } else { +                // TODO: handle virtual view +            } +        } +    } + +    /**       * Called to generate a {@link DisplayHash} for this view.       *       * @param hashAlgorithm The hash algorithm to use when hashing the display. Must be one of diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 38a59373554c..8198254958f4 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -65,9 +65,13 @@ import android.view.animation.Animation;  import android.view.animation.AnimationUtils;  import android.view.animation.LayoutAnimationController;  import android.view.animation.Transformation; +import android.view.autofill.AutofillId;  import android.view.autofill.Helper;  import android.view.inspector.InspectableProperty;  import android.view.inspector.InspectableProperty.EnumEntry; +import android.view.translation.TranslationCapability; +import android.view.translation.TranslationSpec.DataFormat; +import android.view.translation.ViewTranslationRequest;  import com.android.internal.R; @@ -9264,4 +9268,25 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager              mParent.onDescendantUnbufferedRequested();          }      } + +    /** +     * {@inheritDoc} +     * +     * The implementation calls {@link #dispatchRequestTranslation} for all the child views. +     */ +    @Override +    public void dispatchRequestTranslation(@NonNull Map<AutofillId, long[]> viewIds, +            @NonNull @DataFormat int[] supportedFormats, +            @Nullable TranslationCapability capability, +            @NonNull List<ViewTranslationRequest> requests) { +        super.dispatchRequestTranslation(viewIds, supportedFormats, capability, requests); +        final int childCount = getChildCount(); +        if (childCount == 0) { +            return; +        } +        for (int i = 0; i < childCount; ++i) { +            final View child = getChildAt(i); +            child.dispatchRequestTranslation(viewIds, supportedFormats, capability, requests); +        } +    }  } diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java index 15d01ae6a8fc..cfe892f8c8c9 100644 --- a/core/java/android/view/translation/UiTranslationController.java +++ b/core/java/android/view/translation/UiTranslationController.java @@ -33,6 +33,7 @@ import android.util.ArrayMap;  import android.util.Log;  import android.util.Pair;  import android.util.SparseArray; +import android.util.SparseIntArray;  import android.view.View;  import android.view.ViewGroup;  import android.view.ViewRootImpl; @@ -46,6 +47,7 @@ import java.io.PrintWriter;  import java.lang.ref.WeakReference;  import java.util.ArrayList;  import java.util.List; +import java.util.Map;  import java.util.function.BiConsumer;  /** @@ -223,7 +225,6 @@ public class UiTranslationController {          pw.print(outerPrefix); pw.print("isContainsView: "); pw.println(isContainsView);      } -      /**       * The method is used by {@link Translator}, it will be called when the translation is done. The       * translation result can be get from here. @@ -236,12 +237,30 @@ 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); +        final SparseArray<ViewTranslationResponse> viewsResult = new SparseArray<>(); +        final SparseArray<ViewTranslationResponse> virtualViewsResult = new SparseArray<>(); +        final List<AutofillId> viewIds = new ArrayList<>(); +        for (int i = 0; i < translatedResult.size(); i++) { +            final ViewTranslationResponse result = translatedResult.valueAt(i); +            final AutofillId autofillId = result.getAutofillId(); +            if (autofillId.isNonVirtual()) { +                viewsResult.put(translatedResult.keyAt(i), result); +                viewIds.add(autofillId); +            } else { +                virtualViewsResult.put(translatedResult.keyAt(i), result); +            } +        } +        if (viewsResult.size() > 0) { +            onTranslationCompleted(viewsResult, viewIds); +        } +        //TODO(b/177960696): call virtual views onTranslationCompleted()      } -    private void onTranslationCompleted(SparseArray<ViewTranslationResponse> translatedResult) { +    /** +     * The method is used to handle the translation result for non-vertual views. +     */ +    private void onTranslationCompleted(SparseArray<ViewTranslationResponse> translatedResult, +            List<AutofillId> viewIds) {          if (!mActivity.isResumed()) {              if (DEBUG) {                  Log.v(TAG, "onTranslationCompleted: Activity is not resumed."); @@ -258,8 +277,10 @@ public class UiTranslationController {                          + "Skip to show the translated text.");                  return;              } +            // Traverse tree and get views by the responsed AutofillId +            findViewsTraversalByAutofillIds(viewIds);              for (int i = 0; i < resultCount; i++) { -                final ViewTranslationResponse response = translatedResult.get(i); +                final ViewTranslationResponse response = translatedResult.valueAt(i);                  final AutofillId autofillId = response.getAutofillId();                  if (autofillId == null) {                      continue; @@ -319,44 +340,62 @@ 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); +            // Filter the request views's AutofillId +            SparseIntArray virtualViewChildCount = getRequestVirtualViewChildCount(views); +            Map<AutofillId, long[]> viewIds = new ArrayMap<>(); +            for (int i = 0; i < views.size(); i++) { +                AutofillId autofillId = views.get(i); +                if (autofillId.isNonVirtual()) { +                    viewIds.put(autofillId, null); +                } else { +                    // The virtual id get from content capture is long, see getVirtualChildLongId() +                    // e.g. 1001, 1001:2, 1002:1 -> 1001, <1,2>; 1002, <1> +                    AutofillId virtualViewAutofillId = new AutofillId(autofillId.getViewId()); +                    long[] childs; +                    if (viewIds.containsKey(virtualViewAutofillId)) { +                        childs = viewIds.get(virtualViewAutofillId); +                    } else { +                        int childCount = virtualViewChildCount.get(autofillId.getViewId()); +                        childs = new long[childCount]; +                        viewIds.put(virtualViewAutofillId, childs); +                    } +                    int end = childs.length; +                    childs[end] = autofillId.getVirtualChildLongId(); +                } +            } +            ArrayList<ViewTranslationRequest> requests = new ArrayList<>(); +            int[] supportedFormats = getSupportedFormatsLocked(); +            ArrayList<ViewRootImpl> roots = +                    WindowManagerGlobal.getInstance().getRootViews(mActivity.getActivityToken()); +            mActivity.runOnUiThread(() -> { +                // traverse the hierarchy to collect ViewTranslationRequests +                for (int rootNum = 0; rootNum < roots.size(); rootNum++) { +                    View rootView = roots.get(rootNum).getView(); +                    // TODO(b/183589662): call getTranslationCapabilities() for capability +                    rootView.dispatchRequestTranslation(viewIds, supportedFormats, /* capability */ +                            null, requests); +                } +                mWorkerHandler.sendMessage(PooledLambda.obtainMessage( +                        UiTranslationController::sendTranslationRequest, +                        UiTranslationController.this, translator, requests)); +            });          }      } -    /** -     * 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.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); -                } -                if (currentCount == (foundViews.size() - 1)) { -                    Log.v(TAG, "onUiTranslationStarted: collect " + requests.size() -                            + " requests."); -                    mWorkerHandler.sendMessage(PooledLambda.obtainMessage( -                            UiTranslationController::sendTranslationRequest, -                            UiTranslationController.this, translator, requests)); +    private SparseIntArray getRequestVirtualViewChildCount(List<AutofillId> views) { +        SparseIntArray virtualViewCount = new SparseIntArray(); +        for (int i = 0; i < views.size(); i++) { +            AutofillId autofillId = views.get(i); +            if (!autofillId.isNonVirtual()) { +                int virtualViewId = autofillId.getViewId(); +                if (virtualViewCount.indexOfKey(virtualViewId) < 0) { +                    virtualViewCount.put(virtualViewId, 1); +                } else { +                    virtualViewCount.put(virtualViewId, (virtualViewCount.get(virtualViewId) + 1));                  } -            }); +            }          } +        return virtualViewCount;      }      private int[] getSupportedFormatsLocked() { @@ -364,39 +403,36 @@ public class UiTranslationController {          return new int[] {TranslationSpec.DATA_FORMAT_TEXT};      } -    private void findViewsTraversalByAutofillIds(List<AutofillId> sourceViewIds, -            ArrayList<View> foundViews) { +    private void findViewsTraversalByAutofillIds(List<AutofillId> sourceViewIds) {          final ArrayList<ViewRootImpl> roots =                  WindowManagerGlobal.getInstance().getRootViews(mActivity.getActivityToken());          for (int rootNum = 0; rootNum < roots.size(); rootNum++) {              final View rootView = roots.get(rootNum).getView();              if (rootView instanceof ViewGroup) { -                findViewsTraversalByAutofillIds((ViewGroup) rootView, sourceViewIds, foundViews); +                findViewsTraversalByAutofillIds((ViewGroup) rootView, sourceViewIds);              } else { -                addViewIfNeeded(sourceViewIds, rootView, foundViews); +                addViewIfNeeded(sourceViewIds, rootView);              }          }      }      private void findViewsTraversalByAutofillIds(ViewGroup viewGroup, -            List<AutofillId> sourceViewIds, ArrayList<View> foundViews) { +            List<AutofillId> sourceViewIds) {          final int childCount = viewGroup.getChildCount();          for (int i = 0; i < childCount; ++i) {              final View child = viewGroup.getChildAt(i);              if (child instanceof ViewGroup) { -                findViewsTraversalByAutofillIds((ViewGroup) child, sourceViewIds, foundViews); +                findViewsTraversalByAutofillIds((ViewGroup) child, sourceViewIds);              } else { -                addViewIfNeeded(sourceViewIds, child, foundViews); +                addViewIfNeeded(sourceViewIds, child);              }          }      } -    private void addViewIfNeeded(List<AutofillId> sourceViewIds, View view, -            ArrayList<View> foundViews) { +    private void addViewIfNeeded(List<AutofillId> sourceViewIds, View view) {          final AutofillId autofillId = view.getAutofillId();          if (sourceViewIds.contains(autofillId)) {              mViews.put(autofillId, new WeakReference<>(view)); -            foundViews.add(view);          }      } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 6733c0d3a8e1..ffaa31552a6a 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -13872,7 +13872,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener       */      @Nullable      @Override -    public ViewTranslationRequest createTranslationRequest(@NonNull int[] supportedFormats) { +    public ViewTranslationRequest onCreateTranslationRequest(@NonNull int[] supportedFormats) {          if (supportedFormats == null || supportedFormats.length == 0) {              // TODO(b/182433547): remove before S release              if (UiTranslationController.DEBUG) { @@ -13938,7 +13938,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener      /**       * -     * Called when the content from {@link #createTranslationRequest} had been translated by the +     * Called when the content from {@link #onCreateTranslationRequest} had been translated by the       * TranslationService. The default implementation will replace the current       * {@link TransformationMethod} to transform the original text to the translated text display.       *  |