Loading api/current.txt +6 −7 Original line number Diff line number Diff line Loading @@ -47303,10 +47303,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); Loading Loading @@ -47644,7 +47644,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); Loading Loading @@ -47698,9 +47697,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(); Loading Loading @@ -48124,10 +48123,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); } Loading @@ -48152,6 +48147,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); } core/java/android/view/View.java +66 −42 Original line number Diff line number Diff line Loading @@ -4320,7 +4320,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, OnCapturedPointerListener mOnCapturedPointerListener; private ArrayList<OnKeyFallbackListener> mKeyFallbackListeners; private ArrayList<OnUnhandledKeyEventListener> mUnhandledKeyListeners; } ListenerInfo mListenerInfo; Loading Loading @@ -25882,26 +25882,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); } /** Loading Loading @@ -27600,21 +27593,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; } } Loading @@ -27622,31 +27630,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(); } } } } core/java/android/view/ViewGroup.java +68 −0 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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(); Loading Loading @@ -5359,6 +5367,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager removeFromArray(index); if (view.hasUnhandledKeyListener()) { decrementChildUnhandledKeyListeners(); } if (view == mDefaultFocus) { clearDefaultFocus(view); } Loading Loading @@ -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 Loading core/java/android/view/ViewRootImpl.java +21 −50 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; } Loading @@ -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; } Loading Loading @@ -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 { Loading Loading @@ -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) { Loading @@ -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) { Loading Loading
api/current.txt +6 −7 Original line number Diff line number Diff line Loading @@ -47303,10 +47303,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); Loading Loading @@ -47644,7 +47644,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); Loading Loading @@ -47698,9 +47697,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(); Loading Loading @@ -48124,10 +48123,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); } Loading @@ -48152,6 +48147,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); }
core/java/android/view/View.java +66 −42 Original line number Diff line number Diff line Loading @@ -4320,7 +4320,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, OnCapturedPointerListener mOnCapturedPointerListener; private ArrayList<OnKeyFallbackListener> mKeyFallbackListeners; private ArrayList<OnUnhandledKeyEventListener> mUnhandledKeyListeners; } ListenerInfo mListenerInfo; Loading Loading @@ -25882,26 +25882,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); } /** Loading Loading @@ -27600,21 +27593,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; } } Loading @@ -27622,31 +27630,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(); } } } }
core/java/android/view/ViewGroup.java +68 −0 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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(); Loading Loading @@ -5359,6 +5367,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager removeFromArray(index); if (view.hasUnhandledKeyListener()) { decrementChildUnhandledKeyListeners(); } if (view == mDefaultFocus) { clearDefaultFocus(view); } Loading Loading @@ -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 Loading
core/java/android/view/ViewRootImpl.java +21 −50 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; } Loading @@ -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; } Loading Loading @@ -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 { Loading Loading @@ -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) { Loading @@ -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) { Loading