Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 44efac31 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Implement tooltip support in View"

parents a53e2bc3 f847ee3c
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -1357,6 +1357,7 @@ package android {
    field public static final int toYDelta = 16843209; // 0x10101c9
    field public static final int toYScale = 16843205; // 0x10101c5
    field public static final int toolbarStyle = 16843946; // 0x10104aa
    field public static final int tooltip = 16844084; // 0x1010534
    field public static final int top = 16843182; // 0x10101ae
    field public static final int topBright = 16842955; // 0x10100cb
    field public static final int topDark = 16842951; // 0x10100c7
@@ -42833,6 +42834,7 @@ package android.view {
    method public java.lang.Object getTag(int);
    method public int getTextAlignment();
    method public int getTextDirection();
    method public final java.lang.CharSequence getTooltip();
    method public final int getTop();
    method protected float getTopFadingEdgeStrength();
    method protected int getTopPaddingOffset();
@@ -43121,6 +43123,7 @@ package android.view {
    method public void setTag(int, java.lang.Object);
    method public void setTextAlignment(int);
    method public void setTextDirection(int);
    method public final void setTooltip(java.lang.CharSequence);
    method public final void setTop(int);
    method public void setTouchDelegate(android.view.TouchDelegate);
    method public final void setTransitionName(java.lang.String);
+3 −0
Original line number Diff line number Diff line
@@ -1468,6 +1468,7 @@ package android {
    field public static final int toYDelta = 16843209; // 0x10101c9
    field public static final int toYScale = 16843205; // 0x10101c5
    field public static final int toolbarStyle = 16843946; // 0x10104aa
    field public static final int tooltip = 16844084; // 0x1010534
    field public static final int top = 16843182; // 0x10101ae
    field public static final int topBright = 16842955; // 0x10100cb
    field public static final int topDark = 16842951; // 0x10100c7
@@ -45997,6 +45998,7 @@ package android.view {
    method public java.lang.Object getTag(int);
    method public int getTextAlignment();
    method public int getTextDirection();
    method public final java.lang.CharSequence getTooltip();
    method public final int getTop();
    method protected float getTopFadingEdgeStrength();
    method protected int getTopPaddingOffset();
@@ -46285,6 +46287,7 @@ package android.view {
    method public void setTag(int, java.lang.Object);
    method public void setTextAlignment(int);
    method public void setTextDirection(int);
    method public final void setTooltip(java.lang.CharSequence);
    method public final void setTop(int);
    method public void setTouchDelegate(android.view.TouchDelegate);
    method public final void setTransitionName(java.lang.String);
+8 −0
Original line number Diff line number Diff line
@@ -1357,6 +1357,7 @@ package android {
    field public static final int toYDelta = 16843209; // 0x10101c9
    field public static final int toYScale = 16843205; // 0x10101c5
    field public static final int toolbarStyle = 16843946; // 0x10104aa
    field public static final int tooltip = 16844084; // 0x1010534
    field public static final int top = 16843182; // 0x10101ae
    field public static final int topBright = 16842955; // 0x10100cb
    field public static final int topDark = 16842951; // 0x10100c7
@@ -43078,6 +43079,8 @@ package android.view {
    method public java.lang.Object getTag(int);
    method public int getTextAlignment();
    method public int getTextDirection();
    method public final java.lang.CharSequence getTooltip();
    method public android.view.View getTooltipView();
    method public final int getTop();
    method protected float getTopFadingEdgeStrength();
    method protected int getTopPaddingOffset();
@@ -43366,6 +43369,7 @@ package android.view {
    method public void setTag(int, java.lang.Object);
    method public void setTextAlignment(int);
    method public void setTextDirection(int);
    method public final void setTooltip(java.lang.CharSequence);
    method public final void setTop(int);
    method public void setTouchDelegate(android.view.TouchDelegate);
    method public final void setTransitionName(java.lang.String);
@@ -43648,10 +43652,14 @@ package android.view {
    method public static deprecated int getEdgeSlop();
    method public static deprecated int getFadingEdgeLength();
    method public static deprecated long getGlobalActionKeyTimeout();
    method public static int getHoverTooltipHideShortTimeout();
    method public static int getHoverTooltipHideTimeout();
    method public static int getHoverTooltipShowTimeout();
    method public static int getJumpTapTimeout();
    method public static int getKeyRepeatDelay();
    method public static int getKeyRepeatTimeout();
    method public static int getLongPressTimeout();
    method public static int getLongPressTooltipHideTimeout();
    method public static deprecated int getMaximumDrawingCacheSize();
    method public static deprecated int getMaximumFlingVelocity();
    method public static deprecated int getMinimumFlingVelocity();
+277 −26
Original line number Diff line number Diff line
@@ -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,19 +10652,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
                return true;
            }
            if (event.getRepeatCount() == 0) {
                // Long clickable items don't necessarily have to be clickable.
            if (((mViewFlags & CLICKABLE) == CLICKABLE
                    || (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                    && (event.getRepeatCount() == 0)) {
                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;
                }
            }
        }
        return false;
    }
@@ -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
@@ -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:
                    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:
                    if (clickable) {
                        drawableHotspotChanged(x, y);
                    }
                    // Be lenient about moving outside of buttons
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        removeTapCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                        // Remove any future long press/tap checks
                        removeTapCallback();
                        removeLongPressCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            setPressed(false);
                        }
                    }
@@ -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();
    }
}
+63 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.view;

import android.annotation.TestApi;
import android.app.AppGlobals;
import android.content.Context;
import android.content.res.Configuration;
@@ -229,6 +230,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;
    }
}
Loading