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