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