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

Commit 37df2dba authored by Evan Rosky's avatar Evan Rosky
Browse files

Changing initial and default focus behavior

This changes initial focus behavior such that by default,
nothing is focused; and, if nothing is focused, the first focus
navigation will send focus to the default focus rather than
whatever happens to be in the upper-left. This also slightly
tweaks the behavior of the <requestFocus /> tag to make it
easier to use.

This addresses a common problem where developers create dummy
focusable views or make viewgroups focusable(InTouchmode) to
prevent other views from gaining focus on activity start.

In order to have something focused at activity start, developers
now must explicitly provide a <requestFocus /> tag. However,
this tag now requests "default" focus so that, when used in
tandem with the focusedByDefault attribute(s), it can be placed
at the root of the hierarchy to mimic the initial focus behavior
before this CL.

This will only take effect when targetApi >= 26.

Bug: 34520588
Bug: 33016720
Test: Added/Updated CTS tests. Also built some test-apps to verify.

Change-Id: I3e7fb7289f6dd53023ec24087f84c41526eaab2e
parent fbad632f
Loading
Loading
Loading
Loading
+7 −12
Original line number Diff line number Diff line
@@ -833,6 +833,7 @@ public abstract class LayoutInflater {

        final int depth = parser.getDepth();
        int type;
        boolean pendingRequestFocus = false;

        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
@@ -844,7 +845,8 @@ public abstract class LayoutInflater {
            final String name = parser.getName();

            if (TAG_REQUEST_FOCUS.equals(name)) {
                parseRequestFocus(parser, parent);
                pendingRequestFocus = true;
                consumeChildElements(parser);
            } else if (TAG_TAG.equals(name)) {
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {
@@ -863,22 +865,15 @@ public abstract class LayoutInflater {
            }
        }

        if (pendingRequestFocus) {
            parent.restoreDefaultFocus();
        }

        if (finishInflate) {
            parent.onFinishInflate();
        }
    }

    /**
     * Parses a <code>&lt;request-focus&gt;</code> element and requests focus on
     * the containing View.
     */
    private void parseRequestFocus(XmlPullParser parser, View view)
            throws XmlPullParserException, IOException {
        view.requestFocus();

        consumeChildElements(parser);
    }

    /**
     * Parses a <code>&lt;tag&gt;</code> element and sets a keyed tag on the
     * containing View.
+27 −8
Original line number Diff line number Diff line
@@ -162,6 +162,17 @@ public final class ViewRootImpl implements ViewParent,

    static final ArrayList<ComponentCallbacks> sConfigCallbacks = new ArrayList();

    /**
     * Signals that compatibility booleans have been initialized according to
     * target SDK versions.
     */
    private static boolean sCompatibilityDone = false;

    /**
     * Always assign focus if a focusable View is available.
     */
    private static boolean sAlwaysAssignFocus;

    /**
     * This list must only be modified by the main thread, so a lock is only needed when changing
     * the list or when accessing the list from a non-main thread.
@@ -450,6 +461,13 @@ public final class ViewRootImpl implements ViewParent,
        mFallbackEventHandler = new PhoneFallbackEventHandler(context);
        mChoreographer = Choreographer.getInstance();
        mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);

        if (!sCompatibilityDone) {
            sAlwaysAssignFocus = mTargetSdkVersion < Build.VERSION_CODES.O;

            sCompatibilityDone = true;
        }

        loadSystemProperties();
    }

@@ -2179,7 +2197,7 @@ public final class ViewRootImpl implements ViewParent,
            }
        }

        if (mFirst) {
        if (mFirst && sAlwaysAssignFocus) {
            // handle first focus request
            if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: mView.hasFocus()="
                    + mView.hasFocus());
@@ -3281,7 +3299,9 @@ public final class ViewRootImpl implements ViewParent,
        checkThread();
        if (mView != null) {
            if (!mView.hasFocus()) {
                if (sAlwaysAssignFocus) {
                    v.requestFocus();
                }
            } else {
                // the one case where will transfer focus away from the current one
                // is if the current view is a view group that prefers to give focus
@@ -4454,9 +4474,7 @@ public final class ViewRootImpl implements ViewParent,
                        return true;
                    }
                } else {
                    // find the best view to give focus to in this non-touch-mode with no-focus
                    View v = focusSearch(null, direction);
                    if (v != null && v.requestFocus(direction)) {
                    if (mView.restoreDefaultFocus()) {
                        return true;
                    }
                }
@@ -4466,9 +4484,10 @@ public final class ViewRootImpl implements ViewParent,

        private boolean performKeyboardGroupNavigation(int direction) {
            final View focused = mView.findFocus();
            View cluster = focused != null
                    ? focused.keyboardNavigationClusterSearch(null, direction)
                    : keyboardNavigationClusterSearch(null, direction);
            if (focused == null && mView.restoreDefaultFocus()) {
                return true;
            }
            View cluster = focused.keyboardNavigationClusterSearch(null, direction);

            // Since requestFocus only takes "real" focus directions (and therefore also
            // restoreFocusInCluster), convert forward/backward focus into FOCUS_DOWN.