Introduce ViewCompat.setTooltip

This calls View.setTooltip on API26+. The fallback
implementation is a Toast-based tooltip.

Use the new method in ActionMenuItemView,
MediaRouteButton, ScrollingTabContainerView and
TabLayout.

Bug: 31516506
Test: manual in Support7Demos and SupportDesignDemos
Change-Id: I23832a2fd5b589769225a0542c2a0ce4778e673c
diff --git a/api/current.txt b/api/current.txt
index 50ba649..60b09bc 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6858,6 +6858,7 @@
     method public static void setScaleY(android.view.View, float);
     method public static void setScrollIndicators(android.view.View, int);
     method public static void setScrollIndicators(android.view.View, int, int);
+    method public static void setTooltip(android.view.View, java.lang.CharSequence);
     method public static void setTransitionName(android.view.View, java.lang.String);
     method public static void setTranslationX(android.view.View, float);
     method public static void setTranslationY(android.view.View, float);
diff --git a/compat/Android.mk b/compat/Android.mk
index ba5b958..9d8d258 100644
--- a/compat/Android.mk
+++ b/compat/Android.mk
@@ -42,6 +42,7 @@
     $(call all-java-files-under,api22) \
     $(call all-java-files-under,api23) \
     $(call all-java-files-under,api24) \
+    $(call all-java-files-under,api26) \
     $(call all-java-files-under,java) \
     $(call all-Iaidl-files-under,java)
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
diff --git a/compat/api26/android/support/v4/view/ViewCompatApi26.java b/compat/api26/android/support/v4/view/ViewCompatApi26.java
new file mode 100644
index 0000000..3e49404
--- /dev/null
+++ b/compat/api26/android/support/v4/view/ViewCompatApi26.java
@@ -0,0 +1,27 @@
+/**
+ * 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 android.support.v4.view;
+
+import android.support.annotation.RequiresApi;
+import android.view.View;
+
+@RequiresApi(26)
+class ViewCompatApi26 {
+    public static void setTooltip(View view, CharSequence tooltip) {
+        view.setTooltip(tooltip);
+    }
+}
diff --git a/compat/build.gradle b/compat/build.gradle
index 5722998..696c321 100644
--- a/compat/build.gradle
+++ b/compat/build.gradle
@@ -44,6 +44,7 @@
                 'api22',
                 'api23',
                 'api24',
+                'api26',
                 'java'
         ]
         main.aidl.srcDirs = ['java']
diff --git a/compat/java/android/support/v4/internal/view/TooltipCompat.java b/compat/java/android/support/v4/internal/view/TooltipCompat.java
new file mode 100644
index 0000000..aae4f48
--- /dev/null
+++ b/compat/java/android/support/v4/internal/view/TooltipCompat.java
@@ -0,0 +1,87 @@
+/*
+ * 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 android.support.v4.internal.view;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.support.annotation.RestrictTo;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.view.ViewCompat;
+import android.text.TextUtils;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.Toast;
+
+/**
+ * Toast-based popup used to emulate a tooltip on platform versions prior to API 26.
+ * Unlike the platform version of the tooltip, it is invoked only by a long press, but not
+ * mouse hover.
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public class TooltipCompat {
+
+    /**
+     * Set a tooltip on a view. The tooltip text will be displayed on long click,
+     * in a toast aligned with the view.
+     * @param view View to align to
+     * @param tooltip Tooltip text
+     */
+    public static void setTooltip(View view, final CharSequence tooltip) {
+        if (TextUtils.isEmpty(tooltip)) {
+            view.setOnLongClickListener(null);
+            view.setLongClickable(false);
+        } else {
+            view.setOnLongClickListener(new View.OnLongClickListener() {
+                @Override
+                public boolean onLongClick(View v) {
+                    showTooltipToast(v, tooltip);
+                    return true;
+                }
+            });
+        }
+    }
+
+    private static void showTooltipToast(View v, CharSequence tooltip) {
+        final int[] screenPos = new int[2];
+        final Rect displayFrame = new Rect();
+        v.getLocationOnScreen(screenPos);
+        v.getWindowVisibleDisplayFrame(displayFrame);
+
+        final Context context = v.getContext();
+        final int width = v.getWidth();
+        final int height = v.getHeight();
+        final int midy = screenPos[1] + height / 2;
+        int referenceX = screenPos[0] + width / 2;
+        if (ViewCompat.getLayoutDirection(v) == ViewCompat.LAYOUT_DIRECTION_LTR) {
+            final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
+            referenceX = screenWidth - referenceX; // mirror
+        }
+        Toast toast = Toast.makeText(context, tooltip, Toast.LENGTH_SHORT);
+        if (midy < displayFrame.height()) {
+            // Show along the top; follow action buttons
+            toast.setGravity(Gravity.TOP | GravityCompat.END, referenceX,
+                    screenPos[1] + height - displayFrame.top);
+        } else {
+            // Show along the bottom center
+            toast.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, height);
+        }
+        toast.show();
+    }
+}
diff --git a/compat/java/android/support/v4/view/ViewCompat.java b/compat/java/android/support/v4/view/ViewCompat.java
index 434b850..4dab42c 100644
--- a/compat/java/android/support/v4/view/ViewCompat.java
+++ b/compat/java/android/support/v4/view/ViewCompat.java
@@ -31,6 +31,7 @@
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
+import android.support.v4.internal.view.TooltipCompat;
 import android.support.v4.os.BuildCompat;
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
 import android.support.v4.view.accessibility.AccessibilityNodeProviderCompat;
@@ -488,6 +489,7 @@
         void offsetLeftAndRight(View view, int offset);
         void setPointerIcon(View view, PointerIconCompat pointerIcon);
         Display getDisplay(View view);
+        void setTooltip(View view, CharSequence tooltip);
     }
 
     static class BaseViewCompatImpl implements ViewCompatImpl {
@@ -1161,6 +1163,11 @@
         public Display getDisplay(View view) {
             return ViewCompatBase.getDisplay(view);
         }
+
+        @Override
+        public void setTooltip(View view, CharSequence tooltip) {
+            TooltipCompat.setTooltip(view, tooltip);
+        }
     }
 
     static class HCViewCompatImpl extends BaseViewCompatImpl {
@@ -1815,10 +1822,19 @@
         }
     }
 
+    static class Api26ViewCompatImpl extends Api24ViewCompatImpl {
+        @Override
+        public void setTooltip(View view, CharSequence tooltip) {
+            ViewCompatApi26.setTooltip(view, tooltip);
+        }
+    }
+
     static final ViewCompatImpl IMPL;
     static {
         final int version = android.os.Build.VERSION.SDK_INT;
-        if (BuildCompat.isAtLeastN()) {
+        if (BuildCompat.isAtLeastO()) {
+            IMPL = new Api26ViewCompatImpl();
+        } else if (version >= 24) {
             IMPL = new Api24ViewCompatImpl();
         } else if (version >= 23) {
             IMPL = new MarshmallowViewCompatImpl();
@@ -3581,5 +3597,20 @@
         return IMPL.getDisplay(view);
     }
 
+    /**
+     * Sets the tooltip for the view.
+     * <p>
+     * Compatibility:
+     * <ul>
+     * <li>API &lt; 26: Sets or clears (when tooltip is null) the view's OnLongClickListener.
+     * Creates a Toast on long click.
+     * </ul>
+     *
+     * @param tooltip the tooltip text
+     */
+    public static void setTooltip(@NonNull View view, @Nullable CharSequence tooltip) {
+        IMPL.setTooltip(view, tooltip);
+    }
+
     protected ViewCompat() {}
 }
diff --git a/design/src/android/support/design/widget/TabLayout.java b/design/src/android/support/design/widget/TabLayout.java
index 23a15e1..4d392f2 100755
--- a/design/src/android/support/design/widget/TabLayout.java
+++ b/design/src/android/support/design/widget/TabLayout.java
@@ -28,7 +28,6 @@
 import android.database.DataSetObserver;
 import android.graphics.Canvas;
 import android.graphics.Paint;
-import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.support.annotation.ColorInt;
@@ -65,7 +64,6 @@
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
-import android.widget.Toast;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -1485,7 +1483,7 @@
         }
     }
 
-    class TabView extends LinearLayout implements OnLongClickListener {
+    class TabView extends LinearLayout {
         private Tab mTab;
         private TextView mTextView;
         private ImageView mIconView;
@@ -1751,44 +1749,7 @@
                     iconView.requestLayout();
                 }
             }
-
-            if (!hasText && !TextUtils.isEmpty(contentDesc)) {
-                setOnLongClickListener(this);
-            } else {
-                setOnLongClickListener(null);
-                setLongClickable(false);
-            }
-        }
-
-        @Override
-        public boolean onLongClick(final View v) {
-            final int[] screenPos = new int[2];
-            final Rect displayFrame = new Rect();
-            getLocationOnScreen(screenPos);
-            getWindowVisibleDisplayFrame(displayFrame);
-
-            final Context context = getContext();
-            final int width = getWidth();
-            final int height = getHeight();
-            final int midy = screenPos[1] + height / 2;
-            int referenceX = screenPos[0] + width / 2;
-            if (ViewCompat.getLayoutDirection(v) == ViewCompat.LAYOUT_DIRECTION_LTR) {
-                final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
-                referenceX = screenWidth - referenceX; // mirror
-            }
-
-            Toast cheatSheet = Toast.makeText(context, mTab.getContentDescription(),
-                    Toast.LENGTH_SHORT);
-            if (midy < displayFrame.height()) {
-                // Show below the tab view
-                cheatSheet.setGravity(Gravity.TOP | GravityCompat.END, referenceX,
-                        screenPos[1] + height - displayFrame.top);
-            } else {
-                // Show along the bottom center
-                cheatSheet.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, height);
-            }
-            cheatSheet.show();
-            return true;
+            ViewCompat.setTooltip(this, hasText ? null : contentDesc);
         }
 
         public Tab getTab() {
diff --git a/v7/appcompat/src/android/support/v7/view/menu/ActionMenuItemView.java b/v7/appcompat/src/android/support/v7/view/menu/ActionMenuItemView.java
index 77f5f17..5f7aa3e 100644
--- a/v7/appcompat/src/android/support/v7/view/menu/ActionMenuItemView.java
+++ b/v7/appcompat/src/android/support/v7/view/menu/ActionMenuItemView.java
@@ -22,12 +22,10 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
-import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Parcelable;
 import android.support.annotation.RestrictTo;
 import android.support.v4.content.res.ConfigurationHelper;
-import android.support.v4.view.GravityCompat;
 import android.support.v4.view.ViewCompat;
 import android.support.v7.appcompat.R;
 import android.support.v7.widget.ActionMenuView;
@@ -35,18 +33,15 @@
 import android.support.v7.widget.ForwardingListener;
 import android.text.TextUtils;
 import android.util.AttributeSet;
-import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.View;
-import android.widget.Toast;
 
 /**
  * @hide
  */
 @RestrictTo(LIBRARY_GROUP)
 public class ActionMenuItemView extends AppCompatTextView
-        implements MenuView.ItemView, View.OnClickListener, View.OnLongClickListener,
-        ActionMenuView.ActionMenuChildView {
+        implements MenuView.ItemView, View.OnClickListener, ActionMenuView.ActionMenuChildView {
 
     private static final String TAG = "ActionMenuItemView";
 
@@ -87,7 +82,6 @@
         mMaxIconSize = (int) (MAX_ICON_SIZE * density + 0.5f);
 
         setOnClickListener(this);
-        setOnLongClickListener(this);
 
         mSavedPaddingLeft = -1;
         setSaveEnabled(false);
@@ -190,6 +184,9 @@
                 (mItemData.showsTextAsAction() && (mAllowTextWithIcon || mExpandedFormat));
 
         setText(visible ? mTitle : null);
+
+        // Show the tooltip for items that do not already show text.
+        ViewCompat.setTooltip(this, visible ? null : mTitle);
     }
 
     public void setIcon(Drawable icon) {
@@ -242,40 +239,6 @@
     }
 
     @Override
-    public boolean onLongClick(View v) {
-        if (hasText()) {
-            // Don't show the cheat sheet for items that already show text.
-            return false;
-        }
-
-        final int[] screenPos = new int[2];
-        final Rect displayFrame = new Rect();
-        getLocationOnScreen(screenPos);
-        getWindowVisibleDisplayFrame(displayFrame);
-
-        final Context context = getContext();
-        final int width = getWidth();
-        final int height = getHeight();
-        final int midy = screenPos[1] + height / 2;
-        int referenceX = screenPos[0] + width / 2;
-        if (ViewCompat.getLayoutDirection(v) == ViewCompat.LAYOUT_DIRECTION_LTR) {
-            final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
-            referenceX = screenWidth - referenceX; // mirror
-        }
-        Toast cheatSheet = Toast.makeText(context, mItemData.getTitle(), Toast.LENGTH_SHORT);
-        if (midy < displayFrame.height()) {
-            // Show along the top; follow action buttons
-            cheatSheet.setGravity(Gravity.TOP | GravityCompat.END, referenceX,
-                    screenPos[1] + height - displayFrame.top);
-        } else {
-            // Show along the bottom center
-            cheatSheet.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, height);
-        }
-        cheatSheet.show();
-        return true;
-    }
-
-    @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         final boolean textVisible = hasText();
         if (textVisible && mSavedPaddingLeft >= 0) {
diff --git a/v7/appcompat/src/android/support/v7/widget/ScrollingTabContainerView.java b/v7/appcompat/src/android/support/v7/widget/ScrollingTabContainerView.java
index ea536d2..3e729da 100644
--- a/v7/appcompat/src/android/support/v7/widget/ScrollingTabContainerView.java
+++ b/v7/appcompat/src/android/support/v7/widget/ScrollingTabContainerView.java
@@ -46,7 +46,6 @@
 import android.widget.ListView;
 import android.widget.Spinner;
 import android.widget.TextView;
-import android.widget.Toast;
 
 /**
  * This widget implements the dynamic action bar tab behavior that can change across different
@@ -377,7 +376,7 @@
         // no-op
     }
 
-    private class TabView extends LinearLayoutCompat implements OnLongClickListener {
+    private class TabView extends LinearLayoutCompat {
         private final int[] BG_ATTRS = {
                 android.R.attr.background
         };
@@ -511,36 +510,10 @@
                 if (mIconView != null) {
                     mIconView.setContentDescription(tab.getContentDescription());
                 }
-
-                if (!hasText && !TextUtils.isEmpty(tab.getContentDescription())) {
-                    setOnLongClickListener(this);
-                } else {
-                    setOnLongClickListener(null);
-                    setLongClickable(false);
-                }
+                ViewCompat.setTooltip(this, hasText ? null : tab.getContentDescription());
             }
         }
 
-        @Override
-        public boolean onLongClick(View v) {
-            final int[] screenPos = new int[2];
-            getLocationOnScreen(screenPos);
-
-            final Context context = getContext();
-            final int width = getWidth();
-            final int height = getHeight();
-            final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
-
-            Toast cheatSheet = Toast.makeText(context, mTab.getContentDescription(),
-                    Toast.LENGTH_SHORT);
-            // Show under the tab
-            cheatSheet.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL,
-                    (screenPos[0] + width / 2) - screenWidth / 2, height);
-
-            cheatSheet.show();
-            return true;
-        }
-
         public ActionBar.Tab getTab() {
             return mTab;
         }
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java
index 150a3fd..ae74f95 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java
@@ -22,24 +22,20 @@
 import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
-import android.graphics.Rect;
 import android.graphics.drawable.AnimationDrawable;
 import android.graphics.drawable.Drawable;
 import android.support.annotation.NonNull;
 import android.support.v4.app.FragmentActivity;
 import android.support.v4.app.FragmentManager;
 import android.support.v4.graphics.drawable.DrawableCompat;
-import android.support.v4.view.GravityCompat;
+import android.support.v4.view.ViewCompat;
 import android.support.v7.media.MediaRouteSelector;
 import android.support.v7.media.MediaRouter;
 import android.support.v7.mediarouter.R;
 import android.util.AttributeSet;
 import android.util.Log;
-import android.view.Gravity;
-import android.view.HapticFeedbackConstants;
 import android.view.SoundEffectConstants;
 import android.view.View;
-import android.widget.Toast;
 
 /**
  * The media route button allows the user to select routes and to control the
@@ -95,7 +91,6 @@
 
     private Drawable mRemoteIndicator;
     private boolean mRemoteActive;
-    private boolean mCheatSheetEnabled;
     private boolean mIsConnecting;
 
     private ColorStateList mButtonTint;
@@ -141,7 +136,6 @@
 
         updateContentDescription();
         setClickable(true);
-        setLongClickable(true);
     }
 
     /**
@@ -280,7 +274,8 @@
      * button when the button is long pressed.
      */
     void setCheatSheetEnabled(boolean enable) {
-        mCheatSheetEnabled = enable;
+        ViewCompat.setTooltip(this,
+                enable ? getContext().getString(R.string.mr_button_content_description) : null);
     }
 
     @Override
@@ -294,42 +289,6 @@
     }
 
     @Override
-    public boolean performLongClick() {
-        if (super.performLongClick()) {
-            return true;
-        }
-
-        if (!mCheatSheetEnabled) {
-            return false;
-        }
-
-        final int[] screenPos = new int[2];
-        final Rect displayFrame = new Rect();
-        getLocationOnScreen(screenPos);
-        getWindowVisibleDisplayFrame(displayFrame);
-
-        final Context context = getContext();
-        final int width = getWidth();
-        final int height = getHeight();
-        final int midy = screenPos[1] + height / 2;
-        final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
-
-        Toast cheatSheet = Toast.makeText(context, R.string.mr_button_content_description,
-                Toast.LENGTH_SHORT);
-        if (midy < displayFrame.height()) {
-            // Show along the top; follow action buttons
-            cheatSheet.setGravity(Gravity.TOP | GravityCompat.END,
-                    screenWidth - screenPos[0] - width / 2, height);
-        } else {
-            // Show along the bottom center
-            cheatSheet.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, height);
-        }
-        cheatSheet.show();
-        performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
-        return true;
-    }
-
-    @Override
     protected int[] onCreateDrawableState(int extraSpace) {
         final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);