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