1/*
2 * Copyright (C) 2017 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
17package androidx.lifecycle;
18
19import static androidx.lifecycle.Lifecycle.Event.ON_CREATE;
20import static androidx.lifecycle.Lifecycle.Event.ON_DESTROY;
21import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE;
22import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
23import static androidx.lifecycle.Lifecycle.Event.ON_START;
24import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
25import static androidx.lifecycle.Lifecycle.State.CREATED;
26import static androidx.lifecycle.Lifecycle.State.DESTROYED;
27import static androidx.lifecycle.Lifecycle.State.RESUMED;
28import static androidx.lifecycle.Lifecycle.State.STARTED;
29import static androidx.lifecycle.testapp.TestEvent.LIFECYCLE_EVENT;
30import static androidx.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
31
32import static org.hamcrest.CoreMatchers.is;
33import static org.hamcrest.MatcherAssert.assertThat;
34
35import android.app.Activity;
36import android.app.Instrumentation;
37import android.app.Instrumentation.ActivityMonitor;
38import android.support.test.InstrumentationRegistry;
39import android.support.test.rule.ActivityTestRule;
40
41import androidx.core.util.Pair;
42import androidx.lifecycle.testapp.TestEvent;
43
44import java.util.ArrayList;
45import java.util.Arrays;
46import java.util.List;
47import java.util.concurrent.CountDownLatch;
48import java.util.concurrent.TimeUnit;
49
50class TestUtils {
51
52    private static final long TIMEOUT_MS = 2000;
53
54    @SuppressWarnings("unchecked")
55    static <T extends Activity> T recreateActivity(final T activity, ActivityTestRule rule)
56            throws Throwable {
57        ActivityMonitor monitor = new ActivityMonitor(
58                activity.getClass().getCanonicalName(), null, false);
59        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
60        instrumentation.addMonitor(monitor);
61        rule.runOnUiThread(activity::recreate);
62        T result;
63
64        // this guarantee that we will reinstall monitor between notifications about onDestroy
65        // and onCreate
66        //noinspection SynchronizationOnLocalVariableOrMethodParameter
67        synchronized (monitor) {
68            do {
69                // the documetation says "Block until an Activity is created
70                // that matches this monitor." This statement is true, but there are some other
71                // true statements like: "Block until an Activity is destoyed" or
72                // "Block until an Activity is resumed"...
73
74                // this call will release synchronization monitor's monitor
75                result = (T) monitor.waitForActivityWithTimeout(TIMEOUT_MS);
76                if (result == null) {
77                    throw new RuntimeException("Timeout. Failed to recreate an activity");
78                }
79            } while (result == activity);
80        }
81        return result;
82    }
83
84    static void waitTillCreated(final LifecycleOwner owner, ActivityTestRule<?> activityRule)
85            throws Throwable {
86        waitTillState(owner, activityRule, CREATED);
87    }
88
89    static void waitTillStarted(final LifecycleOwner owner, ActivityTestRule<?> activityRule)
90            throws Throwable {
91        waitTillState(owner, activityRule, STARTED);
92    }
93
94    static void waitTillResumed(final LifecycleOwner owner, ActivityTestRule<?> activityRule)
95            throws Throwable {
96        waitTillState(owner, activityRule, RESUMED);
97    }
98
99    static void waitTillDestroyed(final LifecycleOwner owner, ActivityTestRule<?> activityRule)
100            throws Throwable {
101        waitTillState(owner, activityRule, DESTROYED);
102    }
103
104    static void waitTillState(final LifecycleOwner owner, ActivityTestRule<?> activityRule,
105            Lifecycle.State state)
106            throws Throwable {
107        final CountDownLatch latch = new CountDownLatch(1);
108        activityRule.runOnUiThread(() -> {
109            Lifecycle.State currentState = owner.getLifecycle().getCurrentState();
110            if (currentState == state) {
111                latch.countDown();
112            } else {
113                owner.getLifecycle().addObserver(new LifecycleObserver() {
114                    @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
115                    public void onStateChanged(LifecycleOwner provider) {
116                        if (provider.getLifecycle().getCurrentState() == state) {
117                            latch.countDown();
118                            provider.getLifecycle().removeObserver(this);
119                        }
120                    }
121                });
122            }
123        });
124        boolean latchResult = latch.await(1, TimeUnit.MINUTES);
125        assertThat("expected " + state + " never happened. Current state:"
126                        + owner.getLifecycle().getCurrentState(), latchResult, is(true));
127
128        // wait for another loop to ensure all observers are called
129        activityRule.runOnUiThread(() -> {
130            // do nothing
131        });
132    }
133
134    @SafeVarargs
135    static <T> List<T> flatMap(List<T>... items) {
136        ArrayList<T> result = new ArrayList<>();
137        for (List<T> item : items) {
138            result.addAll(item);
139        }
140        return result;
141    }
142
143    /**
144     * Event tuples of {@link TestEvent} and {@link Lifecycle.Event}
145     * in the order they should arrive.
146     */
147    @SuppressWarnings("unchecked")
148    static class OrderedTuples {
149        static final List<Pair<TestEvent, Lifecycle.Event>> CREATE =
150                Arrays.asList(new Pair(OWNER_CALLBACK, ON_CREATE),
151                        new Pair(LIFECYCLE_EVENT, ON_CREATE));
152        static final List<Pair<TestEvent, Lifecycle.Event>> START =
153                Arrays.asList(new Pair(OWNER_CALLBACK, ON_START),
154                        new Pair(LIFECYCLE_EVENT, ON_START));
155        static final List<Pair<TestEvent, Lifecycle.Event>> RESUME =
156                Arrays.asList(new Pair(OWNER_CALLBACK, ON_RESUME),
157                        new Pair(LIFECYCLE_EVENT, ON_RESUME));
158        static final List<Pair<TestEvent, Lifecycle.Event>> PAUSE =
159                Arrays.asList(new Pair(LIFECYCLE_EVENT, ON_PAUSE),
160                        new Pair(OWNER_CALLBACK, ON_PAUSE));
161        static final List<Pair<TestEvent, Lifecycle.Event>> STOP =
162                Arrays.asList(new Pair(LIFECYCLE_EVENT, ON_STOP),
163                        new Pair(OWNER_CALLBACK, ON_STOP));
164        static final List<Pair<TestEvent, Lifecycle.Event>> DESTROY =
165                Arrays.asList(new Pair(LIFECYCLE_EVENT, ON_DESTROY),
166                        new Pair(OWNER_CALLBACK, ON_DESTROY));
167    }
168}
169