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