diff options
-rw-r--r-- | api/current.txt | 3 | ||||
-rw-r--r-- | api/system-current.txt | 3 | ||||
-rw-r--r-- | api/test-current.txt | 8 | ||||
-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 | ||||
-rw-r--r-- | core/java/com/android/internal/view/TooltipPopup.java | 137 | ||||
-rw-r--r-- | core/res/res/anim/tooltip_enter.xml | 23 | ||||
-rw-r--r-- | core/res/res/anim/tooltip_exit.xml | 23 | ||||
-rw-r--r-- | core/res/res/drawable/tooltip_frame.xml | 21 | ||||
-rw-r--r-- | core/res/res/layout/tooltip.xml | 40 | ||||
-rw-r--r-- | core/res/res/values/attrs.xml | 14 | ||||
-rw-r--r-- | core/res/res/values/colors.xml | 3 | ||||
-rw-r--r-- | core/res/res/values/config.xml | 3 | ||||
-rw-r--r-- | core/res/res/values/dimens.xml | 15 | ||||
-rw-r--r-- | core/res/res/values/public.xml | 1 | ||||
-rw-r--r-- | core/res/res/values/strings.xml | 3 | ||||
-rw-r--r-- | core/res/res/values/styles.xml | 10 | ||||
-rw-r--r-- | core/res/res/values/symbols.xml | 5 | ||||
-rw-r--r-- | core/res/res/values/themes.xml | 9 | ||||
-rw-r--r-- | core/res/res/values/themes_material.xml | 8 |
22 files changed, 808 insertions, 27 deletions
diff --git a/api/current.txt b/api/current.txt index 9d93eebe1d54..a38ff53577b4 100644 --- a/api/current.txt +++ b/api/current.txt @@ -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); diff --git a/api/system-current.txt b/api/system-current.txt index e8874647ce26..bcf4897c8661 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -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); diff --git a/api/test-current.txt b/api/test-current.txt index 0a76d78ef8cc..fa75e9b4d0a5 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -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(); 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. */ diff --git a/core/java/com/android/internal/view/TooltipPopup.java b/core/java/com/android/internal/view/TooltipPopup.java new file mode 100644 index 000000000000..4f48b9645c69 --- /dev/null +++ b/core/java/com/android/internal/view/TooltipPopup.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.view; + +import android.content.Context; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.WindowManager; +import android.widget.TextView; + +public class TooltipPopup { + private final Context mContext; + + private final View mContentView; + private final TextView mMessageView; + + private final WindowManager.LayoutParams mLayoutParams = new WindowManager.LayoutParams(); + private final Rect mTmpDisplayFrame = new Rect(); + private final int[] mTmpAnchorPos = new int[2]; + + public TooltipPopup(Context context) { + mContext = context; + + mContentView = LayoutInflater.from(mContext).inflate( + com.android.internal.R.layout.tooltip, null); + mMessageView = (TextView) mContentView.findViewById( + com.android.internal.R.id.message); + + mLayoutParams.setTitle( + mContext.getString(com.android.internal.R.string.tooltip_popup_title)); + mLayoutParams.packageName = mContext.getOpPackageName(); + mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL; + mLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT; + mLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT; + mLayoutParams.format = PixelFormat.TRANSLUCENT; + mLayoutParams.windowAnimations = com.android.internal.R.style.Animation_Tooltip; + mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; + } + + public void show(View anchorView, int anchorX, int anchorY, CharSequence tooltipText) { + if (isShowing()) { + hide(); + } + + mMessageView.setText(tooltipText); + + computePosition(anchorView, anchorX, anchorY, mLayoutParams); + + WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); + wm.addView(mContentView, mLayoutParams); + } + + public void hide() { + if (!isShowing()) { + return; + } + + WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); + wm.removeView(mContentView); + } + + public View getContentView() { + return mContentView; + } + + public boolean isShowing() { + return mContentView.getParent() != null; + } + + public void updateContent(CharSequence tooltipText) { + mMessageView.setText(tooltipText); + } + + private void computePosition(View anchorView, int anchorX, int anchorY, + WindowManager.LayoutParams outParams) { + final int tooltipPreciseAnchorThreshold = mContext.getResources().getDimensionPixelOffset( + com.android.internal.R.dimen.tooltip_precise_anchor_threshold); + + final int offsetX; + if (anchorView.getWidth() >= tooltipPreciseAnchorThreshold) { + // Wide view. Align the tooltip horizontally to the precise X position. + offsetX = anchorX; + } else { + // Otherwise anchor the tooltip to the view center. + offsetX = anchorView.getWidth() / 2; // Center on the view horizontally. + } + + final int offsetBelow; + final int offsetAbove; + if (anchorView.getHeight() >= tooltipPreciseAnchorThreshold) { + // Tall view. Align the tooltip vertically to the precise Y position. + offsetBelow = anchorY; + offsetAbove = anchorY; + } else { + // Otherwise anchor the tooltip to the view center. + offsetBelow = anchorView.getHeight(); // Place below the view in most cases. + offsetAbove = 0; // Place above the view if the tooltip does not fit below. + } + + outParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP; + + final int tooltipOffset = mContext.getResources().getDimensionPixelOffset( + com.android.internal.R.dimen.tooltip_y_offset); + + anchorView.getWindowVisibleDisplayFrame(mTmpDisplayFrame); + anchorView.getLocationInWindow(mTmpAnchorPos); + outParams.x = mTmpAnchorPos[0] + offsetX - mTmpDisplayFrame.width() / 2; + outParams.y = mTmpAnchorPos[1] + offsetBelow + tooltipOffset; + + final int spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); + mContentView.measure(spec, spec); + final int tooltipHeight = mContentView.getMeasuredHeight(); + + if (outParams.y + tooltipHeight > mTmpDisplayFrame.height()) { + // The tooltip does not fit below the anchor point, show above instead. + outParams.y = mTmpAnchorPos[1] + offsetAbove - (tooltipOffset + tooltipHeight); + } + } +} diff --git a/core/res/res/anim/tooltip_enter.xml b/core/res/res/anim/tooltip_enter.xml new file mode 100644 index 000000000000..7eceb4c8c86b --- /dev/null +++ b/core/res/res/anim/tooltip_enter.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2016, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<alpha xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@interpolator/decelerate_quad" + android:fromAlpha="0.0" android:toAlpha="1.0" + android:duration="@android:integer/config_tooltipAnimTime" /> diff --git a/core/res/res/anim/tooltip_exit.xml b/core/res/res/anim/tooltip_exit.xml new file mode 100644 index 000000000000..e346ca943eaf --- /dev/null +++ b/core/res/res/anim/tooltip_exit.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2016, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<alpha xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@interpolator/accelerate_quad" + android:fromAlpha="1.0" android:toAlpha="0.0" + android:duration="@android:integer/config_tooltipAnimTime" /> diff --git a/core/res/res/drawable/tooltip_frame.xml b/core/res/res/drawable/tooltip_frame.xml new file mode 100644 index 000000000000..14130c899e96 --- /dev/null +++ b/core/res/res/drawable/tooltip_frame.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <solid android:color="?attr/tooltipBackgroundColor" /> + <corners android:radius="@dimen/tooltip_corner_radius" /> +</shape>
\ No newline at end of file diff --git a/core/res/res/layout/tooltip.xml b/core/res/res/layout/tooltip.xml new file mode 100644 index 000000000000..0aa6a8781d91 --- /dev/null +++ b/core/res/res/layout/tooltip.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2016, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <TextView + android:id="@android:id/message" + android:layout_width="wrap_content" + android:layout_height="@dimen/tooltip_height" + android:layout_margin="@dimen/tooltip_margin" + android:paddingStart="@dimen/tooltip_horizontal_padding" + android:paddingEnd="@dimen/tooltip_horizontal_padding" + android:gravity="center" + android:background="?android:attr/tooltipFrameBackground" + android:textAppearance="@style/TextAppearance.Tooltip" + android:textColor="?android:attr/tooltipForegroundColor" + android:singleLine="true" + android:ellipsize="end" + /> + +</LinearLayout> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 2d61e6ec3bc5..acafbcfd428a 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -582,7 +582,7 @@ <!-- Image elements --> <!-- ============== --> <eat-comment /> -i + <!-- Background that can be used behind parts of a UI that provide details on data the user is selecting. For example, this is the background element of PreferenceActivity's embedded @@ -1008,6 +1008,15 @@ i <!-- Background to use for toasts --> <attr name="toastFrameBackground" format="reference" /> + <!-- Background to use for tooltip popups --> + <attr name="tooltipFrameBackground" format="reference" /> + + <!-- Foreground color to use for tooltip popups --> + <attr name="tooltipForegroundColor" format="reference|color" /> + + <!-- Background color to use for tooltip popups --> + <attr name="tooltipBackgroundColor" format="reference|color" /> + <!-- Theme to use for Search Dialogs --> <attr name="searchDialogTheme" format="reference" /> @@ -2865,6 +2874,9 @@ i {@link android.view.View#forceHasOverlappingRendering(boolean)}. --> <attr name="forceHasOverlappingRendering" format="boolean" /> + <!-- Defines text displayed in a small popup window on hover or long press. --> + <attr name="tooltip" format="string" localization="suggested" /> + </declare-styleable> <!-- Attributes that can be assigned to a tag for a particular View. --> diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index de86cefb5bc7..0995bc3f0799 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -186,4 +186,7 @@ <color name="resize_shadow_start_color">#2a000000</color> <color name="resize_shadow_end_color">#00000000</color> + + <color name="tooltip_background_dark">#e6616161</color> + <color name="tooltip_background_light">#e6FFFFFF</color> </resources> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index d4119d072a46..a7c5b2ab3c80 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -128,6 +128,9 @@ <integer name="config_activityShortDur">150</integer> <integer name="config_activityDefaultDur">220</integer> + <!-- The duration (in milliseconds) of the tooltip show/hide animations. --> + <integer name="config_tooltipAnimTime">150</integer> + <!-- Duration for the dim animation behind a dialog. This may be either a percentage, which is relative to the duration of the enter/open animation of the window being shown that is dimming behind, or it may diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 91d7227c6a60..5efa55ca1138 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -487,4 +487,19 @@ <!-- Minimum "smallest width" of the display for cascading menus to be enabled. --> <dimen name="cascading_menus_min_smallest_width">720dp</dimen> + + <!-- Tooltip dimensions. --> + <!-- Vertical offset from the edge of the anchor view. --> + <dimen name="tooltip_y_offset">20dp</dimen> + <!-- Height of the tooltip. --> + <dimen name="tooltip_height">32dp</dimen> + <!-- The tooltip does not get closer than this to the window edge --> + <dimen name="tooltip_margin">8dp</dimen> + <!-- Left/right padding of the tooltip text. --> + <dimen name="tooltip_horizontal_padding">16dp</dimen> + <!-- Border corner radius of the tooltip window. --> + <dimen name="tooltip_corner_radius">2dp</dimen> + <!-- View with the height equal or above this threshold will have a tooltip anchored + to the mouse/touch position --> + <dimen name="tooltip_precise_anchor_threshold">96dp</dimen> </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index ed685822ee51..ae82128b88cf 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2759,6 +2759,7 @@ <public name="fontStyle" /> <public name="font" /> <public name="fontWeight" /> + <public name="tooltip" /> </public-group> <public-group type="style" first-id="0x010302e0"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index d42ec9067ac4..baf3cd817d5b 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -4450,4 +4450,7 @@ <!-- Label used by Telephony code, assigned as the display name for conference calls [CHAR LIMIT=60] --> <string name="conference_call">Conference Call</string> + + <!-- Title for a tooltip popup window [CHAR LIMIT=NONE] --> + <string name="tooltip_popup_title">Tooltip Popup</string> </resources> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 937428b12c60..0f756b94a321 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -159,6 +159,11 @@ please see styles_device_defaults.xml. <item name="windowExitAnimation">@anim/toast_exit</item> </style> + <style name="Animation.Tooltip"> + <item name="windowEnterAnimation">@anim/tooltip_enter</item> + <item name="windowExitAnimation">@anim/tooltip_exit</item> + </style> + <style name="Animation.DropDownDown"> <item name="windowEnterAnimation">@anim/grow_fade_in</item> <item name="windowExitAnimation">@anim/shrink_fade_out</item> @@ -950,6 +955,11 @@ please see styles_device_defaults.xml. <item name="fontFamily">sans-serif-condensed</item> </style> + <style name="TextAppearance.Tooltip"> + <item name="fontFamily">sans-serif</item> + <item name="textSize">14sp</item> + </style> + <style name="Widget.ActivityChooserView"> <item name="gravity">center</item> <item name="background">@drawable/ab_share_pack_holo_dark</item> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index c719664c38a8..5b608b84c51d 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -435,6 +435,8 @@ <java-symbol type="dimen" name="search_view_preferred_height" /> <java-symbol type="dimen" name="textview_error_popup_default_width" /> <java-symbol type="dimen" name="toast_y_offset" /> + <java-symbol type="dimen" name="tooltip_precise_anchor_threshold" /> + <java-symbol type="dimen" name="tooltip_y_offset" /> <java-symbol type="dimen" name="action_bar_stacked_max_height" /> <java-symbol type="dimen" name="action_bar_stacked_tab_max_width" /> <java-symbol type="dimen" name="notification_text_size" /> @@ -1123,6 +1125,7 @@ <java-symbol type="string" name="demo_starting_message" /> <java-symbol type="string" name="demo_restarting_message" /> <java-symbol type="string" name="conference_call" /> + <java-symbol type="string" name="tooltip_popup_title" /> <java-symbol type="plurals" name="bugreport_countdown" /> @@ -1373,6 +1376,7 @@ <java-symbol type="layout" name="textview_hint" /> <java-symbol type="layout" name="time_picker_legacy" /> <java-symbol type="layout" name="time_picker_dialog" /> + <java-symbol type="layout" name="tooltip" /> <java-symbol type="layout" name="transient_notification" /> <java-symbol type="layout" name="voice_interaction_session" /> <java-symbol type="layout" name="web_text_view_dropdown" /> @@ -1423,6 +1427,7 @@ <java-symbol type="style" name="Animation.DropDownUp" /> <java-symbol type="style" name="Animation.DropDownDown" /> <java-symbol type="style" name="Animation.PopupWindow" /> + <java-symbol type="style" name="Animation.Tooltip" /> <java-symbol type="style" name="Animation.TypingFilter" /> <java-symbol type="style" name="Animation.TypingFilterRestore" /> <java-symbol type="style" name="Animation.Dream" /> diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index 5b2522f97c8b..357eb4b88c52 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -439,6 +439,11 @@ please see themes_device_defaults.xml. <item name="lightRadius">@dimen/light_radius</item> <item name="ambientShadowAlpha">@dimen/ambient_shadow_alpha</item> <item name="spotShadowAlpha">@dimen/spot_shadow_alpha</item> + + <!-- Tooltip popup properties --> + <item name="tooltipFrameBackground">@drawable/tooltip_frame</item> + <item name="tooltipForegroundColor">@color/bright_foreground_light</item> + <item name="tooltipBackgroundColor">@color/tooltip_background_light</item> </style> <!-- Variant of {@link #Theme} with no title bar --> @@ -553,6 +558,10 @@ please see themes_device_defaults.xml. <item name="floatingToolbarItemBackgroundDrawable">@drawable/item_background_material_light</item> <item name="floatingToolbarOpenDrawable">@drawable/ic_menu_moreoverflow_material_light</item> <item name="floatingToolbarPopupBackgroundDrawable">@drawable/floating_popup_background_light</item> + + <!-- Tooltip popup colors --> + <item name="tooltipForegroundColor">@color/bright_foreground_dark</item> + <item name="tooltipBackgroundColor">@color/tooltip_background_dark</item> </style> <!-- Variant of {@link #Theme_Light} with no title bar --> diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml index 0de773bc3724..92bb3ea4681d 100644 --- a/core/res/res/values/themes_material.xml +++ b/core/res/res/values/themes_material.xml @@ -398,6 +398,10 @@ please see themes_device_defaults.xml. <item name="colorControlHighlight">@color/ripple_material_dark</item> <item name="colorButtonNormal">@color/btn_default_material_dark</item> <item name="colorSwitchThumbNormal">@color/switch_thumb_material_dark</item> + + <!-- Tooltip popup properties --> + <item name="tooltipForegroundColor">@color/foreground_material_light</item> + <item name="tooltipBackgroundColor">@color/tooltip_background_light</item> </style> <!-- Material theme (light version). --> @@ -762,6 +766,10 @@ please see themes_device_defaults.xml. <item name="colorControlHighlight">@color/ripple_material_light</item> <item name="colorButtonNormal">@color/btn_default_material_light</item> <item name="colorSwitchThumbNormal">@color/switch_thumb_material_light</item> + + <!-- Tooltip popup properties --> + <item name="tooltipForegroundColor">@color/foreground_material_dark</item> + <item name="tooltipBackgroundColor">@color/tooltip_background_dark</item> </style> <!-- Variant of the material (light) theme that has a solid (opaque) action bar |