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