Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0363341f authored by Yigit Boyar's avatar Yigit Boyar
Browse files

Recover focus on size change in ListView

This CL fixes a bug in ListView where if items are not selectable,
not focusable(in terms of ListView flags) but get focus (because
they can), ListView would not recover the focus properly although
it would make the child visible.

The major issue seems like AbsListView marks data set changed when
the list resizes. It seems like a workaround for some other issue
and the code is from 2009 so instead of changing it, I've decided
to implement a workaround to minimize the potential of breaking
something else.

Bug: 27488391
Change-Id: I5babd00e97bba7cb8cc9cfd0697ef79dfae12b97
parent fb811aaf
Loading
Loading
Loading
Loading
+72 −11
Original line number Diff line number Diff line
@@ -16,15 +16,14 @@

package android.widget;

import android.annotation.Nullable;
import android.os.Bundle;
import android.os.Trace;
import com.google.android.collect.Lists;

import com.android.internal.R;
import com.android.internal.util.Predicate;
import com.google.android.collect.Lists;

import android.annotation.IdRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
@@ -33,6 +32,8 @@ import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Trace;
import android.util.AttributeSet;
import android.util.MathUtils;
import android.util.SparseBooleanArray;
@@ -1106,20 +1107,63 @@ public class ListView extends AbsListView {
    }

    private class FocusSelector implements Runnable {
        // the selector is waiting to set selection on the list view
        private static final int STATE_SET_SELECTION = 1;
        // the selector set the selection on the list view, waiting for a layoutChildren pass
        private static final int STATE_WAIT_FOR_LAYOUT = 2;
        // the selector's selection has been honored and it is waiting to request focus on the
        // target child.
        private static final int STATE_REQUEST_FOCUS = 3;

        private int mAction;
        private int mPosition;
        private int mPositionTop;

        public FocusSelector setup(int position, int top) {
        FocusSelector setupForSetSelection(int position, int top) {
            mPosition = position;
            mPositionTop = top;
            mAction = STATE_SET_SELECTION;
            return this;
        }

        public void run() {
            if (mAction == STATE_SET_SELECTION) {
                setSelectionFromTop(mPosition, mPositionTop);
                mAction = STATE_WAIT_FOR_LAYOUT;
            } else if (mAction == STATE_REQUEST_FOCUS) {
                final int childIndex = mPosition - mFirstPosition;
                final View child = getChildAt(childIndex);
                if (child != null) {
                    child.requestFocus();
                }
                mAction = -1;
            }
        }

        @Nullable Runnable setupFocusIfValid(int position) {
            if (mAction != STATE_WAIT_FOR_LAYOUT || position != mPosition) {
                return null;
            }
            mAction = STATE_REQUEST_FOCUS;
            return this;
        }

        void onLayoutComplete() {
            if (mAction == STATE_WAIT_FOR_LAYOUT) {
                mAction = -1;
            }
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        if (mFocusSelector != null) {
            removeCallbacks(mFocusSelector);
            mFocusSelector = null;
        }
        super.onDetachedFromWindow();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        if (getChildCount() > 0) {
@@ -1132,7 +1176,7 @@ public class ListView extends AbsListView {
                if (mFocusSelector == null) {
                    mFocusSelector = new FocusSelector();
                }
                post(mFocusSelector.setup(childPosition, top));
                post(mFocusSelector.setupForSetSelection(childPosition, top));
            }
        }
        super.onSizeChanged(w, h, oldw, oldh);
@@ -1672,7 +1716,21 @@ public class ListView extends AbsListView {
                adjustViewsUpOrDown();
                break;
            case LAYOUT_SPECIFIC:
                sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);
                final int selectedPosition = reconcileSelectedPosition();
                sel = fillSpecific(selectedPosition, mSpecificTop);
                /**
                 * When ListView is resized, FocusSelector requests an async selection for the
                 * previously focused item to make sure it is still visible. If the item is not
                 * selectable, it won't regain focus so instead we call FocusSelector
                 * to directly request focus on the view after it is visible.
                 */
                if (sel == null && mFocusSelector != null) {
                    final Runnable focusRunnable = mFocusSelector
                            .setupFocusIfValid(selectedPosition);
                    if (focusRunnable != null) {
                        post(focusRunnable);
                    }
                }
                break;
            case LAYOUT_MOVE_SELECTION:
                sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
@@ -1812,6 +1870,9 @@ public class ListView extends AbsListView {

            invokeOnItemScrollListener();
        } finally {
            if (mFocusSelector != null) {
                mFocusSelector.onLayoutComplete();
            }
            if (!blockLayoutRequests) {
                mBlockLayoutRequests = false;
            }