diff options
| author | Vladislav Kaznacheev <kaznacheev@google.com> | 2016-11-21 14:11:00 -0800 |
|---|---|---|
| committer | Vladislav Kaznacheev <kaznacheev@google.com> | 2016-11-22 09:32:07 -0800 |
| commit | f847ee3c3d68e58b0a1a545bd7358ebb32f6948a (patch) | |
| tree | 12b56e4f5093dddf876833a87982c11c48fcc675 /core/java/android | |
| parent | 76932df9ec7f7c2a18f9d899767846c8d7ede4fc (diff) | |
Implement tooltip support in View
Adding View.setTooltip/getTooltip and 'tooltip' layout attribute.
Following Material Design spec for styles and behavior.
Bug: 31515376
Test: cts-tradefed run singleCommand cts -m CtsViewTestCases
--test android.view.cts.TooltipTest
Change-Id: I2d2527f642cd7446ffc88d4beffc7b81d7a2f6d6
Diffstat (limited to 'core/java/android')
| -rw-r--r-- | core/java/android/view/View.java | 303 | ||||
| -rw-r--r-- | core/java/android/view/ViewConfiguration.java | 63 | ||||
| -rw-r--r-- | core/java/android/view/ViewGroup.java | 106 | ||||
| -rw-r--r-- | core/java/android/view/ViewRootImpl.java | 34 |
4 files changed, 480 insertions, 26 deletions
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 02a85216cc20..84d7548363d1 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -37,6 +37,7 @@ import android.annotation.LayoutRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Size; +import android.annotation.TestApi; import android.annotation.UiThread; import android.content.ClipData; import android.content.Context; @@ -111,6 +112,7 @@ import android.widget.ScrollBarDrawable; import com.android.internal.R; import com.android.internal.util.Predicate; +import com.android.internal.view.TooltipPopup; import com.android.internal.view.menu.MenuBuilder; import com.android.internal.widget.ScrollBarUtils; @@ -1196,6 +1198,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private static Paint sDebugPaint; + /** + * <p>Indicates this view can display a tooltip on hover or long press.</p> + * {@hide} + */ + static final int TOOLTIP = 0x40000000; + /** @hide */ @IntDef(flag = true, value = { @@ -3619,6 +3627,39 @@ public class View implements Drawable.Callback, KeyEvent.Callback, ListenerInfo mListenerInfo; + private static class TooltipInfo { + /** + * Text to be displayed in a tooltip popup. + */ + @Nullable + CharSequence mTooltip; + + /** + * View-relative position of the tooltip anchor point. + */ + int mAnchorX; + int mAnchorY; + + /** + * The tooltip popup. + */ + @Nullable + TooltipPopup mTooltipPopup; + + /** + * Set to true if the tooltip was shown as a result of a long click. + */ + boolean mTooltipFromLongClick; + + /** + * Keep these Runnables so that they can be used to reschedule. + */ + Runnable mShowTooltipRunnable; + Runnable mHideTooltipRunnable; + } + + TooltipInfo mTooltipInfo; + // Temporary values used to hold (x,y) coordinates when delegating from the // two-arg performLongClick() method to the legacy no-arg version. private float mLongClickX = Float.NaN; @@ -4576,6 +4617,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } break; + case R.styleable.View_tooltip: + setTooltip(a.getText(attr)); + break; } } @@ -5712,6 +5756,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final boolean isAnchored = !Float.isNaN(x) && !Float.isNaN(y); handled = isAnchored ? showContextMenu(x, y) : showContextMenu(); } + if ((mViewFlags & TOOLTIP) == TOOLTIP) { + if (!handled) { + handled = showLongClickTooltip((int) x, (int) y); + } + } if (handled) { performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); } @@ -10603,17 +10652,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return true; } - // Long clickable items don't necessarily have to be clickable. - if (((mViewFlags & CLICKABLE) == CLICKABLE - || (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) - && (event.getRepeatCount() == 0)) { - // For the purposes of menu anchoring and drawable hotspots, - // key events are considered to be at the center of the view. - final float x = getWidth() / 2f; - final float y = getHeight() / 2f; - setPressed(true, x, y); - checkForLongClick(0, x, y); - return true; + if (event.getRepeatCount() == 0) { + // Long clickable items don't necessarily have to be clickable. + final boolean clickable = (mViewFlags & CLICKABLE) == CLICKABLE + || (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE; + if (clickable || (mViewFlags & TOOLTIP) == TOOLTIP) { + // For the purposes of menu anchoring and drawable hotspots, + // key events are considered to be at the center of the view. + final float x = getWidth() / 2f; + final float y = getHeight() / 2f; + if (clickable) { + setPressed(true, x, y); + } + checkForLongClick(0, x, y); + return true; + } } } @@ -11160,15 +11213,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final int viewFlags = mViewFlags; final int action = event.getAction(); + final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE + || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) + || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE; + if ((viewFlags & ENABLED_MASK) == DISABLED) { if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. - return (((viewFlags & CLICKABLE) == CLICKABLE - || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) - || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE); + return clickable; } if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { @@ -11176,11 +11231,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } - if (((viewFlags & CLICKABLE) == CLICKABLE || - (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || - (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) { + if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) { switch (action) { case MotionEvent.ACTION_UP: + if ((viewFlags & TOOLTIP) == TOOLTIP) { + handleTooltipUp(); + } + if (!clickable) { + removeTapCallback(); + removeLongPressCallback(); + mInContextButtonPress = false; + mHasPerformedLongPress = false; + mIgnoreNextUpEvent = false; + break; + } boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { // take focus if we don't have it already and we should in @@ -11196,7 +11260,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // state now (before scheduling the click) to ensure // the user sees it. setPressed(true, x, y); - } + } if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) { // This is a tap, so remove the longpress check @@ -11236,6 +11300,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, case MotionEvent.ACTION_DOWN: mHasPerformedLongPress = false; + if (!clickable) { + checkForLongClick(0, x, y); + break; + } + if (performButtonActionOnTouchDown(event)) { break; } @@ -11261,7 +11330,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, break; case MotionEvent.ACTION_CANCEL: - setPressed(false); + if (clickable) { + setPressed(false); + } removeTapCallback(); removeLongPressCallback(); mInContextButtonPress = false; @@ -11270,16 +11341,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, break; case MotionEvent.ACTION_MOVE: - drawableHotspotChanged(x, y); + if (clickable) { + drawableHotspotChanged(x, y); + } // Be lenient about moving outside of buttons if (!pointInView(x, y, mTouchSlop)) { // Outside button + // Remove any future long press/tap checks removeTapCallback(); + removeLongPressCallback(); if ((mPrivateFlags & PFLAG_PRESSED) != 0) { - // Remove any future long press/tap checks - removeLongPressCallback(); - setPressed(false); } } @@ -11311,7 +11383,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ private void removeLongPressCallback() { if (mPendingCheckForLongPress != null) { - removeCallbacks(mPendingCheckForLongPress); + removeCallbacks(mPendingCheckForLongPress); } } @@ -15379,6 +15451,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, cleanupDraw(); mCurrentAnimation = null; + + if ((mViewFlags & TOOLTIP) == TOOLTIP) { + hideTooltip(); + } } private void cleanupDraw() { @@ -21031,7 +21107,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } private void checkForLongClick(int delayOffset, float x, float y) { - if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) { + if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) { mHasPerformedLongPress = false; if (mPendingCheckForLongPress == null) { @@ -21039,6 +21115,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } mPendingCheckForLongPress.setAnchor(x, y); mPendingCheckForLongPress.rememberWindowAttachCount(); + mPendingCheckForLongPress.rememberPressedState(); postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout() - delayOffset); } @@ -22439,10 +22516,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private int mOriginalWindowAttachCount; private float mX; private float mY; + private boolean mOriginalPressedState; @Override public void run() { - if (isPressed() && (mParent != null) + if ((mOriginalPressedState == isPressed()) && (mParent != null) && mOriginalWindowAttachCount == mWindowAttachCount) { if (performLongClick(mX, mY)) { mHasPerformedLongPress = true; @@ -22458,6 +22536,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public void rememberWindowAttachCount() { mOriginalWindowAttachCount = mWindowAttachCount; } + + public void rememberPressedState() { + mOriginalPressedState = isPressed(); + } } private final class CheckForTap implements Runnable { @@ -23246,6 +23328,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ public Surface mDragSurface; + + /** + * The view that currently has a tooltip displayed. + */ + View mTooltipHost; + /** * Creates a new set of attachment information with the specified * events handler and thread. @@ -23982,4 +24070,167 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return mAttachInfo.mTmpLocation[0] == insets.getStableInsetLeft() && mAttachInfo.mTmpLocation[1] == insets.getStableInsetTop(); } + + /** + * Sets the tooltip text which will be displayed in a small popup next to the view. + * <p> + * The tooltip will be displayed: + * <li>On long click, unless is not handled otherwise (by OnLongClickListener or a context + * menu). </li> + * <li>On hover, after a brief delay since the pointer has stopped moving </li> + * + * @param tooltip the tooltip text, or null if no tooltip is required + */ + public final void setTooltip(@Nullable CharSequence tooltip) { + if (TextUtils.isEmpty(tooltip)) { + setFlags(0, TOOLTIP); + hideTooltip(); + mTooltipInfo = null; + } else { + setFlags(TOOLTIP, TOOLTIP); + if (mTooltipInfo == null) { + mTooltipInfo = new TooltipInfo(); + mTooltipInfo.mShowTooltipRunnable = this::showHoverTooltip; + mTooltipInfo.mHideTooltipRunnable = this::hideTooltip; + } + mTooltipInfo.mTooltip = tooltip; + if (mTooltipInfo.mTooltipPopup != null && mTooltipInfo.mTooltipPopup.isShowing()) { + mTooltipInfo.mTooltipPopup.updateContent(mTooltipInfo.mTooltip); + } + } + } + + /** + * Returns the view's tooltip text. + * + * @return the tooltip text + */ + @Nullable + public final CharSequence getTooltip() { + return mTooltipInfo != null ? mTooltipInfo.mTooltip : null; + } + + private boolean showTooltip(int x, int y, boolean fromLongClick) { + if (mAttachInfo == null) { + return false; + } + if ((mViewFlags & ENABLED_MASK) != ENABLED) { + return false; + } + final CharSequence tooltipText = getTooltip(); + if (TextUtils.isEmpty(tooltipText)) { + return false; + } + hideTooltip(); + mTooltipInfo.mTooltipFromLongClick = fromLongClick; + mTooltipInfo.mTooltipPopup = new TooltipPopup(getContext()); + mTooltipInfo.mTooltipPopup.show(this, x, y, tooltipText); + mAttachInfo.mTooltipHost = this; + return true; + } + + void hideTooltip() { + if (mTooltipInfo == null) { + return; + } + removeCallbacks(mTooltipInfo.mShowTooltipRunnable); + if (mTooltipInfo.mTooltipPopup == null) { + return; + } + mTooltipInfo.mTooltipPopup.hide(); + mTooltipInfo.mTooltipPopup = null; + mTooltipInfo.mTooltipFromLongClick = false; + if (mAttachInfo != null) { + mAttachInfo.mTooltipHost = null; + } + } + + private boolean showLongClickTooltip(int x, int y) { + removeCallbacks(mTooltipInfo.mShowTooltipRunnable); + removeCallbacks(mTooltipInfo.mHideTooltipRunnable); + return showTooltip(x, y, true); + } + + private void showHoverTooltip() { + showTooltip(mTooltipInfo.mAnchorX, mTooltipInfo.mAnchorY, false); + } + + boolean dispatchTooltipHoverEvent(MotionEvent event) { + if (mTooltipInfo == null) { + return false; + } + switch(event.getAction()) { + case MotionEvent.ACTION_HOVER_MOVE: + if ((mViewFlags & TOOLTIP) != TOOLTIP || (mViewFlags & ENABLED_MASK) != ENABLED) { + break; + } + if (!mTooltipInfo.mTooltipFromLongClick) { + if (mTooltipInfo.mTooltipPopup == null) { + // Schedule showing the tooltip after a timeout. + mTooltipInfo.mAnchorX = (int) event.getX(); + mTooltipInfo.mAnchorY = (int) event.getY(); + removeCallbacks(mTooltipInfo.mShowTooltipRunnable); + postDelayed(mTooltipInfo.mShowTooltipRunnable, + ViewConfiguration.getHoverTooltipShowTimeout()); + } + + // Hide hover-triggered tooltip after a period of inactivity. + // Match the timeout used by NativeInputManager to hide the mouse pointer + // (depends on SYSTEM_UI_FLAG_LOW_PROFILE being set). + final int timeout; + if ((getWindowSystemUiVisibility() & SYSTEM_UI_FLAG_LOW_PROFILE) + == SYSTEM_UI_FLAG_LOW_PROFILE) { + timeout = ViewConfiguration.getHoverTooltipHideShortTimeout(); + } else { + timeout = ViewConfiguration.getHoverTooltipHideTimeout(); + } + removeCallbacks(mTooltipInfo.mHideTooltipRunnable); + postDelayed(mTooltipInfo.mHideTooltipRunnable, timeout); + } + return true; + + case MotionEvent.ACTION_HOVER_EXIT: + if (!mTooltipInfo.mTooltipFromLongClick) { + hideTooltip(); + } + break; + } + return false; + } + + void handleTooltipKey(KeyEvent event) { + switch (event.getAction()) { + case KeyEvent.ACTION_DOWN: + if (event.getRepeatCount() == 0) { + hideTooltip(); + } + break; + + case KeyEvent.ACTION_UP: + handleTooltipUp(); + break; + } + } + + private void handleTooltipUp() { + if (mTooltipInfo == null || mTooltipInfo.mTooltipPopup == null) { + return; + } + removeCallbacks(mTooltipInfo.mHideTooltipRunnable); + postDelayed(mTooltipInfo.mHideTooltipRunnable, + ViewConfiguration.getLongPressTooltipHideTimeout()); + } + + /** + * @return The content view of the tooltip popup currently being shown, or null if the tooltip + * is not showing. + * @hide + */ + @TestApi + public View getTooltipView() { + if (mTooltipInfo == null || mTooltipInfo.mTooltipPopup == null) { + return null; + } + return mTooltipInfo.mTooltipPopup.getContentView(); + } } diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java index 33b488fbc6b4..6d2f850b94f4 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -16,6 +16,7 @@ package android.view; +import android.annotation.TestApi; import android.app.AppGlobals; import android.content.Context; import android.content.res.Configuration; @@ -230,6 +231,29 @@ public class ViewConfiguration { private static final long ACTION_MODE_HIDE_DURATION_DEFAULT = 2000; /** + * Defines the duration in milliseconds before an end of a long press causes a tooltip to be + * hidden. + */ + private static final int LONG_PRESS_TOOLTIP_HIDE_TIMEOUT = 1500; + + /** + * Defines the duration in milliseconds before a hover event causes a tooltip to be shown. + */ + private static final int HOVER_TOOLTIP_SHOW_TIMEOUT = 500; + + /** + * Defines the duration in milliseconds before mouse inactivity causes a tooltip to be hidden. + * (default variant to be used when {@link View#SYSTEM_UI_FLAG_LOW_PROFILE} is not set). + */ + private static final int HOVER_TOOLTIP_HIDE_TIMEOUT = 15000; + + /** + * Defines the duration in milliseconds before mouse inactivity causes a tooltip to be hidden + * (short version to be used when {@link View#SYSTEM_UI_FLAG_LOW_PROFILE} is set). + */ + private static final int HOVER_TOOLTIP_HIDE_SHORT_TIMEOUT = 3000; + + /** * Configuration values for overriding {@link #hasPermanentMenuKey()} behavior. * These constants must match the definition in res/values/config.xml. */ @@ -800,4 +824,43 @@ public class ViewConfiguration { public boolean isFadingMarqueeEnabled() { return mFadingMarqueeEnabled; } + + /** + * @return the duration in milliseconds before an end of a long press causes a tooltip to be + * hidden + * @hide + */ + @TestApi + public static int getLongPressTooltipHideTimeout() { + return LONG_PRESS_TOOLTIP_HIDE_TIMEOUT; + } + + /** + * @return the duration in milliseconds before a hover event causes a tooltip to be shown + * @hide + */ + @TestApi + public static int getHoverTooltipShowTimeout() { + return HOVER_TOOLTIP_SHOW_TIMEOUT; + } + + /** + * @return the duration in milliseconds before mouse inactivity causes a tooltip to be hidden + * (default variant to be used when {@link View#SYSTEM_UI_FLAG_LOW_PROFILE} is not set). + * @hide + */ + @TestApi + public static int getHoverTooltipHideTimeout() { + return HOVER_TOOLTIP_HIDE_TIMEOUT; + } + + /** + * @return the duration in milliseconds before mouse inactivity causes a tooltip to be hidden + * (shorter variant to be used when {@link View#SYSTEM_UI_FLAG_LOW_PROFILE} is set). + * @hide + */ + @TestApi + public static int getHoverTooltipHideShortTimeout() { + return HOVER_TOOLTIP_HIDE_SHORT_TIMEOUT; + } } diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index e39cb96ce59e..c0191ce0b791 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -199,6 +199,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager // It might not have actually handled the hover event. private boolean mHoveredSelf; + // The child capable of showing a tooltip and currently under the pointer. + private View mTooltipHoverTarget; + + // True if the view group is capable of showing a tooltip and the pointer is directly + // over the view group but not one of its child views. + private boolean mTooltipHoveredSelf; + /** * Internal flags. * @@ -1970,6 +1977,104 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } + @Override + boolean dispatchTooltipHoverEvent(MotionEvent event) { + final int action = event.getAction(); + switch (action) { + case MotionEvent.ACTION_HOVER_ENTER: + break; + + case MotionEvent.ACTION_HOVER_MOVE: + View newTarget = null; + + // Check what the child under the pointer says about the tooltip. + final int childrenCount = mChildrenCount; + if (childrenCount != 0) { + final float x = event.getX(); + final float y = event.getY(); + + final ArrayList<View> preorderedList = buildOrderedChildList(); + final boolean customOrder = preorderedList == null + && isChildrenDrawingOrderEnabled(); + final View[] children = mChildren; + for (int i = childrenCount - 1; i >= 0; i--) { + final int childIndex = + getAndVerifyPreorderedIndex(childrenCount, i, customOrder); + final View child = + getAndVerifyPreorderedView(preorderedList, children, childIndex); + final PointF point = getLocalPoint(); + if (isTransformedTouchPointInView(x, y, child, point)) { + if (dispatchTooltipHoverEvent(event, child)) { + newTarget = child; + } + break; + } + } + if (preorderedList != null) preorderedList.clear(); + } + + if (mTooltipHoverTarget != newTarget) { + if (mTooltipHoverTarget != null) { + event.setAction(MotionEvent.ACTION_HOVER_EXIT); + mTooltipHoverTarget.dispatchTooltipHoverEvent(event); + event.setAction(action); + } + mTooltipHoverTarget = newTarget; + } + + if (mTooltipHoverTarget != null) { + if (mTooltipHoveredSelf) { + mTooltipHoveredSelf = false; + event.setAction(MotionEvent.ACTION_HOVER_EXIT); + super.dispatchTooltipHoverEvent(event); + event.setAction(action); + } + return true; + } + + mTooltipHoveredSelf = super.dispatchTooltipHoverEvent(event); + return mTooltipHoveredSelf; + + case MotionEvent.ACTION_HOVER_EXIT: + if (mTooltipHoverTarget != null) { + mTooltipHoverTarget.dispatchTooltipHoverEvent(event); + mTooltipHoverTarget = null; + } else if (mTooltipHoveredSelf) { + super.dispatchTooltipHoverEvent(event); + mTooltipHoveredSelf = false; + } + break; + } + return false; + } + + private boolean dispatchTooltipHoverEvent(MotionEvent event, View child) { + final boolean result; + if (!child.hasIdentityMatrix()) { + MotionEvent transformedEvent = getTransformedMotionEvent(event, child); + result = child.dispatchTooltipHoverEvent(transformedEvent); + transformedEvent.recycle(); + } else { + final float offsetX = mScrollX - child.mLeft; + final float offsetY = mScrollY - child.mTop; + event.offsetLocation(offsetX, offsetY); + result = child.dispatchTooltipHoverEvent(event); + event.offsetLocation(-offsetX, -offsetY); + } + return result; + } + + private void exitTooltipHoverTargets() { + if (mTooltipHoveredSelf || mTooltipHoverTarget != null) { + final long now = SystemClock.uptimeMillis(); + MotionEvent event = MotionEvent.obtain(now, now, + MotionEvent.ACTION_HOVER_EXIT, 0.0f, 0.0f, 0); + event.setSource(InputDevice.SOURCE_TOUCHSCREEN); + dispatchTooltipHoverEvent(event); + event.recycle(); + } + } + /** @hide */ @Override protected boolean hasHoveredChild() { @@ -3186,6 +3291,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager // Similarly, set ACTION_EXIT to all hover targets and clear them. exitHoverTargets(); + exitTooltipHoverTargets(); // In case view is detached while transition is running mLayoutCalledWhileSuppressed = false; diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 1ff8fb0bd8ee..e030e767732e 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -3589,6 +3589,10 @@ public final class ViewRootImpl implements ViewParent, mAttachInfo.mKeyDispatchState.reset(); mView.dispatchWindowFocusChanged(hasWindowFocus); mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus); + + if (mAttachInfo.mTooltipHost != null) { + mAttachInfo.mTooltipHost.hideTooltip(); + } } // Note: must be done after the focus change callbacks, @@ -4206,6 +4210,10 @@ public final class ViewRootImpl implements ViewParent, private int processKeyEvent(QueuedInputEvent q) { final KeyEvent event = (KeyEvent)q.mEvent; + if (mAttachInfo.mTooltipHost != null) { + mAttachInfo.mTooltipHost.handleTooltipKey(event); + } + // If the key's purpose is to exit touch mode then we consume it // and consider it handled. if (checkForLeavingTouchModeAndConsume(event)) { @@ -4232,6 +4240,10 @@ public final class ViewRootImpl implements ViewParent, ensureTouchMode(true); } + if (action == MotionEvent.ACTION_DOWN && mAttachInfo.mTooltipHost != null) { + mAttachInfo.mTooltipHost.hideTooltip(); + } + // Offset the scroll position. if (mCurScrollY != 0) { event.offsetLocation(0, mCurScrollY); @@ -4425,6 +4437,7 @@ public final class ViewRootImpl implements ViewParent, mAttachInfo.mHandlingPointerEvent = true; boolean handled = eventTarget.dispatchPointerEvent(event); maybeUpdatePointerIcon(event); + maybeUpdateTooltip(event); mAttachInfo.mHandlingPointerEvent = false; if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) { mUnbufferedInputDispatch = true; @@ -4512,6 +4525,27 @@ public final class ViewRootImpl implements ViewParent, return true; } + private void maybeUpdateTooltip(MotionEvent event) { + if (event.getPointerCount() != 1) { + return; + } + final int action = event.getActionMasked(); + if (action != MotionEvent.ACTION_HOVER_ENTER + && action != MotionEvent.ACTION_HOVER_MOVE + && action != MotionEvent.ACTION_HOVER_EXIT) { + return; + } + AccessibilityManager manager = AccessibilityManager.getInstance(mContext); + if (manager.isEnabled() && manager.isTouchExplorationEnabled()) { + return; + } + if (mView == null) { + Slog.d(mTag, "maybeUpdateTooltip called after view was removed"); + return; + } + mView.dispatchTooltipHoverEvent(event); + } + /** * Performs synthesis of new input events from unhandled input events. */ |
