1 /* 2 * Copyright (C) 2008 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.widget.cts29; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertFalse; 21 import static org.junit.Assert.assertNotNull; 22 import static org.junit.Assert.assertNull; 23 import static org.junit.Assert.assertSame; 24 import static org.junit.Assert.assertTrue; 25 import static org.junit.Assume.assumeFalse; 26 27 import android.app.NotificationManager; 28 import android.content.Context; 29 import android.content.pm.PackageManager; 30 import android.graphics.drawable.Drawable; 31 import android.os.SystemClock; 32 import android.provider.Settings; 33 import android.view.Gravity; 34 import android.view.View; 35 import android.view.ViewTreeObserver; 36 import android.view.WindowManager; 37 import android.view.accessibility.AccessibilityManager; 38 import android.widget.ImageView; 39 import android.widget.TextView; 40 import android.widget.Toast; 41 42 import androidx.annotation.NonNull; 43 import androidx.annotation.Nullable; 44 import androidx.test.InstrumentationRegistry; 45 import androidx.test.annotation.UiThreadTest; 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.PollingCheck; 51 import com.android.compatibility.common.util.SystemUtil; 52 import com.android.compatibility.common.util.TestUtils; 53 54 import junit.framework.Assert; 55 56 import org.junit.After; 57 import org.junit.Before; 58 import org.junit.Rule; 59 import org.junit.Test; 60 import org.junit.runner.RunWith; 61 62 import java.util.concurrent.CountDownLatch; 63 import java.util.concurrent.TimeUnit; 64 65 @LargeTest 66 @RunWith(AndroidJUnit4.class) 67 public class ToastTest { 68 private static final String TEST_TOAST_TEXT = "test toast"; 69 private static final String TEST_CUSTOM_TOAST_TEXT = "test custom toast"; 70 private static final String SETTINGS_ACCESSIBILITY_UI_TIMEOUT = 71 "accessibility_non_interactive_ui_timeout_ms"; 72 private static final int ACCESSIBILITY_STATE_WAIT_TIMEOUT_MS = 3000; 73 private static final long TIME_FOR_UI_OPERATION = 1000L; 74 private static final long TIME_OUT = 5000L; 75 76 private Toast mToast; 77 private Context mContext; 78 private boolean mLayoutDone; 79 private ViewTreeObserver.OnGlobalLayoutListener mLayoutListener; 80 private NotificationManager mNotificationManager; 81 82 @Rule 83 public ActivityTestRule<CtsActivity> mActivityRule = 84 new ActivityTestRule<>(CtsActivity.class); 85 86 @Before setup()87 public void setup() { 88 mContext = InstrumentationRegistry.getTargetContext(); 89 mLayoutListener = () -> mLayoutDone = true; 90 mNotificationManager = 91 mContext.getSystemService(NotificationManager.class); 92 // disable rate limiting for tests 93 SystemUtil.runWithShellPermissionIdentity(() -> mNotificationManager 94 .setToastRateLimitingEnabled(false)); 95 } 96 97 @After teardown()98 public void teardown() { 99 // re-enable rate limiting 100 SystemUtil.runWithShellPermissionIdentity(() -> mNotificationManager 101 .setToastRateLimitingEnabled(true)); 102 } 103 104 @UiThreadTest 105 @Test testConstructor()106 public void testConstructor() { 107 new Toast(mContext); 108 } 109 110 @UiThreadTest 111 @Test(expected=NullPointerException.class) testConstructorNullContext()112 public void testConstructorNullContext() { 113 new Toast(null); 114 } 115 assertShowToast(final View view)116 private static void assertShowToast(final View view) { 117 PollingCheck.waitFor(TIME_OUT, () -> null != view.getParent()); 118 } 119 assertShowAndHide(final View view)120 private static void assertShowAndHide(final View view) { 121 assertShowToast(view); 122 PollingCheck.waitFor(TIME_OUT, () -> null == view.getParent()); 123 } 124 assertNotShowToast(final View view)125 private static void assertNotShowToast(final View view) { 126 // sleep a while and then make sure do not show toast 127 SystemClock.sleep(TIME_FOR_UI_OPERATION); 128 assertNull(view.getParent()); 129 } 130 registerLayoutListener(final View view)131 private void registerLayoutListener(final View view) { 132 mLayoutDone = false; 133 view.getViewTreeObserver().addOnGlobalLayoutListener(mLayoutListener); 134 } 135 assertLayoutDone(final View view)136 private void assertLayoutDone(final View view) { 137 PollingCheck.waitFor(TIME_OUT, () -> mLayoutDone); 138 view.getViewTreeObserver().removeOnGlobalLayoutListener(mLayoutListener); 139 } 140 makeToast()141 private void makeToast() throws Throwable { 142 mActivityRule.runOnUiThread( 143 () -> mToast = Toast.makeText(mContext, TEST_TOAST_TEXT, Toast.LENGTH_LONG)); 144 } 145 makeCustomToast()146 private void makeCustomToast() throws Throwable { 147 mActivityRule.runOnUiThread( 148 () -> { 149 mToast = new Toast(mContext); 150 mToast.setDuration(Toast.LENGTH_LONG); 151 TextView view = new TextView(mContext); 152 view.setText(TEST_CUSTOM_TOAST_TEXT); 153 mToast.setView(view); 154 } 155 ); 156 } 157 158 @Test testShow()159 public void testShow() throws Throwable { 160 makeToast(); 161 162 final View view = mToast.getView(); 163 164 // view has not been attached to screen yet 165 assertNull(view.getParent()); 166 assertEquals(View.VISIBLE, view.getVisibility()); 167 168 runOnMainAndDrawSync(view, mToast::show); 169 170 // view will be attached to screen when show it 171 assertEquals(View.VISIBLE, view.getVisibility()); 172 assertShowAndHide(view); 173 } 174 175 @UiThreadTest 176 @Test(expected=RuntimeException.class) testShowFailure()177 public void testShowFailure() { 178 Toast toast = new Toast(mContext); 179 // do not have any views. 180 assertNull(toast.getView()); 181 toast.show(); 182 } 183 184 @Test testCancel()185 public void testCancel() throws Throwable { 186 makeToast(); 187 188 final View view = mToast.getView(); 189 190 // view has not been attached to screen yet 191 assertNull(view.getParent()); 192 mActivityRule.runOnUiThread(() -> { 193 mToast.show(); 194 mToast.cancel(); 195 }); 196 197 assertNotShowToast(view); 198 } 199 200 @Test testAccessView()201 public void testAccessView() throws Throwable { 202 makeToast(); 203 assertFalse(mToast.getView() instanceof ImageView); 204 205 final ImageView imageView = new ImageView(mContext); 206 Drawable drawable = mContext.getResources().getDrawable(R.drawable.pass); 207 imageView.setImageDrawable(drawable); 208 209 runOnMainAndDrawSync(imageView, () -> { 210 mToast.setView(imageView); 211 mToast.show(); 212 }); 213 assertSame(imageView, mToast.getView()); 214 assertShowAndHide(imageView); 215 } 216 217 @Test testAccessDuration()218 public void testAccessDuration() throws Throwable { 219 long start = SystemClock.uptimeMillis(); 220 makeToast(); 221 runOnMainAndDrawSync(mToast.getView(), mToast::show); 222 assertEquals(Toast.LENGTH_LONG, mToast.getDuration()); 223 224 View view = mToast.getView(); 225 assertShowAndHide(view); 226 long longDuration = SystemClock.uptimeMillis() - start; 227 228 start = SystemClock.uptimeMillis(); 229 runOnMainAndDrawSync(mToast.getView(), () -> { 230 mToast.setDuration(Toast.LENGTH_SHORT); 231 mToast.show(); 232 }); 233 assertEquals(Toast.LENGTH_SHORT, mToast.getDuration()); 234 235 view = mToast.getView(); 236 assertShowAndHide(view); 237 long shortDuration = SystemClock.uptimeMillis() - start; 238 239 assertTrue(longDuration > shortDuration); 240 } 241 242 @Test testAccessDuration_withA11yTimeoutEnabled()243 public void testAccessDuration_withA11yTimeoutEnabled() throws Throwable { 244 makeToast(); 245 final Runnable showToast = () -> { 246 mToast.setDuration(Toast.LENGTH_SHORT); 247 mToast.show(); 248 }; 249 long start = SystemClock.uptimeMillis(); 250 runOnMainAndDrawSync(mToast.getView(), showToast); 251 assertShowAndHide(mToast.getView()); 252 final long shortDuration = SystemClock.uptimeMillis() - start; 253 254 final String originalSetting = Settings.Secure.getString(mContext.getContentResolver(), 255 SETTINGS_ACCESSIBILITY_UI_TIMEOUT); 256 try { 257 final int a11ySettingDuration = (int) shortDuration + 1000; 258 putSecureSetting(SETTINGS_ACCESSIBILITY_UI_TIMEOUT, 259 Integer.toString(a11ySettingDuration)); 260 waitForA11yRecommendedTimeoutChanged(mContext, 261 ACCESSIBILITY_STATE_WAIT_TIMEOUT_MS, a11ySettingDuration); 262 start = SystemClock.uptimeMillis(); 263 runOnMainAndDrawSync(mToast.getView(), showToast); 264 assertShowAndHide(mToast.getView()); 265 final long a11yDuration = SystemClock.uptimeMillis() - start; 266 assertTrue(a11yDuration >= a11ySettingDuration); 267 } finally { 268 putSecureSetting(SETTINGS_ACCESSIBILITY_UI_TIMEOUT, originalSetting); 269 } 270 } 271 272 /** 273 * Wait for accessibility recommended timeout changed and equals to expected timeout. 274 * 275 * @param expectedTimeoutMs expected recommended timeout 276 */ waitForA11yRecommendedTimeoutChanged(Context context, long waitTimeoutMs, int expectedTimeoutMs)277 private void waitForA11yRecommendedTimeoutChanged(Context context, 278 long waitTimeoutMs, int expectedTimeoutMs) { 279 final AccessibilityManager manager = 280 (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); 281 final Object lock = new Object(); 282 AccessibilityManager.AccessibilityServicesStateChangeListener listener = (m) -> { 283 synchronized (lock) { 284 lock.notifyAll(); 285 } 286 }; 287 manager.addAccessibilityServicesStateChangeListener(listener); 288 try { 289 TestUtils.waitOn(lock, 290 () -> manager.getRecommendedTimeoutMillis(0, 291 AccessibilityManager.FLAG_CONTENT_TEXT) == expectedTimeoutMs, 292 waitTimeoutMs, 293 "Wait for accessibility recommended timeout changed"); 294 } finally { 295 manager.removeAccessibilityServicesStateChangeListener(listener); 296 } 297 } 298 putSecureSetting(String name, String value)299 private void putSecureSetting(String name, String value) { 300 final StringBuilder cmd = new StringBuilder("settings put secure ") 301 .append(name).append(" ") 302 .append(value); 303 SystemUtil.runShellCommand(cmd.toString()); 304 } 305 306 @Test testAccessMargin()307 public void testAccessMargin() throws Throwable { 308 assumeFalse("Skipping test: Auto does not support toast with margin", isCar()); 309 310 makeToast(); 311 View view = mToast.getView(); 312 assertFalse(view.getLayoutParams() instanceof WindowManager.LayoutParams); 313 314 final float horizontal1 = 1.0f; 315 final float vertical1 = 1.0f; 316 runOnMainAndDrawSync(view, () -> { 317 mToast.setMargin(horizontal1, vertical1); 318 mToast.show(); 319 registerLayoutListener(mToast.getView()); 320 }); 321 assertShowToast(view); 322 323 assertEquals(horizontal1, mToast.getHorizontalMargin(), 0.0f); 324 assertEquals(vertical1, mToast.getVerticalMargin(), 0.0f); 325 WindowManager.LayoutParams params1 = (WindowManager.LayoutParams) view.getLayoutParams(); 326 assertEquals(horizontal1, params1.horizontalMargin, 0.0f); 327 assertEquals(vertical1, params1.verticalMargin, 0.0f); 328 assertLayoutDone(view); 329 330 int[] xy1 = new int[2]; 331 view.getLocationOnScreen(xy1); 332 assertShowAndHide(view); 333 334 final float horizontal2 = 0.1f; 335 final float vertical2 = 0.1f; 336 runOnMainAndDrawSync(view, () -> { 337 mToast.setMargin(horizontal2, vertical2); 338 mToast.show(); 339 registerLayoutListener(mToast.getView()); 340 }); 341 assertShowToast(view); 342 343 assertEquals(horizontal2, mToast.getHorizontalMargin(), 0.0f); 344 assertEquals(vertical2, mToast.getVerticalMargin(), 0.0f); 345 WindowManager.LayoutParams params2 = (WindowManager.LayoutParams) view.getLayoutParams(); 346 assertEquals(horizontal2, params2.horizontalMargin, 0.0f); 347 assertEquals(vertical2, params2.verticalMargin, 0.0f); 348 349 assertLayoutDone(view); 350 int[] xy2 = new int[2]; 351 view.getLocationOnScreen(xy2); 352 assertShowAndHide(view); 353 354 /** Check if the test is being run on a watch. 355 * 356 * Change I8180e5080e0a6860b40dbb2faa791f0ede926ca7 updated how toast are displayed on the 357 * watch. Unlike the phone, which displays toast centered horizontally at the bottom of the 358 * screen, the watch now displays toast in the center of the screen. 359 */ 360 if (Gravity.CENTER == mToast.getGravity()) { 361 assertTrue(xy1[0] > xy2[0]); 362 assertTrue(xy1[1] > xy2[1]); 363 } else { 364 assertTrue(xy1[0] > xy2[0]); 365 assertTrue(xy1[1] < xy2[1]); 366 } 367 } 368 369 @Test 370 public void testAccessGravity() throws Throwable { 371 assumeFalse("Skipping test: Auto does not support toast with gravity", isCar()); 372 373 makeToast(); 374 runOnMainAndDrawSync(mToast.getView(), () -> { 375 mToast.setGravity(Gravity.CENTER, 0, 0); 376 mToast.show(); 377 registerLayoutListener(mToast.getView()); 378 }); 379 View view = mToast.getView(); 380 assertShowToast(view); 381 assertEquals(Gravity.CENTER, mToast.getGravity()); 382 assertEquals(0, mToast.getXOffset()); 383 assertEquals(0, mToast.getYOffset()); 384 assertLayoutDone(view); 385 int[] centerXY = new int[2]; 386 view.getLocationOnScreen(centerXY); 387 assertShowAndHide(view); 388 389 runOnMainAndDrawSync(mToast.getView(), () -> { 390 mToast.setGravity(Gravity.BOTTOM, 0, 0); 391 mToast.show(); 392 registerLayoutListener(mToast.getView()); 393 }); 394 view = mToast.getView(); 395 assertShowToast(view); 396 assertEquals(Gravity.BOTTOM, mToast.getGravity()); 397 assertEquals(0, mToast.getXOffset()); 398 assertEquals(0, mToast.getYOffset()); 399 assertLayoutDone(view); 400 int[] bottomXY = new int[2]; 401 view.getLocationOnScreen(bottomXY); 402 assertShowAndHide(view); 403 404 // x coordinate is the same 405 assertEquals(centerXY[0], bottomXY[0]); 406 // bottom view is below of center view 407 assertTrue(centerXY[1] < bottomXY[1]); 408 409 final int xOffset = 20; 410 final int yOffset = 10; 411 runOnMainAndDrawSync(mToast.getView(), () -> { 412 mToast.setGravity(Gravity.BOTTOM, xOffset, yOffset); 413 mToast.show(); 414 registerLayoutListener(mToast.getView()); 415 }); 416 view = mToast.getView(); 417 assertShowToast(view); 418 assertEquals(Gravity.BOTTOM, mToast.getGravity()); 419 assertEquals(xOffset, mToast.getXOffset()); 420 assertEquals(yOffset, mToast.getYOffset()); 421 assertLayoutDone(view); 422 int[] bottomOffsetXY = new int[2]; 423 view.getLocationOnScreen(bottomOffsetXY); 424 assertShowAndHide(view); 425 426 assertEquals(bottomXY[0] + xOffset, bottomOffsetXY[0]); 427 assertEquals(bottomXY[1] - yOffset, bottomOffsetXY[1]); 428 } 429 430 @UiThreadTest 431 @Test testMakeTextFromString()432 public void testMakeTextFromString() { 433 Toast toast = Toast.makeText(mContext, "android", Toast.LENGTH_SHORT); 434 assertNotNull(toast); 435 assertEquals(Toast.LENGTH_SHORT, toast.getDuration()); 436 View view = toast.getView(); 437 assertNotNull(view); 438 439 toast = Toast.makeText(mContext, "cts", Toast.LENGTH_LONG); 440 assertNotNull(toast); 441 assertEquals(Toast.LENGTH_LONG, toast.getDuration()); 442 view = toast.getView(); 443 assertNotNull(view); 444 445 toast = Toast.makeText(mContext, null, Toast.LENGTH_LONG); 446 assertNotNull(toast); 447 assertEquals(Toast.LENGTH_LONG, toast.getDuration()); 448 view = toast.getView(); 449 assertNotNull(view); 450 } 451 452 @UiThreadTest 453 @Test(expected=NullPointerException.class) testMakeTextFromStringNullContext()454 public void testMakeTextFromStringNullContext() { 455 Toast.makeText(null, "test", Toast.LENGTH_LONG); 456 } 457 458 @UiThreadTest 459 @Test testMakeTextFromResource()460 public void testMakeTextFromResource() { 461 Toast toast = Toast.makeText(mContext, R.string.hello_world, Toast.LENGTH_LONG); 462 463 assertNotNull(toast); 464 assertEquals(Toast.LENGTH_LONG, toast.getDuration()); 465 View view = toast.getView(); 466 assertNotNull(view); 467 468 toast = Toast.makeText(mContext, R.string.hello_android, Toast.LENGTH_SHORT); 469 assertNotNull(toast); 470 assertEquals(Toast.LENGTH_SHORT, toast.getDuration()); 471 view = toast.getView(); 472 assertNotNull(view); 473 } 474 475 @UiThreadTest 476 @Test(expected=NullPointerException.class) testMakeTextFromResourceNullContext()477 public void testMakeTextFromResourceNullContext() { 478 Toast.makeText(null, R.string.hello_android, Toast.LENGTH_SHORT); 479 } 480 481 @UiThreadTest 482 @Test testSetTextFromResource()483 public void testSetTextFromResource() { 484 Toast toast = Toast.makeText(mContext, R.string.text, Toast.LENGTH_LONG); 485 486 toast.setText(R.string.hello_world); 487 // TODO: how to getText to assert? 488 489 toast.setText(R.string.hello_android); 490 // TODO: how to getText to assert? 491 } 492 493 @UiThreadTest 494 @Test(expected=RuntimeException.class) testSetTextFromInvalidResource()495 public void testSetTextFromInvalidResource() { 496 Toast toast = Toast.makeText(mContext, R.string.text, Toast.LENGTH_LONG); 497 toast.setText(-1); 498 } 499 500 @UiThreadTest 501 @Test testSetTextFromString()502 public void testSetTextFromString() { 503 Toast toast = Toast.makeText(mContext, R.string.text, Toast.LENGTH_LONG); 504 505 toast.setText("cts"); 506 // TODO: how to getText to assert? 507 508 toast.setText("android"); 509 // TODO: how to getText to assert? 510 } 511 512 @UiThreadTest 513 @Test(expected=RuntimeException.class) testSetTextFromStringNullView()514 public void testSetTextFromStringNullView() { 515 Toast toast = Toast.makeText(mContext, R.string.text, Toast.LENGTH_LONG); 516 toast.setView(null); 517 toast.setText(null); 518 } 519 520 @Test testTextToastAllowed_whenInTheForeground()521 public void testTextToastAllowed_whenInTheForeground() throws Throwable { 522 makeToast(); 523 View view = mToast.getView(); 524 525 mActivityRule.runOnUiThread(mToast::show); 526 527 assertShowAndHide(view); 528 } 529 530 @Test testCustomToastAllowed_whenInTheForeground()531 public void testCustomToastAllowed_whenInTheForeground() throws Throwable { 532 makeCustomToast(); 533 View view = mToast.getView(); 534 // View has not been attached to screen yet 535 assertNull(view.getParent()); 536 537 mActivityRule.runOnUiThread(mToast::show); 538 539 assertShowAndHide(view); 540 } 541 542 @Test testTextToastAllowed_whenInTheBackground()543 public void testTextToastAllowed_whenInTheBackground() throws Throwable { 544 // Make it background 545 mActivityRule.finishActivity(); 546 makeToast(); 547 View view = mToast.getView(); 548 549 mActivityRule.runOnUiThread(mToast::show); 550 551 assertShowAndHide(view); 552 } 553 554 @Test testCustomToastAllowed_whenInTheBackground()555 public void testCustomToastAllowed_whenInTheBackground() throws Throwable { 556 // Make it background 557 mActivityRule.finishActivity(); 558 makeCustomToast(); 559 View view = mToast.getView(); 560 // View has not been attached to screen yet 561 assertNull(view.getParent()); 562 563 mActivityRule.runOnUiThread(mToast::show); 564 565 assertShowAndHide(view); 566 } 567 568 @UiThreadTest 569 @Test testGetWindowParams_whenTextToast_doesNotReturnNull()570 public void testGetWindowParams_whenTextToast_doesNotReturnNull() { 571 Toast toast = Toast.makeText(mContext, "Text", Toast.LENGTH_LONG); 572 assertNotNull(toast.getWindowParams()); 573 } 574 575 @UiThreadTest 576 @Test testGetWindowParams_whenCustomToast_doesNotReturnNull()577 public void testGetWindowParams_whenCustomToast_doesNotReturnNull() { 578 Toast toast = new Toast(mContext); 579 toast.setView(new TextView(mContext)); 580 assertNotNull(toast.getWindowParams()); 581 } 582 runOnMainAndDrawSync(@onNull final View toastView, @Nullable final Runnable runner)583 private void runOnMainAndDrawSync(@NonNull final View toastView, 584 @Nullable final Runnable runner) { 585 final CountDownLatch latch = new CountDownLatch(1); 586 587 try { 588 mActivityRule.runOnUiThread(() -> { 589 final ViewTreeObserver.OnDrawListener listener = 590 new ViewTreeObserver.OnDrawListener() { 591 @Override 592 public void onDraw() { 593 // posting so that the sync happens after the draw that's about 594 // to happen 595 toastView.post(() -> { 596 toastView.getViewTreeObserver().removeOnDrawListener(this); 597 latch.countDown(); 598 }); 599 } 600 }; 601 602 toastView.getViewTreeObserver().addOnDrawListener(listener); 603 604 if (runner != null) { 605 runner.run(); 606 } 607 toastView.invalidate(); 608 }); 609 610 Assert.assertTrue("Expected toast draw pass occurred within 5 seconds", 611 latch.await(5, TimeUnit.SECONDS)); 612 } catch (Throwable t) { 613 throw new RuntimeException(t); 614 } 615 } 616 isCar()617 private boolean isCar() { 618 PackageManager pm = mContext.getPackageManager(); 619 return pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); 620 } 621 } 622