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

Commit 3ac64637 authored by Evan Rosky's avatar Evan Rosky
Browse files

Fixed default-cluster and empty-cluster navigation behavior

Cluster navigation would get stuck if:
 - there was an empty cluster (nothing to focus)
 - there were views NOT in a cluster and the global
   default-focused view was in a cluster.

This will skip over empty clusters during cluster navigation
and when the default "cluster" is active, it will try to
focus a non-clustered focusable instead of the global default
(and if there aren't any non-clustered focusables, it will
skip over the default "cluster" as it now does with actual
clusters.

Bug: 35273052
Bug: 34361282
Test: Added/Updated CTS tests and checked behavior in a test app.
Change-Id: I0b4d55707203f6a0453f25313f940927d59a9b31
parent 79b9853f
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -45122,6 +45122,8 @@ package android.view {
    method public static int resolveSize(int, int);
    method public static int resolveSizeAndState(int, int, int);
    method public boolean restoreDefaultFocus();
    method public boolean restoreFocusInCluster(int);
    method public boolean restoreFocusNotInCluster();
    method public void restoreHierarchyState(android.util.SparseArray<android.os.Parcelable>);
    method public void saveHierarchyState(android.util.SparseArray<android.os.Parcelable>);
    method public void scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable, long);
+18 −2
Original line number Diff line number Diff line
@@ -9491,7 +9491,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
    public void addKeyboardNavigationClusters(
            @NonNull Collection<View> views,
            int direction) {
        if (!(isKeyboardNavigationCluster())) {
        if (!isKeyboardNavigationCluster()) {
            return;
        }
        if (!hasFocusable()) {
            return;
        }
        views.add(this);
@@ -9698,11 +9701,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
    }
    /**
     * Public for testing. This will request focus for whichever View was last focused within this
     * This will request focus for whichever View was last focused within this
     * cluster before a focus-jump out of it.
     *
     * @hide
     */
    @TestApi
    public boolean restoreFocusInCluster(@FocusRealDirection int direction) {
        // Prioritize focusableByDefault over algorithmic focus selection.
        if (restoreDefaultFocus()) {
@@ -9711,6 +9715,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        return requestFocus(direction);
    }
    /**
     * This will request focus for whichever View not in a cluster was last focused before a
     * focus-jump to a cluster. If no non-cluster View has previously had focus, this will focus
     * the "first" focusable view it finds.
     *
     * @hide
     */
    @TestApi
    public boolean restoreFocusNotInCluster() {
        return requestFocus(View.FOCUS_DOWN);
    }
    /**
     * Gives focus to the default-focus view in the view hierarchy that has this view as a root.
     * If the default-focus view cannot be found, falls back to calling {@link #requestFocus(int)}.
+36 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.animation.LayoutTransition;
import android.annotation.CallSuper;
import android.annotation.IdRes;
import android.annotation.NonNull;
import android.annotation.TestApi;
import android.annotation.UiThread;
import android.content.ClipData;
import android.content.Context;
@@ -3166,6 +3167,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
    /**
     * @hide
     */
    @TestApi
    @Override
    public boolean restoreFocusInCluster(@FocusRealDirection int direction) {
        if (mFocusedInCluster != null && !mFocusedInCluster.isKeyboardNavigationCluster()
@@ -3177,6 +3179,40 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
        return super.restoreFocusInCluster(direction);
    }

    /**
     * @hide
     */
    @Override
    public boolean restoreFocusNotInCluster() {
        if (mFocusedInCluster != null) {
            // since clusters don't nest; we can assume that a non-null mFocusedInCluster
            // will refer to a view not-in a cluster.
            return restoreFocusInCluster(View.FOCUS_DOWN);
        }
        if (isKeyboardNavigationCluster()) {
            return false;
        }
        int descendentFocusability = getDescendantFocusability();
        if (descendentFocusability == FOCUS_BLOCK_DESCENDANTS) {
            return super.requestFocus(FOCUS_DOWN, null);
        }
        if (descendentFocusability == FOCUS_BEFORE_DESCENDANTS
                && super.requestFocus(FOCUS_DOWN, null)) {
            return true;
        }
        for (int i = 0; i < mChildrenCount; ++i) {
            View child = mChildren[i];
            if (!child.isKeyboardNavigationCluster()
                    && child.restoreFocusNotInCluster()) {
                return true;
            }
        }
        if (descendentFocusability == FOCUS_AFTER_DESCENDANTS) {
            return super.requestFocus(FOCUS_DOWN, null);
        }
        return false;
    }

    /**
     * {@inheritDoc}
     *
+10 −1
Original line number Diff line number Diff line
@@ -4440,7 +4440,7 @@ public final class ViewRootImpl implements ViewParent,

        private boolean performKeyboardGroupNavigation(int direction) {
            final View focused = mView.findFocus();
            final View cluster = focused != null
            View cluster = focused != null
                    ? focused.keyboardNavigationClusterSearch(null, direction)
                    : keyboardNavigationClusterSearch(null, direction);

@@ -4451,6 +4451,15 @@ public final class ViewRootImpl implements ViewParent,
                realDirection = View.FOCUS_DOWN;
            }

            if (cluster != null && cluster.isRootNamespace()) {
                // the default cluster. Try to find a non-clustered view to focus.
                if (cluster.restoreFocusNotInCluster()) {
                    return true;
                }
                // otherwise skip to next actual cluster
                cluster = keyboardNavigationClusterSearch(null, direction);
            }

            if (cluster != null && cluster.restoreFocusInCluster(realDirection)) {
                return true;
            }