diff options
| author | 2021-03-23 23:46:17 +0800 | |
|---|---|---|
| committer | 2021-03-25 22:31:54 -0700 | |
| commit | f51da99ac179cfa35f89c26245440dc2a85f60e3 (patch) | |
| tree | 6afa84d211bfe4a57c23471c74410aafda9b0e1b | |
| parent | ae6711b2a78edb30fdec09a083480df002dde0d1 (diff) | |
Implement dispatchRequestTranslation for getting translation information.
This change doesn't contain the virtual view part and the API, it
will be done in the next change.
Bug: 178046780
Test: manual
Test: atest CtsTranslationTestCases
CTS-Coverage-Bug: 177960696
Change-Id: Idba66a882a90168ecdd93423c0d5d054ab040dad
| -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. * |