summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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.
*