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.cts; 18 19 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 20 21 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 22 23 import static com.google.common.truth.Truth.assertThat; 24 25 import static org.junit.Assert.assertEquals; 26 import static org.junit.Assert.assertFalse; 27 import static org.junit.Assert.assertNotNull; 28 import static org.junit.Assert.assertNull; 29 import static org.junit.Assert.assertSame; 30 import static org.junit.Assert.assertTrue; 31 import static org.junit.Assume.assumeFalse; 32 33 import static java.util.stream.Collectors.toList; 34 35 import android.app.ActivityOptions; 36 import android.app.NotificationManager; 37 import android.app.UiAutomation; 38 import android.app.UiAutomation.AccessibilityEventFilter; 39 import android.content.BroadcastReceiver; 40 import android.content.ComponentName; 41 import android.content.Context; 42 import android.content.Intent; 43 import android.content.IntentFilter; 44 import android.content.pm.PackageManager; 45 import android.graphics.drawable.Drawable; 46 import android.os.ConditionVariable; 47 import android.os.SystemClock; 48 import android.provider.Settings; 49 import android.view.Gravity; 50 import android.view.View; 51 import android.view.ViewTreeObserver; 52 import android.view.WindowManager; 53 import android.view.accessibility.AccessibilityEvent; 54 import android.view.accessibility.AccessibilityManager; 55 import android.widget.ImageView; 56 import android.widget.TextView; 57 import android.widget.Toast; 58 59 import androidx.annotation.NonNull; 60 import androidx.annotation.Nullable; 61 import androidx.test.annotation.UiThreadTest; 62 import androidx.test.filters.LargeTest; 63 import androidx.test.rule.ActivityTestRule; 64 import androidx.test.runner.AndroidJUnit4; 65 66 import com.android.compatibility.common.util.PollingCheck; 67 import com.android.compatibility.common.util.SystemUtil; 68 import com.android.compatibility.common.util.TestUtils; 69 70 import junit.framework.Assert; 71 72 import org.junit.After; 73 import org.junit.Before; 74 import org.junit.Rule; 75 import org.junit.Test; 76 import org.junit.runner.RunWith; 77 78 import java.util.ArrayList; 79 import java.util.List; 80 import java.util.concurrent.CompletableFuture; 81 import java.util.concurrent.CountDownLatch; 82 import java.util.concurrent.TimeUnit; 83 import java.util.stream.Stream; 84 85 @LargeTest 86 @RunWith(AndroidJUnit4.class) 87 public class ToastTest { 88 private static final String TEST_TOAST_TEXT = "test toast"; 89 private static final String TEST_CUSTOM_TOAST_TEXT = "test custom toast"; 90 private static final String SETTINGS_ACCESSIBILITY_UI_TIMEOUT = 91 "accessibility_non_interactive_ui_timeout_ms"; 92 private static final int ACCESSIBILITY_STATE_WAIT_TIMEOUT_MS = 3000; 93 private static final long TIME_FOR_UI_OPERATION = 1000L; 94 private static final long TIME_OUT = 5000L; 95 private static final int MAX_PACKAGE_TOASTS_LIMIT = 5; 96 private static final String ACTION_TRANSLUCENT_ACTIVITY_RESUMED = 97 "android.widget.cts.app.TRANSLUCENT_ACTIVITY_RESUMED"; 98 private static final String ACTION_TRANSLUCENT_ACTIVITY_FINISH = 99 "android.widget.cts.app.TRANSLUCENT_ACTIVITY_FINISH"; 100 private static final ComponentName COMPONENT_TRANSLUCENT_ACTIVITY = 101 ComponentName.unflattenFromString("android.widget.cts.app/.TranslucentActivity"); 102 private static final double TOAST_DURATION_ERROR_TOLERANCE_FRACTION = 0.25; 103 104 // The following two fields work together to define rate limits for toasts, where each limit is 105 // defined as TOAST_RATE_LIMITS[i] toasts are allowed in the window of length 106 // TOAST_WINDOW_SIZES_MS[i]. 107 private static final int[] TOAST_RATE_LIMITS = {3, 5, 6}; 108 private static final long[] TOAST_WINDOW_SIZES_MS = {20_000, 42_000, 68_000}; 109 110 private Toast mToast; 111 private Context mContext; 112 private boolean mLayoutDone; 113 private ViewTreeObserver.OnGlobalLayoutListener mLayoutListener; 114 private ConditionVariable mToastShown; 115 private ConditionVariable mToastHidden; 116 private NotificationManager mNotificationManager; 117 118 @Rule 119 public ActivityTestRule<CtsActivity> mActivityRule = 120 new ActivityTestRule<>(CtsActivity.class); 121 private UiAutomation mUiAutomation; 122 123 @Before setup()124 public void setup() { 125 mContext = getInstrumentation().getContext(); 126 mUiAutomation = getInstrumentation().getUiAutomation(); 127 mLayoutListener = () -> mLayoutDone = true; 128 mNotificationManager = 129 mContext.getSystemService(NotificationManager.class); 130 // disable rate limiting for tests 131 SystemUtil.runWithShellPermissionIdentity(() -> mNotificationManager 132 .setToastRateLimitingEnabled(false)); 133 } 134 135 @After teardown()136 public void teardown() { 137 waitForToastToExpire(); 138 // re-enable rate limiting 139 SystemUtil.runWithShellPermissionIdentity(() -> mNotificationManager 140 .setToastRateLimitingEnabled(true)); 141 } 142 143 @UiThreadTest 144 @Test testConstructor()145 public void testConstructor() { 146 new Toast(mContext); 147 } 148 149 @UiThreadTest 150 @Test(expected=NullPointerException.class) testConstructorNullContext()151 public void testConstructorNullContext() { 152 new Toast(null); 153 } 154 assertCustomToastShown(final View view)155 private static void assertCustomToastShown(final View view) { 156 PollingCheck.waitFor(TIME_OUT, () -> null != view.getParent()); 157 } 158 assertCustomToastShown(CustomToastInfo customToastInfo)159 private static void assertCustomToastShown(CustomToastInfo customToastInfo) { 160 PollingCheck.waitFor(TIME_OUT, customToastInfo::isShowing); 161 } 162 assertCustomToastHidden(CustomToastInfo customToastInfo)163 private static void assertCustomToastHidden(CustomToastInfo customToastInfo) { 164 PollingCheck.waitFor(TIME_OUT, () -> !customToastInfo.isShowing()); 165 } 166 assertCustomToastShownAndHidden(final View view)167 private static void assertCustomToastShownAndHidden(final View view) { 168 assertCustomToastShown(view); 169 PollingCheck.waitFor(TIME_OUT, () -> null == view.getParent()); 170 } 171 assertCustomToastShownAndHidden(CustomToastInfo customToastInfo)172 private static void assertCustomToastShownAndHidden(CustomToastInfo customToastInfo) { 173 assertCustomToastShown(customToastInfo); 174 assertCustomToastHidden(customToastInfo); 175 } 176 assertTextToastShownAndHidden()177 private void assertTextToastShownAndHidden() { 178 assertTrue(mToastShown.block(TIME_OUT)); 179 assertTrue(mToastHidden.block(TIME_OUT)); 180 } 181 assertTextToastShownAndHidden(TextToastInfo textToastInfo)182 private void assertTextToastShownAndHidden(TextToastInfo textToastInfo) { 183 assertTrue(textToastInfo.blockOnToastShown(TIME_OUT)); 184 assertTrue(textToastInfo.blockOnToastHidden(TIME_OUT)); 185 } 186 assertCustomToastNotShown(final View view)187 private static void assertCustomToastNotShown(final View view) { 188 // sleep a while and then make sure do not show toast 189 SystemClock.sleep(TIME_FOR_UI_OPERATION); 190 assertNull(view.getParent()); 191 } 192 assertCustomToastNotShown(CustomToastInfo customToastInfo)193 private static void assertCustomToastNotShown(CustomToastInfo customToastInfo) { 194 assertThat(customToastInfo.isShowing()).isFalse(); 195 196 // sleep a while and then make sure it's still not shown 197 SystemClock.sleep(TIME_FOR_UI_OPERATION); 198 assertThat(customToastInfo.isShowing()).isFalse(); 199 } 200 assertTextToastNotShown(TextToastInfo textToastInfo)201 private void assertTextToastNotShown(TextToastInfo textToastInfo) { 202 assertFalse(textToastInfo.blockOnToastShown(TIME_FOR_UI_OPERATION)); 203 } 204 registerLayoutListener(final View view)205 private void registerLayoutListener(final View view) { 206 mLayoutDone = false; 207 view.getViewTreeObserver().addOnGlobalLayoutListener(mLayoutListener); 208 } 209 assertLayoutDone(final View view)210 private void assertLayoutDone(final View view) { 211 PollingCheck.waitFor(TIME_OUT, () -> mLayoutDone); 212 view.getViewTreeObserver().removeOnGlobalLayoutListener(mLayoutListener); 213 } 214 makeTextToast()215 private void makeTextToast() throws Throwable { 216 mToastShown = new ConditionVariable(false); 217 mToastHidden = new ConditionVariable(false); 218 mActivityRule.runOnUiThread( 219 () -> { 220 mToast = Toast.makeText(mContext, TEST_TOAST_TEXT, Toast.LENGTH_LONG); 221 mToast.addCallback(new ConditionCallback(mToastShown, mToastHidden)); 222 }); 223 } 224 makeCustomToast()225 private void makeCustomToast() throws Throwable { 226 mActivityRule.runOnUiThread( 227 () -> { 228 mToast = new Toast(mContext); 229 mToast.setDuration(Toast.LENGTH_LONG); 230 TextView view = new TextView(mContext); 231 view.setText(TEST_CUSTOM_TOAST_TEXT); 232 mToast.setView(view); 233 } 234 ); 235 } 236 waitForToastToExpire()237 private void waitForToastToExpire() { 238 if (mToast == null) { 239 return; 240 } 241 // text toast case 242 if (mToastShown != null && mToastHidden != null) { 243 boolean toastShown = mToastShown.block(/* return immediately */ 1); 244 boolean toastHidden = mToastHidden.block(/* return immediately */ 1); 245 246 if (toastShown && !toastHidden) { 247 assertTrue(mToastHidden.block(TIME_OUT)); 248 } 249 return; 250 } 251 252 // custom toast case 253 View view = mToast.getView(); 254 if (view != null && view.getParent() != null) { 255 PollingCheck.waitFor(TIME_OUT, () -> view.getParent() == null); 256 } 257 } 258 259 @Test testShow_whenCustomToast()260 public void testShow_whenCustomToast() throws Throwable { 261 makeCustomToast(); 262 263 final View view = mToast.getView(); 264 265 // view has not been attached to screen yet 266 assertNull(view.getParent()); 267 assertEquals(View.VISIBLE, view.getVisibility()); 268 269 runOnMainAndDrawSync(view, mToast::show); 270 271 // view will be attached to screen when show it 272 assertEquals(View.VISIBLE, view.getVisibility()); 273 assertCustomToastShownAndHidden(view); 274 } 275 276 @Test testShow_whenTextToast()277 public void testShow_whenTextToast() throws Throwable { 278 makeTextToast(); 279 280 mActivityRule.runOnUiThread(mToast::show); 281 282 assertTextToastShownAndHidden(); 283 } 284 285 @Test testHideTextToastAfterExpirationOfFirstShowCall_despiteRepeatedShowCalls()286 public void testHideTextToastAfterExpirationOfFirstShowCall_despiteRepeatedShowCalls() 287 throws Throwable { 288 // Measure the length of a long toast. 289 makeTextToast(); 290 long start1 = SystemClock.uptimeMillis(); 291 mActivityRule.runOnUiThread(mToast::show); 292 assertEquals(Toast.LENGTH_LONG, mToast.getDuration()); 293 assertTextToastShownAndHidden(); 294 long longDurationMs = SystemClock.uptimeMillis() - start1; 295 296 // Call show in the middle of the toast duration. 297 makeTextToast(); 298 long start2 = SystemClock.uptimeMillis(); 299 mActivityRule.runOnUiThread(mToast::show); 300 assertEquals(Toast.LENGTH_LONG, mToast.getDuration()); 301 SystemClock.sleep(longDurationMs / 2); 302 mActivityRule.runOnUiThread(mToast::show); 303 assertTextToastShownAndHidden(); 304 long repeatCallDurationMs = SystemClock.uptimeMillis() - start2; 305 306 // Assert duration was roughly the same despite a repeat call. 307 assertThat((double) repeatCallDurationMs) 308 .isWithin(longDurationMs * TOAST_DURATION_ERROR_TOLERANCE_FRACTION) 309 .of(longDurationMs); 310 } 311 312 @Test testHideCustomToastAfterExpirationOfFirstShowCall_despiteRepeatedShowCalls()313 public void testHideCustomToastAfterExpirationOfFirstShowCall_despiteRepeatedShowCalls() 314 throws Throwable { 315 // Measure the length of a long toast. 316 makeCustomToast(); 317 long start1 = SystemClock.uptimeMillis(); 318 mActivityRule.runOnUiThread(mToast::show); 319 assertEquals(Toast.LENGTH_LONG, mToast.getDuration()); 320 assertCustomToastShownAndHidden(mToast.getView()); 321 long longDurationMs = SystemClock.uptimeMillis() - start1; 322 323 // Call show in the middle of the toast duration. 324 makeCustomToast(); 325 long start2 = SystemClock.uptimeMillis(); 326 mActivityRule.runOnUiThread(mToast::show); 327 assertEquals(Toast.LENGTH_LONG, mToast.getDuration()); 328 SystemClock.sleep(longDurationMs / 2); 329 mActivityRule.runOnUiThread(mToast::show); 330 assertCustomToastShownAndHidden(mToast.getView()); 331 long repeatCallDurationMs = SystemClock.uptimeMillis() - start2; 332 333 // Assert duration was roughly the same despite a repeat call. 334 assertThat((double) repeatCallDurationMs) 335 .isWithin(longDurationMs * TOAST_DURATION_ERROR_TOLERANCE_FRACTION) 336 .of(longDurationMs); 337 } 338 339 @UiThreadTest 340 @Test(expected=RuntimeException.class) testShowFailure()341 public void testShowFailure() { 342 Toast toast = new Toast(mContext); 343 // do not have any views or text 344 assertNull(toast.getView()); 345 toast.show(); 346 } 347 348 @Test testCancel_whenCustomToast()349 public void testCancel_whenCustomToast() throws Throwable { 350 makeCustomToast(); 351 352 final View view = mToast.getView(); 353 354 // view has not been attached to screen yet 355 assertNull(view.getParent()); 356 mActivityRule.runOnUiThread(() -> { 357 mToast.show(); 358 mToast.cancel(); 359 }); 360 361 assertCustomToastNotShown(view); 362 } 363 364 @Test testAccessView_whenCustomToast()365 public void testAccessView_whenCustomToast() throws Throwable { 366 makeTextToast(); 367 assertFalse(mToast.getView() instanceof ImageView); 368 369 final ImageView imageView = new ImageView(mContext); 370 Drawable drawable = mContext.getResources().getDrawable(R.drawable.pass); 371 imageView.setImageDrawable(drawable); 372 373 runOnMainAndDrawSync(imageView, () -> { 374 mToast.setView(imageView); 375 mToast.show(); 376 }); 377 assertSame(imageView, mToast.getView()); 378 assertCustomToastShownAndHidden(imageView); 379 } 380 381 @Test testAccessDuration_whenCustomToast()382 public void testAccessDuration_whenCustomToast() throws Throwable { 383 long start = SystemClock.uptimeMillis(); 384 makeCustomToast(); 385 runOnMainAndDrawSync(mToast.getView(), mToast::show); 386 assertEquals(Toast.LENGTH_LONG, mToast.getDuration()); 387 388 View view = mToast.getView(); 389 assertCustomToastShownAndHidden(view); 390 long longDuration = SystemClock.uptimeMillis() - start; 391 392 start = SystemClock.uptimeMillis(); 393 runOnMainAndDrawSync(mToast.getView(), () -> { 394 mToast.setDuration(Toast.LENGTH_SHORT); 395 mToast.show(); 396 }); 397 assertEquals(Toast.LENGTH_SHORT, mToast.getDuration()); 398 399 view = mToast.getView(); 400 assertCustomToastShownAndHidden(view); 401 long shortDuration = SystemClock.uptimeMillis() - start; 402 403 assertTrue(longDuration > shortDuration); 404 } 405 406 @Test testAccessDuration_whenTextToast()407 public void testAccessDuration_whenTextToast() throws Throwable { 408 long start = SystemClock.uptimeMillis(); 409 makeTextToast(); 410 mActivityRule.runOnUiThread(mToast::show); 411 assertEquals(Toast.LENGTH_LONG, mToast.getDuration()); 412 413 assertTextToastShownAndHidden(); 414 long longDuration = SystemClock.uptimeMillis() - start; 415 416 start = SystemClock.uptimeMillis(); 417 makeTextToast(); 418 mActivityRule.runOnUiThread(() -> { 419 mToast.setDuration(Toast.LENGTH_SHORT); 420 mToast.show(); 421 }); 422 assertEquals(Toast.LENGTH_SHORT, mToast.getDuration()); 423 424 assertTextToastShownAndHidden(); 425 long shortDuration = SystemClock.uptimeMillis() - start; 426 427 assertTrue(longDuration > shortDuration); 428 } 429 430 @Test testAccessDuration_whenCustomToastAndWithA11yTimeoutEnabled()431 public void testAccessDuration_whenCustomToastAndWithA11yTimeoutEnabled() throws Throwable { 432 makeCustomToast(); 433 final Runnable showToast = () -> { 434 mToast.setDuration(Toast.LENGTH_SHORT); 435 mToast.show(); 436 }; 437 long start = SystemClock.uptimeMillis(); 438 runOnMainAndDrawSync(mToast.getView(), showToast); 439 assertCustomToastShownAndHidden(mToast.getView()); 440 final long shortDuration = SystemClock.uptimeMillis() - start; 441 442 final String originalSetting = Settings.Secure.getString(mContext.getContentResolver(), 443 SETTINGS_ACCESSIBILITY_UI_TIMEOUT); 444 try { 445 final int a11ySettingDuration = (int) shortDuration + 1000; 446 putSecureSetting(SETTINGS_ACCESSIBILITY_UI_TIMEOUT, 447 Integer.toString(a11ySettingDuration)); 448 waitForA11yRecommendedTimeoutChanged(mContext, 449 ACCESSIBILITY_STATE_WAIT_TIMEOUT_MS, a11ySettingDuration); 450 start = SystemClock.uptimeMillis(); 451 runOnMainAndDrawSync(mToast.getView(), showToast); 452 assertCustomToastShownAndHidden(mToast.getView()); 453 final long a11yDuration = SystemClock.uptimeMillis() - start; 454 assertTrue("Toast duration " + a11yDuration + "ms < A11y setting " + a11ySettingDuration 455 + "ms", a11yDuration >= a11ySettingDuration); 456 } finally { 457 putSecureSetting(SETTINGS_ACCESSIBILITY_UI_TIMEOUT, originalSetting); 458 } 459 } 460 461 @Test testAccessDuration_whenTextToastAndWithA11yTimeoutEnabled()462 public void testAccessDuration_whenTextToastAndWithA11yTimeoutEnabled() throws Throwable { 463 makeTextToast(); 464 final Runnable showToast = () -> { 465 mToast.setDuration(Toast.LENGTH_SHORT); 466 mToast.show(); 467 }; 468 long start = SystemClock.uptimeMillis(); 469 mActivityRule.runOnUiThread(showToast); 470 assertTextToastShownAndHidden(); 471 final long shortDuration = SystemClock.uptimeMillis() - start; 472 473 final String originalSetting = Settings.Secure.getString(mContext.getContentResolver(), 474 SETTINGS_ACCESSIBILITY_UI_TIMEOUT); 475 try { 476 final int a11ySettingDuration = (int) shortDuration + 1000; 477 putSecureSetting(SETTINGS_ACCESSIBILITY_UI_TIMEOUT, 478 Integer.toString(a11ySettingDuration)); 479 waitForA11yRecommendedTimeoutChanged(mContext, 480 ACCESSIBILITY_STATE_WAIT_TIMEOUT_MS, a11ySettingDuration); 481 makeTextToast(); 482 start = SystemClock.uptimeMillis(); 483 mActivityRule.runOnUiThread(showToast); 484 assertTextToastShownAndHidden(); 485 final long a11yDuration = SystemClock.uptimeMillis() - start; 486 assertTrue("Toast duration " + a11yDuration + "ms < A11y setting " + a11ySettingDuration 487 + "ms", a11yDuration >= a11ySettingDuration); 488 } finally { 489 putSecureSetting(SETTINGS_ACCESSIBILITY_UI_TIMEOUT, originalSetting); 490 } 491 } 492 493 /** 494 * Wait for accessibility recommended timeout changed and equals to expected timeout. 495 * 496 * @param expectedTimeoutMs expected recommended timeout 497 */ waitForA11yRecommendedTimeoutChanged(Context context, long waitTimeoutMs, int expectedTimeoutMs)498 private void waitForA11yRecommendedTimeoutChanged(Context context, 499 long waitTimeoutMs, int expectedTimeoutMs) { 500 final AccessibilityManager manager = 501 (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); 502 final Object lock = new Object(); 503 AccessibilityManager.AccessibilityServicesStateChangeListener listener = (m) -> { 504 synchronized (lock) { 505 lock.notifyAll(); 506 } 507 }; 508 manager.addAccessibilityServicesStateChangeListener(listener, null); 509 try { 510 TestUtils.waitOn(lock, 511 () -> manager.getRecommendedTimeoutMillis(0, 512 AccessibilityManager.FLAG_CONTENT_TEXT) == expectedTimeoutMs, 513 waitTimeoutMs, 514 "Wait for accessibility recommended timeout changed"); 515 } finally { 516 manager.removeAccessibilityServicesStateChangeListener(listener); 517 } 518 } 519 putSecureSetting(String name, String value)520 private void putSecureSetting(String name, String value) { 521 final StringBuilder cmd = new StringBuilder("settings put secure ") 522 .append(name).append(" ") 523 .append(value); 524 SystemUtil.runShellCommand(cmd.toString()); 525 } 526 527 @Test testAccessMargin_whenCustomToast()528 public void testAccessMargin_whenCustomToast() throws Throwable { 529 assumeFalse("Skipping test: Auto does not support toast with margin", isCar()); 530 531 makeCustomToast(); 532 View view = mToast.getView(); 533 assertFalse(view.getLayoutParams() instanceof WindowManager.LayoutParams); 534 535 final float horizontal1 = 1.0f; 536 final float vertical1 = 1.0f; 537 runOnMainAndDrawSync(view, () -> { 538 mToast.setMargin(horizontal1, vertical1); 539 mToast.show(); 540 registerLayoutListener(mToast.getView()); 541 }); 542 assertCustomToastShown(view); 543 544 assertEquals(horizontal1, mToast.getHorizontalMargin(), 0.0f); 545 assertEquals(vertical1, mToast.getVerticalMargin(), 0.0f); 546 WindowManager.LayoutParams params1 = (WindowManager.LayoutParams) view.getLayoutParams(); 547 assertEquals(horizontal1, params1.horizontalMargin, 0.0f); 548 assertEquals(vertical1, params1.verticalMargin, 0.0f); 549 assertLayoutDone(view); 550 551 int[] xy1 = new int[2]; 552 view.getLocationOnScreen(xy1); 553 assertCustomToastShownAndHidden(view); 554 555 final float horizontal2 = 0.1f; 556 final float vertical2 = 0.1f; 557 runOnMainAndDrawSync(view, () -> { 558 mToast.setMargin(horizontal2, vertical2); 559 mToast.show(); 560 registerLayoutListener(mToast.getView()); 561 }); 562 assertCustomToastShown(view); 563 564 assertEquals(horizontal2, mToast.getHorizontalMargin(), 0.0f); 565 assertEquals(vertical2, mToast.getVerticalMargin(), 0.0f); 566 WindowManager.LayoutParams params2 = (WindowManager.LayoutParams) view.getLayoutParams(); 567 assertEquals(horizontal2, params2.horizontalMargin, 0.0f); 568 assertEquals(vertical2, params2.verticalMargin, 0.0f); 569 570 assertLayoutDone(view); 571 int[] xy2 = new int[2]; 572 view.getLocationOnScreen(xy2); 573 assertCustomToastShownAndHidden(view); 574 575 /** Check if the test is being run on a watch. 576 * 577 * Change I8180e5080e0a6860b40dbb2faa791f0ede926ca7 updated how toast are displayed on the 578 * watch. Unlike the phone, which displays toast centered horizontally at the bottom of the 579 * screen, the watch now displays toast in the center of the screen. 580 */ 581 if (Gravity.CENTER == mToast.getGravity()) { 582 assertTrue(xy1[0] > xy2[0]); 583 assertTrue(xy1[1] > xy2[1]); 584 } else { 585 assertTrue(xy1[0] > xy2[0]); 586 assertTrue(xy1[1] < xy2[1]); 587 } 588 } 589 590 @Test 591 public void testAccessGravity_whenCustomToast() throws Throwable { 592 assumeFalse("Skipping test: Auto does not support toast with gravity", isCar()); 593 594 makeCustomToast(); 595 runOnMainAndDrawSync(mToast.getView(), () -> { 596 mToast.setGravity(Gravity.CENTER, 0, 0); 597 mToast.show(); 598 registerLayoutListener(mToast.getView()); 599 }); 600 View view = mToast.getView(); 601 assertCustomToastShown(view); 602 assertEquals(Gravity.CENTER, mToast.getGravity()); 603 assertEquals(0, mToast.getXOffset()); 604 assertEquals(0, mToast.getYOffset()); 605 assertLayoutDone(view); 606 int[] centerXY = new int[2]; 607 view.getLocationOnScreen(centerXY); 608 assertCustomToastShownAndHidden(view); 609 610 runOnMainAndDrawSync(mToast.getView(), () -> { 611 mToast.setGravity(Gravity.BOTTOM, 0, 0); 612 mToast.show(); 613 registerLayoutListener(mToast.getView()); 614 }); 615 view = mToast.getView(); 616 assertCustomToastShown(view); 617 assertEquals(Gravity.BOTTOM, mToast.getGravity()); 618 assertEquals(0, mToast.getXOffset()); 619 assertEquals(0, mToast.getYOffset()); 620 assertLayoutDone(view); 621 int[] bottomXY = new int[2]; 622 view.getLocationOnScreen(bottomXY); 623 assertCustomToastShownAndHidden(view); 624 625 // x coordinate is the same 626 assertEquals(centerXY[0], bottomXY[0]); 627 // bottom view is below of center view 628 assertTrue(centerXY[1] < bottomXY[1]); 629 630 final int xOffset = 20; 631 final int yOffset = 10; 632 runOnMainAndDrawSync(mToast.getView(), () -> { 633 mToast.setGravity(Gravity.BOTTOM, xOffset, yOffset); 634 mToast.show(); 635 registerLayoutListener(mToast.getView()); 636 }); 637 view = mToast.getView(); 638 assertCustomToastShown(view); 639 assertEquals(Gravity.BOTTOM, mToast.getGravity()); 640 assertEquals(xOffset, mToast.getXOffset()); 641 assertEquals(yOffset, mToast.getYOffset()); 642 assertLayoutDone(view); 643 int[] bottomOffsetXY = new int[2]; 644 view.getLocationOnScreen(bottomOffsetXY); 645 assertCustomToastShownAndHidden(view); 646 647 assertEquals(bottomXY[0] + xOffset, bottomOffsetXY[0]); 648 assertEquals(bottomXY[1] - yOffset, bottomOffsetXY[1]); 649 } 650 651 @UiThreadTest 652 @Test testMakeTextFromString()653 public void testMakeTextFromString() { 654 assumeFalse("Skipping test: Watch does not support new Toast behavior yet", isWatch()); 655 Toast toast = Toast.makeText(mContext, "android", Toast.LENGTH_SHORT); 656 assertNotNull(toast); 657 assertEquals(Toast.LENGTH_SHORT, toast.getDuration()); 658 View view = toast.getView(); 659 assertNull(view); 660 661 toast = Toast.makeText(mContext, "cts", Toast.LENGTH_LONG); 662 assertNotNull(toast); 663 assertEquals(Toast.LENGTH_LONG, toast.getDuration()); 664 view = toast.getView(); 665 assertNull(view); 666 667 toast = Toast.makeText(mContext, null, Toast.LENGTH_LONG); 668 assertNotNull(toast); 669 assertEquals(Toast.LENGTH_LONG, toast.getDuration()); 670 view = toast.getView(); 671 assertNull(view); 672 } 673 674 @UiThreadTest 675 @Test(expected=NullPointerException.class) testMakeTextFromStringNullContext()676 public void testMakeTextFromStringNullContext() { 677 Toast.makeText(null, "test", Toast.LENGTH_LONG); 678 } 679 680 @UiThreadTest 681 @Test testMakeTextFromResource()682 public void testMakeTextFromResource() { 683 assumeFalse("Skipping test: Watch does not support new Toast behavior yet", isWatch()); 684 Toast toast = Toast.makeText(mContext, R.string.hello_world, Toast.LENGTH_LONG); 685 686 assertNotNull(toast); 687 assertEquals(Toast.LENGTH_LONG, toast.getDuration()); 688 View view = toast.getView(); 689 assertNull(view); 690 691 toast = Toast.makeText(mContext, R.string.hello_android, Toast.LENGTH_SHORT); 692 assertNotNull(toast); 693 assertEquals(Toast.LENGTH_SHORT, toast.getDuration()); 694 view = toast.getView(); 695 assertNull(view); 696 } 697 698 @UiThreadTest 699 @Test(expected=NullPointerException.class) testMakeTextFromResourceNullContext()700 public void testMakeTextFromResourceNullContext() { 701 Toast.makeText(null, R.string.hello_android, Toast.LENGTH_SHORT); 702 } 703 704 @UiThreadTest 705 @Test testSetTextFromResource()706 public void testSetTextFromResource() { 707 Toast toast = Toast.makeText(mContext, R.string.text, Toast.LENGTH_LONG); 708 709 toast.setText(R.string.hello_world); 710 // TODO: how to getText to assert? 711 712 toast.setText(R.string.hello_android); 713 // TODO: how to getText to assert? 714 } 715 716 @UiThreadTest 717 @Test(expected=RuntimeException.class) testSetTextFromInvalidResource()718 public void testSetTextFromInvalidResource() { 719 Toast toast = Toast.makeText(mContext, R.string.text, Toast.LENGTH_LONG); 720 toast.setText(-1); 721 } 722 723 @UiThreadTest 724 @Test testSetTextFromString()725 public void testSetTextFromString() { 726 Toast toast = Toast.makeText(mContext, R.string.text, Toast.LENGTH_LONG); 727 728 toast.setText("cts"); 729 // TODO: how to getText to assert? 730 731 toast.setText("android"); 732 // TODO: how to getText to assert? 733 } 734 735 @UiThreadTest 736 @Test(expected = IllegalStateException.class) testSetTextFromStringNonNullView()737 public void testSetTextFromStringNonNullView() { 738 assumeFalse("Skipping test: Watch does not support new Toast behavior yet", isWatch()); 739 Toast toast = Toast.makeText(mContext, R.string.text, Toast.LENGTH_LONG); 740 toast.setView(new TextView(mContext)); 741 toast.setText(null); 742 } 743 744 @Test testRemovedCallbackIsNotCalled()745 public void testRemovedCallbackIsNotCalled() throws Throwable { 746 CompletableFuture<Void> toastShown = new CompletableFuture<>(); 747 CompletableFuture<Void> toastHidden = new CompletableFuture<>(); 748 Toast.Callback testCallback = new Toast.Callback() { 749 @Override 750 public void onToastShown() { 751 toastShown.complete(null); 752 } 753 @Override 754 public void onToastHidden() { 755 toastHidden.complete(null); 756 } 757 }; 758 mToastShown = new ConditionVariable(false); 759 mToastHidden = new ConditionVariable(false); 760 mActivityRule.runOnUiThread( 761 () -> { 762 mToast = Toast.makeText(mContext, TEST_TOAST_TEXT, Toast.LENGTH_LONG); 763 mToast.addCallback(testCallback); 764 mToast.addCallback(new ConditionCallback(mToastShown, mToastHidden)); 765 mToast.removeCallback(testCallback); 766 }); 767 768 mActivityRule.runOnUiThread(mToast::show); 769 770 assertTextToastShownAndHidden(); 771 assertFalse(toastShown.isDone()); 772 assertFalse(toastHidden.isDone()); 773 } 774 775 @Test(expected = NullPointerException.class) testAddCallback_whenNull_throws()776 public void testAddCallback_whenNull_throws() throws Throwable { 777 makeTextToast(); 778 mToast.addCallback(null); 779 } 780 781 @Test testCallback_whenTextToast_isCalled()782 public void testCallback_whenTextToast_isCalled() throws Throwable { 783 ConditionVariable toastShown = new ConditionVariable(false); 784 ConditionVariable toastHidden = new ConditionVariable(false); 785 mActivityRule.runOnUiThread( 786 () -> { 787 mToast = Toast.makeText(mContext, TEST_TOAST_TEXT, Toast.LENGTH_LONG); 788 mToast.addCallback(new ConditionCallback(toastShown, toastHidden)); 789 }); 790 791 mActivityRule.runOnUiThread(mToast::show); 792 793 assertTrue(toastShown.block(TIME_OUT)); 794 assertTrue(toastHidden.block(TIME_OUT)); 795 } 796 797 @Test testCallback_whenCustomToast_isCalled()798 public void testCallback_whenCustomToast_isCalled() throws Throwable { 799 makeCustomToast(); 800 ConditionVariable toastShown = new ConditionVariable(false); 801 ConditionVariable toastHidden = new ConditionVariable(false); 802 mActivityRule.runOnUiThread( 803 () -> mToast.addCallback(new ConditionCallback(toastShown, toastHidden))); 804 805 mActivityRule.runOnUiThread(mToast::show); 806 807 assertTrue(toastShown.block(TIME_OUT)); 808 assertTrue(toastHidden.block(TIME_OUT)); 809 } 810 811 @Test testTextToastAllowed_whenInTheForeground()812 public void testTextToastAllowed_whenInTheForeground() throws Throwable { 813 makeTextToast(); 814 815 mActivityRule.runOnUiThread(mToast::show); 816 817 assertTextToastShownAndHidden(); 818 } 819 820 @Test testCustomToastAllowed_whenInTheForeground()821 public void testCustomToastAllowed_whenInTheForeground() throws Throwable { 822 makeCustomToast(); 823 View view = mToast.getView(); 824 // View has not been attached to screen yet 825 assertNull(view.getParent()); 826 827 mActivityRule.runOnUiThread(mToast::show); 828 829 assertCustomToastShownAndHidden(view); 830 } 831 832 @Test testTextToastAllowed_whenInTheBackground()833 public void testTextToastAllowed_whenInTheBackground() throws Throwable { 834 assumeFalse("Skipping test: Watch does not support new Toast behavior yet", isWatch()); 835 // Make it background 836 mActivityRule.finishActivity(); 837 makeTextToast(); 838 839 mActivityRule.runOnUiThread(mToast::show); 840 841 assertTextToastShownAndHidden(); 842 } 843 844 @Test testCustomToastBlocked_whenInTheBackground()845 public void testCustomToastBlocked_whenInTheBackground() throws Throwable { 846 // Make it background 847 mActivityRule.finishActivity(); 848 makeCustomToast(); 849 View view = mToast.getView(); 850 // View has not been attached to screen yet 851 assertNull(view.getParent()); 852 853 mActivityRule.runOnUiThread(mToast::show); 854 855 assertCustomToastNotShown(view); 856 } 857 858 @Test testCustomToastBlocked_whenBehindTranslucentActivity()859 public void testCustomToastBlocked_whenBehindTranslucentActivity() throws Throwable { 860 ConditionVariable activityStarted = registerBlockingReceiver( 861 ACTION_TRANSLUCENT_ACTIVITY_RESUMED); 862 Intent intent = new Intent(); 863 intent.setComponent(COMPONENT_TRANSLUCENT_ACTIVITY); 864 // Launch the translucent activity in fullscreen to ensure the test activity won't resume 865 // even on the freeform-first multi-window device. 866 final ActivityOptions options = ActivityOptions.makeBasic(); 867 options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN); 868 mActivityRule.getActivity().startActivity(intent, options.toBundle()); 869 activityStarted.block(); 870 makeCustomToast(); 871 View view = mToast.getView(); 872 873 mActivityRule.runOnUiThread(mToast::show); 874 875 assertCustomToastNotShown(view); 876 mContext.sendBroadcast(new Intent(ACTION_TRANSLUCENT_ACTIVITY_FINISH)); 877 } 878 879 @UiThreadTest 880 @Test testGetWindowParams_whenTextToast_returnsNull()881 public void testGetWindowParams_whenTextToast_returnsNull() { 882 assumeFalse("Skipping test: Watch does not support new Toast behavior yet", isWatch()); 883 Toast toast = Toast.makeText(mContext, "Text", Toast.LENGTH_LONG); 884 assertNull(toast.getWindowParams()); 885 } 886 887 @UiThreadTest 888 @Test testGetWindowParams_whenCustomToast_doesNotReturnNull()889 public void testGetWindowParams_whenCustomToast_doesNotReturnNull() { 890 Toast toast = new Toast(mContext); 891 toast.setView(new TextView(mContext)); 892 assertNotNull(toast.getWindowParams()); 893 } 894 895 @Test testShow_whenTextToast_sendsAccessibilityEvent()896 public void testShow_whenTextToast_sendsAccessibilityEvent() throws Throwable { 897 makeTextToast(); 898 AccessibilityEventFilter filter = 899 event -> event.getEventType() == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED; 900 901 AccessibilityEvent event = mUiAutomation.executeAndWaitForEvent( 902 () -> uncheck(() -> mActivityRule.runOnUiThread(mToast::show)), filter, TIME_OUT); 903 904 assertThat(event.getEventType()).isEqualTo( 905 AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED); 906 assertThat(event.getClassName()).isEqualTo(Toast.class.getCanonicalName()); 907 assertThat(event.getPackageName()).isEqualTo(mContext.getPackageName()); 908 assertThat(event.getText()).contains(TEST_TOAST_TEXT); 909 } 910 911 @Test testShow_whenCustomToast_sendsAccessibilityEvent()912 public void testShow_whenCustomToast_sendsAccessibilityEvent() throws Throwable { 913 makeCustomToast(); 914 AccessibilityEventFilter filter = 915 event -> event.getEventType() == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED; 916 917 AccessibilityEvent event = mUiAutomation.executeAndWaitForEvent( 918 () -> uncheck(() -> mActivityRule.runOnUiThread(mToast::show)), filter, TIME_OUT); 919 920 assertThat(event.getEventType()).isEqualTo( 921 AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED); 922 assertThat(event.getClassName()).isEqualTo(Toast.class.getCanonicalName()); 923 assertThat(event.getPackageName()).isEqualTo(mContext.getPackageName()); 924 assertThat(event.getText()).contains(TEST_CUSTOM_TOAST_TEXT); 925 } 926 927 @Test testPackageCantPostMoreThanMaxToastsQuickly()928 public void testPackageCantPostMoreThanMaxToastsQuickly() throws Throwable { 929 List<TextToastInfo> toasts = 930 createTextToasts(MAX_PACKAGE_TOASTS_LIMIT + 1, "Text", Toast.LENGTH_SHORT); 931 showToasts(toasts); 932 933 assertTextToastsShownAndHidden(toasts.subList(0, MAX_PACKAGE_TOASTS_LIMIT)); 934 assertTextToastNotShown(toasts.get(MAX_PACKAGE_TOASTS_LIMIT)); 935 } 936 937 /** 938 * Long running test (~50 seconds) - we need to test toast rate limiting limits, which requires 939 * us to wait a given amount of time to test that the limit has been enforced/lifted. 940 */ 941 @Test testRateLimitingToastsWhenInBackground()942 public void testRateLimitingToastsWhenInBackground() throws Throwable { 943 // enable rate limiting to test it 944 SystemUtil.runWithShellPermissionIdentity(() -> mNotificationManager 945 .setToastRateLimitingEnabled(true)); 946 // move to background 947 mActivityRule.finishActivity(); 948 949 long totalTimeSpentMs = 0; 950 int shownToastsNum = 0; 951 // We add additional 3 seconds just to be sure we get into the next window. 952 long additionalWaitTime = 3_000L; 953 954 for (int i = 0; i < TOAST_RATE_LIMITS.length; i++) { 955 int currentToastNum = TOAST_RATE_LIMITS[i] - shownToastsNum; 956 List<TextToastInfo> toasts = 957 createTextToasts(currentToastNum + 1, "Text", Toast.LENGTH_SHORT); 958 long startTime = SystemClock.elapsedRealtime(); 959 showToasts(toasts); 960 961 assertTextToastsShownAndHidden(toasts.subList(0, currentToastNum)); 962 assertTextToastNotShown(toasts.get(currentToastNum)); 963 long endTime = SystemClock.elapsedRealtime(); 964 965 // We won't check after the last limit, no need to sleep then. 966 if (i != TOAST_RATE_LIMITS.length - 1) { 967 totalTimeSpentMs += endTime - startTime; 968 shownToastsNum += currentToastNum; 969 long sleepTime = Math.max( 970 TOAST_WINDOW_SIZES_MS[i] - totalTimeSpentMs + additionalWaitTime, 0); 971 SystemClock.sleep(sleepTime); 972 totalTimeSpentMs += sleepTime; 973 } 974 } 975 } 976 977 @Test testDontRateLimitToastsWhenInForeground()978 public void testDontRateLimitToastsWhenInForeground() throws Throwable { 979 // enable rate limiting to test it 980 SystemUtil.runWithShellPermissionIdentity(() -> mNotificationManager 981 .setToastRateLimitingEnabled(true)); 982 983 List<TextToastInfo> toasts = 984 createTextToasts(TOAST_RATE_LIMITS[0] + 1, "Text", Toast.LENGTH_SHORT); 985 showToasts(toasts); 986 assertTextToastsShownAndHidden(toasts); 987 } 988 989 @Test testCustomToastPostedWhileInForeground_notShownWhenAppGoesToBackground()990 public void testCustomToastPostedWhileInForeground_notShownWhenAppGoesToBackground() 991 throws Throwable { 992 List<CustomToastInfo> toasts = createCustomToasts(2, "Custom", Toast.LENGTH_SHORT); 993 showToasts(toasts); 994 assertCustomToastShown(toasts.get(0)); 995 996 // move to background 997 mActivityRule.finishActivity(); 998 999 assertCustomToastHidden(toasts.get(0)); 1000 assertCustomToastNotShown(toasts.get(1)); 1001 } 1002 1003 @Test testAppWithUnlimitedToastsPermissionCanPostUnlimitedToasts()1004 public void testAppWithUnlimitedToastsPermissionCanPostUnlimitedToasts() throws Throwable { 1005 // enable rate limiting to test it 1006 SystemUtil.runWithShellPermissionIdentity(() -> mNotificationManager 1007 .setToastRateLimitingEnabled(true)); 1008 // move to background 1009 mActivityRule.finishActivity(); 1010 1011 int highestToastRateLimit = TOAST_RATE_LIMITS[TOAST_RATE_LIMITS.length - 1]; 1012 List<TextToastInfo> toasts = createTextToasts(highestToastRateLimit + 1, "Text", 1013 Toast.LENGTH_SHORT); 1014 1015 // We have to show one by one to avoid max number of toasts enqueued by a single package at 1016 // a time. 1017 for (TextToastInfo t : toasts) { 1018 // The shell has the android.permission.UNLIMITED_TOASTS permission. 1019 SystemUtil.runWithShellPermissionIdentity(() -> { 1020 try { 1021 showToast(t); 1022 } catch (Throwable throwable) { 1023 throw new RuntimeException(throwable); 1024 } 1025 }); 1026 assertTextToastShownAndHidden(t); 1027 } 1028 } 1029 1030 /** Create given number of text toasts with the same given text and length. */ createTextToasts(int num, String text, int length)1031 private List<TextToastInfo> createTextToasts(int num, String text, int length) 1032 throws Throwable { 1033 List<TextToastInfo> toasts = new ArrayList<>(); 1034 mActivityRule.runOnUiThread(() -> { 1035 toasts.addAll(Stream 1036 .generate(() -> TextToastInfo.create(mContext, text, length)) 1037 .limit(num) 1038 .collect(toList())); 1039 }); 1040 return toasts; 1041 } 1042 1043 /** Create given number of custom toasts with the same given text and length. */ createCustomToasts(int num, String text, int length)1044 private List<CustomToastInfo> createCustomToasts(int num, String text, int length) 1045 throws Throwable { 1046 List<CustomToastInfo> toasts = new ArrayList<>(); 1047 mActivityRule.runOnUiThread(() -> { 1048 toasts.addAll(Stream 1049 .generate(() -> CustomToastInfo.create(mContext, text, length)) 1050 .limit(num) 1051 .collect(toList())); 1052 }); 1053 return toasts; 1054 } 1055 showToasts(List<? extends ToastInfo> toasts)1056 private void showToasts(List<? extends ToastInfo> toasts) throws Throwable { 1057 mActivityRule.runOnUiThread(() -> { 1058 for (ToastInfo t : toasts) { 1059 t.getToast().show(); 1060 } 1061 }); 1062 } 1063 showToast(ToastInfo toast)1064 private void showToast(ToastInfo toast) throws Throwable { 1065 mActivityRule.runOnUiThread(() -> { 1066 toast.getToast().show(); 1067 }); 1068 } 1069 assertTextToastsShownAndHidden(List<TextToastInfo> toasts)1070 private void assertTextToastsShownAndHidden(List<TextToastInfo> toasts) { 1071 for (int i = 0; i < toasts.size(); i++) { 1072 assertTextToastShownAndHidden(toasts.get(i)); 1073 } 1074 } 1075 registerBlockingReceiver(String action)1076 private ConditionVariable registerBlockingReceiver(String action) { 1077 ConditionVariable broadcastReceived = new ConditionVariable(false); 1078 IntentFilter filter = new IntentFilter(action); 1079 mContext.registerReceiver(new BroadcastReceiver() { 1080 @Override 1081 public void onReceive(Context context, Intent intent) { 1082 broadcastReceived.open(); 1083 } 1084 }, filter); 1085 return broadcastReceived; 1086 } 1087 runOnMainAndDrawSync(@onNull final View toastView, @Nullable final Runnable runner)1088 private void runOnMainAndDrawSync(@NonNull final View toastView, 1089 @Nullable final Runnable runner) { 1090 final CountDownLatch latch = new CountDownLatch(1); 1091 1092 try { 1093 mActivityRule.runOnUiThread(() -> { 1094 final ViewTreeObserver.OnDrawListener listener = 1095 new ViewTreeObserver.OnDrawListener() { 1096 @Override 1097 public void onDraw() { 1098 // posting so that the sync happens after the draw that's about 1099 // to happen 1100 toastView.post(() -> { 1101 toastView.getViewTreeObserver().removeOnDrawListener(this); 1102 latch.countDown(); 1103 }); 1104 } 1105 }; 1106 1107 toastView.getViewTreeObserver().addOnDrawListener(listener); 1108 1109 if (runner != null) { 1110 runner.run(); 1111 } 1112 toastView.invalidate(); 1113 }); 1114 1115 Assert.assertTrue("Expected toast draw pass occurred within 5 seconds", 1116 latch.await(5, TimeUnit.SECONDS)); 1117 } catch (Throwable t) { 1118 throw new RuntimeException(t); 1119 } 1120 } 1121 isCar()1122 private boolean isCar() { 1123 PackageManager pm = mContext.getPackageManager(); 1124 return pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); 1125 } 1126 isWatch()1127 private boolean isWatch() { 1128 PackageManager pm = mContext.getPackageManager(); 1129 return pm.hasSystemFeature(PackageManager.FEATURE_WATCH); 1130 } 1131 uncheck(ThrowingRunnable runnable)1132 private static void uncheck(ThrowingRunnable runnable) { 1133 try { 1134 runnable.run(); 1135 } catch (Throwable e) { 1136 throw new RuntimeException(e); 1137 } 1138 } 1139 1140 private interface ThrowingRunnable { 1141 void run() throws Throwable; 1142 } 1143 1144 private static class ConditionCallback extends Toast.Callback { 1145 private final ConditionVariable mToastShown; 1146 private final ConditionVariable mToastHidden; 1147 ConditionCallback(ConditionVariable toastShown, ConditionVariable toastHidden)1148 ConditionCallback(ConditionVariable toastShown, ConditionVariable toastHidden) { 1149 mToastShown = toastShown; 1150 mToastHidden = toastHidden; 1151 } 1152 1153 @Override onToastShown()1154 public void onToastShown() { 1155 mToastShown.open(); 1156 } 1157 1158 @Override onToastHidden()1159 public void onToastHidden() { 1160 mToastHidden.open(); 1161 } 1162 } 1163 1164 private static class TextToastInfo implements ToastInfo { 1165 private final Toast mToast; 1166 private final ConditionVariable mToastShown; 1167 private final ConditionVariable mToastHidden; 1168 TextToastInfo( Toast toast, ConditionVariable toastShown, ConditionVariable toastHidden)1169 TextToastInfo( 1170 Toast toast, 1171 ConditionVariable toastShown, 1172 ConditionVariable toastHidden) { 1173 mToast = toast; 1174 mToastShown = toastShown; 1175 mToastHidden = toastHidden; 1176 } 1177 create(Context context, String text, int toastLength)1178 static TextToastInfo create(Context context, String text, int toastLength) { 1179 Toast t = Toast.makeText(context, text, toastLength); 1180 ConditionVariable toastShown = new ConditionVariable(false); 1181 ConditionVariable toastHidden = new ConditionVariable(false); 1182 t.addCallback(new ConditionCallback(toastShown, toastHidden)); 1183 return new TextToastInfo(t, toastShown, toastHidden); 1184 } 1185 1186 @Override getToast()1187 public Toast getToast() { 1188 return mToast; 1189 } 1190 blockOnToastShown(long timeout)1191 boolean blockOnToastShown(long timeout) { 1192 return mToastShown.block(timeout); 1193 } 1194 blockOnToastHidden(long timeout)1195 boolean blockOnToastHidden(long timeout) { 1196 return mToastHidden.block(timeout); 1197 } 1198 } 1199 1200 private static class CustomToastInfo implements ToastInfo { 1201 private final Toast mToast; 1202 CustomToastInfo(Toast toast)1203 CustomToastInfo(Toast toast) { 1204 mToast = toast; 1205 } 1206 create(Context context, String text, int toastLength)1207 static CustomToastInfo create(Context context, String text, int toastLength) { 1208 Toast t = new Toast(context); 1209 t.setDuration(toastLength); 1210 TextView view = new TextView(context); 1211 view.setText(text); 1212 t.setView(view); 1213 return new CustomToastInfo(t); 1214 } 1215 1216 @Override getToast()1217 public Toast getToast() { 1218 return mToast; 1219 } 1220 isShowing()1221 boolean isShowing() { 1222 return mToast.getView().getParent() != null; 1223 } 1224 } 1225 1226 interface ToastInfo { 1227 Toast getToast(); 1228 } 1229 } 1230