• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.widget.cts29;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertFalse;
21 import static org.junit.Assert.assertNotNull;
22 import static org.junit.Assert.assertNull;
23 import static org.junit.Assert.assertSame;
24 import static org.junit.Assert.assertTrue;
25 import static org.junit.Assume.assumeFalse;
26 
27 import android.app.NotificationManager;
28 import android.content.Context;
29 import android.content.pm.PackageManager;
30 import android.graphics.drawable.Drawable;
31 import android.os.SystemClock;
32 import android.provider.Settings;
33 import android.view.Gravity;
34 import android.view.View;
35 import android.view.ViewTreeObserver;
36 import android.view.WindowManager;
37 import android.view.accessibility.AccessibilityManager;
38 import android.widget.ImageView;
39 import android.widget.TextView;
40 import android.widget.Toast;
41 
42 import androidx.annotation.NonNull;
43 import androidx.annotation.Nullable;
44 import androidx.test.InstrumentationRegistry;
45 import androidx.test.annotation.UiThreadTest;
46 import androidx.test.filters.LargeTest;
47 import androidx.test.rule.ActivityTestRule;
48 import androidx.test.runner.AndroidJUnit4;
49 
50 import com.android.compatibility.common.util.PollingCheck;
51 import com.android.compatibility.common.util.SystemUtil;
52 import com.android.compatibility.common.util.TestUtils;
53 
54 import junit.framework.Assert;
55 
56 import org.junit.After;
57 import org.junit.Before;
58 import org.junit.Rule;
59 import org.junit.Test;
60 import org.junit.runner.RunWith;
61 
62 import java.util.concurrent.CountDownLatch;
63 import java.util.concurrent.TimeUnit;
64 
65 @LargeTest
66 @RunWith(AndroidJUnit4.class)
67 public class ToastTest {
68     private static final String TEST_TOAST_TEXT = "test toast";
69     private static final String TEST_CUSTOM_TOAST_TEXT = "test custom toast";
70     private static final String SETTINGS_ACCESSIBILITY_UI_TIMEOUT =
71             "accessibility_non_interactive_ui_timeout_ms";
72     private static final int ACCESSIBILITY_STATE_WAIT_TIMEOUT_MS = 3000;
73     private static final long TIME_FOR_UI_OPERATION  = 1000L;
74     private static final long TIME_OUT = 5000L;
75 
76     private Toast mToast;
77     private Context mContext;
78     private boolean mLayoutDone;
79     private ViewTreeObserver.OnGlobalLayoutListener mLayoutListener;
80     private NotificationManager mNotificationManager;
81 
82     @Rule
83     public ActivityTestRule<CtsActivity> mActivityRule =
84             new ActivityTestRule<>(CtsActivity.class);
85 
86     @Before
setup()87     public void setup() {
88         mContext = InstrumentationRegistry.getTargetContext();
89         mLayoutListener = () -> mLayoutDone = true;
90         mNotificationManager =
91                 mContext.getSystemService(NotificationManager.class);
92         // disable rate limiting for tests
93         SystemUtil.runWithShellPermissionIdentity(() -> mNotificationManager
94                 .setToastRateLimitingEnabled(false));
95     }
96 
97     @After
teardown()98     public void teardown() {
99         // re-enable rate limiting
100         SystemUtil.runWithShellPermissionIdentity(() -> mNotificationManager
101                 .setToastRateLimitingEnabled(true));
102     }
103 
104     @UiThreadTest
105     @Test
testConstructor()106     public void testConstructor() {
107         new Toast(mContext);
108     }
109 
110     @UiThreadTest
111     @Test(expected=NullPointerException.class)
testConstructorNullContext()112     public void testConstructorNullContext() {
113         new Toast(null);
114     }
115 
assertShowToast(final View view)116     private static void assertShowToast(final View view) {
117         PollingCheck.waitFor(TIME_OUT, () -> null != view.getParent());
118     }
119 
assertShowAndHide(final View view)120     private static void assertShowAndHide(final View view) {
121         assertShowToast(view);
122         PollingCheck.waitFor(TIME_OUT, () -> null == view.getParent());
123     }
124 
assertNotShowToast(final View view)125     private static void assertNotShowToast(final View view) {
126         // sleep a while and then make sure do not show toast
127         SystemClock.sleep(TIME_FOR_UI_OPERATION);
128         assertNull(view.getParent());
129     }
130 
registerLayoutListener(final View view)131     private void registerLayoutListener(final View view) {
132         mLayoutDone = false;
133         view.getViewTreeObserver().addOnGlobalLayoutListener(mLayoutListener);
134     }
135 
assertLayoutDone(final View view)136     private void assertLayoutDone(final View view) {
137         PollingCheck.waitFor(TIME_OUT, () -> mLayoutDone);
138         view.getViewTreeObserver().removeOnGlobalLayoutListener(mLayoutListener);
139     }
140 
makeToast()141     private void makeToast() throws Throwable {
142         mActivityRule.runOnUiThread(
143                 () -> mToast = Toast.makeText(mContext, TEST_TOAST_TEXT, Toast.LENGTH_LONG));
144     }
145 
makeCustomToast()146     private void makeCustomToast() throws Throwable {
147         mActivityRule.runOnUiThread(
148                 () -> {
149                     mToast = new Toast(mContext);
150                     mToast.setDuration(Toast.LENGTH_LONG);
151                     TextView view = new TextView(mContext);
152                     view.setText(TEST_CUSTOM_TOAST_TEXT);
153                     mToast.setView(view);
154                 }
155         );
156     }
157 
158     @Test
testShow()159     public void testShow() throws Throwable {
160         makeToast();
161 
162         final View view = mToast.getView();
163 
164         // view has not been attached to screen yet
165         assertNull(view.getParent());
166         assertEquals(View.VISIBLE, view.getVisibility());
167 
168         runOnMainAndDrawSync(view, mToast::show);
169 
170         // view will be attached to screen when show it
171         assertEquals(View.VISIBLE, view.getVisibility());
172         assertShowAndHide(view);
173     }
174 
175     @UiThreadTest
176     @Test(expected=RuntimeException.class)
testShowFailure()177     public void testShowFailure() {
178         Toast toast = new Toast(mContext);
179         // do not have any views.
180         assertNull(toast.getView());
181         toast.show();
182     }
183 
184     @Test
testCancel()185     public void testCancel() throws Throwable {
186         makeToast();
187 
188         final View view = mToast.getView();
189 
190         // view has not been attached to screen yet
191         assertNull(view.getParent());
192         mActivityRule.runOnUiThread(() -> {
193             mToast.show();
194             mToast.cancel();
195         });
196 
197         assertNotShowToast(view);
198     }
199 
200     @Test
testAccessView()201     public void testAccessView() throws Throwable {
202         makeToast();
203         assertFalse(mToast.getView() instanceof ImageView);
204 
205         final ImageView imageView = new ImageView(mContext);
206         Drawable drawable = mContext.getResources().getDrawable(R.drawable.pass);
207         imageView.setImageDrawable(drawable);
208 
209         runOnMainAndDrawSync(imageView, () -> {
210             mToast.setView(imageView);
211             mToast.show();
212         });
213         assertSame(imageView, mToast.getView());
214         assertShowAndHide(imageView);
215     }
216 
217     @Test
testAccessDuration()218     public void testAccessDuration() throws Throwable {
219         long start = SystemClock.uptimeMillis();
220         makeToast();
221         runOnMainAndDrawSync(mToast.getView(), mToast::show);
222         assertEquals(Toast.LENGTH_LONG, mToast.getDuration());
223 
224         View view = mToast.getView();
225         assertShowAndHide(view);
226         long longDuration = SystemClock.uptimeMillis() - start;
227 
228         start = SystemClock.uptimeMillis();
229         runOnMainAndDrawSync(mToast.getView(), () -> {
230             mToast.setDuration(Toast.LENGTH_SHORT);
231             mToast.show();
232         });
233         assertEquals(Toast.LENGTH_SHORT, mToast.getDuration());
234 
235         view = mToast.getView();
236         assertShowAndHide(view);
237         long shortDuration = SystemClock.uptimeMillis() - start;
238 
239         assertTrue(longDuration > shortDuration);
240     }
241 
242     @Test
testAccessDuration_withA11yTimeoutEnabled()243     public void testAccessDuration_withA11yTimeoutEnabled() throws Throwable {
244         makeToast();
245         final Runnable showToast = () -> {
246             mToast.setDuration(Toast.LENGTH_SHORT);
247             mToast.show();
248         };
249         long start = SystemClock.uptimeMillis();
250         runOnMainAndDrawSync(mToast.getView(), showToast);
251         assertShowAndHide(mToast.getView());
252         final long shortDuration = SystemClock.uptimeMillis() - start;
253 
254         final String originalSetting = Settings.Secure.getString(mContext.getContentResolver(),
255                 SETTINGS_ACCESSIBILITY_UI_TIMEOUT);
256         try {
257             final int a11ySettingDuration = (int) shortDuration + 1000;
258             putSecureSetting(SETTINGS_ACCESSIBILITY_UI_TIMEOUT,
259                     Integer.toString(a11ySettingDuration));
260             waitForA11yRecommendedTimeoutChanged(mContext,
261                     ACCESSIBILITY_STATE_WAIT_TIMEOUT_MS, a11ySettingDuration);
262             start = SystemClock.uptimeMillis();
263             runOnMainAndDrawSync(mToast.getView(), showToast);
264             assertShowAndHide(mToast.getView());
265             final long a11yDuration = SystemClock.uptimeMillis() - start;
266             assertTrue(a11yDuration >= a11ySettingDuration);
267         } finally {
268             putSecureSetting(SETTINGS_ACCESSIBILITY_UI_TIMEOUT, originalSetting);
269         }
270     }
271 
272     /**
273      * Wait for accessibility recommended timeout changed and equals to expected timeout.
274      *
275      * @param expectedTimeoutMs expected recommended timeout
276      */
waitForA11yRecommendedTimeoutChanged(Context context, long waitTimeoutMs, int expectedTimeoutMs)277     private void waitForA11yRecommendedTimeoutChanged(Context context,
278             long waitTimeoutMs, int expectedTimeoutMs) {
279         final AccessibilityManager manager =
280                 (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
281         final Object lock = new Object();
282         AccessibilityManager.AccessibilityServicesStateChangeListener listener = (m) -> {
283             synchronized (lock) {
284                 lock.notifyAll();
285             }
286         };
287         manager.addAccessibilityServicesStateChangeListener(listener);
288         try {
289             TestUtils.waitOn(lock,
290                     () -> manager.getRecommendedTimeoutMillis(0,
291                             AccessibilityManager.FLAG_CONTENT_TEXT) == expectedTimeoutMs,
292                     waitTimeoutMs,
293                     "Wait for accessibility recommended timeout changed");
294         } finally {
295             manager.removeAccessibilityServicesStateChangeListener(listener);
296         }
297     }
298 
putSecureSetting(String name, String value)299     private void putSecureSetting(String name, String value) {
300         final StringBuilder cmd = new StringBuilder("settings put secure ")
301                 .append(name).append(" ")
302                 .append(value);
303         SystemUtil.runShellCommand(cmd.toString());
304     }
305 
306     @Test
testAccessMargin()307     public void testAccessMargin() throws Throwable {
308         assumeFalse("Skipping test: Auto does not support toast with margin", isCar());
309 
310         makeToast();
311         View view = mToast.getView();
312         assertFalse(view.getLayoutParams() instanceof WindowManager.LayoutParams);
313 
314         final float horizontal1 = 1.0f;
315         final float vertical1 = 1.0f;
316         runOnMainAndDrawSync(view, () -> {
317             mToast.setMargin(horizontal1, vertical1);
318             mToast.show();
319             registerLayoutListener(mToast.getView());
320         });
321         assertShowToast(view);
322 
323         assertEquals(horizontal1, mToast.getHorizontalMargin(), 0.0f);
324         assertEquals(vertical1, mToast.getVerticalMargin(), 0.0f);
325         WindowManager.LayoutParams params1 = (WindowManager.LayoutParams) view.getLayoutParams();
326         assertEquals(horizontal1, params1.horizontalMargin, 0.0f);
327         assertEquals(vertical1, params1.verticalMargin, 0.0f);
328         assertLayoutDone(view);
329 
330         int[] xy1 = new int[2];
331         view.getLocationOnScreen(xy1);
332         assertShowAndHide(view);
333 
334         final float horizontal2 = 0.1f;
335         final float vertical2 = 0.1f;
336         runOnMainAndDrawSync(view, () -> {
337             mToast.setMargin(horizontal2, vertical2);
338             mToast.show();
339             registerLayoutListener(mToast.getView());
340         });
341         assertShowToast(view);
342 
343         assertEquals(horizontal2, mToast.getHorizontalMargin(), 0.0f);
344         assertEquals(vertical2, mToast.getVerticalMargin(), 0.0f);
345         WindowManager.LayoutParams params2 = (WindowManager.LayoutParams) view.getLayoutParams();
346         assertEquals(horizontal2, params2.horizontalMargin, 0.0f);
347         assertEquals(vertical2, params2.verticalMargin, 0.0f);
348 
349         assertLayoutDone(view);
350         int[] xy2 = new int[2];
351         view.getLocationOnScreen(xy2);
352         assertShowAndHide(view);
353 
354         /** Check if the test is being run on a watch.
355          *
356          * Change I8180e5080e0a6860b40dbb2faa791f0ede926ca7 updated how toast are displayed on the
357          * watch. Unlike the phone, which displays toast centered horizontally at the bottom of the
358          * screen, the watch now displays toast in the center of the screen.
359          */
360         if (Gravity.CENTER == mToast.getGravity()) {
361             assertTrue(xy1[0] > xy2[0]);
362             assertTrue(xy1[1] > xy2[1]);
363         } else {
364             assertTrue(xy1[0] > xy2[0]);
365             assertTrue(xy1[1] < xy2[1]);
366         }
367     }
368 
369     @Test
370     public void testAccessGravity() throws Throwable {
371         assumeFalse("Skipping test: Auto does not support toast with gravity", isCar());
372 
373         makeToast();
374         runOnMainAndDrawSync(mToast.getView(), () -> {
375             mToast.setGravity(Gravity.CENTER, 0, 0);
376             mToast.show();
377             registerLayoutListener(mToast.getView());
378         });
379         View view = mToast.getView();
380         assertShowToast(view);
381         assertEquals(Gravity.CENTER, mToast.getGravity());
382         assertEquals(0, mToast.getXOffset());
383         assertEquals(0, mToast.getYOffset());
384         assertLayoutDone(view);
385         int[] centerXY = new int[2];
386         view.getLocationOnScreen(centerXY);
387         assertShowAndHide(view);
388 
389         runOnMainAndDrawSync(mToast.getView(), () -> {
390             mToast.setGravity(Gravity.BOTTOM, 0, 0);
391             mToast.show();
392             registerLayoutListener(mToast.getView());
393         });
394         view = mToast.getView();
395         assertShowToast(view);
396         assertEquals(Gravity.BOTTOM, mToast.getGravity());
397         assertEquals(0, mToast.getXOffset());
398         assertEquals(0, mToast.getYOffset());
399         assertLayoutDone(view);
400         int[] bottomXY = new int[2];
401         view.getLocationOnScreen(bottomXY);
402         assertShowAndHide(view);
403 
404         // x coordinate is the same
405         assertEquals(centerXY[0], bottomXY[0]);
406         // bottom view is below of center view
407         assertTrue(centerXY[1] < bottomXY[1]);
408 
409         final int xOffset = 20;
410         final int yOffset = 10;
411         runOnMainAndDrawSync(mToast.getView(), () -> {
412             mToast.setGravity(Gravity.BOTTOM, xOffset, yOffset);
413             mToast.show();
414             registerLayoutListener(mToast.getView());
415         });
416         view = mToast.getView();
417         assertShowToast(view);
418         assertEquals(Gravity.BOTTOM, mToast.getGravity());
419         assertEquals(xOffset, mToast.getXOffset());
420         assertEquals(yOffset, mToast.getYOffset());
421         assertLayoutDone(view);
422         int[] bottomOffsetXY = new int[2];
423         view.getLocationOnScreen(bottomOffsetXY);
424         assertShowAndHide(view);
425 
426         assertEquals(bottomXY[0] + xOffset, bottomOffsetXY[0]);
427         assertEquals(bottomXY[1] - yOffset, bottomOffsetXY[1]);
428     }
429 
430     @UiThreadTest
431     @Test
testMakeTextFromString()432     public void testMakeTextFromString() {
433         Toast toast = Toast.makeText(mContext, "android", Toast.LENGTH_SHORT);
434         assertNotNull(toast);
435         assertEquals(Toast.LENGTH_SHORT, toast.getDuration());
436         View view = toast.getView();
437         assertNotNull(view);
438 
439         toast = Toast.makeText(mContext, "cts", Toast.LENGTH_LONG);
440         assertNotNull(toast);
441         assertEquals(Toast.LENGTH_LONG, toast.getDuration());
442         view = toast.getView();
443         assertNotNull(view);
444 
445         toast = Toast.makeText(mContext, null, Toast.LENGTH_LONG);
446         assertNotNull(toast);
447         assertEquals(Toast.LENGTH_LONG, toast.getDuration());
448         view = toast.getView();
449         assertNotNull(view);
450     }
451 
452     @UiThreadTest
453     @Test(expected=NullPointerException.class)
testMakeTextFromStringNullContext()454     public void testMakeTextFromStringNullContext() {
455         Toast.makeText(null, "test", Toast.LENGTH_LONG);
456     }
457 
458     @UiThreadTest
459     @Test
testMakeTextFromResource()460     public void testMakeTextFromResource() {
461         Toast toast = Toast.makeText(mContext, R.string.hello_world, Toast.LENGTH_LONG);
462 
463         assertNotNull(toast);
464         assertEquals(Toast.LENGTH_LONG, toast.getDuration());
465         View view = toast.getView();
466         assertNotNull(view);
467 
468         toast = Toast.makeText(mContext, R.string.hello_android, Toast.LENGTH_SHORT);
469         assertNotNull(toast);
470         assertEquals(Toast.LENGTH_SHORT, toast.getDuration());
471         view = toast.getView();
472         assertNotNull(view);
473     }
474 
475     @UiThreadTest
476     @Test(expected=NullPointerException.class)
testMakeTextFromResourceNullContext()477     public void testMakeTextFromResourceNullContext() {
478         Toast.makeText(null, R.string.hello_android, Toast.LENGTH_SHORT);
479     }
480 
481     @UiThreadTest
482     @Test
testSetTextFromResource()483     public void testSetTextFromResource() {
484         Toast toast = Toast.makeText(mContext, R.string.text, Toast.LENGTH_LONG);
485 
486         toast.setText(R.string.hello_world);
487         // TODO: how to getText to assert?
488 
489         toast.setText(R.string.hello_android);
490         // TODO: how to getText to assert?
491     }
492 
493     @UiThreadTest
494     @Test(expected=RuntimeException.class)
testSetTextFromInvalidResource()495     public void testSetTextFromInvalidResource() {
496         Toast toast = Toast.makeText(mContext, R.string.text, Toast.LENGTH_LONG);
497         toast.setText(-1);
498     }
499 
500     @UiThreadTest
501     @Test
testSetTextFromString()502     public void testSetTextFromString() {
503         Toast toast = Toast.makeText(mContext, R.string.text, Toast.LENGTH_LONG);
504 
505         toast.setText("cts");
506         // TODO: how to getText to assert?
507 
508         toast.setText("android");
509         // TODO: how to getText to assert?
510     }
511 
512     @UiThreadTest
513     @Test(expected=RuntimeException.class)
testSetTextFromStringNullView()514     public void testSetTextFromStringNullView() {
515         Toast toast = Toast.makeText(mContext, R.string.text, Toast.LENGTH_LONG);
516         toast.setView(null);
517         toast.setText(null);
518     }
519 
520     @Test
testTextToastAllowed_whenInTheForeground()521     public void testTextToastAllowed_whenInTheForeground() throws Throwable {
522         makeToast();
523         View view = mToast.getView();
524 
525         mActivityRule.runOnUiThread(mToast::show);
526 
527         assertShowAndHide(view);
528     }
529 
530     @Test
testCustomToastAllowed_whenInTheForeground()531     public void testCustomToastAllowed_whenInTheForeground() throws Throwable {
532         makeCustomToast();
533         View view = mToast.getView();
534         // View has not been attached to screen yet
535         assertNull(view.getParent());
536 
537         mActivityRule.runOnUiThread(mToast::show);
538 
539         assertShowAndHide(view);
540     }
541 
542     @Test
testTextToastAllowed_whenInTheBackground()543     public void testTextToastAllowed_whenInTheBackground() throws Throwable {
544         // Make it background
545         mActivityRule.finishActivity();
546         makeToast();
547         View view = mToast.getView();
548 
549         mActivityRule.runOnUiThread(mToast::show);
550 
551         assertShowAndHide(view);
552     }
553 
554     @Test
testCustomToastAllowed_whenInTheBackground()555     public void testCustomToastAllowed_whenInTheBackground() throws Throwable {
556         // Make it background
557         mActivityRule.finishActivity();
558         makeCustomToast();
559         View view = mToast.getView();
560         // View has not been attached to screen yet
561         assertNull(view.getParent());
562 
563         mActivityRule.runOnUiThread(mToast::show);
564 
565         assertShowAndHide(view);
566     }
567 
568     @UiThreadTest
569     @Test
testGetWindowParams_whenTextToast_doesNotReturnNull()570     public void testGetWindowParams_whenTextToast_doesNotReturnNull() {
571         Toast toast = Toast.makeText(mContext, "Text", Toast.LENGTH_LONG);
572         assertNotNull(toast.getWindowParams());
573     }
574 
575     @UiThreadTest
576     @Test
testGetWindowParams_whenCustomToast_doesNotReturnNull()577     public void testGetWindowParams_whenCustomToast_doesNotReturnNull() {
578         Toast toast = new Toast(mContext);
579         toast.setView(new TextView(mContext));
580         assertNotNull(toast.getWindowParams());
581     }
582 
runOnMainAndDrawSync(@onNull final View toastView, @Nullable final Runnable runner)583     private void runOnMainAndDrawSync(@NonNull final View toastView,
584             @Nullable final Runnable runner) {
585         final CountDownLatch latch = new CountDownLatch(1);
586 
587         try {
588             mActivityRule.runOnUiThread(() -> {
589                 final ViewTreeObserver.OnDrawListener listener =
590                         new ViewTreeObserver.OnDrawListener() {
591                             @Override
592                             public void onDraw() {
593                                 // posting so that the sync happens after the draw that's about
594                                 // to happen
595                                 toastView.post(() -> {
596                                     toastView.getViewTreeObserver().removeOnDrawListener(this);
597                                     latch.countDown();
598                                 });
599                             }
600                         };
601 
602                 toastView.getViewTreeObserver().addOnDrawListener(listener);
603 
604                 if (runner != null) {
605                     runner.run();
606                 }
607                 toastView.invalidate();
608             });
609 
610             Assert.assertTrue("Expected toast draw pass occurred within 5 seconds",
611                     latch.await(5, TimeUnit.SECONDS));
612         } catch (Throwable t) {
613             throw new RuntimeException(t);
614         }
615     }
616 
isCar()617     private boolean isCar() {
618         PackageManager pm = mContext.getPackageManager();
619         return pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
620     }
621 }
622