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

Commit 4807ae25 authored by Evan Rosky's avatar Evan Rosky
Browse files

Renamed KeyFallbackEvent to UnhandledKeyEvent and exposed dispatch

This new naming clashes less with the existing notion of FLAG_FALLBACK
in KeyEvents.

Bug: 72562800
Test: ViewTest#testUnhandledKeys
Change-Id: Ibd713860601e62d955443fe6811fd974b5bb0092
parent 2ed9bf43
Loading
Loading
Loading
Loading
+6 −7
Original line number Diff line number Diff line
@@ -47254,10 +47254,10 @@ package android.view {
    method public void addExtraDataToAccessibilityNodeInfo(android.view.accessibility.AccessibilityNodeInfo, java.lang.String, android.os.Bundle);
    method public void addFocusables(java.util.ArrayList<android.view.View>, int);
    method public void addFocusables(java.util.ArrayList<android.view.View>, int, int);
    method public void addKeyFallbackListener(android.view.View.OnKeyFallbackListener);
    method public void addKeyboardNavigationClusters(java.util.Collection<android.view.View>, int);
    method public void addOnAttachStateChangeListener(android.view.View.OnAttachStateChangeListener);
    method public void addOnLayoutChangeListener(android.view.View.OnLayoutChangeListener);
    method public void addOnUnhandledKeyEventListener(android.view.View.OnUnhandledKeyEventListener);
    method public void addTouchables(java.util.ArrayList<android.view.View>);
    method public android.view.ViewPropertyAnimator animate();
    method public void announceForAccessibility(java.lang.CharSequence);
@@ -47595,7 +47595,6 @@ package android.view {
    method public void onInitializeAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
    method public void onInitializeAccessibilityNodeInfo(android.view.accessibility.AccessibilityNodeInfo);
    method public boolean onKeyDown(int, android.view.KeyEvent);
    method public boolean onKeyFallback(android.view.KeyEvent);
    method public boolean onKeyLongPress(int, android.view.KeyEvent);
    method public boolean onKeyMultiple(int, int, android.view.KeyEvent);
    method public boolean onKeyPreIme(int, android.view.KeyEvent);
@@ -47649,9 +47648,9 @@ package android.view {
    method public void refreshDrawableState();
    method public void releasePointerCapture();
    method public boolean removeCallbacks(java.lang.Runnable);
    method public void removeKeyFallbackListener(android.view.View.OnKeyFallbackListener);
    method public void removeOnAttachStateChangeListener(android.view.View.OnAttachStateChangeListener);
    method public void removeOnLayoutChangeListener(android.view.View.OnLayoutChangeListener);
    method public void removeOnUnhandledKeyEventListener(android.view.View.OnUnhandledKeyEventListener);
    method public void requestApplyInsets();
    method public deprecated void requestFitSystemWindows();
    method public final boolean requestFocus();
@@ -48075,10 +48074,6 @@ package android.view {
    method public abstract boolean onHover(android.view.View, android.view.MotionEvent);
  }
  public static abstract interface View.OnKeyFallbackListener {
    method public abstract boolean onKeyFallback(android.view.View, android.view.KeyEvent);
  }
  public static abstract interface View.OnKeyListener {
    method public abstract boolean onKey(android.view.View, int, android.view.KeyEvent);
  }
@@ -48103,6 +48098,10 @@ package android.view {
    method public abstract boolean onTouch(android.view.View, android.view.MotionEvent);
  }
  public static abstract interface View.OnUnhandledKeyEventListener {
    method public abstract boolean onUnhandledKeyEvent(android.view.View, android.view.KeyEvent);
  }
  public final class ViewAnimationUtils {
    method public static android.animation.Animator createCircularReveal(android.view.View, int, int, float, float);
  }
+66 −42
Original line number Diff line number Diff line
@@ -4320,7 +4320,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        OnCapturedPointerListener mOnCapturedPointerListener;
        private ArrayList<OnKeyFallbackListener> mKeyFallbackListeners;
        private ArrayList<OnUnhandledKeyEventListener> mUnhandledKeyListeners;
    }
    ListenerInfo mListenerInfo;
@@ -25885,26 +25885,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
    }
    /**
     * Interface definition for a callback to be invoked when a hardware key event is
     * dispatched to this view during the fallback phase. This means no view in the hierarchy
     * has handled this event.
     * Interface definition for a callback to be invoked when a hardware key event hasn't
     * been handled by the view hierarchy.
     */
    public interface OnKeyFallbackListener {
    public interface OnUnhandledKeyEventListener {
        /**
         * Called when a hardware key is dispatched to a view in the fallback phase. This allows
         * listeners to respond to events after the view hierarchy has had a chance to respond.
         * <p>Key presses in software keyboards will generally NOT trigger this method,
         * although some may elect to do so in some situations. Do not assume a
         * software input method has to be key-based; even if it is, it may use key presses
         * in a different way than you expect, so there is no way to reliably catch soft
         * input key presses.
         * Called when a hardware key is dispatched to a view after being unhandled during normal
         * {@link KeyEvent} dispatch.
         *
         * @param v The view the key has been dispatched to.
         * @param event The KeyEvent object containing full information about
         *        the event.
         * @return True if the listener has consumed the event, false otherwise.
         * @param event The KeyEvent object containing information about the event.
         * @return {@code true} if the listener has consumed the event, {@code false} otherwise.
         */
        boolean onKeyFallback(View v, KeyEvent event);
        boolean onUnhandledKeyEvent(View v, KeyEvent event);
    }
    /**
@@ -27603,21 +27596,36 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        return sUseDefaultFocusHighlight;
    }
    /**
     * Dispatch a previously unhandled {@link KeyEvent} to this view. Unlike normal key dispatch,
     * this dispatches to ALL child views until it is consumed. The dispatch order is z-order
     * (visually on-top views first).
     *
     * @param evt the previously unhandled {@link KeyEvent}.
     * @return the {@link View} which consumed the event or {@code null} if not consumed.
     */
    View dispatchUnhandledKeyEvent(KeyEvent evt) {
        if (onUnhandledKeyEvent(evt)) {
            return this;
        }
        return null;
    }
    /**
     * Allows this view to handle {@link KeyEvent}s which weren't handled by normal dispatch. This
     * occurs after the normal view hierarchy dispatch, but before the window callback. By default,
     * this will dispatch into all the listeners registered via
     * {@link #addKeyFallbackListener(OnKeyFallbackListener)} in last-in-first-out order (most
     * recently added will receive events first).
     * {@link #addOnUnhandledKeyEventListener(OnUnhandledKeyEventListener)} in last-in-first-out
     * order (most recently added will receive events first).
     *
     * @param event A not-previously-handled event.
     * @param event An unhandled event.
     * @return {@code true} if the event was handled, {@code false} otherwise.
     * @see #addKeyFallbackListener
     * @see #addOnUnhandledKeyEventListener
     */
    public boolean onKeyFallback(@NonNull KeyEvent event) {
        if (mListenerInfo != null && mListenerInfo.mKeyFallbackListeners != null) {
            for (int i = mListenerInfo.mKeyFallbackListeners.size() - 1; i >= 0; --i) {
                if (mListenerInfo.mKeyFallbackListeners.get(i).onKeyFallback(this, event)) {
    boolean onUnhandledKeyEvent(@NonNull KeyEvent event) {
        if (mListenerInfo != null && mListenerInfo.mUnhandledKeyListeners != null) {
            for (int i = mListenerInfo.mUnhandledKeyListeners.size() - 1; i >= 0; --i) {
                if (mListenerInfo.mUnhandledKeyListeners.get(i).onUnhandledKeyEvent(this, event)) {
                    return true;
                }
            }
@@ -27625,31 +27633,47 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        return false;
    }
    boolean hasUnhandledKeyListener() {
        return (mListenerInfo != null && mListenerInfo.mUnhandledKeyListeners != null
                && !mListenerInfo.mUnhandledKeyListeners.isEmpty());
    }
    /**
     * Adds a listener which will receive unhandled {@link KeyEvent}s.
     * @param listener the receiver of fallback {@link KeyEvent}s.
     * @see #onKeyFallback(KeyEvent)
     * Adds a listener which will receive unhandled {@link KeyEvent}s. This must be called on the
     * UI thread.
     *
     * @param listener a receiver of unhandled {@link KeyEvent}s.
     * @see #removeOnUnhandledKeyEventListener
     */
    public void addKeyFallbackListener(OnKeyFallbackListener listener) {
        ArrayList<OnKeyFallbackListener> fallbacks = getListenerInfo().mKeyFallbackListeners;
        if (fallbacks == null) {
            fallbacks = new ArrayList<>();
            getListenerInfo().mKeyFallbackListeners = fallbacks;
    public void addOnUnhandledKeyEventListener(OnUnhandledKeyEventListener listener) {
        ArrayList<OnUnhandledKeyEventListener> listeners = getListenerInfo().mUnhandledKeyListeners;
        if (listeners == null) {
            listeners = new ArrayList<>();
            getListenerInfo().mUnhandledKeyListeners = listeners;
        }
        listeners.add(listener);
        if (listeners.size() == 1 && mParent instanceof ViewGroup) {
            ((ViewGroup) mParent).incrementChildUnhandledKeyListeners();
        }
        fallbacks.add(listener);
    }
    /**
     * Removes a listener which will receive unhandled {@link KeyEvent}s.
     * @param listener the receiver of fallback {@link KeyEvent}s.
     * @see #onKeyFallback(KeyEvent)
     * Removes a listener which will receive unhandled {@link KeyEvent}s. This must be called on the
     * UI thread.
     *
     * @param listener a receiver of unhandled {@link KeyEvent}s.
     * @see #addOnUnhandledKeyEventListener
     */
    public void removeKeyFallbackListener(OnKeyFallbackListener listener) {
    public void removeOnUnhandledKeyEventListener(OnUnhandledKeyEventListener listener) {
        if (mListenerInfo != null) {
            if (mListenerInfo.mKeyFallbackListeners != null) {
                mListenerInfo.mKeyFallbackListeners.remove(listener);
                if (mListenerInfo.mKeyFallbackListeners.isEmpty()) {
                    mListenerInfo.mKeyFallbackListeners = null;
            if (mListenerInfo.mUnhandledKeyListeners != null
                    && !mListenerInfo.mUnhandledKeyListeners.isEmpty()) {
                mListenerInfo.mUnhandledKeyListeners.remove(listener);
                if (mListenerInfo.mUnhandledKeyListeners.isEmpty()) {
                    mListenerInfo.mUnhandledKeyListeners = null;
                    if (mParent instanceof ViewGroup) {
                        ((ViewGroup) mParent).decrementChildUnhandledKeyListeners();
                    }
                }
            }
        }
+68 −0
Original line number Diff line number Diff line
@@ -583,6 +583,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
    private List<Integer> mTransientIndices = null;
    private List<View> mTransientViews = null;

    /**
     * Keeps track of how many child views have UnhandledKeyEventListeners. This should only be
     * updated on the UI thread so shouldn't require explicit synchronization.
     */
    int mChildUnhandledKeyListeners = 0;

    /**
     * Empty ActionMode used as a sentinel in recursive entries to startActionModeForChild.
@@ -5055,6 +5060,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
            child.assignParent(this);
        } else {
            child.mParent = this;
            if (child.hasUnhandledKeyListener()) {
                incrementChildUnhandledKeyListeners();
            }
        }

        final boolean childHasFocus = child.hasFocus();
@@ -5359,6 +5367,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager

        removeFromArray(index);

        if (view.hasUnhandledKeyListener()) {
            decrementChildUnhandledKeyListeners();
        }

        if (view == mDefaultFocus) {
            clearDefaultFocus(view);
        }
@@ -7537,6 +7549,62 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
        }
    }

    @Override
    boolean hasUnhandledKeyListener() {
        return (mChildUnhandledKeyListeners > 0) || super.hasUnhandledKeyListener();
    }

    void incrementChildUnhandledKeyListeners() {
        mChildUnhandledKeyListeners += 1;
        if (mChildUnhandledKeyListeners == 1) {
            if (mParent instanceof ViewGroup) {
                ((ViewGroup) mParent).incrementChildUnhandledKeyListeners();
            }
        }
    }

    void decrementChildUnhandledKeyListeners() {
        mChildUnhandledKeyListeners -= 1;
        if (mChildUnhandledKeyListeners == 0) {
            if (mParent instanceof ViewGroup) {
                ((ViewGroup) mParent).decrementChildUnhandledKeyListeners();
            }
        }
    }

    @Override
    View dispatchUnhandledKeyEvent(KeyEvent evt) {
        if (!hasUnhandledKeyListener()) {
            return null;
        }
        ArrayList<View> orderedViews = buildOrderedChildList();
        if (orderedViews != null) {
            try {
                for (int i = orderedViews.size() - 1; i >= 0; --i) {
                    View v = orderedViews.get(i);
                    View consumer = v.dispatchUnhandledKeyEvent(evt);
                    if (consumer != null) {
                        return consumer;
                    }
                }
            } finally {
                orderedViews.clear();
            }
        } else {
            for (int i = getChildCount() - 1; i >= 0; --i) {
                View v = getChildAt(i);
                View consumer = v.dispatchUnhandledKeyEvent(evt);
                if (consumer != null) {
                    return consumer;
                }
            }
        }
        if (onUnhandledKeyEvent(evt)) {
            return this;
        }
        return null;
    }

    /**
     * LayoutParams are used by views to tell their parents how they want to be
     * laid out. See
+21 −50
Original line number Diff line number Diff line
@@ -380,7 +380,7 @@ public final class ViewRootImpl implements ViewParent,
    InputStage mFirstPostImeInputStage;
    InputStage mSyntheticInputStage;

    private final KeyFallbackManager mKeyFallbackManager = new KeyFallbackManager();
    private final UnhandledKeyManager mUnhandledKeyManager = new UnhandledKeyManager();

    boolean mWindowAttributesChanged = false;
    int mWindowAttributesChangesFlag = 0;
@@ -4975,10 +4975,10 @@ public final class ViewRootImpl implements ViewParent,
        private int processKeyEvent(QueuedInputEvent q) {
            final KeyEvent event = (KeyEvent)q.mEvent;

            mKeyFallbackManager.mDispatched = false;
            mUnhandledKeyManager.mDispatched = false;

            if (mKeyFallbackManager.hasFocus()
                    && mKeyFallbackManager.dispatchUnique(mView, event)) {
            if (mUnhandledKeyManager.hasFocus()
                    && mUnhandledKeyManager.dispatchUnique(mView, event)) {
                return FINISH_HANDLED;
            }

@@ -4991,7 +4991,7 @@ public final class ViewRootImpl implements ViewParent,
                return FINISH_NOT_HANDLED;
            }

            if (mKeyFallbackManager.dispatchUnique(mView, event)) {
            if (mUnhandledKeyManager.dispatchUnique(mView, event)) {
                return FINISH_HANDLED;
            }

@@ -7798,7 +7798,7 @@ public final class ViewRootImpl implements ViewParent,
     * @return {@code true} if the event was handled, {@code false} otherwise.
     */
    public boolean dispatchKeyFallbackEvent(KeyEvent event) {
        return mKeyFallbackManager.dispatch(mView, event);
        return mUnhandledKeyManager.dispatch(mView, event);
    }

    class TakenSurfaceHolder extends BaseSurfaceHolder {
@@ -8374,18 +8374,17 @@ public final class ViewRootImpl implements ViewParent,
        }
    }

    private static class KeyFallbackManager {
    private static class UnhandledKeyManager {

        // This is used to ensure that key-fallback events are only dispatched once. We attempt
        // This is used to ensure that unhandled events are only dispatched once. We attempt
        // to dispatch more than once in order to achieve a certain order. Specifically, if we
        // are in an Activity or Dialog (and have a Window.Callback), the keyfallback events should
        // are in an Activity or Dialog (and have a Window.Callback), the unhandled events should
        // be dispatched after the view hierarchy, but before the Activity. However, if we aren't
        // in an activity, we still want key fallbacks to be dispatched.
        // in an activity, we still want unhandled keys to be dispatched.
        boolean mDispatched = false;

        SparseBooleanArray mCapturedKeys = new SparseBooleanArray();
        WeakReference<View> mFallbackReceiver = null;
        int mVisitCount = 0;
        WeakReference<View> mCurrentReceiver = null;

        private void updateCaptureState(KeyEvent event) {
            if (event.getAction() == KeyEvent.ACTION_DOWN) {
@@ -8402,56 +8401,28 @@ public final class ViewRootImpl implements ViewParent,

            updateCaptureState(event);

            if (mFallbackReceiver != null) {
                View target = mFallbackReceiver.get();
            if (mCurrentReceiver != null) {
                View target = mCurrentReceiver.get();
                if (mCapturedKeys.size() == 0) {
                    mFallbackReceiver = null;
                    mCurrentReceiver = null;
                }
                if (target != null && target.isAttachedToWindow()) {
                    return target.onKeyFallback(event);
                    target.onUnhandledKeyEvent(event);
                }
                // consume anyways so that we don't feed uncaptured key events to other views
                return true;
            }

            boolean result = dispatchInZOrder(root, event);
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            return result;
        }

        private boolean dispatchInZOrder(View view, KeyEvent evt) {
            if (view instanceof ViewGroup) {
                ViewGroup vg = (ViewGroup) view;
                ArrayList<View> orderedViews = vg.buildOrderedChildList();
                if (orderedViews != null) {
                    try {
                        for (int i = orderedViews.size() - 1; i >= 0; --i) {
                            View v = orderedViews.get(i);
                            if (dispatchInZOrder(v, evt)) {
                                return true;
                            }
            View consumer = root.dispatchUnhandledKeyEvent(event);
            if (consumer != null) {
                mCurrentReceiver = new WeakReference<>(consumer);
            }
                    } finally {
                        orderedViews.clear();
                    }
                } else {
                    for (int i = vg.getChildCount() - 1; i >= 0; --i) {
                        View v = vg.getChildAt(i);
                        if (dispatchInZOrder(v, evt)) {
                            return true;
                        }
                    }
                }
            }
            if (view.onKeyFallback(evt)) {
                mFallbackReceiver = new WeakReference<>(view);
                return true;
            }
            return false;
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            return consumer != null;
        }

        boolean hasFocus() {
            return mFallbackReceiver != null;
            return mCurrentReceiver != null;
        }

        boolean dispatchUnique(View root, KeyEvent event) {