1 /*
2  * Copyright 2018 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 package androidx.fragment.app.test;
17 
18 import static org.junit.Assert.assertFalse;
19 
20 import android.app.Activity;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.os.Build.VERSION;
24 import android.os.Build.VERSION_CODES;
25 import android.os.Bundle;
26 import android.transition.Transition;
27 import android.transition.Transition.TransitionListener;
28 import android.transition.TransitionInflater;
29 import android.view.LayoutInflater;
30 import android.view.View;
31 import android.view.ViewGroup;
32 
33 import androidx.annotation.Nullable;
34 import androidx.fragment.app.Fragment;
35 import androidx.fragment.app.FragmentActivity;
36 import androidx.fragment.test.R;
37 
38 import java.util.concurrent.CountDownLatch;
39 import java.util.concurrent.TimeUnit;
40 
41 /**
42  * A simple activity used for Fragment Transitions and lifecycle event ordering
43  */
44 public class FragmentTestActivity extends FragmentActivity {
45     public final CountDownLatch onDestroyLatch = new CountDownLatch(1);
46 
47     @Override
onCreate(Bundle icicle)48     public void onCreate(Bundle icicle) {
49         super.onCreate(icicle);
50         setContentView(R.layout.activity_content);
51         Intent intent = getIntent();
52         if (intent != null && intent.getBooleanExtra("finishEarly", false)) {
53             finish();
54             getSupportFragmentManager().beginTransaction()
55                     .add(new AssertNotDestroyed(), "not destroyed")
56                     .commit();
57         }
58     }
59 
60     @Override
onDestroy()61     protected void onDestroy() {
62         super.onDestroy();
63         onDestroyLatch.countDown();
64     }
65 
66     public static class TestFragment extends Fragment {
67         public static final int ENTER = 0;
68         public static final int RETURN = 1;
69         public static final int EXIT = 2;
70         public static final int REENTER = 3;
71         public static final int SHARED_ELEMENT_ENTER = 4;
72         public static final int SHARED_ELEMENT_RETURN = 5;
73         private static final int TRANSITION_COUNT = 6;
74 
75         private static final String LAYOUT_ID = "layoutId";
76         private static final String TRANSITION_KEY = "transition_";
77         private int mLayoutId = R.layout.fragment_start;
78         private final int[] mTransitionIds = new int[] {
79                 R.transition.fade,
80                 R.transition.fade,
81                 R.transition.fade,
82                 R.transition.fade,
83                 R.transition.change_bounds,
84                 R.transition.change_bounds,
85         };
86         private final Object[] mListeners = new Object[TRANSITION_COUNT];
87 
TestFragment()88         public TestFragment() {
89             if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
90                 for (int i = 0; i < TRANSITION_COUNT; i++) {
91                     mListeners[i] = new TransitionCalledListener();
92                 }
93             }
94         }
95 
create(int layoutId)96         public static TestFragment create(int layoutId) {
97             TestFragment testFragment = new TestFragment();
98             testFragment.mLayoutId = layoutId;
99             return testFragment;
100         }
101 
clearTransitions()102         public void clearTransitions() {
103             for (int i = 0; i < TRANSITION_COUNT; i++) {
104                 mTransitionIds[i] = 0;
105             }
106         }
107 
clearNotifications()108         public void clearNotifications() {
109             for (int i = 0; i < TRANSITION_COUNT; i++) {
110                 ((TransitionCalledListener)mListeners[i]).startLatch = new CountDownLatch(1);
111                 ((TransitionCalledListener)mListeners[i]).endLatch = new CountDownLatch(1);
112             }
113         }
114 
115         @Override
onCreate(Bundle savedInstanceState)116         public void onCreate(Bundle savedInstanceState) {
117             super.onCreate(savedInstanceState);
118             if (savedInstanceState != null) {
119                 mLayoutId = savedInstanceState.getInt(LAYOUT_ID, mLayoutId);
120                 for (int i = 0; i < TRANSITION_COUNT; i++) {
121                     String key = TRANSITION_KEY + i;
122                     mTransitionIds[i] = savedInstanceState.getInt(key, mTransitionIds[i]);
123                 }
124             }
125         }
126 
127         @Override
onSaveInstanceState(Bundle outState)128         public void onSaveInstanceState(Bundle outState) {
129             super.onSaveInstanceState(outState);
130             outState.putInt(LAYOUT_ID, mLayoutId);
131             for (int i = 0; i < TRANSITION_COUNT; i++) {
132                 String key = TRANSITION_KEY + i;
133                 outState.putInt(key, mTransitionIds[i]);
134             }
135         }
136 
137         @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)138         public View onCreateView(LayoutInflater inflater, ViewGroup container,
139                 Bundle savedInstanceState) {
140             return inflater.inflate(mLayoutId, container, false);
141         }
142 
143         @SuppressWarnings("deprecation")
144         @Override
onAttach(Activity activity)145         public void onAttach(Activity activity) {
146             super.onAttach(activity);
147             if (VERSION.SDK_INT > VERSION_CODES.KITKAT) {
148                 setEnterTransition(loadTransition(ENTER));
149                 setReenterTransition(loadTransition(REENTER));
150                 setExitTransition(loadTransition(EXIT));
151                 setReturnTransition(loadTransition(RETURN));
152                 setSharedElementEnterTransition(loadTransition(SHARED_ELEMENT_ENTER));
153                 setSharedElementReturnTransition(loadTransition(SHARED_ELEMENT_RETURN));
154             }
155         }
156 
wasStartCalled(int transitionKey)157         public boolean wasStartCalled(int transitionKey) {
158             return ((TransitionCalledListener)mListeners[transitionKey]).startLatch.getCount() == 0;
159         }
160 
wasEndCalled(int transitionKey)161         public boolean wasEndCalled(int transitionKey) {
162             return ((TransitionCalledListener)mListeners[transitionKey]).endLatch.getCount() == 0;
163         }
164 
waitForStart(int transitionKey)165         public boolean waitForStart(int transitionKey)
166                 throws InterruptedException {
167             TransitionCalledListener l = ((TransitionCalledListener)mListeners[transitionKey]);
168             return l.startLatch.await(500,TimeUnit.MILLISECONDS);
169         }
170 
waitForEnd(int transitionKey)171         public boolean waitForEnd(int transitionKey)
172                 throws InterruptedException {
173             TransitionCalledListener l = ((TransitionCalledListener)mListeners[transitionKey]);
174             return l.endLatch.await(500,TimeUnit.MILLISECONDS);
175         }
176 
loadTransition(int key)177         private Transition loadTransition(int key) {
178             final int id = mTransitionIds[key];
179             if (id == 0) {
180                 return null;
181             }
182             Transition transition = TransitionInflater.from(getActivity()).inflateTransition(id);
183             transition.addListener(((TransitionCalledListener)mListeners[key]));
184             return transition;
185         }
186 
187         private class TransitionCalledListener implements TransitionListener {
188             public CountDownLatch startLatch = new CountDownLatch(1);
189             public CountDownLatch endLatch = new CountDownLatch(1);
190 
TransitionCalledListener()191             public TransitionCalledListener() {
192             }
193 
194             @Override
onTransitionStart(Transition transition)195             public void onTransitionStart(Transition transition) {
196                 startLatch.countDown();
197             }
198 
199             @Override
onTransitionEnd(Transition transition)200             public void onTransitionEnd(Transition transition) {
201                 endLatch.countDown();
202             }
203 
204             @Override
onTransitionCancel(Transition transition)205             public void onTransitionCancel(Transition transition) {
206             }
207 
208             @Override
onTransitionPause(Transition transition)209             public void onTransitionPause(Transition transition) {
210             }
211 
212             @Override
onTransitionResume(Transition transition)213             public void onTransitionResume(Transition transition) {
214             }
215         }
216     }
217 
218     public static class ParentFragment extends Fragment {
219         static final String CHILD_FRAGMENT_TAG = "childFragment";
220         public boolean wasAttachedInTime;
221 
222         private boolean mRetainChild;
223 
224         @Override
onCreate(Bundle savedInstanceState)225         public void onCreate(Bundle savedInstanceState) {
226             super.onCreate(savedInstanceState);
227 
228             ChildFragment f = getChildFragment();
229             if (f == null) {
230                 f = new ChildFragment();
231                 if (mRetainChild) {
232                     f.setRetainInstance(true);
233                 }
234                 getChildFragmentManager().beginTransaction().add(f, CHILD_FRAGMENT_TAG).commitNow();
235             }
236             wasAttachedInTime = f.attached;
237         }
238 
getChildFragment()239         public ChildFragment getChildFragment() {
240             return (ChildFragment) getChildFragmentManager().findFragmentByTag(CHILD_FRAGMENT_TAG);
241         }
242 
setRetainChildInstance(boolean retainChild)243         public void setRetainChildInstance(boolean retainChild) {
244             mRetainChild = retainChild;
245         }
246     }
247 
248     public static class ChildFragment extends Fragment {
249         private OnAttachListener mOnAttachListener;
250 
251         public boolean attached;
252         public boolean onActivityResultCalled;
253         public int onActivityResultRequestCode;
254         public int onActivityResultResultCode;
255 
256         @Override
onAttach(Context activity)257         public void onAttach(Context activity) {
258             super.onAttach(activity);
259             attached = true;
260             if (mOnAttachListener != null) {
261                 mOnAttachListener.onAttach(activity, this);
262             }
263         }
264 
setOnAttachListener(OnAttachListener listener)265         public void setOnAttachListener(OnAttachListener listener) {
266             mOnAttachListener = listener;
267         }
268 
269         public interface OnAttachListener {
onAttach(Context activity, ChildFragment fragment)270             void onAttach(Context activity, ChildFragment fragment);
271         }
272 
273         @Override
onActivityResult(int requestCode, int resultCode, Intent data)274         public void onActivityResult(int requestCode, int resultCode, Intent data) {
275             onActivityResultCalled = true;
276             onActivityResultRequestCode = requestCode;
277             onActivityResultResultCode = resultCode;
278         }
279     }
280 
281     public static class AssertNotDestroyed extends Fragment {
282         @Override
onActivityCreated(@ullable Bundle savedInstanceState)283         public void onActivityCreated(@Nullable Bundle savedInstanceState) {
284             super.onActivityCreated(savedInstanceState);
285             if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) {
286                 assertFalse(getActivity().isDestroyed());
287             }
288         }
289     }
290 }
291