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