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