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_RESUME;
20
21import static org.hamcrest.CoreMatchers.is;
22import static org.hamcrest.CoreMatchers.not;
23import static org.hamcrest.CoreMatchers.notNullValue;
24import static org.hamcrest.MatcherAssert.assertThat;
25
26import android.app.Instrumentation;
27import android.support.test.InstrumentationRegistry;
28import android.support.test.annotation.UiThreadTest;
29import android.support.test.filters.MediumTest;
30import android.support.test.rule.ActivityTestRule;
31import android.support.test.runner.AndroidJUnit4;
32
33import androidx.fragment.app.Fragment;
34import androidx.fragment.app.FragmentActivity;
35import androidx.fragment.app.FragmentManager;
36import androidx.lifecycle.viewmodeltest.TestViewModel;
37import androidx.lifecycle.viewmodeltest.ViewModelActivity;
38import androidx.lifecycle.viewmodeltest.ViewModelActivity.ViewModelFragment;
39
40import org.junit.Rule;
41import org.junit.Test;
42import org.junit.runner.RunWith;
43
44import java.util.concurrent.CountDownLatch;
45import java.util.concurrent.TimeUnit;
46
47@MediumTest
48@RunWith(AndroidJUnit4.class)
49public class ViewModelTest {
50    private static final int TIMEOUT = 2; // secs
51
52    @Rule
53    public ActivityTestRule<ViewModelActivity> mActivityRule =
54            new ActivityTestRule<>(ViewModelActivity.class);
55
56    @Test
57    public void ensureSameViewHolders() throws Throwable {
58        final TestViewModel[] activityModel = new TestViewModel[1];
59        final TestViewModel[] defaultActivityModel = new TestViewModel[1];
60        final TestViewModel[] fragment1Model = new TestViewModel[1];
61        final TestViewModel[] fragment2Model = new TestViewModel[1];
62        final ViewModelActivity[] viewModelActivity = new ViewModelActivity[1];
63        viewModelActivity[0] = mActivityRule.getActivity();
64        mActivityRule.runOnUiThread(new Runnable() {
65            @Override
66            public void run() {
67                ViewModelFragment fragment1 = getFragment(viewModelActivity[0],
68                        ViewModelActivity.FRAGMENT_TAG_1);
69                ViewModelFragment fragment2 = getFragment(viewModelActivity[0],
70                        ViewModelActivity.FRAGMENT_TAG_2);
71                assertThat(fragment1, notNullValue());
72                assertThat(fragment2, notNullValue());
73                assertThat(fragment1.activityModel, is(fragment2.activityModel));
74                assertThat(fragment1.fragmentModel, not(is(fragment2.activityModel)));
75                assertThat(mActivityRule.getActivity().activityModel, is(fragment1.activityModel));
76                activityModel[0] = mActivityRule.getActivity().activityModel;
77                defaultActivityModel[0] = mActivityRule.getActivity().defaultActivityModel;
78                assertThat(defaultActivityModel[0], not(is(activityModel[0])));
79                fragment1Model[0] = fragment1.fragmentModel;
80                fragment2Model[0] = fragment2.fragmentModel;
81            }
82        });
83        viewModelActivity[0] = recreateActivity();
84        mActivityRule.runOnUiThread(new Runnable() {
85            @Override
86            public void run() {
87                ViewModelFragment fragment1 = getFragment(viewModelActivity[0],
88                        ViewModelActivity.FRAGMENT_TAG_1);
89                ViewModelFragment fragment2 = getFragment(viewModelActivity[0],
90                        ViewModelActivity.FRAGMENT_TAG_2);
91                assertThat(fragment1, notNullValue());
92                assertThat(fragment2, notNullValue());
93
94                assertThat(fragment1.activityModel, is(activityModel[0]));
95                assertThat(fragment2.activityModel, is(activityModel[0]));
96                assertThat(fragment1.fragmentModel, is(fragment1Model[0]));
97                assertThat(fragment2.fragmentModel, is(fragment2Model[0]));
98                assertThat(fragment1.defaultActivityModel, is(defaultActivityModel[0]));
99                assertThat(fragment2.defaultActivityModel, is(defaultActivityModel[0]));
100                assertThat(mActivityRule.getActivity().activityModel, is(activityModel[0]));
101                assertThat(mActivityRule.getActivity().defaultActivityModel,
102                        is(defaultActivityModel[0]));
103            }
104        });
105    }
106
107    @Test
108    @UiThreadTest
109    public void testGetApplication() {
110        TestViewModel activityModel = mActivityRule.getActivity().activityModel;
111        assertThat(activityModel.getApplication(),
112                is(InstrumentationRegistry.getTargetContext().getApplicationContext()));
113    }
114
115    @Test
116    public void testOnClear() throws Throwable {
117        final ViewModelActivity activity = mActivityRule.getActivity();
118        final CountDownLatch latch = new CountDownLatch(1);
119        final LifecycleObserver observer = new LifecycleObserver() {
120            @SuppressWarnings("unused")
121            @OnLifecycleEvent(ON_RESUME)
122            void onResume() {
123                try {
124                    final FragmentManager manager = activity.getSupportFragmentManager();
125                    Fragment fragment = new Fragment();
126                    manager.beginTransaction().add(fragment, "temp").commitNow();
127                    ViewModel1 vm = ViewModelProviders.of(fragment).get(ViewModel1.class);
128                    assertThat(vm.mCleared, is(false));
129                    manager.beginTransaction().remove(fragment).commitNow();
130                    assertThat(vm.mCleared, is(true));
131                } finally {
132                    latch.countDown();
133                }
134            }
135        };
136
137        mActivityRule.runOnUiThread(new Runnable() {
138            @Override
139            public void run() {
140                activity.getLifecycle().addObserver(observer);
141            }
142        });
143        assertThat(latch.await(TIMEOUT, TimeUnit.SECONDS), is(true));
144    }
145
146    private ViewModelFragment getFragment(FragmentActivity activity, String tag) {
147        return (ViewModelFragment) activity.getSupportFragmentManager()
148                .findFragmentByTag(tag);
149    }
150
151    private ViewModelActivity recreateActivity() throws Throwable {
152        Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
153                ViewModelActivity.class.getCanonicalName(), null, false);
154        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
155        instrumentation.addMonitor(monitor);
156        final ViewModelActivity previous = mActivityRule.getActivity();
157        mActivityRule.runOnUiThread(new Runnable() {
158            @Override
159            public void run() {
160                previous.recreate();
161            }
162        });
163        ViewModelActivity result;
164
165        // this guarantee that we will reinstall monitor between notifications about onDestroy
166        // and onCreate
167        //noinspection SynchronizationOnLocalVariableOrMethodParameter
168        synchronized (monitor) {
169            do {
170                // the documentation says "Block until an Activity is created
171                // that matches this monitor." This statement is true, but there are some other
172                // true statements like: "Block until an Activity is destroyed" or
173                // "Block until an Activity is resumed"...
174
175                // this call will release synchronization monitor's monitor
176                result = (ViewModelActivity) monitor.waitForActivityWithTimeout(4000);
177                if (result == null) {
178                    throw new RuntimeException("Timeout. Failed to recreate an activity");
179                }
180            } while (result == previous);
181        }
182        return result;
183    }
184
185    public static class ViewModel1 extends ViewModel {
186        boolean mCleared = false;
187
188        @Override
189        protected void onCleared() {
190            mCleared = true;
191        }
192    }
193}
194