Restore accessibility focus after ListView layout.
Bug: 6439454
Change-Id: Ia61f5153b32c6ce5d18301f74e7e79c86349b987
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index e011c13..938979a 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -29,6 +29,7 @@
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
+import android.util.MathUtils;
import android.util.SparseBooleanArray;
import android.view.FocusFinder;
import android.view.KeyEvent;
@@ -1490,6 +1491,10 @@
View focusLayoutRestoreView = null;
+ AccessibilityNodeInfo accessibilityFocusLayoutRestoreNode = null;
+ View accessibilityFocusLayoutRestoreView = null;
+ int accessibilityFocusPosition = INVALID_POSITION;
+
// Remember stuff we will need down below
switch (mLayoutMode) {
case LAYOUT_SET_SELECTION:
@@ -1584,6 +1589,25 @@
requestFocus();
}
+ // Remember which child, if any, had accessibility focus.
+ final View accessFocusedView = getViewRootImpl().getAccessibilityFocusedHost();
+ if (accessFocusedView != null) {
+ final View accessFocusedChild = findAccessibilityFocusedChild(accessFocusedView);
+ if (accessFocusedChild != null) {
+ if (!dataChanged || isDirectChildHeaderOrFooter(accessFocusedChild)) {
+ // If the views won't be changing, try to maintain focus
+ // on the current view host and (if applicable) its
+ // virtual view.
+ accessibilityFocusLayoutRestoreView = accessFocusedView;
+ accessibilityFocusLayoutRestoreNode = getViewRootImpl()
+ .getAccessibilityFocusedVirtualView();
+ } else {
+ // Otherwise, try to maintain focus at the same position.
+ accessibilityFocusPosition = getPositionForView(accessFocusedChild);
+ }
+ }
+ }
+
// Clear out old views
detachAllViewsFromParent();
recycleBin.removeSkippedScrap();
@@ -1682,6 +1706,22 @@
}
}
+ // Attempt to restore accessibility focus.
+ if (accessibilityFocusLayoutRestoreNode != null) {
+ accessibilityFocusLayoutRestoreNode.performAction(
+ AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+ } else if (accessibilityFocusLayoutRestoreView != null) {
+ accessibilityFocusLayoutRestoreView.requestAccessibilityFocus();
+ } else if (accessibilityFocusPosition != INVALID_POSITION) {
+ // Bound the position within the visible children.
+ final int position = MathUtils.constrain(
+ (accessibilityFocusPosition - mFirstPosition), 0, (getChildCount() - 1));
+ final View restoreView = getChildAt(position);
+ if (restoreView != null) {
+ restoreView.requestAccessibilityFocus();
+ }
+ }
+
// tell focus view we are done mucking with it, if it is still in
// our view hierarchy.
if (focusLayoutRestoreView != null
@@ -1713,6 +1753,22 @@
}
/**
+ * @param focusedView the view that has accessibility focus.
+ * @return the direct child that contains accessibility focus.
+ */
+ private View findAccessibilityFocusedChild(View focusedView) {
+ ViewParent viewParent = focusedView.getParent();
+ while ((viewParent instanceof View) && (viewParent != this)) {
+ focusedView = (View) viewParent;
+ viewParent = viewParent.getParent();
+ }
+ if (!(viewParent instanceof View)) {
+ return null;
+ }
+ return focusedView;
+ }
+
+ /**
* @param child a direct child of this list.
* @return Whether child is a header or footer view.
*/