Add "KeyFallback" handling ability to Views
This gives any view the ability to receive unhandled KeyEvents. The order of Views receiving fallback key events is inverse drawing order: this means higher views will receive fallback events first. FallbackHandlers can be added to any view via addKeyFallbackListener. Within a view, listeners are tapped in reverse order (such that more-recently added listeners will receive the event first). Bug: 32722450 Test: Added a CTS test ViewTest#testKeyFallback Change-Id: Ibfff4db70de8fb98db0035e5aeb09271be1574c6
This commit is contained in:
@@ -45931,6 +45931,7 @@ 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);
|
||||
@@ -46265,6 +46266,7 @@ 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);
|
||||
@@ -46318,6 +46320,7 @@ 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 requestApplyInsets();
|
||||
@@ -46736,6 +46739,10 @@ 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);
|
||||
}
|
||||
|
||||
@@ -49665,6 +49665,7 @@ 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);
|
||||
@@ -49999,6 +50000,7 @@ 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);
|
||||
@@ -50052,6 +50054,7 @@ 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 requestApplyInsets();
|
||||
@@ -50470,6 +50473,10 @@ 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);
|
||||
}
|
||||
|
||||
@@ -46552,6 +46552,7 @@ 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);
|
||||
@@ -46888,6 +46889,7 @@ 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);
|
||||
@@ -46941,6 +46943,7 @@ 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 requestApplyInsets();
|
||||
@@ -47363,6 +47366,10 @@ 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);
|
||||
}
|
||||
|
||||
@@ -4245,6 +4245,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
OnApplyWindowInsetsListener mOnApplyWindowInsetsListener;
|
||||
|
||||
OnCapturedPointerListener mOnCapturedPointerListener;
|
||||
|
||||
private ArrayList<OnKeyFallbackListener> mKeyFallbackListeners;
|
||||
}
|
||||
|
||||
ListenerInfo mListenerInfo;
|
||||
@@ -25171,6 +25173,29 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
boolean onKey(View v, int keyCode, KeyEvent event);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public interface OnKeyFallbackListener {
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
boolean onKeyFallback(View v, KeyEvent event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface definition for a callback to be invoked when a touch event is
|
||||
* dispatched to this view. The callback will be invoked before the touch
|
||||
@@ -26844,4 +26869,56 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
}
|
||||
return mTooltipInfo.mTooltipPopup.getContentView();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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).
|
||||
*
|
||||
* @param event A not-previously-handled event.
|
||||
* @return {@code true} if the event was handled, {@code false} otherwise.
|
||||
* @see #addKeyFallbackListener
|
||||
*/
|
||||
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)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener which will receive unhandled {@link KeyEvent}s.
|
||||
* @param listener the receiver of fallback {@link KeyEvent}s.
|
||||
* @see #onKeyFallback(KeyEvent)
|
||||
*/
|
||||
public void addKeyFallbackListener(OnKeyFallbackListener listener) {
|
||||
ArrayList<OnKeyFallbackListener> fallbacks = getListenerInfo().mKeyFallbackListeners;
|
||||
if (fallbacks == null) {
|
||||
fallbacks = new ArrayList<>();
|
||||
getListenerInfo().mKeyFallbackListeners = fallbacks;
|
||||
}
|
||||
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)
|
||||
*/
|
||||
public void removeKeyFallbackListener(OnKeyFallbackListener listener) {
|
||||
if (mListenerInfo != null) {
|
||||
if (mListenerInfo.mKeyFallbackListeners != null) {
|
||||
mListenerInfo.mKeyFallbackListeners.remove(listener);
|
||||
if (mListenerInfo.mKeyFallbackListeners.isEmpty()) {
|
||||
mListenerInfo.mKeyFallbackListeners = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@ import android.util.Log;
|
||||
import android.util.MergedConfiguration;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.util.TimeUtils;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Surface.OutOfResourcesException;
|
||||
@@ -362,6 +363,8 @@ public final class ViewRootImpl implements ViewParent,
|
||||
InputStage mFirstPostImeInputStage;
|
||||
InputStage mSyntheticInputStage;
|
||||
|
||||
private final KeyFallbackManager mKeyFallbackManager = new KeyFallbackManager();
|
||||
|
||||
boolean mWindowAttributesChanged = false;
|
||||
int mWindowAttributesChangesFlag = 0;
|
||||
|
||||
@@ -4764,6 +4767,13 @@ public final class ViewRootImpl implements ViewParent,
|
||||
private int processKeyEvent(QueuedInputEvent q) {
|
||||
final KeyEvent event = (KeyEvent)q.mEvent;
|
||||
|
||||
mKeyFallbackManager.mDispatched = false;
|
||||
|
||||
if (mKeyFallbackManager.hasFocus()
|
||||
&& mKeyFallbackManager.dispatchUnique(mView, event)) {
|
||||
return FINISH_HANDLED;
|
||||
}
|
||||
|
||||
// Deliver the key to the view hierarchy.
|
||||
if (mView.dispatchKeyEvent(event)) {
|
||||
return FINISH_HANDLED;
|
||||
@@ -4773,6 +4783,10 @@ public final class ViewRootImpl implements ViewParent,
|
||||
return FINISH_NOT_HANDLED;
|
||||
}
|
||||
|
||||
if (mKeyFallbackManager.dispatchUnique(mView, event)) {
|
||||
return FINISH_HANDLED;
|
||||
}
|
||||
|
||||
int groupNavigationDirection = 0;
|
||||
|
||||
if (event.getAction() == KeyEvent.ACTION_DOWN
|
||||
@@ -7529,6 +7543,16 @@ public final class ViewRootImpl implements ViewParent,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches a KeyEvent to all registered key fallback handlers.
|
||||
*
|
||||
* @param event
|
||||
* @return {@code true} if the event was handled, {@code false} otherwise.
|
||||
*/
|
||||
public boolean dispatchKeyFallbackEvent(KeyEvent event) {
|
||||
return mKeyFallbackManager.dispatch(mView, event);
|
||||
}
|
||||
|
||||
class TakenSurfaceHolder extends BaseSurfaceHolder {
|
||||
@Override
|
||||
public boolean onAllowLockCanvas() {
|
||||
@@ -8093,4 +8117,92 @@ public final class ViewRootImpl implements ViewParent,
|
||||
run();
|
||||
}
|
||||
}
|
||||
|
||||
private static class KeyFallbackManager {
|
||||
|
||||
// This is used to ensure that key-fallback 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
|
||||
// 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.
|
||||
boolean mDispatched = false;
|
||||
|
||||
SparseBooleanArray mCapturedKeys = new SparseBooleanArray();
|
||||
WeakReference<View> mFallbackReceiver = null;
|
||||
int mVisitCount = 0;
|
||||
|
||||
private void updateCaptureState(KeyEvent event) {
|
||||
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
||||
mCapturedKeys.append(event.getKeyCode(), true);
|
||||
}
|
||||
if (event.getAction() == KeyEvent.ACTION_UP) {
|
||||
mCapturedKeys.delete(event.getKeyCode());
|
||||
}
|
||||
}
|
||||
|
||||
boolean dispatch(View root, KeyEvent event) {
|
||||
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "KeyFallback dispatch");
|
||||
mDispatched = true;
|
||||
|
||||
updateCaptureState(event);
|
||||
|
||||
if (mFallbackReceiver != null) {
|
||||
View target = mFallbackReceiver.get();
|
||||
if (mCapturedKeys.size() == 0) {
|
||||
mFallbackReceiver = null;
|
||||
}
|
||||
if (target != null && target.isAttachedToWindow()) {
|
||||
return target.onKeyFallback(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;
|
||||
}
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
|
||||
boolean hasFocus() {
|
||||
return mFallbackReceiver != null;
|
||||
}
|
||||
|
||||
boolean dispatchUnique(View root, KeyEvent event) {
|
||||
if (mDispatched) {
|
||||
return false;
|
||||
}
|
||||
return dispatch(root, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -431,7 +431,11 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
|
||||
}
|
||||
}
|
||||
|
||||
return super.dispatchKeyEvent(event);
|
||||
if (super.dispatchKeyEvent(event)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (getViewRootImpl() != null) && getViewRootImpl().dispatchKeyFallbackEvent(event);
|
||||
}
|
||||
|
||||
public boolean superDispatchKeyShortcutEvent(KeyEvent event) {
|
||||
|
||||
Reference in New Issue
Block a user