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