• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2020 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.base.test.util;
6 
7 import android.app.Activity;
8 
9 import androidx.test.runner.lifecycle.ActivityLifecycleCallback;
10 import androidx.test.runner.lifecycle.ActivityLifecycleMonitor;
11 import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
12 import androidx.test.runner.lifecycle.Stage;
13 
14 import org.junit.Assert;
15 
16 import org.chromium.base.ThreadUtils;
17 
18 import java.util.concurrent.TimeUnit;
19 import java.util.concurrent.TimeoutException;
20 import java.util.concurrent.atomic.AtomicReference;
21 
22 /** Methods used for testing Application-level behavior. */
23 public class ApplicationTestUtils {
24     private static final ActivityLifecycleMonitor sMonitor =
25             ActivityLifecycleMonitorRegistry.getInstance();
26 
27     private static final long ACTIVITY_TIMEOUT = 10000;
28 
29     /** Waits until the given activity transitions to the given state. */
waitForActivityState(Activity activity, Stage stage)30     public static void waitForActivityState(Activity activity, Stage stage) {
31         waitForActivityState(
32                 "Activity "
33                         + activity.getLocalClassName()
34                         + " did not reach stage: "
35                         + stage
36                         + ". Is the device screen turned on?",
37                 activity,
38                 stage);
39     }
40 
41     /** Waits until the given activity transitions to the given state. */
waitForActivityState(String failureReason, Activity activity, Stage stage)42     public static void waitForActivityState(String failureReason, Activity activity, Stage stage) {
43         CriteriaHelper.pollUiThread(
44                 () -> {
45                     return sMonitor.getLifecycleStageOf(activity) == stage;
46                 },
47                 failureReason,
48                 ACTIVITY_TIMEOUT,
49                 CriteriaHelper.DEFAULT_POLLING_INTERVAL);
50         // De-flake by flushing the tasks that are already queued on the Looper's Handler.
51         // TODO(https://crbug.com/1424788): Remove this and properly fix flaky tests.
52         TestThreadUtils.flushNonDelayedLooperTasks();
53     }
54 
55     /** Finishes the given activity and waits for its onDestroy() to be called. */
finishActivity(final Activity activity)56     public static void finishActivity(final Activity activity) throws Exception {
57         ThreadUtils.runOnUiThreadBlocking(
58                 () -> {
59                     if (sMonitor.getLifecycleStageOf(activity) != Stage.DESTROYED) {
60                         activity.finish();
61                     }
62                 });
63         final String error =
64                 "Failed to finish the Activity. Did you start a second Activity and "
65                         + "not finish it?";
66         waitForActivityState(error, activity, Stage.DESTROYED);
67     }
68 
69     /**
70      * Recreates the provided Activity, returning the newly created Activity once it's finished
71      * starting up.
72      * @param activity The Activity to recreate.
73      * @return The newly created Activity.
74      */
recreateActivity(T activity)75     public static <T extends Activity> T recreateActivity(T activity) {
76         return waitForActivityWithClass(
77                 activity.getClass(), Stage.RESUMED, () -> activity.recreate());
78     }
79 
80     /**
81      * Waits for an activity of the specified class to reach the specified Activity {@link Stage},
82      * triggered by running the provided trigger.
83      *
84      * @param activityClass The class type to wait for.
85      * @param state The Activity {@link Stage} to wait for an activity of the right class type to
86      *         reach.
87      * @param uiThreadTrigger The Runnable that will trigger the state change to wait for. The
88      *         Runnable will be run on the UI thread
89      */
waitForActivityWithClass( Class<? extends Activity> activityClass, Stage stage, Runnable uiThreadTrigger)90     public static <T extends Activity> T waitForActivityWithClass(
91             Class<? extends Activity> activityClass, Stage stage, Runnable uiThreadTrigger) {
92         return waitForActivityWithClass(activityClass, stage, uiThreadTrigger, null);
93     }
94 
95     /**
96      * Waits for an activity of the specified class to reach the specified Activity {@link Stage},
97      * triggered by running the provided trigger.
98      *
99      * @param activityClass The class type to wait for.
100      * @param state The Activity {@link Stage} to wait for an activity of the right class type to
101      *     reach.
102      * @param uiThreadTrigger The Runnable that will trigger the state change to wait for, which
103      *     will be run on the UI thread.
104      * @param backgroundThreadTrigger The Runnable that will trigger the state change to wait for,
105      *     which will be run on the UI thread.
106      */
waitForActivityWithClass( Class<? extends Activity> activityClass, Stage stage, Runnable uiThreadTrigger, Runnable backgroundThreadTrigger)107     public static <T extends Activity> T waitForActivityWithClass(
108             Class<? extends Activity> activityClass,
109             Stage stage,
110             Runnable uiThreadTrigger,
111             Runnable backgroundThreadTrigger) {
112         ThreadUtils.assertOnBackgroundThread();
113         final CallbackHelper activityCallback = new CallbackHelper();
114         final AtomicReference<T> activityRef = new AtomicReference<>();
115         ActivityLifecycleCallback stateListener =
116                 (Activity newActivity, Stage newStage) -> {
117                     if (newStage == stage) {
118                         if (!activityClass.isAssignableFrom(newActivity.getClass())) return;
119 
120                         activityRef.set((T) newActivity);
121                         ThreadUtils.postOnUiThread(() -> activityCallback.notifyCalled());
122                     }
123                 };
124         sMonitor.addLifecycleCallback(stateListener);
125 
126         try {
127             if (uiThreadTrigger != null) {
128                 ThreadUtils.runOnUiThreadBlocking(() -> uiThreadTrigger.run());
129             }
130             if (backgroundThreadTrigger != null) backgroundThreadTrigger.run();
131             activityCallback.waitForFirst(
132                     "No Activity reached target state.", ACTIVITY_TIMEOUT, TimeUnit.MILLISECONDS);
133             T createdActivity = activityRef.get();
134             Assert.assertNotNull("Activity reference is null.", createdActivity);
135             return createdActivity;
136         } catch (TimeoutException e) {
137             throw new RuntimeException(e);
138         } finally {
139             sMonitor.removeLifecycleCallback(stateListener);
140         }
141     }
142 }
143