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