public class

KeyEventDispatcher

extends java.lang.Object

 java.lang.Object

↳androidx.core.view.KeyEventDispatcher

Gradle dependencies

compile group: 'androidx.core', name: 'core', version: '1.9.0-alpha04'

  • groupId: androidx.core
  • artifactId: core
  • version: 1.9.0-alpha04

Artifact androidx.core:core:1.9.0-alpha04 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.core:core com.android.support:support-compat

Androidx class mapping:

androidx.core.view.KeyEventDispatcher android.support.v4.view.KeyEventDispatcher

Overview

Allows dependent components to enable full dispatch compatibility in core. To use this, implement KeyEventDispatcher.Component and call the dispatch methods at appropriate times. This must be used for some core compatibility features to function fully.

Summary

Methods
public static booleandispatchBeforeHierarchy(View root, KeyEvent event)

Call this before dispatching a to the view hierarchy.

public static booleandispatchKeyEvent(KeyEventDispatcher.Component component, View root, Window.Callback callback, KeyEvent event)

Call this when dispatching a key event.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Methods

public static boolean dispatchBeforeHierarchy(View root, KeyEvent event)

Call this before dispatching a to the view hierarchy.

Parameters:

root: the root of the hierarchy that this event will be dispatched to
event: the event to dispatch

Returns:

true if the event was consumed in this stage, false otherwise

public static boolean dispatchKeyEvent(KeyEventDispatcher.Component component, View root, Window.Callback callback, KeyEvent event)

Call this when dispatching a key event. This is usually called in lieu of the Window.Callback dispatchKeyEvent implementation (eg. instead of calling super.dispatchKeyEvent in an Activity).

Parameters:

component: the component implementation
root: the root of the view hierarchy that this event will be dispatched to
callback: a implementation or null if there isn't one
event: the event to dispatch

Returns:

true if the event was consumed, false otherwise

Source

/*
 * Copyright 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.core.view;

import android.annotation.SuppressLint;
import android.app.ActionBar;
import android.app.Activity;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Build;
import android.view.KeyEvent;
import android.view.View;
import android.view.Window;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * Allows dependent components to enable full {@link KeyEvent} dispatch compatibility in core.
 * To use this, implement {@link Component} and call the dispatch methods at appropriate times.
 *
 * This must be used for some core compatibility features to function fully.
 * @hide
 */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
public class KeyEventDispatcher {
    // reflection accessors
    private static boolean sActionBarFieldsFetched = false;
    private static Method sActionBarOnMenuKeyMethod = null;
    private static boolean sDialogFieldsFetched = false;
    private static Field sDialogKeyListenerField = null;

    private KeyEventDispatcher() {
        // Not instantiable
    }

    /**
     * Call this before dispatching a {@link KeyEvent} to the view hierarchy.
     *
     * @param root the root of the hierarchy that this event will be dispatched to
     * @param event the event to dispatch
     * @return {@code true} if the event was consumed in this stage, {@code false} otherwise
     */
    public static boolean dispatchBeforeHierarchy(@NonNull View root, @NonNull KeyEvent event) {
        return ViewCompat.dispatchUnhandledKeyEventBeforeHierarchy(root, event);
    }

    /**
     * Call this when dispatching a key event. This is usually called in lieu of the
     * Window.Callback dispatchKeyEvent implementation (eg. instead of calling
     * super.dispatchKeyEvent in an Activity).
     *
     * @param component the component implementation
     * @param root the root of the view hierarchy that this event will be dispatched to
     * @param callback a {@link Window.Callback} implementation or {@code null} if there isn't one
     * @param event the event to dispatch
     * @return {@code true} if the event was consumed, {@code false} otherwise
     *
     */
    @SuppressLint("LambdaLast")
    public static boolean dispatchKeyEvent(@NonNull Component component,
            @Nullable View root, @Nullable Window.Callback callback, @NonNull KeyEvent event) {
        if (component == null) {
            return false;
        }
        if (Build.VERSION.SDK_INT >= 28) {
            return component.superDispatchKeyEvent(event);
        }
        if (callback instanceof Activity) {
            return activitySuperDispatchKeyEventPre28((Activity) callback, event);
        } else if (callback instanceof Dialog) {
            return dialogSuperDispatchKeyEventPre28((Dialog) callback, event);
        }
        return (root != null && ViewCompat.dispatchUnhandledKeyEventBeforeCallback(root, event))
                || component.superDispatchKeyEvent(event);
    }

    private static boolean actionBarOnMenuKeyEventPre28(ActionBar actionBar, KeyEvent event) {
        if (!sActionBarFieldsFetched) {
            try {
                sActionBarOnMenuKeyMethod =
                        actionBar.getClass().getMethod("onMenuKeyEvent", KeyEvent.class);
            } catch (NoSuchMethodException ignored) {
            }
            sActionBarFieldsFetched = true;
        }
        if (sActionBarOnMenuKeyMethod != null) {
            try {
                Object value = sActionBarOnMenuKeyMethod.invoke(actionBar, event);
                if (value == null) {
                    return false;
                }
                return (Boolean) value;
            } catch (IllegalAccessException ignored) {
            } catch (InvocationTargetException ignored) {
            }
        }
        return false;
    }

    private static boolean activitySuperDispatchKeyEventPre28(Activity activity, KeyEvent event) {
        activity.onUserInteraction();

        Window win = activity.getWindow();

        // Let action bars open menus in response to the menu key prioritized over
        // the window handling it
        if (win.hasFeature(Window.FEATURE_ACTION_BAR)) {
            ActionBar actionBar = activity.getActionBar();
            final int keyCode = event.getKeyCode();
            if (keyCode == KeyEvent.KEYCODE_MENU
                    && actionBar != null && actionBarOnMenuKeyEventPre28(actionBar, event)) {
                return true;
            }
        }

        if (win.superDispatchKeyEvent(event)) {
            return true;
        }
        View decor = win.getDecorView();
        if (ViewCompat.dispatchUnhandledKeyEventBeforeCallback(decor, event)) {
            return true;
        }
        return event.dispatch(activity, decor != null
                ? decor.getKeyDispatcherState() : null, activity);
    }

    @SuppressWarnings("JavaReflectionMemberAccess")
    private static DialogInterface.OnKeyListener getDialogKeyListenerPre28(Dialog dialog) {
        if (!sDialogFieldsFetched) {
            try {
                sDialogKeyListenerField = Dialog.class.getDeclaredField("mOnKeyListener");
                sDialogKeyListenerField.setAccessible(true);
            } catch (NoSuchFieldException ignored) {
            }
            sDialogFieldsFetched = true;
        }

        if (sDialogKeyListenerField != null) {
            try {
                return (DialogInterface.OnKeyListener) sDialogKeyListenerField.get(dialog);
            } catch (IllegalAccessException ignored) {
            }
        }
        return null;
    }

    private static boolean dialogSuperDispatchKeyEventPre28(Dialog dialog, KeyEvent event) {
        DialogInterface.OnKeyListener onKeyListener = getDialogKeyListenerPre28(dialog);
        if ((onKeyListener != null) && onKeyListener.onKey(dialog, event.getKeyCode(), event)) {
            return true;
        }
        Window win = dialog.getWindow();
        if (win.superDispatchKeyEvent(event)) {
            return true;
        }
        View decor = win.getDecorView();
        if (ViewCompat.dispatchUnhandledKeyEventBeforeCallback(decor, event)) {
            return true;
        }
        return event.dispatch(dialog, decor != null
                ? decor.getKeyDispatcherState() : null, dialog);
    }

    /**
     * Implement this in any component that dispatches {@link KeyEvent}s.
     */
    public interface Component {
        /**
         * Expected to call into the super implementation of
         * {@link Window.Callback#dispatchKeyEvent}.
         *
         * If you're not implementing Window.Callback, this should contain dispatch logic
         * that occurs <b>after</b> {@link android.view.View.OnUnhandledKeyEventListener}s.
         *
         * @param event The event being dispatched
         * @return {@code true} if consuming the event, {@code false} otherwise
         */
        boolean superDispatchKeyEvent(@NonNull KeyEvent event);
    }
}