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 ComponentName COMPONENT_CTS_ACTIVITY =
99             ComponentName.unflattenFromString("android.widget.cts/.CtsActivity");
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 
877         // Start CtsActivity with CLEAR_TOP flag to finish the TranslucentActivity on top.
878         intent = new Intent();
879         intent.setComponent(COMPONENT_CTS_ACTIVITY);
880         intent.setAction(Intent.ACTION_MAIN);
881         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP
882                 | Intent.FLAG_ACTIVITY_SINGLE_TOP);
883         mActivityRule.getActivity().startActivity(intent);
884     }
885 
886     @UiThreadTest
887     @Test
testGetWindowParams_whenTextToast_returnsNull()888     public void testGetWindowParams_whenTextToast_returnsNull() {
889           assumeFalse("Skipping test: Watch does not support new Toast behavior yet", isWatch());
890         Toast toast = Toast.makeText(mContext, "Text", Toast.LENGTH_LONG);
891         assertNull(toast.getWindowParams());
892     }
893 
894     @UiThreadTest
895     @Test
testGetWindowParams_whenCustomToast_doesNotReturnNull()896     public void testGetWindowParams_whenCustomToast_doesNotReturnNull() {
897         Toast toast = new Toast(mContext);
898         toast.setView(new TextView(mContext));
899         assertNotNull(toast.getWindowParams());
900     }
901 
902     @Test
testShow_whenTextToast_sendsAccessibilityEvent()903     public void testShow_whenTextToast_sendsAccessibilityEvent() throws Throwable {
904         makeTextToast();
905         AccessibilityEventFilter filter =
906                 event -> event.getEventType() == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED;
907 
908         AccessibilityEvent event = mUiAutomation.executeAndWaitForEvent(
909                 () -> uncheck(() -> mActivityRule.runOnUiThread(mToast::show)), filter, TIME_OUT);
910 
911         assertThat(event.getEventType()).isEqualTo(
912                 AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
913         assertThat(event.getClassName()).isEqualTo(Toast.class.getCanonicalName());
914         assertThat(event.getPackageName()).isEqualTo(mContext.getPackageName());
915         assertThat(event.getText()).contains(TEST_TOAST_TEXT);
916     }
917 
918     @Test
testShow_whenCustomToast_sendsAccessibilityEvent()919     public void testShow_whenCustomToast_sendsAccessibilityEvent() throws Throwable {
920         makeCustomToast();
921         AccessibilityEventFilter filter =
922                 event -> event.getEventType() == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED;
923 
924         AccessibilityEvent event = mUiAutomation.executeAndWaitForEvent(
925                 () -> uncheck(() -> mActivityRule.runOnUiThread(mToast::show)), filter, TIME_OUT);
926 
927         assertThat(event.getEventType()).isEqualTo(
928                 AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
929         assertThat(event.getClassName()).isEqualTo(Toast.class.getCanonicalName());
930         assertThat(event.getPackageName()).isEqualTo(mContext.getPackageName());
931         assertThat(event.getText()).contains(TEST_CUSTOM_TOAST_TEXT);
932     }
933 
934     @Test
testPackageCantPostMoreThanMaxToastsQuickly()935     public void testPackageCantPostMoreThanMaxToastsQuickly() throws Throwable {
936         List<TextToastInfo> toasts =
937                 createTextToasts(MAX_PACKAGE_TOASTS_LIMIT + 1, "Text", Toast.LENGTH_SHORT);
938         showToasts(toasts);
939 
940         assertTextToastsShownAndHidden(toasts.subList(0, MAX_PACKAGE_TOASTS_LIMIT));
941         assertTextToastNotShown(toasts.get(MAX_PACKAGE_TOASTS_LIMIT));
942     }
943 
944     /**
945      * Long running test (~50 seconds) - we need to test toast rate limiting limits, which requires
946      * us to wait a given amount of time to test that the limit has been enforced/lifted.
947      */
948     @Test
testRateLimitingToastsWhenInBackground()949     public void testRateLimitingToastsWhenInBackground() throws Throwable {
950         // enable rate limiting to test it
951         SystemUtil.runWithShellPermissionIdentity(() -> mNotificationManager
952                 .setToastRateLimitingEnabled(true));
953         // move to background
954         mActivityRule.finishActivity();
955 
956         long totalTimeSpentMs = 0;
957         int shownToastsNum = 0;
958         // We add additional 3 seconds just to be sure we get into the next window.
959         long additionalWaitTime = 3_000L;
960 
961         for (int i = 0; i < TOAST_RATE_LIMITS.length; i++) {
962             int currentToastNum = TOAST_RATE_LIMITS[i] - shownToastsNum;
963             List<TextToastInfo> toasts =
964                     createTextToasts(currentToastNum + 1, "Text", Toast.LENGTH_SHORT);
965             long startTime = SystemClock.elapsedRealtime();
966             showToasts(toasts);
967 
968             assertTextToastsShownAndHidden(toasts.subList(0, currentToastNum));
969             assertTextToastNotShown(toasts.get(currentToastNum));
970             long endTime = SystemClock.elapsedRealtime();
971 
972             // We won't check after the last limit, no need to sleep then.
973             if (i != TOAST_RATE_LIMITS.length - 1) {
974                 totalTimeSpentMs += endTime - startTime;
975                 shownToastsNum += currentToastNum;
976                 long sleepTime = Math.max(
977                         TOAST_WINDOW_SIZES_MS[i] - totalTimeSpentMs + additionalWaitTime, 0);
978                 SystemClock.sleep(sleepTime);
979                 totalTimeSpentMs += sleepTime;
980             }
981         }
982     }
983 
984     @Test
testDontRateLimitToastsWhenInForeground()985     public void testDontRateLimitToastsWhenInForeground() throws Throwable {
986         // enable rate limiting to test it
987         SystemUtil.runWithShellPermissionIdentity(() -> mNotificationManager
988                 .setToastRateLimitingEnabled(true));
989 
990         List<TextToastInfo> toasts =
991                 createTextToasts(TOAST_RATE_LIMITS[0] + 1, "Text", Toast.LENGTH_SHORT);
992         showToasts(toasts);
993         assertTextToastsShownAndHidden(toasts);
994     }
995 
996     @Test
testCustomToastPostedWhileInForeground_notShownWhenAppGoesToBackground()997     public void testCustomToastPostedWhileInForeground_notShownWhenAppGoesToBackground()
998             throws Throwable {
999         List<CustomToastInfo> toasts = createCustomToasts(2, "Custom", Toast.LENGTH_SHORT);
1000         showToasts(toasts);
1001         assertCustomToastShown(toasts.get(0));
1002 
1003         // move to background
1004         mActivityRule.finishActivity();
1005 
1006         assertCustomToastHidden(toasts.get(0));
1007         assertCustomToastNotShown(toasts.get(1));
1008     }
1009 
1010     @Test
testAppWithUnlimitedToastsPermissionCanPostUnlimitedToasts()1011     public void testAppWithUnlimitedToastsPermissionCanPostUnlimitedToasts() throws Throwable {
1012         // enable rate limiting to test it
1013         SystemUtil.runWithShellPermissionIdentity(() -> mNotificationManager
1014                 .setToastRateLimitingEnabled(true));
1015         // move to background
1016         mActivityRule.finishActivity();
1017 
1018         int highestToastRateLimit = TOAST_RATE_LIMITS[TOAST_RATE_LIMITS.length - 1];
1019         List<TextToastInfo> toasts = createTextToasts(highestToastRateLimit + 1, "Text",
1020                 Toast.LENGTH_SHORT);
1021 
1022         // We have to show one by one to avoid max number of toasts enqueued by a single package at
1023         // a time.
1024         for (TextToastInfo t : toasts) {
1025             // The shell has the android.permission.UNLIMITED_TOASTS permission.
1026             SystemUtil.runWithShellPermissionIdentity(() -> {
1027                 try {
1028                     showToast(t);
1029                 } catch (Throwable throwable) {
1030                     throw new RuntimeException(throwable);
1031                 }
1032             });
1033             assertTextToastShownAndHidden(t);
1034         }
1035     }
1036 
1037     /** Create given number of text toasts with the same given text and length. */
createTextToasts(int num, String text, int length)1038     private List<TextToastInfo> createTextToasts(int num, String text, int length)
1039             throws Throwable {
1040         List<TextToastInfo> toasts = new ArrayList<>();
1041         mActivityRule.runOnUiThread(() -> {
1042             toasts.addAll(Stream
1043                     .generate(() -> TextToastInfo.create(mContext, text, length))
1044                     .limit(num)
1045                     .collect(toList()));
1046         });
1047         return toasts;
1048     }
1049 
1050     /** Create given number of custom toasts with the same given text and length. */
createCustomToasts(int num, String text, int length)1051     private List<CustomToastInfo> createCustomToasts(int num, String text, int length)
1052             throws Throwable {
1053         List<CustomToastInfo> toasts = new ArrayList<>();
1054         mActivityRule.runOnUiThread(() -> {
1055             toasts.addAll(Stream
1056                     .generate(() -> CustomToastInfo.create(mContext, text, length))
1057                     .limit(num)
1058                     .collect(toList()));
1059         });
1060         return toasts;
1061     }
1062 
showToasts(List<? extends ToastInfo> toasts)1063     private void showToasts(List<? extends ToastInfo> toasts) throws Throwable {
1064         mActivityRule.runOnUiThread(() -> {
1065             for (ToastInfo t : toasts) {
1066                 t.getToast().show();
1067             }
1068         });
1069     }
1070 
showToast(ToastInfo toast)1071     private void showToast(ToastInfo toast) throws Throwable {
1072         mActivityRule.runOnUiThread(() -> {
1073             toast.getToast().show();
1074         });
1075     }
1076 
assertTextToastsShownAndHidden(List<TextToastInfo> toasts)1077     private void assertTextToastsShownAndHidden(List<TextToastInfo> toasts) {
1078         for (int i = 0; i < toasts.size(); i++) {
1079             assertTextToastShownAndHidden(toasts.get(i));
1080         }
1081     }
1082 
registerBlockingReceiver(String action)1083     private ConditionVariable registerBlockingReceiver(String action) {
1084         ConditionVariable broadcastReceived = new ConditionVariable(false);
1085         IntentFilter filter = new IntentFilter(action);
1086         mContext.registerReceiver(new BroadcastReceiver() {
1087             @Override
1088             public void onReceive(Context context, Intent intent) {
1089                 broadcastReceived.open();
1090             }
1091         }, filter);
1092         return broadcastReceived;
1093     }
1094 
runOnMainAndDrawSync(@onNull final View toastView, @Nullable final Runnable runner)1095     private void runOnMainAndDrawSync(@NonNull final View toastView,
1096             @Nullable final Runnable runner) {
1097         final CountDownLatch latch = new CountDownLatch(1);
1098 
1099         try {
1100             mActivityRule.runOnUiThread(() -> {
1101                 final ViewTreeObserver.OnDrawListener listener =
1102                         new ViewTreeObserver.OnDrawListener() {
1103                             @Override
1104                             public void onDraw() {
1105                                 // posting so that the sync happens after the draw that's about
1106                                 // to happen
1107                                 toastView.post(() -> {
1108                                     toastView.getViewTreeObserver().removeOnDrawListener(this);
1109                                     latch.countDown();
1110                                 });
1111                             }
1112                         };
1113 
1114                 toastView.getViewTreeObserver().addOnDrawListener(listener);
1115 
1116                 if (runner != null) {
1117                     runner.run();
1118                 }
1119                 toastView.invalidate();
1120             });
1121 
1122             Assert.assertTrue("Expected toast draw pass occurred within 5 seconds",
1123                     latch.await(5, TimeUnit.SECONDS));
1124         } catch (Throwable t) {
1125             throw new RuntimeException(t);
1126         }
1127     }
1128 
isCar()1129     private boolean isCar() {
1130         PackageManager pm = mContext.getPackageManager();
1131         return pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
1132     }
1133 
isWatch()1134     private boolean isWatch() {
1135         PackageManager pm = mContext.getPackageManager();
1136         return pm.hasSystemFeature(PackageManager.FEATURE_WATCH);
1137     }
1138 
uncheck(ThrowingRunnable runnable)1139     private static void uncheck(ThrowingRunnable runnable) {
1140         try {
1141             runnable.run();
1142         } catch (Throwable e) {
1143             throw new RuntimeException(e);
1144         }
1145     }
1146 
1147     private interface ThrowingRunnable {
1148         void run() throws Throwable;
1149     }
1150 
1151     private static class ConditionCallback extends Toast.Callback {
1152         private final ConditionVariable mToastShown;
1153         private final ConditionVariable mToastHidden;
1154 
ConditionCallback(ConditionVariable toastShown, ConditionVariable toastHidden)1155         ConditionCallback(ConditionVariable toastShown, ConditionVariable toastHidden) {
1156             mToastShown = toastShown;
1157             mToastHidden = toastHidden;
1158         }
1159 
1160         @Override
onToastShown()1161         public void onToastShown() {
1162             mToastShown.open();
1163         }
1164 
1165         @Override
onToastHidden()1166         public void onToastHidden() {
1167             mToastHidden.open();
1168         }
1169     }
1170 
1171     private static class TextToastInfo implements ToastInfo {
1172         private final Toast mToast;
1173         private final ConditionVariable mToastShown;
1174         private final ConditionVariable mToastHidden;
1175 
TextToastInfo( Toast toast, ConditionVariable toastShown, ConditionVariable toastHidden)1176         TextToastInfo(
1177                 Toast toast,
1178                 ConditionVariable toastShown,
1179                 ConditionVariable toastHidden) {
1180             mToast = toast;
1181             mToastShown = toastShown;
1182             mToastHidden = toastHidden;
1183         }
1184 
create(Context context, String text, int toastLength)1185         static TextToastInfo create(Context context, String text, int toastLength) {
1186             Toast t = Toast.makeText(context, text, toastLength);
1187             ConditionVariable toastShown = new ConditionVariable(false);
1188             ConditionVariable toastHidden = new ConditionVariable(false);
1189             t.addCallback(new ConditionCallback(toastShown, toastHidden));
1190             return new TextToastInfo(t, toastShown, toastHidden);
1191         }
1192 
1193         @Override
getToast()1194         public Toast getToast() {
1195             return mToast;
1196         }
1197 
blockOnToastShown(long timeout)1198         boolean blockOnToastShown(long timeout) {
1199             return mToastShown.block(timeout);
1200         }
1201 
blockOnToastHidden(long timeout)1202         boolean blockOnToastHidden(long timeout) {
1203             return mToastHidden.block(timeout);
1204         }
1205     }
1206 
1207     private static class CustomToastInfo implements ToastInfo {
1208         private final Toast mToast;
1209 
CustomToastInfo(Toast toast)1210         CustomToastInfo(Toast toast) {
1211             mToast = toast;
1212         }
1213 
create(Context context, String text, int toastLength)1214         static CustomToastInfo create(Context context, String text, int toastLength) {
1215             Toast t = new Toast(context);
1216             t.setDuration(toastLength);
1217             TextView view = new TextView(context);
1218             view.setText(text);
1219             t.setView(view);
1220             return new CustomToastInfo(t);
1221         }
1222 
1223         @Override
getToast()1224         public Toast getToast() {
1225             return mToast;
1226         }
1227 
isShowing()1228         boolean isShowing() {
1229             return mToast.getView().getParent() != null;
1230         }
1231     }
1232 
1233     interface ToastInfo {
1234         Toast getToast();
1235     }
1236 }
1237