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

Commit 0e8a6832 authored by Evan Rosky's avatar Evan Rosky
Browse files

Ignore nested keyboard navigation clusters

Keyboard navigation clusters should not be nested; however,
if they are, the behavior is to pretend like they aren't
clusters. This includes ignoring the keyboardNavigationCluster
+ touchscreenBlocksFocus combo altogether.

Bug: 32804858
Test: Added some nested cluster scenarios to ViewGroupTest CTS
Change-Id: I776942842b8c1147200fc39f7eacca493e37c20c
parent 055b51af
Loading
Loading
Loading
Loading
+10 −7
Original line number Diff line number Diff line
@@ -124,20 +124,23 @@ public class FocusFinder {
        if (focused == null || focused == root) {
            return root;
        }
        ViewParent effective = focused.getParent();
        ViewGroup effective = null;
        ViewParent nextParent = focused.getParent();
        do {
            if (effective == root) {
                return root;
            if (nextParent == root) {
                return effective != null ? effective : root;
            }
            ViewGroup vg = (ViewGroup) effective;
            ViewGroup vg = (ViewGroup) nextParent;
            if (vg.getTouchscreenBlocksFocus()
                    && focused.getContext().getPackageManager().hasSystemFeature(
                            PackageManager.FEATURE_TOUCHSCREEN)
                    && vg.isKeyboardNavigationCluster()) {
                return vg;
                // Don't stop and return here because the cluster could be nested and we only
                // care about the top-most one.
                effective = vg;
            }
            effective = effective.getParent();
        } while (effective != null);
            nextParent = nextParent.getParent();
        } while (nextParent instanceof ViewGroup);
        return root;
    }

+33 −3
Original line number Diff line number Diff line
@@ -9876,6 +9876,25 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        return (mPrivateFlags3 & PFLAG3_CLUSTER) != 0;
    }
    /**
     * Searches up the view hierarchy to find the top-most cluster. All deeper/nested clusters
     * will be ignored.
     *
     * @return the keyboard navigation cluster that this view is in (can be this view)
     *         or {@code null} if not in one
     */
    View findKeyboardNavigationCluster() {
        if (mParent instanceof View) {
            View cluster = ((View) mParent).findKeyboardNavigationCluster();
            if (cluster != null) {
                return cluster;
            } else if (isKeyboardNavigationCluster()) {
                return this;
            }
        }
        return null;
    }
    /**
     * Set whether this view is a root of a keyboard navigation cluster.
     *
@@ -9898,9 +9917,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     *
     * @hide
     */
    public void setFocusedInCluster() {
        if (mParent instanceof ViewGroup) {
            ((ViewGroup) mParent).setFocusInCluster(this);
    public final void setFocusedInCluster() {
        View top = findKeyboardNavigationCluster();
        if (top == this) {
            return;
        }
        ViewParent parent = mParent;
        View child = this;
        while (parent instanceof ViewGroup) {
            ((ViewGroup) parent).setFocusedInCluster(child);
            if (parent == top) {
                return;
            }
            child = (View) parent;
            parent = parent.getParent();
        }
    }
+22 −28
Original line number Diff line number Diff line
@@ -808,33 +808,27 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
        return mDefaultFocus != null || super.hasDefaultFocus();
    }

    void setFocusInCluster(View child) {
        // Stop at the root of the cluster
        if (child.isKeyboardNavigationCluster()) {
            return;
        }

    void setFocusedInCluster(View child) {
        mFocusedInCluster = child;

        if (mParent instanceof ViewGroup) {
            ((ViewGroup) mParent).setFocusInCluster(this);
        }
    }

    void clearFocusInCluster(View child) {
    /**
     * Removes {@code child} (and associated focusedInCluster chain) from the cluster containing
     * it.
     * <br>
     * This is intended to be run on {@code child}'s immediate parent. This is necessary because
     * the chain is sometimes cleared after {@code child} has been detached.
     */
    void clearFocusedInCluster(View child) {
        if (mFocusedInCluster != child) {
            return;
        }

        if (child.isKeyboardNavigationCluster()) {
            return;
        }

        mFocusedInCluster = null;

        if (mParent instanceof ViewGroup) {
            ((ViewGroup) mParent).clearFocusInCluster(this);
        }
        View top = findKeyboardNavigationCluster();
        ViewParent parent = this;
        do {
            ((ViewGroup) parent).mFocusedInCluster = null;
            parent = parent.getParent();
        } while (parent != top && parent instanceof ViewGroup);
    }

    @Override
@@ -1282,7 +1276,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
    public void setTouchscreenBlocksFocus(boolean touchscreenBlocksFocus) {
        if (touchscreenBlocksFocus) {
            mGroupFlags |= FLAG_TOUCHSCREEN_BLOCKS_FOCUS;
            if (hasFocus()) {
            if (hasFocus() && !isKeyboardNavigationCluster()) {
                final View focusedChild = getDeepestFocusedChild();
                if (!focusedChild.isFocusableInTouchMode()) {
                    final View newFocus = focusSearch(FOCUS_FORWARD);
@@ -1317,7 +1311,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
        // cluster, focus is free to move around within it.
        return getTouchscreenBlocksFocus() &&
                mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
                && (!hasFocus() || !isKeyboardNavigationCluster());
                && !(isKeyboardNavigationCluster()
                        && (hasFocus() || (findKeyboardNavigationCluster() != this)));
    }

    @Override
@@ -3218,8 +3213,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
    }

    private boolean restoreFocusInClusterInternal(@FocusRealDirection int direction) {
        if (mFocusedInCluster != null && !mFocusedInCluster.isKeyboardNavigationCluster()
                && getDescendantFocusability() != FOCUS_BLOCK_DESCENDANTS
        if (mFocusedInCluster != null && getDescendantFocusability() != FOCUS_BLOCK_DESCENDANTS
                && (mFocusedInCluster.mViewFlags & VISIBILITY_MASK) == VISIBLE
                && mFocusedInCluster.restoreFocusInCluster(direction)) {
            return true;
@@ -5183,7 +5177,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
            clearChildFocus = true;
        }
        if (view == mFocusedInCluster) {
            clearFocusInCluster(view);
            clearFocusedInCluster(view);
        }

        view.clearAccessibilityFocus();
@@ -5303,7 +5297,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
                clearDefaultFocus = view;
            }
            if (view == mFocusedInCluster) {
                clearFocusInCluster(view);
                clearFocusedInCluster(view);
            }

            view.clearAccessibilityFocus();
@@ -5459,7 +5453,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
            clearDefaultFocus(child);
        }
        if (child == mFocusedInCluster) {
            clearFocusInCluster(child);
            clearFocusedInCluster(child);
        }

        child.clearAccessibilityFocus();