diff options
-rw-r--r-- | api/current.txt | 7 | ||||
-rw-r--r-- | api/system-current.txt | 7 | ||||
-rw-r--r-- | api/test-current.txt | 7 | ||||
-rw-r--r-- | core/java/android/view/View.java | 77 | ||||
-rw-r--r-- | core/java/android/view/ViewRootImpl.java | 112 | ||||
-rw-r--r-- | core/java/com/android/internal/policy/DecorView.java | 6 |
6 files changed, 215 insertions, 1 deletions
diff --git a/api/current.txt b/api/current.txt index f5f4bf4d7697..a0abc1a330de 100644 --- a/api/current.txt +++ b/api/current.txt @@ -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); } diff --git a/api/system-current.txt b/api/system-current.txt index d50fb4ecbbf9..9a5968ed3eda 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -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); } diff --git a/api/test-current.txt b/api/test-current.txt index cee4789aaea4..d645b26e9347 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -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); } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index c043dcacf11f..499a4c8c4abd 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -4245,6 +4245,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, OnApplyWindowInsetsListener mOnApplyWindowInsetsListener; OnCapturedPointerListener mOnCapturedPointerListener; + + private ArrayList<OnKeyFallbackListener> mKeyFallbackListeners; } ListenerInfo mListenerInfo; @@ -25172,6 +25174,29 @@ 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. + */ + 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 * event is given to the view. @@ -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; + } + } + } + } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 37829f0bf221..1c1933f2ba6e 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -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); + } + } } diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index 85251d4bfcfc..5fddfba632c5 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -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) { |