diff options
| author | Ahaan Ugale <augale@google.com> | 2021-04-02 18:36:24 -0700 |
|---|---|---|
| committer | Ahaan Ugale <augale@google.com> | 2021-04-02 23:19:55 -0700 |
| commit | d3ef7757378ccf92cee364bc862920af34f7f438 (patch) | |
| tree | 53e8d721ee280435a94cb64b0f17e1323915c2d2 /core/java/android | |
| parent | de94a02e3188ff261ab38fc51b02d1fe03943f5f (diff) | |
Fix UiTranslation for ListView.
There are 2 changes:
* When a view is scrapped, Autofill IDs in its subtree are reset. This
prevents flaky behavior like translating the wrong views when events are
ordered a certain way.
* Views that are being translated are marked as having transient state
since the system needs to attach the response to it later as well as
deliver UI Translation state changes to it.
Bug: 182491706
Test: atest CtsTranslationTestCases
Test: atest CtsContentCaptureServiceTestCases
Test: atest CtsAutoFillServiceTestCases
Test: manual - check in logs that autofill ids aren't reused on
scrolling or new views appearing
Test: manual - translated views stay translated while on the screen
when other views are scrolled off the screen
Test: manual - translated views stay translated when new views appear
Change-Id: I20a52415e3fd191768442d70614d536e11633dfa
Diffstat (limited to 'core/java/android')
| -rw-r--r-- | core/java/android/view/View.java | 43 | ||||
| -rw-r--r-- | core/java/android/view/ViewGroup.java | 11 | ||||
| -rw-r--r-- | core/java/android/view/translation/UiTranslationController.java | 2 | ||||
| -rw-r--r-- | core/java/android/widget/AbsListView.java | 3 |
4 files changed, 56 insertions, 3 deletions
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 1e2b2aedfc08..e7542ee70aef 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -9285,6 +9285,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * parentView.addView(reusableView); * </pre> * + * <p>NOTE: If this view is a descendant of an {@link android.widget.AdapterView}, the system + * may reset its autofill id when this view is recycled. If the autofill ids need to be stable, + * they should be set again in + * {@link android.widget.Adapter#getView(int, android.view.View, android.view.ViewGroup)}. + * * @param id an autofill ID that is unique in the {@link android.app.Activity} hosting the view, * or {@code null} to reset it. Usually it's an id previously allocated to another view (and * obtained through {@link #getAutofillId()}), or a new value obtained through @@ -9321,6 +9326,30 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Forces a reset of the autofill ids of the subtree rooted at this view. Like calling + * {@link #setAutofillId(AutofillId) setAutofillId(null)} for each view, but works even if the + * views are attached to a window. + * + * <p>This is useful if the views are being recycled, since an autofill id should uniquely + * identify a particular piece of content. + * + * @hide + */ + public void resetSubtreeAutofillIds() { + if (mAutofillViewId == NO_ID) { + return; + } + if (Log.isLoggable(CONTENT_CAPTURE_LOG_TAG, Log.VERBOSE)) { + Log.v(CONTENT_CAPTURE_LOG_TAG, "resetAutofillId() for " + mAutofillViewId); + } else if (Log.isLoggable(AUTOFILL_LOG_TAG, Log.VERBOSE)) { + Log.v(AUTOFILL_LOG_TAG, "resetAutofillId() for " + mAutofillViewId); + } + mAutofillId = null; + mAutofillViewId = NO_ID; + mPrivateFlags3 &= ~PFLAG3_AUTOFILLID_EXPLICITLY_SET; + } + + /** * Describes the autofill type of this view, so an * {@link android.service.autofill.AutofillService} can create the proper {@link AutofillValue} * when autofilling the view. @@ -30838,8 +30867,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * {@link android.view.translation.Translator} to translate the requests. All the * {@link ViewTranslationRequest}s must be added when the traversal is done. * - * <p> The default implementation will call {@link View#onCreateTranslationRequest} to build - * {@link ViewTranslationRequest} if the view should be translated. </p> + * <p> The default implementation calls {@link View#onCreateTranslationRequest} to build + * {@link ViewTranslationRequest} if the view should be translated. The view is marked as having + * {@link #setHasTransientState(boolean) transient state} so that recycling of views doesn't + * prevent the system from attaching the response to it.</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 @@ -30860,6 +30891,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, ViewTranslationRequest request = onCreateTranslationRequest(supportedFormats); if (request != null && request.getKeys().size() > 0) { requests.add(request); + if (Log.isLoggable(CONTENT_CAPTURE_LOG_TAG, Log.VERBOSE)) { + Log.v(CONTENT_CAPTURE_LOG_TAG, "Calling setHasTransientState(true) for " + + autofillId); + } + // TODO: Add a default ViewTranslationCallback for View that resets this in + // onClearTranslation(). Also update the javadoc for this method to mention + // that. + setHasTransientState(true); } } else { onCreateTranslationRequests(viewIds.get(autofillId), supportedFormats, request -> { diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 5b695f4c425a..04e2cdee56a0 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -3773,6 +3773,17 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager /** @hide */ @Override + public void resetSubtreeAutofillIds() { + super.resetSubtreeAutofillIds(); + View[] children = mChildren; + final int childCount = mChildrenCount; + for (int i = 0; i < childCount; i++) { + children[i].resetSubtreeAutofillIds(); + } + } + + /** @hide */ + @Override @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfoInternal(info); diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java index 53e354f8200e..f4fdf35fb50f 100644 --- a/core/java/android/view/translation/UiTranslationController.java +++ b/core/java/android/view/translation/UiTranslationController.java @@ -388,7 +388,7 @@ public class UiTranslationController { private void sendTranslationRequest(Translator translator, List<ViewTranslationRequest> requests) { if (requests.size() == 0) { - Log.wtf(TAG, "No ViewTranslationRequest was collected."); + Log.w(TAG, "No ViewTranslationRequest was collected."); return; } final TranslationRequest request = new TranslationRequest.Builder() diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 94a0790c86a2..eb16cef15248 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -6425,6 +6425,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) { views.add(child); child.setAccessibilityDelegate(null); + child.resetSubtreeAutofillIds(); if (listener != null) { // Pretend they went through the scrap heap listener.onMovedToScrapHeap(child); @@ -7365,10 +7366,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te private void clearScrapForRebind(View view) { view.clearAccessibilityFocus(); view.setAccessibilityDelegate(null); + view.resetSubtreeAutofillIds(); } private void removeDetachedView(View child, boolean animate) { child.setAccessibilityDelegate(null); + child.resetSubtreeAutofillIds(); AbsListView.this.removeDetachedView(child, animate); } } |
