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