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