Merge "Renamed KeyFallbackEvent to UnhandledKeyEvent and exposed dispatch" into pi-dev
diff --git a/api/current.txt b/api/current.txt
index 37c5c6f..992c60c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -47301,10 +47301,10 @@
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);
@@ -47642,7 +47642,6 @@
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);
@@ -47696,9 +47695,9 @@
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();
@@ -48122,10 +48121,6 @@
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);
}
@@ -48150,6 +48145,10 @@
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);
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index ea3710c..7e6ee76 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -4320,7 +4320,7 @@
OnCapturedPointerListener mOnCapturedPointerListener;
- private ArrayList<OnKeyFallbackListener> mKeyFallbackListeners;
+ private ArrayList<OnUnhandledKeyEventListener> mUnhandledKeyListeners;
}
ListenerInfo mListenerInfo;
@@ -25882,26 +25882,19 @@
}
/**
- * 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);
}
/**
@@ -27600,21 +27593,36 @@
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;
}
}
@@ -27622,31 +27630,47 @@
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);
+ boolean hasUnhandledKeyListener() {
+ return (mListenerInfo != null && mListenerInfo.mUnhandledKeyListeners != null
+ && !mListenerInfo.mUnhandledKeyListeners.isEmpty());
}
/**
- * Removes 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 removeKeyFallbackListener(OnKeyFallbackListener listener) {
+ 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();
+ }
+ }
+
+ /**
+ * 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 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();
+ }
}
}
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 33fcf6a..6002fe5 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -583,6 +583,11 @@
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 @@
child.assignParent(this);
} else {
child.mParent = this;
+ if (child.hasUnhandledKeyListener()) {
+ incrementChildUnhandledKeyListeners();
+ }
}
final boolean childHasFocus = child.hasFocus();
@@ -5359,6 +5367,10 @@
removeFromArray(index);
+ if (view.hasUnhandledKeyListener()) {
+ decrementChildUnhandledKeyListeners();
+ }
+
if (view == mDefaultFocus) {
clearDefaultFocus(view);
}
@@ -7537,6 +7549,62 @@
}
}
+ @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
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index d521684..433c90b 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -380,7 +380,7 @@
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 @@
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 @@
return FINISH_NOT_HANDLED;
}
- if (mKeyFallbackManager.dispatchUnique(mView, event)) {
+ if (mUnhandledKeyManager.dispatchUnique(mView, event)) {
return FINISH_HANDLED;
}
@@ -7798,7 +7798,7 @@
* @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 @@
}
}
- 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 @@
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);
+ View consumer = root.dispatchUnhandledKeyEvent(event);
+ if (consumer != null) {
+ mCurrentReceiver = new WeakReference<>(consumer);
+ }
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;
+ return consumer != null;
}
boolean hasFocus() {
- return mFallbackReceiver != null;
+ return mCurrentReceiver != null;
}
boolean dispatchUnique(View root, KeyEvent event) {