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

Commit aee802f3 authored by Evan Rosky's avatar Evan Rosky
Browse files

Allow cluster navigation to jump into touchscreenBlocksFocus

Adds an exception to touchscreenBlocksFocus ViewGroups which
are also keyboardNavigationClusters. The behavior we want is
that cluster navigation can jump into touchscreenBlocksFocus
clusters but normal keyboard navigation can't. Once focus is
in a touchscreenBlocksFocus cluster; however, we allow focus
navigation to move freely within that cluster. It remains in
that cluster until a subsequent cluster navigation brings it
back out.

Adds back the touchscreenBlocksFocus attributes to Toolbar
and actionbar so that they behave like they did before.

Bug: 34363323
Test: Added CTS test. Verified desired behavior in a test app

Change-Id: I555bf5570b16a57f0d4c8a020ae509a1e1b33910
parent dd08db08
Loading
Loading
Loading
Loading
+34 −3
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.view;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.PackageManager;
import android.graphics.Rect;
import android.util.ArrayMap;
import android.util.SparseArray;
@@ -88,8 +89,9 @@ public class FocusFinder {

    private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
        View next = null;
        ViewGroup effectiveRoot = getEffectiveRoot(root, focused);
        if (focused != null) {
            next = findNextUserSpecifiedFocus(root, focused, direction);
            next = findNextUserSpecifiedFocus(effectiveRoot, focused, direction);
        }
        if (next != null) {
            return next;
@@ -97,9 +99,9 @@ public class FocusFinder {
        ArrayList<View> focusables = mTempList;
        try {
            focusables.clear();
            root.addFocusables(focusables, direction);
            effectiveRoot.addFocusables(focusables, direction);
            if (!focusables.isEmpty()) {
                next = findNextFocus(root, focused, focusedRect, direction, focusables);
                next = findNextFocus(effectiveRoot, focused, focusedRect, direction, focusables);
            }
        } finally {
            focusables.clear();
@@ -107,6 +109,35 @@ public class FocusFinder {
        return next;
    }

    /**
     * Returns the "effective" root of a view. The "effective" root is the closest ancestor
     * within-which focus should cycle.
     * <p>
     * For example: normal focus navigation would stay within a ViewGroup marked as
     * touchscreenBlocksFocus and keyboardNavigationCluster until a cluster-jump out.
     * @return the "effective" root of {@param focused}
     */
    private ViewGroup getEffectiveRoot(ViewGroup root, View focused) {
        if (focused == null) {
            return root;
        }
        ViewParent effective = focused.getParent();
        do {
            if (effective == root) {
                return root;
            }
            ViewGroup vg = (ViewGroup) effective;
            if (vg.getTouchscreenBlocksFocus()
                    && focused.getContext().getPackageManager().hasSystemFeature(
                            PackageManager.FEATURE_TOUCHSCREEN)
                    && vg.isKeyboardNavigationCluster()) {
                return vg;
            }
            effective = effective.getParent();
        } while (effective != null);
        return root;
    }

    /**
     * Find the root of the next keyboard navigation cluster after the current one.
     * @param root The view tree to look inside. Cannot be null
+44 −4
Original line number Diff line number Diff line
@@ -1190,7 +1190,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
        final int focusableCount = views.size();

        final int descendantFocusability = getDescendantFocusability();
        final boolean focusSelf = (isFocusableInTouchMode() || !shouldBlockFocusForTouchscreen());
        final boolean blockFocusForTouchscreen = shouldBlockFocusForTouchscreen();
        final boolean focusSelf = (isFocusableInTouchMode() || !blockFocusForTouchscreen);

        if (descendantFocusability == FOCUS_BLOCK_DESCENDANTS) {
            if (focusSelf) {
@@ -1199,7 +1200,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
            return;
        }

        if (shouldBlockFocusForTouchscreen()) {
        if (blockFocusForTouchscreen) {
            focusableMode |= FOCUSABLES_TOUCH_MODE;
        }

@@ -1234,7 +1235,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
    public void addKeyboardNavigationClusters(Collection<View> views, int direction) {
        final int focusableCount = views.size();

        if (isKeyboardNavigationCluster()) {
            // Cluster-navigation can enter a touchscreenBlocksFocus cluster, so temporarily
            // disable touchscreenBlocksFocus to evaluate whether it contains focusables.
            final boolean blockedFocus = getTouchscreenBlocksFocus();
            try {
                setTouchscreenBlocksFocusNoRefocus(false);
                super.addKeyboardNavigationClusters(views, direction);
            } finally {
                setTouchscreenBlocksFocusNoRefocus(blockedFocus);
            }
        } else {
            super.addKeyboardNavigationClusters(views, direction);
        }

        if (focusableCount != views.size()) {
            // No need to look for groups inside a group.
@@ -1280,6 +1293,14 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
        }
    }

    private void setTouchscreenBlocksFocusNoRefocus(boolean touchscreenBlocksFocus) {
        if (touchscreenBlocksFocus) {
            mGroupFlags |= FLAG_TOUCHSCREEN_BLOCKS_FOCUS;
        } else {
            mGroupFlags &= ~FLAG_TOUCHSCREEN_BLOCKS_FOCUS;
        }
    }

    /**
     * Check whether this ViewGroup should ignore focus requests for itself and its children.
     */
@@ -1288,8 +1309,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
    }

    boolean shouldBlockFocusForTouchscreen() {
        // There is a special case for keyboard-navigation clusters. We allow cluster navigation
        // to jump into blockFocusForTouchscreen ViewGroups which are clusters. Once in the
        // cluster, focus is free to move around within it.
        return getTouchscreenBlocksFocus() &&
                mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN);
                mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
                && (!hasFocus() || !isKeyboardNavigationCluster());
    }

    @Override
@@ -3175,6 +3200,21 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
    @TestApi
    @Override
    public boolean restoreFocusInCluster(@FocusRealDirection int direction) {
        // Allow cluster-navigation to enter touchscreenBlocksFocus ViewGroups.
        if (isKeyboardNavigationCluster()) {
            final boolean blockedFocus = getTouchscreenBlocksFocus();
            try {
                setTouchscreenBlocksFocusNoRefocus(false);
                return restoreFocusInClusterInternal(direction);
            } finally {
                setTouchscreenBlocksFocusNoRefocus(blockedFocus);
            }
        } else {
            return restoreFocusInClusterInternal(direction);
        }
    }

    private boolean restoreFocusInClusterInternal(@FocusRealDirection int direction) {
        if (mFocusedInCluster != null && !mFocusedInCluster.isKeyboardNavigationCluster()
                && getDescendantFocusability() != FOCUS_BLOCK_DESCENDANTS
                && (mFocusedInCluster.mViewFlags & VISIBILITY_MASK) == VISIBLE
+3 −0
Original line number Diff line number Diff line
@@ -343,6 +343,9 @@ public class Toolbar extends ViewGroup {
            final ViewGroup vgParent = (ViewGroup) parent;
            if (vgParent.isKeyboardNavigationCluster()) {
                setKeyboardNavigationCluster(false);
                if (vgParent.getTouchscreenBlocksFocus()) {
                    setTouchscreenBlocksFocus(false);
                }
                break;
            }
            parent = vgParent.getParent();
+2 −0
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ This is an optimized layout for a screen with the Action Bar enabled.
        android:layout_alignParentTop="true"
        style="?attr/actionBarStyle"
        android:transitionName="android:action_bar"
        android:touchscreenBlocksFocus="true"
        android:keyboardNavigationCluster="true"
        android:gravity="top">
        <com.android.internal.widget.ActionBarView
@@ -54,6 +55,7 @@ This is an optimized layout for a screen with the Action Bar enabled.
                  android:layout_height="wrap_content"
                  style="?attr/actionBarSplitStyle"
                  android:visibility="gone"
                  android:touchscreenBlocksFocus="true"
                  android:keyboardNavigationCluster="true"
                  android:gravity="center"/>
</com.android.internal.widget.ActionBarOverlayLayout>
+1 −0
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ This is an optimized layout for a screen with a toolbar enabled.
        android:layout_alignParentTop="true"
        style="?attr/actionBarStyle"
        android:transitionName="android:action_bar"
        android:touchscreenBlocksFocus="true"
        android:keyboardNavigationCluster="true"
        android:gravity="top">
        <Toolbar
Loading