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