Home | History | Annotate | Download | only in cts
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.view.cts;
     18 
     19 import static org.junit.Assert.assertEquals;
     20 import static org.junit.Assert.assertFalse;
     21 import static org.junit.Assert.assertTrue;
     22 
     23 import android.app.Activity;
     24 import android.app.Instrumentation;
     25 import android.os.SystemClock;
     26 import android.support.test.InstrumentationRegistry;
     27 import android.support.test.filters.LargeTest;
     28 import android.support.test.rule.ActivityTestRule;
     29 import android.support.test.runner.AndroidJUnit4;
     30 import android.util.Log;
     31 import android.view.Gravity;
     32 import android.view.InputDevice;
     33 import android.view.KeyEvent;
     34 import android.view.MotionEvent;
     35 import android.view.View;
     36 import android.view.ViewConfiguration;
     37 import android.view.ViewGroup;
     38 import android.widget.PopupWindow;
     39 import android.widget.TextView;
     40 
     41 import com.android.compatibility.common.util.CtsTouchUtils;
     42 import com.android.compatibility.common.util.PollingCheck;
     43 
     44 import org.junit.Before;
     45 import org.junit.Rule;
     46 import org.junit.Test;
     47 import org.junit.runner.RunWith;
     48 
     49 /**
     50  * Test {@link View}.
     51  */
     52 @LargeTest
     53 @RunWith(AndroidJUnit4.class)
     54 public class TooltipTest {
     55     private static final String LOG_TAG = "TooltipTest";
     56 
     57     private static final long TIMEOUT_DELTA = 10000;
     58     private static final long WAIT_MARGIN = 100;
     59 
     60     private Instrumentation mInstrumentation;
     61     private Activity mActivity;
     62     private ViewGroup mTopmostView;
     63     private ViewGroup mGroupView;
     64     private View mNoTooltipView;
     65     private View mTooltipView;
     66     private View mNoTooltipView2;
     67     private View mEmptyGroup;
     68 
     69     @Rule
     70     public ActivityTestRule<TooltipActivity> mActivityRule =
     71             new ActivityTestRule<>(TooltipActivity.class);
     72 
     73     @Rule
     74     public ActivityTestRule<CtsActivity> mCtsActivityRule =
     75             new ActivityTestRule<>(CtsActivity.class, false, false);
     76 
     77     @Before
     78     public void setup() {
     79         mInstrumentation = InstrumentationRegistry.getInstrumentation();
     80         mActivity = mActivityRule.getActivity();
     81         mTopmostView = (ViewGroup) mActivity.findViewById(R.id.tooltip_layout);
     82         mGroupView = (ViewGroup) mActivity.findViewById(R.id.tooltip_group);
     83         mNoTooltipView = mActivity.findViewById(R.id.no_tooltip);
     84         mTooltipView = mActivity.findViewById(R.id.has_tooltip);
     85         mNoTooltipView2 = mActivity.findViewById(R.id.no_tooltip2);
     86         mEmptyGroup = mActivity.findViewById(R.id.empty_group);
     87 
     88         PollingCheck.waitFor(TIMEOUT_DELTA, mActivity::hasWindowFocus);
     89     }
     90 
     91     private void waitOut(long msDelay) {
     92         try {
     93             Thread.sleep(msDelay + WAIT_MARGIN);
     94         } catch (InterruptedException e) {
     95             Log.e(LOG_TAG, "Wait interrupted. Test may fail!", e);
     96         }
     97     }
     98 
     99     private void setTooltipText(View view, CharSequence tooltipText) throws Throwable {
    100         mActivityRule.runOnUiThread(() -> view.setTooltipText(tooltipText));
    101     }
    102 
    103     private boolean hasTooltip(View view) {
    104         final View tooltipView = view.getTooltipView();
    105         return tooltipView != null && tooltipView.getParent() != null;
    106     }
    107 
    108 
    109     private void addView(ViewGroup parent, View view) throws Throwable {
    110         mActivityRule.runOnUiThread(() -> parent.addView(view));
    111         mInstrumentation.waitForIdleSync();
    112     }
    113 
    114     private void removeView(View view) throws Throwable {
    115         mActivityRule.runOnUiThread(() -> ((ViewGroup) (view.getParent())).removeView(view));
    116         mInstrumentation.waitForIdleSync();
    117     }
    118 
    119     private void setVisibility(View view, int visibility) throws Throwable {
    120         mActivityRule.runOnUiThread(() -> view.setVisibility(visibility));
    121     }
    122 
    123     private void setClickable(View view) throws Throwable {
    124         mActivityRule.runOnUiThread(() -> view.setClickable(true));
    125     }
    126 
    127     private void setLongClickable(View view) throws Throwable {
    128         mActivityRule.runOnUiThread(() -> view.setLongClickable(true));
    129     }
    130 
    131     private void setContextClickable(View view) throws Throwable {
    132         mActivityRule.runOnUiThread(() -> view.setContextClickable(true));
    133     }
    134 
    135     private void callPerformLongClick(View view) throws Throwable {
    136         mActivityRule.runOnUiThread(() -> view.performLongClick(0, 0));
    137     }
    138 
    139     private void requestLowProfileSystemUi() throws Throwable {
    140         final int flag = View.SYSTEM_UI_FLAG_LOW_PROFILE;
    141         mActivityRule.runOnUiThread(() -> mTooltipView.setSystemUiVisibility(flag));
    142         PollingCheck.waitFor(TIMEOUT_DELTA,
    143                 () -> (mTooltipView.getWindowSystemUiVisibility() & flag) == flag);
    144     }
    145 
    146     private void injectKeyPress(View target, int keyCode, int duration) throws Throwable {
    147         if (target != null) {
    148             mActivityRule.runOnUiThread(() -> {
    149                 target.setFocusableInTouchMode(true);
    150                 target.requestFocus();
    151             });
    152             mInstrumentation.waitForIdleSync();
    153             assertTrue(target.isFocused());
    154         }
    155         mInstrumentation.sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
    156         waitOut(duration);
    157         mInstrumentation.sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
    158     }
    159 
    160     private void injectArbitraryShortKeyPress() throws Throwable {
    161         injectKeyPress(null, KeyEvent.KEYCODE_0, 0);
    162     }
    163 
    164     private void injectLongKeyPress(View target, int keyCode) throws Throwable {
    165         injectKeyPress(target, keyCode, ViewConfiguration.getLongPressTimeout());
    166     }
    167 
    168     private void injectLongEnter(View target) throws Throwable {
    169         injectLongKeyPress(target, KeyEvent.KEYCODE_ENTER);
    170     }
    171 
    172     private void injectShortClick(View target) {
    173         CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, target);
    174     }
    175 
    176     private void injectLongClick(View target) {
    177         CtsTouchUtils.emulateLongPressOnView(mInstrumentation, target,
    178                 target.getWidth() / 2, target.getHeight() / 2);
    179     }
    180 
    181     private void injectMotionEvent(MotionEvent event) {
    182         mInstrumentation.sendPointerSync(event);
    183     }
    184 
    185     private void injectHoverMove(int source, View target, int offsetX, int offsetY) {
    186         injectMotionEvent(obtainMotionEvent(
    187                     source, target, MotionEvent.ACTION_HOVER_MOVE, offsetX,  offsetY));
    188     }
    189 
    190     private void injectHoverMove(View target, int offsetX, int offsetY) {
    191         injectHoverMove(InputDevice.SOURCE_MOUSE, target, offsetX,  offsetY);
    192     }
    193 
    194     private void injectHoverMove(View target) {
    195         injectHoverMove(target, 0, 0);
    196     }
    197 
    198     private void injectLongHoverMove(View target) {
    199         injectHoverMove(target);
    200         waitOut(ViewConfiguration.getHoverTooltipShowTimeout());
    201     }
    202 
    203     private static MotionEvent obtainMouseEvent(View target, int action, int offsetX, int offsetY) {
    204         return obtainMotionEvent(InputDevice.SOURCE_MOUSE, target, action, offsetX, offsetY);
    205     }
    206 
    207     private static MotionEvent obtainMotionEvent(
    208                 int source, View target, int action, int offsetX, int offsetY) {
    209         final long eventTime = SystemClock.uptimeMillis();
    210         final int[] xy = new int[2];
    211         target.getLocationOnScreen(xy);
    212         MotionEvent event = MotionEvent.obtain(eventTime, eventTime, action,
    213                 xy[0] + target.getWidth() / 2 + offsetX, xy[1] + target.getHeight() / 2 + offsetY,
    214                 0);
    215         event.setSource(source);
    216         return event;
    217     }
    218 
    219     @Test
    220     public void testGetSetTooltip() throws Throwable {
    221         // No tooltip set in resource
    222         assertEquals(null, mNoTooltipView.getTooltipText());
    223 
    224         // Set the tooltip, read it back
    225         final String tooltipText1 = "new tooltip";
    226         setTooltipText(mNoTooltipView, tooltipText1);
    227         assertEquals(tooltipText1, mNoTooltipView.getTooltipText());
    228 
    229         // Clear the tooltip.
    230         setTooltipText(mNoTooltipView, null);
    231         assertEquals(null, mNoTooltipView.getTooltipText());
    232 
    233         // Check the tooltip set in resource
    234         assertEquals("tooltip text", mTooltipView.getTooltipText());
    235 
    236         // Clear the tooltip set in resource
    237         setTooltipText(mTooltipView, null);
    238         assertEquals(null, mTooltipView.getTooltipText());
    239 
    240         // Set the tooltip again, read it back
    241         final String tooltipText2 = "new tooltip 2";
    242         setTooltipText(mTooltipView, tooltipText2);
    243         assertEquals(tooltipText2, mTooltipView.getTooltipText());
    244     }
    245 
    246     @Test
    247     public void testNoTooltipWhenNotSet() throws Throwable {
    248         callPerformLongClick(mNoTooltipView);
    249         assertFalse(hasTooltip(mNoTooltipView));
    250 
    251         injectLongClick(mNoTooltipView);
    252         assertFalse(hasTooltip(mNoTooltipView));
    253 
    254         injectLongEnter(mNoTooltipView);
    255         assertFalse(hasTooltip(mNoTooltipView));
    256 
    257         injectLongHoverMove(mNoTooltipView);
    258         assertFalse(hasTooltip(mNoTooltipView));
    259     }
    260 
    261     @Test
    262     public void testTooltipOnDisabledView() throws Throwable {
    263         mActivityRule.runOnUiThread(() -> mTooltipView.setEnabled(false));
    264 
    265         // Long click has no effect on a disabled view.
    266         injectLongClick(mTooltipView);
    267         assertFalse(hasTooltip(mTooltipView));
    268 
    269         // Hover does show the tooltip on a disabled view.
    270         injectLongHoverMove(mTooltipView);
    271         assertTrue(hasTooltip(mTooltipView));
    272     }
    273 
    274     @Test
    275     public void testUpdateOpenTooltip() throws Throwable {
    276         callPerformLongClick(mTooltipView);
    277         assertTrue(hasTooltip(mTooltipView));
    278 
    279         setTooltipText(mTooltipView, "updated tooltip");
    280         assertTrue(hasTooltip(mTooltipView));
    281 
    282         setTooltipText(mTooltipView, null);
    283         assertFalse(hasTooltip(mTooltipView));
    284     }
    285 
    286     @Test
    287     public void testTooltipHidesOnActivityFocusChange() throws Throwable {
    288         callPerformLongClick(mTooltipView);
    289         assertTrue(hasTooltip(mTooltipView));
    290 
    291         CtsActivity activity = mCtsActivityRule.launchActivity(null);
    292         PollingCheck.waitFor(TIMEOUT_DELTA, () -> !mActivity.hasWindowFocus());
    293         assertFalse(hasTooltip(mTooltipView));
    294         activity.finish();
    295     }
    296 
    297     @Test
    298     public void testTooltipHidesOnWindowFocusChange() throws Throwable {
    299         callPerformLongClick(mTooltipView);
    300         assertTrue(hasTooltip(mTooltipView));
    301 
    302         // Show a context menu on another widget.
    303         mActivity.registerForContextMenu(mNoTooltipView);
    304         mActivityRule.runOnUiThread(() -> mNoTooltipView.showContextMenu(0, 0));
    305 
    306         PollingCheck.waitFor(TIMEOUT_DELTA, () -> !mTooltipView.hasWindowFocus());
    307         mInstrumentation.waitForIdleSync();
    308         assertFalse(hasTooltip(mTooltipView));
    309     }
    310 
    311     // Tests for tooltips triggered by long click.
    312 
    313     @Test
    314     public void testShortClickDoesNotShowTooltip() throws Throwable {
    315         injectShortClick(mTooltipView);
    316         assertFalse(hasTooltip(mTooltipView));
    317     }
    318 
    319     @Test
    320     public void testPerformLongClickShowsTooltipImmediately() throws Throwable {
    321         callPerformLongClick(mTooltipView);
    322         assertTrue(hasTooltip(mTooltipView));
    323     }
    324 
    325     @Test
    326     public void testLongClickTooltipBlockedByLongClickListener() throws Throwable {
    327         mTooltipView.setOnLongClickListener(v -> true);
    328         injectLongClick(mTooltipView);
    329         assertFalse(hasTooltip(mTooltipView));
    330     }
    331 
    332     @Test
    333     public void testLongClickTooltipBlockedByContextMenu() throws Throwable {
    334         mActivity.registerForContextMenu(mTooltipView);
    335         injectLongClick(mTooltipView);
    336         assertFalse(hasTooltip(mTooltipView));
    337     }
    338 
    339     @Test
    340     public void testLongClickTooltipOnNonClickableView() throws Throwable {
    341         injectLongClick(mTooltipView);
    342         assertTrue(hasTooltip(mTooltipView));
    343     }
    344 
    345     @Test
    346     public void testLongClickTooltipOnClickableView() throws Throwable {
    347         setClickable(mTooltipView);
    348         injectLongClick(mTooltipView);
    349         assertTrue(hasTooltip(mTooltipView));
    350     }
    351 
    352     @Test
    353     public void testLongClickTooltipOnLongClickableView() throws Throwable {
    354         setLongClickable(mTooltipView);
    355         injectLongClick(mTooltipView);
    356         assertTrue(hasTooltip(mTooltipView));
    357     }
    358 
    359     @Test
    360     public void testLongClickTooltipOnContextClickableView() throws Throwable {
    361         setContextClickable(mTooltipView);
    362         injectLongClick(mTooltipView);
    363         assertTrue(hasTooltip(mTooltipView));
    364     }
    365 
    366     @Test
    367     public void testLongClickTooltipStaysOnMouseMove() throws Throwable {
    368         injectLongClick(mTooltipView);
    369         assertTrue(hasTooltip(mTooltipView));
    370 
    371         // Tooltip stays while the mouse moves over the widget.
    372         injectHoverMove(mTooltipView);
    373         assertTrue(hasTooltip(mTooltipView));
    374 
    375         // Long-click-triggered tooltip stays while the mouse to another widget.
    376         injectHoverMove(mNoTooltipView);
    377         assertTrue(hasTooltip(mTooltipView));
    378     }
    379 
    380     @Test
    381     public void testLongClickTooltipHidesAfterUp() throws Throwable {
    382         injectLongClick(mTooltipView);
    383         assertTrue(hasTooltip(mTooltipView));
    384 
    385         // Long-click-triggered tooltip hides after ACTION_UP (with a delay).
    386         waitOut(ViewConfiguration.getLongPressTooltipHideTimeout());
    387         assertFalse(hasTooltip(mTooltipView));
    388     }
    389 
    390     @Test
    391     public void testLongClickTooltipHidesOnClick() throws Throwable {
    392         injectLongClick(mTooltipView);
    393         assertTrue(hasTooltip(mTooltipView));
    394 
    395         injectShortClick(mTooltipView);
    396         assertFalse(hasTooltip(mTooltipView));
    397     }
    398 
    399     @Test
    400     public void testLongClickTooltipHidesOnClickElsewhere() throws Throwable {
    401         injectLongClick(mTooltipView);
    402         assertTrue(hasTooltip(mTooltipView));
    403 
    404         injectShortClick(mNoTooltipView);
    405         assertFalse(hasTooltip(mTooltipView));
    406     }
    407 
    408     @Test
    409     public void testLongClickTooltipHidesOnKey() throws Throwable {
    410         injectLongClick(mTooltipView);
    411         assertTrue(hasTooltip(mTooltipView));
    412 
    413         injectArbitraryShortKeyPress();
    414         assertFalse(hasTooltip(mTooltipView));
    415     }
    416 
    417     // Tests for tooltips triggered by long key press.
    418 
    419     @Test
    420     public void testShortKeyPressDoesNotShowTooltip() throws Throwable {
    421         injectKeyPress(null, KeyEvent.KEYCODE_ENTER, 0);
    422         assertFalse(hasTooltip(mTooltipView));
    423 
    424         injectKeyPress(mTooltipView, KeyEvent.KEYCODE_ENTER, 0);
    425         assertFalse(hasTooltip(mTooltipView));
    426     }
    427 
    428     @Test
    429     public void testLongArbitraryKeyPressDoesNotShowTooltip() throws Throwable {
    430         injectLongKeyPress(mTooltipView, KeyEvent.KEYCODE_0);
    431         assertFalse(hasTooltip(mTooltipView));
    432     }
    433 
    434     @Test
    435     public void testLongKeyPressWithoutFocusDoesNotShowTooltip() throws Throwable {
    436         injectLongEnter(null);
    437         assertFalse(hasTooltip(mTooltipView));
    438     }
    439 
    440     @Test
    441     public void testLongKeyPressOnAnotherViewDoesNotShowTooltip() throws Throwable {
    442         injectLongEnter(mNoTooltipView);
    443         assertFalse(hasTooltip(mTooltipView));
    444     }
    445 
    446     @Test
    447     public void testLongKeyPressTooltipOnNonClickableView() throws Throwable {
    448         injectLongEnter(mTooltipView);
    449         assertTrue(hasTooltip(mTooltipView));
    450     }
    451 
    452     @Test
    453     public void testLongKeyPressTooltipOnClickableView() throws Throwable {
    454         setClickable(mTooltipView);
    455         injectLongEnter(mTooltipView);
    456         assertTrue(hasTooltip(mTooltipView));
    457     }
    458 
    459     @Test
    460     public void testLongKeyPressTooltipOnLongClickableView() throws Throwable {
    461         setLongClickable(mTooltipView);
    462         injectLongEnter(mTooltipView);
    463         assertTrue(hasTooltip(mTooltipView));
    464     }
    465 
    466     @Test
    467     public void testLongKeyPressTooltipOnContextClickableView() throws Throwable {
    468         setContextClickable(mTooltipView);
    469         injectLongEnter(mTooltipView);
    470         assertTrue(hasTooltip(mTooltipView));
    471     }
    472 
    473     @Test
    474     public void testLongKeyPressTooltipStaysOnMouseMove() throws Throwable {
    475         injectLongEnter(mTooltipView);
    476         assertTrue(hasTooltip(mTooltipView));
    477 
    478         // Tooltip stays while the mouse moves over the widget.
    479         injectHoverMove(mTooltipView);
    480         assertTrue(hasTooltip(mTooltipView));
    481 
    482         // Long-keypress-triggered tooltip stays while the mouse to another widget.
    483         injectHoverMove(mNoTooltipView);
    484         assertTrue(hasTooltip(mTooltipView));
    485     }
    486 
    487     @Test
    488     public void testLongKeyPressTooltipHidesAfterUp() throws Throwable {
    489         injectLongEnter(mTooltipView);
    490         assertTrue(hasTooltip(mTooltipView));
    491 
    492         // Long-keypress-triggered tooltip hides after ACTION_UP (with a delay).
    493         waitOut(ViewConfiguration.getLongPressTooltipHideTimeout());
    494         assertFalse(hasTooltip(mTooltipView));
    495     }
    496 
    497     @Test
    498     public void testLongKeyPressTooltipHidesOnClick() throws Throwable {
    499         injectLongEnter(mTooltipView);
    500         assertTrue(hasTooltip(mTooltipView));
    501 
    502         injectShortClick(mTooltipView);
    503         assertFalse(hasTooltip(mTooltipView));
    504     }
    505 
    506     @Test
    507     public void testLongKeyPressTooltipHidesOnClickElsewhere() throws Throwable {
    508         injectLongEnter(mTooltipView);
    509         assertTrue(hasTooltip(mTooltipView));
    510 
    511         injectShortClick(mNoTooltipView);
    512         assertFalse(hasTooltip(mTooltipView));
    513     }
    514 
    515     @Test
    516     public void testLongKeyPressTooltipHidesOnKey() throws Throwable {
    517         injectLongEnter(mTooltipView);
    518         assertTrue(hasTooltip(mTooltipView));
    519 
    520         injectArbitraryShortKeyPress();
    521         assertFalse(hasTooltip(mTooltipView));
    522     }
    523 
    524     // Tests for tooltips triggered by mouse hover.
    525 
    526     @Test
    527     public void testMouseClickDoesNotShowTooltip() throws Throwable {
    528         injectMotionEvent(obtainMouseEvent(mTooltipView, MotionEvent.ACTION_DOWN, 0, 0));
    529         injectMotionEvent(obtainMouseEvent(mTooltipView, MotionEvent.ACTION_BUTTON_PRESS, 0, 0));
    530         injectMotionEvent(obtainMouseEvent(mTooltipView, MotionEvent.ACTION_BUTTON_RELEASE, 0, 0));
    531         injectMotionEvent(obtainMouseEvent(mTooltipView, MotionEvent.ACTION_UP, 0, 0));
    532         assertFalse(hasTooltip(mTooltipView));
    533     }
    534 
    535     @Test
    536     public void testMouseHoverDoesNotShowTooltipImmediately() throws Throwable {
    537         injectHoverMove(mTooltipView, 0, 0);
    538         assertFalse(hasTooltip(mTooltipView));
    539 
    540         injectHoverMove(mTooltipView, 1, 1);
    541         assertFalse(hasTooltip(mTooltipView));
    542 
    543         injectHoverMove(mTooltipView, 2, 2);
    544         assertFalse(hasTooltip(mTooltipView));
    545     }
    546 
    547     @Test
    548     public void testMouseHoverExitCancelsPendingTooltip() throws Throwable {
    549         injectHoverMove(mTooltipView);
    550         assertFalse(hasTooltip(mTooltipView));
    551 
    552         injectLongHoverMove(mNoTooltipView);
    553         assertFalse(hasTooltip(mTooltipView));
    554     }
    555 
    556     @Test
    557     public void testMouseHoverTooltipOnClickableView() throws Throwable {
    558         setClickable(mTooltipView);
    559         injectLongHoverMove(mTooltipView);
    560         assertTrue(hasTooltip(mTooltipView));
    561     }
    562 
    563     @Test
    564     public void testMouseHoverTooltipOnLongClickableView() throws Throwable {
    565         setLongClickable(mTooltipView);
    566         injectLongHoverMove(mTooltipView);
    567         assertTrue(hasTooltip(mTooltipView));
    568     }
    569 
    570     @Test
    571     public void testMouseHoverTooltipOnContextClickableView() throws Throwable {
    572         setContextClickable(mTooltipView);
    573         injectLongHoverMove(mTooltipView);
    574         assertTrue(hasTooltip(mTooltipView));
    575     }
    576 
    577     @Test
    578     public void testMouseHoverTooltipStaysOnMouseMove() throws Throwable {
    579         injectLongHoverMove(mTooltipView);
    580         assertTrue(hasTooltip(mTooltipView));
    581 
    582         // Tooltip stays while the mouse moves over the widget.
    583         injectHoverMove(mTooltipView, 1, 1);
    584         assertTrue(hasTooltip(mTooltipView));
    585 
    586         injectHoverMove(mTooltipView, 2, 2);
    587         assertTrue(hasTooltip(mTooltipView));
    588     }
    589 
    590     @Test
    591     public void testMouseHoverTooltipHidesOnExit() throws Throwable {
    592         injectLongHoverMove(mTooltipView);
    593         assertTrue(hasTooltip(mTooltipView));
    594 
    595         // Tooltip hides once the mouse moves out of the widget.
    596         injectHoverMove(mNoTooltipView);
    597         assertFalse(hasTooltip(mTooltipView));
    598     }
    599 
    600     @Test
    601     public void testMouseHoverTooltipHidesOnClick() throws Throwable {
    602         injectLongHoverMove(mTooltipView);
    603         assertTrue(hasTooltip(mTooltipView));
    604 
    605         injectShortClick(mTooltipView);
    606         assertFalse(hasTooltip(mTooltipView));
    607     }
    608 
    609     @Test
    610     public void testMouseHoverTooltipHidesOnClickOnElsewhere() throws Throwable {
    611         injectLongHoverMove(mTooltipView);
    612         assertTrue(hasTooltip(mTooltipView));
    613 
    614         injectShortClick(mNoTooltipView);
    615         assertFalse(hasTooltip(mTooltipView));
    616     }
    617 
    618     @Test
    619     public void testMouseHoverTooltipHidesOnKey() throws Throwable {
    620         injectLongHoverMove(mTooltipView);
    621         assertTrue(hasTooltip(mTooltipView));
    622 
    623         injectArbitraryShortKeyPress();
    624         assertFalse(hasTooltip(mTooltipView));
    625     }
    626 
    627     @Test
    628     public void testMouseHoverTooltipHidesOnTimeout() throws Throwable {
    629         injectLongHoverMove(mTooltipView);
    630         assertTrue(hasTooltip(mTooltipView));
    631 
    632         waitOut(ViewConfiguration.getHoverTooltipHideTimeout());
    633         assertFalse(hasTooltip(mTooltipView));
    634     }
    635 
    636     @Test
    637     public void testMouseHoverTooltipHidesOnShortTimeout() throws Throwable {
    638         requestLowProfileSystemUi();
    639 
    640         injectLongHoverMove(mTooltipView);
    641         assertTrue(hasTooltip(mTooltipView));
    642 
    643         waitOut(ViewConfiguration.getHoverTooltipHideShortTimeout());
    644         assertFalse(hasTooltip(mTooltipView));
    645     }
    646 
    647     @Test
    648     public void testMouseHoverTooltipWithHoverListener() throws Throwable {
    649         mTooltipView.setOnHoverListener((v, event) -> true);
    650         injectLongHoverMove(mTooltipView);
    651         assertTrue(hasTooltip(mTooltipView));
    652     }
    653 
    654     @Test
    655     public void testMouseHoverTooltipUnsetWhileHovering() throws Throwable {
    656         injectHoverMove(mTooltipView);
    657         setTooltipText(mTooltipView, null);
    658         waitOut(ViewConfiguration.getHoverTooltipShowTimeout());
    659         assertFalse(hasTooltip(mTooltipView));
    660     }
    661 
    662     @Test
    663     public void testMouseHoverTooltipDisableWhileHovering() throws Throwable {
    664         injectHoverMove(mTooltipView);
    665         mActivityRule.runOnUiThread(() -> mTooltipView.setEnabled(false));
    666         waitOut(ViewConfiguration.getHoverTooltipShowTimeout());
    667         // Disabled view still displays a hover tooltip.
    668         assertTrue(hasTooltip(mTooltipView));
    669     }
    670 
    671     @Test
    672     public void testMouseHoverTooltipFromParent() throws Throwable {
    673         // Hover listeners should not interfere with tooltip dispatch.
    674         mNoTooltipView.setOnHoverListener((v, event) -> true);
    675         mTooltipView.setOnHoverListener((v, event) -> true);
    676 
    677         setTooltipText(mTopmostView, "tooltip");
    678 
    679         // Hover over a child with a tooltip works normally.
    680         injectLongHoverMove(mTooltipView);
    681         assertFalse(hasTooltip(mTopmostView));
    682         assertTrue(hasTooltip(mTooltipView));
    683         injectShortClick(mTopmostView);
    684         assertFalse(hasTooltip(mTooltipView));
    685 
    686         // Hover over a child with no tooltip triggers a tooltip on its parent.
    687         injectLongHoverMove(mNoTooltipView2);
    688         assertFalse(hasTooltip(mNoTooltipView2));
    689         assertTrue(hasTooltip(mTopmostView));
    690         injectShortClick(mTopmostView);
    691         assertFalse(hasTooltip(mTopmostView));
    692 
    693         // Same but the child is and empty view group.
    694         injectLongHoverMove(mEmptyGroup);
    695         assertFalse(hasTooltip(mEmptyGroup));
    696         assertTrue(hasTooltip(mTopmostView));
    697         injectShortClick(mTopmostView);
    698         assertFalse(hasTooltip(mTopmostView));
    699 
    700         // Hover over a grandchild with no tooltip triggers a tooltip on its grandparent.
    701         injectLongHoverMove(mNoTooltipView);
    702         assertFalse(hasTooltip(mNoTooltipView));
    703         assertTrue(hasTooltip(mTopmostView));
    704         // Move to another child one level up, the tooltip stays.
    705         injectHoverMove(mNoTooltipView2);
    706         assertTrue(hasTooltip(mTopmostView));
    707         injectShortClick(mTopmostView);
    708         assertFalse(hasTooltip(mTopmostView));
    709 
    710         // Set a tooltip on the intermediate parent, now it is showing tooltips.
    711         setTooltipText(mGroupView, "tooltip");
    712         injectLongHoverMove(mNoTooltipView);
    713         assertFalse(hasTooltip(mNoTooltipView));
    714         assertFalse(hasTooltip(mTopmostView));
    715         assertTrue(hasTooltip(mGroupView));
    716 
    717         // Move out of this group, the tooltip is now back on the grandparent.
    718         injectLongHoverMove(mNoTooltipView2);
    719         assertFalse(hasTooltip(mGroupView));
    720         assertTrue(hasTooltip(mTopmostView));
    721         injectShortClick(mTopmostView);
    722         assertFalse(hasTooltip(mTopmostView));
    723     }
    724 
    725     @Test
    726     public void testMouseHoverTooltipRemoveWhileWaiting() throws Throwable {
    727         // Remove the view while hovering.
    728         injectHoverMove(mTooltipView);
    729         removeView(mTooltipView);
    730         waitOut(ViewConfiguration.getHoverTooltipShowTimeout());
    731         assertFalse(hasTooltip(mTooltipView));
    732         addView(mGroupView, mTooltipView);
    733 
    734         // Remove and re-add the view while hovering.
    735         injectHoverMove(mTooltipView);
    736         removeView(mTooltipView);
    737         addView(mGroupView, mTooltipView);
    738         waitOut(ViewConfiguration.getHoverTooltipShowTimeout());
    739         assertFalse(hasTooltip(mTooltipView));
    740 
    741         // Remove the view's parent while hovering.
    742         injectHoverMove(mTooltipView);
    743         removeView(mGroupView);
    744         waitOut(ViewConfiguration.getHoverTooltipShowTimeout());
    745         assertFalse(hasTooltip(mTooltipView));
    746         addView(mTopmostView, mGroupView);
    747 
    748         // Remove and re-add view's parent while hovering.
    749         injectHoverMove(mTooltipView);
    750         removeView(mGroupView);
    751         addView(mTopmostView, mGroupView);
    752         waitOut(ViewConfiguration.getHoverTooltipShowTimeout());
    753         assertFalse(hasTooltip(mTooltipView));
    754     }
    755 
    756     @Test
    757     public void testMouseHoverTooltipRemoveWhileShowing() throws Throwable {
    758         // Remove the view while showing the tooltip.
    759         injectLongHoverMove(mTooltipView);
    760         assertTrue(hasTooltip(mTooltipView));
    761         removeView(mTooltipView);
    762         assertFalse(hasTooltip(mTooltipView));
    763         addView(mGroupView, mTooltipView);
    764         assertFalse(hasTooltip(mTooltipView));
    765 
    766         // Remove the view's parent while showing the tooltip.
    767         injectLongHoverMove(mTooltipView);
    768         assertTrue(hasTooltip(mTooltipView));
    769         removeView(mGroupView);
    770         assertFalse(hasTooltip(mTooltipView));
    771         addView(mTopmostView, mGroupView);
    772         assertFalse(hasTooltip(mTooltipView));
    773     }
    774 
    775     @Test
    776     public void testMouseHoverOverlap() throws Throwable {
    777         final View parent = mActivity.findViewById(R.id.overlap_group);
    778         final View child1 = mActivity.findViewById(R.id.overlap1);
    779         final View child2 = mActivity.findViewById(R.id.overlap2);
    780         final View child3 = mActivity.findViewById(R.id.overlap3);
    781 
    782         injectLongHoverMove(parent);
    783         assertTrue(hasTooltip(child3));
    784 
    785         setVisibility(child3, View.GONE);
    786         injectLongHoverMove(parent);
    787         assertTrue(hasTooltip(child2));
    788 
    789         setTooltipText(child2, null);
    790         injectLongHoverMove(parent);
    791         assertTrue(hasTooltip(child1));
    792 
    793         setVisibility(child1, View.INVISIBLE);
    794         injectLongHoverMove(parent);
    795         assertTrue(hasTooltip(parent));
    796     }
    797 
    798     @Test
    799     public void testMouseHoverWithJitter() throws Throwable {
    800         testHoverWithJitter(InputDevice.SOURCE_MOUSE);
    801     }
    802 
    803     @Test
    804     public void testStylusHoverWithJitter() throws Throwable {
    805         testHoverWithJitter(InputDevice.SOURCE_STYLUS);
    806     }
    807 
    808     @Test
    809     public void testTouchscreenHoverWithJitter() throws Throwable {
    810         testHoverWithJitter(InputDevice.SOURCE_TOUCHSCREEN);
    811     }
    812 
    813     private void testHoverWithJitter(int source) {
    814         final int hoverSlop = ViewConfiguration.get(mTooltipView.getContext()).getScaledHoverSlop();
    815         if (hoverSlop == 0) {
    816             // Zero hoverSlop makes this test redundant.
    817             return;
    818         }
    819 
    820         final int tooltipTimeout = ViewConfiguration.getHoverTooltipShowTimeout();
    821         final long halfTimeout = tooltipTimeout / 2;
    822         assertTrue(halfTimeout + WAIT_MARGIN < tooltipTimeout);
    823 
    824         // Imitate strong jitter (above hoverSlop threshold). No tooltip should be shown.
    825         int jitterHigh = hoverSlop + 1;
    826         assertTrue(jitterHigh <= mTooltipView.getWidth());
    827         assertTrue(jitterHigh <= mTooltipView.getHeight());
    828 
    829         injectHoverMove(source, mTooltipView, 0, 0);
    830         waitOut(halfTimeout);
    831         assertFalse(hasTooltip(mTooltipView));
    832 
    833         injectHoverMove(source, mTooltipView, jitterHigh, 0);
    834         waitOut(halfTimeout);
    835         assertFalse(hasTooltip(mTooltipView));
    836 
    837         injectHoverMove(source, mTooltipView, 0, 0);
    838         waitOut(halfTimeout);
    839         assertFalse(hasTooltip(mTooltipView));
    840 
    841         injectHoverMove(source, mTooltipView, 0, jitterHigh);
    842         waitOut(halfTimeout);
    843         assertFalse(hasTooltip(mTooltipView));
    844 
    845         // Jitter below threshold should be ignored and the tooltip should be shown.
    846         injectHoverMove(source, mTooltipView, 0, 0);
    847         waitOut(halfTimeout);
    848         assertFalse(hasTooltip(mTooltipView));
    849 
    850         int jitterLow = hoverSlop - 1;
    851         injectHoverMove(source, mTooltipView, jitterLow, 0);
    852         waitOut(halfTimeout);
    853         assertTrue(hasTooltip(mTooltipView));
    854 
    855         // Dismiss the tooltip
    856         injectShortClick(mTooltipView);
    857         assertFalse(hasTooltip(mTooltipView));
    858 
    859         injectHoverMove(source, mTooltipView, 0, 0);
    860         waitOut(halfTimeout);
    861         assertFalse(hasTooltip(mTooltipView));
    862 
    863         injectHoverMove(source, mTooltipView, 0, jitterLow);
    864         waitOut(halfTimeout);
    865         assertTrue(hasTooltip(mTooltipView));
    866     }
    867 
    868     @Test
    869     public void testTooltipInPopup() throws Throwable {
    870         TextView popupContent = new TextView(mActivity);
    871 
    872         mActivityRule.runOnUiThread(() -> {
    873             popupContent.setText("Popup view");
    874             popupContent.setTooltipText("Tooltip");
    875 
    876             PopupWindow popup = new PopupWindow(popupContent,
    877                     ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
    878             popup.showAtLocation(mGroupView, Gravity.CENTER, 0, 0);
    879         });
    880         mInstrumentation.waitForIdleSync();
    881 
    882         injectLongClick(popupContent);
    883         assertTrue(hasTooltip(popupContent));
    884     }
    885 }
    886