• Home
  • History
  • Annotate
  • 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 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
setup()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  
waitOut(long msDelay)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  
setTooltipText(View view, CharSequence tooltipText)99      private void setTooltipText(View view, CharSequence tooltipText) throws Throwable {
100          mActivityRule.runOnUiThread(() -> view.setTooltipText(tooltipText));
101      }
102  
hasTooltip(View view)103      private boolean hasTooltip(View view) {
104          final View tooltipView = view.getTooltipView();
105          return tooltipView != null && tooltipView.getParent() != null;
106      }
107  
108  
addView(ViewGroup parent, View view)109      private void addView(ViewGroup parent, View view) throws Throwable {
110          mActivityRule.runOnUiThread(() -> parent.addView(view));
111          mInstrumentation.waitForIdleSync();
112      }
113  
removeView(View view)114      private void removeView(View view) throws Throwable {
115          mActivityRule.runOnUiThread(() -> ((ViewGroup) (view.getParent())).removeView(view));
116          mInstrumentation.waitForIdleSync();
117      }
118  
setVisibility(View view, int visibility)119      private void setVisibility(View view, int visibility) throws Throwable {
120          mActivityRule.runOnUiThread(() -> view.setVisibility(visibility));
121      }
122  
setClickable(View view)123      private void setClickable(View view) throws Throwable {
124          mActivityRule.runOnUiThread(() -> view.setClickable(true));
125      }
126  
setLongClickable(View view)127      private void setLongClickable(View view) throws Throwable {
128          mActivityRule.runOnUiThread(() -> view.setLongClickable(true));
129      }
130  
setContextClickable(View view)131      private void setContextClickable(View view) throws Throwable {
132          mActivityRule.runOnUiThread(() -> view.setContextClickable(true));
133      }
134  
callPerformLongClick(View view)135      private void callPerformLongClick(View view) throws Throwable {
136          mActivityRule.runOnUiThread(() -> view.performLongClick(0, 0));
137      }
138  
requestLowProfileSystemUi()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  
injectKeyPress(View target, int keyCode, int duration)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  
injectArbitraryShortKeyPress()160      private void injectArbitraryShortKeyPress() throws Throwable {
161          injectKeyPress(null, KeyEvent.KEYCODE_0, 0);
162      }
163  
injectLongKeyPress(View target, int keyCode)164      private void injectLongKeyPress(View target, int keyCode) throws Throwable {
165          injectKeyPress(target, keyCode, ViewConfiguration.getLongPressTimeout());
166      }
167  
injectLongEnter(View target)168      private void injectLongEnter(View target) throws Throwable {
169          injectLongKeyPress(target, KeyEvent.KEYCODE_ENTER);
170      }
171  
injectShortClick(View target)172      private void injectShortClick(View target) {
173          CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, target);
174      }
175  
injectLongClick(View target)176      private void injectLongClick(View target) {
177          CtsTouchUtils.emulateLongPressOnView(mInstrumentation, target,
178                  target.getWidth() / 2, target.getHeight() / 2);
179      }
180  
injectMotionEvent(MotionEvent event)181      private void injectMotionEvent(MotionEvent event) {
182          mInstrumentation.sendPointerSync(event);
183      }
184  
injectHoverMove(int source, View target, int offsetX, int offsetY)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  
injectHoverMove(View target, int offsetX, int offsetY)190      private void injectHoverMove(View target, int offsetX, int offsetY) {
191          injectHoverMove(InputDevice.SOURCE_MOUSE, target, offsetX,  offsetY);
192      }
193  
injectHoverMove(View target)194      private void injectHoverMove(View target) {
195          injectHoverMove(target, 0, 0);
196      }
197  
injectLongHoverMove(View target)198      private void injectLongHoverMove(View target) {
199          injectHoverMove(target);
200          waitOut(ViewConfiguration.getHoverTooltipShowTimeout());
201      }
202  
obtainMouseEvent(View target, int action, int offsetX, int offsetY)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  
obtainMotionEvent( int source, View target, int action, int offsetX, int offsetY)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
testGetSetTooltip()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
testNoTooltipWhenNotSet()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
testTooltipOnDisabledView()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
testUpdateOpenTooltip()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
testTooltipHidesOnActivityFocusChange()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
testTooltipHidesOnWindowFocusChange()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
testShortClickDoesNotShowTooltip()314      public void testShortClickDoesNotShowTooltip() throws Throwable {
315          injectShortClick(mTooltipView);
316          assertFalse(hasTooltip(mTooltipView));
317      }
318  
319      @Test
testPerformLongClickShowsTooltipImmediately()320      public void testPerformLongClickShowsTooltipImmediately() throws Throwable {
321          callPerformLongClick(mTooltipView);
322          assertTrue(hasTooltip(mTooltipView));
323      }
324  
325      @Test
testLongClickTooltipBlockedByLongClickListener()326      public void testLongClickTooltipBlockedByLongClickListener() throws Throwable {
327          mTooltipView.setOnLongClickListener(v -> true);
328          injectLongClick(mTooltipView);
329          assertFalse(hasTooltip(mTooltipView));
330      }
331  
332      @Test
testLongClickTooltipBlockedByContextMenu()333      public void testLongClickTooltipBlockedByContextMenu() throws Throwable {
334          mActivity.registerForContextMenu(mTooltipView);
335          injectLongClick(mTooltipView);
336          assertFalse(hasTooltip(mTooltipView));
337      }
338  
339      @Test
testLongClickTooltipOnNonClickableView()340      public void testLongClickTooltipOnNonClickableView() throws Throwable {
341          injectLongClick(mTooltipView);
342          assertTrue(hasTooltip(mTooltipView));
343      }
344  
345      @Test
testLongClickTooltipOnClickableView()346      public void testLongClickTooltipOnClickableView() throws Throwable {
347          setClickable(mTooltipView);
348          injectLongClick(mTooltipView);
349          assertTrue(hasTooltip(mTooltipView));
350      }
351  
352      @Test
testLongClickTooltipOnLongClickableView()353      public void testLongClickTooltipOnLongClickableView() throws Throwable {
354          setLongClickable(mTooltipView);
355          injectLongClick(mTooltipView);
356          assertTrue(hasTooltip(mTooltipView));
357      }
358  
359      @Test
testLongClickTooltipOnContextClickableView()360      public void testLongClickTooltipOnContextClickableView() throws Throwable {
361          setContextClickable(mTooltipView);
362          injectLongClick(mTooltipView);
363          assertTrue(hasTooltip(mTooltipView));
364      }
365  
366      @Test
testLongClickTooltipStaysOnMouseMove()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
testLongClickTooltipHidesAfterUp()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
testLongClickTooltipHidesOnClick()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
testLongClickTooltipHidesOnClickElsewhere()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
testLongClickTooltipHidesOnKey()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
testShortKeyPressDoesNotShowTooltip()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
testLongArbitraryKeyPressDoesNotShowTooltip()429      public void testLongArbitraryKeyPressDoesNotShowTooltip() throws Throwable {
430          injectLongKeyPress(mTooltipView, KeyEvent.KEYCODE_0);
431          assertFalse(hasTooltip(mTooltipView));
432      }
433  
434      @Test
testLongKeyPressWithoutFocusDoesNotShowTooltip()435      public void testLongKeyPressWithoutFocusDoesNotShowTooltip() throws Throwable {
436          injectLongEnter(null);
437          assertFalse(hasTooltip(mTooltipView));
438      }
439  
440      @Test
testLongKeyPressOnAnotherViewDoesNotShowTooltip()441      public void testLongKeyPressOnAnotherViewDoesNotShowTooltip() throws Throwable {
442          injectLongEnter(mNoTooltipView);
443          assertFalse(hasTooltip(mTooltipView));
444      }
445  
446      @Test
testLongKeyPressTooltipOnNonClickableView()447      public void testLongKeyPressTooltipOnNonClickableView() throws Throwable {
448          injectLongEnter(mTooltipView);
449          assertTrue(hasTooltip(mTooltipView));
450      }
451  
452      @Test
testLongKeyPressTooltipOnClickableView()453      public void testLongKeyPressTooltipOnClickableView() throws Throwable {
454          setClickable(mTooltipView);
455          injectLongEnter(mTooltipView);
456          assertTrue(hasTooltip(mTooltipView));
457      }
458  
459      @Test
testLongKeyPressTooltipOnLongClickableView()460      public void testLongKeyPressTooltipOnLongClickableView() throws Throwable {
461          setLongClickable(mTooltipView);
462          injectLongEnter(mTooltipView);
463          assertTrue(hasTooltip(mTooltipView));
464      }
465  
466      @Test
testLongKeyPressTooltipOnContextClickableView()467      public void testLongKeyPressTooltipOnContextClickableView() throws Throwable {
468          setContextClickable(mTooltipView);
469          injectLongEnter(mTooltipView);
470          assertTrue(hasTooltip(mTooltipView));
471      }
472  
473      @Test
testLongKeyPressTooltipStaysOnMouseMove()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
testLongKeyPressTooltipHidesAfterUp()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
testLongKeyPressTooltipHidesOnClick()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
testLongKeyPressTooltipHidesOnClickElsewhere()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
testLongKeyPressTooltipHidesOnKey()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
testMouseClickDoesNotShowTooltip()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
testMouseHoverDoesNotShowTooltipImmediately()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
testMouseHoverExitCancelsPendingTooltip()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
testMouseHoverTooltipOnClickableView()557      public void testMouseHoverTooltipOnClickableView() throws Throwable {
558          setClickable(mTooltipView);
559          injectLongHoverMove(mTooltipView);
560          assertTrue(hasTooltip(mTooltipView));
561      }
562  
563      @Test
testMouseHoverTooltipOnLongClickableView()564      public void testMouseHoverTooltipOnLongClickableView() throws Throwable {
565          setLongClickable(mTooltipView);
566          injectLongHoverMove(mTooltipView);
567          assertTrue(hasTooltip(mTooltipView));
568      }
569  
570      @Test
testMouseHoverTooltipOnContextClickableView()571      public void testMouseHoverTooltipOnContextClickableView() throws Throwable {
572          setContextClickable(mTooltipView);
573          injectLongHoverMove(mTooltipView);
574          assertTrue(hasTooltip(mTooltipView));
575      }
576  
577      @Test
testMouseHoverTooltipStaysOnMouseMove()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
testMouseHoverTooltipHidesOnExit()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
testMouseHoverTooltipHidesOnClick()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
testMouseHoverTooltipHidesOnClickOnElsewhere()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
testMouseHoverTooltipHidesOnKey()619      public void testMouseHoverTooltipHidesOnKey() throws Throwable {
620          injectLongHoverMove(mTooltipView);
621          assertTrue(hasTooltip(mTooltipView));
622  
623          injectArbitraryShortKeyPress();
624          assertFalse(hasTooltip(mTooltipView));
625      }
626  
627      @Test
testMouseHoverTooltipHidesOnTimeout()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
testMouseHoverTooltipHidesOnShortTimeout()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
testMouseHoverTooltipWithHoverListener()648      public void testMouseHoverTooltipWithHoverListener() throws Throwable {
649          mTooltipView.setOnHoverListener((v, event) -> true);
650          injectLongHoverMove(mTooltipView);
651          assertTrue(hasTooltip(mTooltipView));
652      }
653  
654      @Test
testMouseHoverTooltipUnsetWhileHovering()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
testMouseHoverTooltipDisableWhileHovering()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
testMouseHoverTooltipFromParent()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
testMouseHoverTooltipRemoveWhileWaiting()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
testMouseHoverTooltipRemoveWhileShowing()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
testMouseHoverOverlap()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
testMouseHoverWithJitter()799      public void testMouseHoverWithJitter() throws Throwable {
800          testHoverWithJitter(InputDevice.SOURCE_MOUSE);
801      }
802  
803      @Test
testStylusHoverWithJitter()804      public void testStylusHoverWithJitter() throws Throwable {
805          testHoverWithJitter(InputDevice.SOURCE_STYLUS);
806      }
807  
808      @Test
testTouchscreenHoverWithJitter()809      public void testTouchscreenHoverWithJitter() throws Throwable {
810          testHoverWithJitter(InputDevice.SOURCE_TOUCHSCREEN);
811      }
812  
testHoverWithJitter(int source)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
testTooltipInPopup()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