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