summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Joanne Chung <joannechung@google.com> 2021-03-23 23:46:17 +0800
committer Ahaan Ugale <augale@google.com> 2021-03-25 22:31:54 -0700
commitf51da99ac179cfa35f89c26245440dc2a85f60e3 (patch)
tree6afa84d211bfe4a57c23471c74410aafda9b0e1b
parentae6711b2a78edb30fdec09a083480df002dde0d1 (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.txt3
-rw-r--r--core/java/android/view/View.java47
-rw-r--r--core/java/android/view/ViewGroup.java25
-rw-r--r--core/java/android/view/translation/UiTranslationController.java134
-rw-r--r--core/java/android/widget/TextView.java4
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.
*