1 /*
2  * Copyright (C) 2016 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 
17 
18 package android.fragment.cts;
19 
20 import android.app.Fragment;
21 import android.content.Context;
22 import android.os.Bundle;
23 
24 /**
25  * This fragment watches its primary lifecycle events and throws IllegalStateException
26  * if any of them are called out of order or from a bad/unexpected state.
27  */
28 public class StrictFragment extends Fragment {
29     public static final int DETACHED = 0;
30     public static final int ATTACHED = 1;
31     public static final int CREATED = 2;
32     public static final int ACTIVITY_CREATED = 3;
33     public static final int STARTED = 4;
34     public static final int RESUMED = 5;
35 
36     int mState;
37 
38     boolean mCalledOnAttach, mCalledOnCreate, mCalledOnActivityCreated,
39             mCalledOnStart, mCalledOnResume, mCalledOnSaveInstanceState,
40             mCalledOnPause, mCalledOnStop, mCalledOnDestroy, mCalledOnDetach,
41             mCalledOnAttachFragment;
42 
stateToString(int state)43     static String stateToString(int state) {
44         switch (state) {
45             case DETACHED: return "DETACHED";
46             case ATTACHED: return "ATTACHED";
47             case CREATED: return "CREATED";
48             case ACTIVITY_CREATED: return "ACTIVITY_CREATED";
49             case STARTED: return "STARTED";
50             case RESUMED: return "RESUMED";
51         }
52         return "(unknown " + state + ")";
53     }
54 
onStateChanged(int fromState)55     public void onStateChanged(int fromState) {
56         checkGetActivity();
57     }
58 
checkGetActivity()59     public void checkGetActivity() {
60         if (getActivity() == null) {
61             throw new IllegalStateException("getActivity() returned null at unexpected time");
62         }
63     }
64 
checkState(String caller, int... expected)65     public void checkState(String caller, int... expected) {
66         if (expected == null || expected.length == 0) {
67             throw new IllegalArgumentException("must supply at least one expected state");
68         }
69         for (int expect : expected) {
70             if (mState == expect) {
71                 return;
72             }
73         }
74         final StringBuilder expectString = new StringBuilder(stateToString(expected[0]));
75         for (int i = 1; i < expected.length; i++) {
76             expectString.append(" or ").append(stateToString(expected[i]));
77         }
78         throw new IllegalStateException(caller + " called while fragment was "
79                 + stateToString(mState) + "; expected " + expectString.toString());
80     }
81 
checkStateAtLeast(String caller, int minState)82     public void checkStateAtLeast(String caller, int minState) {
83         if (mState < minState) {
84             throw new IllegalStateException(caller + " called while fragment was "
85                     + stateToString(mState) + "; expected at least " + stateToString(minState));
86         }
87     }
88 
89     @Override
onAttachFragment(Fragment childFragment)90     public void onAttachFragment(Fragment childFragment) {
91         super.onAttachFragment(childFragment);
92         mCalledOnAttachFragment = true;
93     }
94 
95     @Override
onAttach(Context context)96     public void onAttach(Context context) {
97         super.onAttach(context);
98         mCalledOnAttach = true;
99         checkState("onAttach", DETACHED);
100         mState = ATTACHED;
101         onStateChanged(DETACHED);
102     }
103 
104     @Override
onCreate(Bundle savedInstanceState)105     public void onCreate(Bundle savedInstanceState) {
106         super.onCreate(savedInstanceState);
107         if (mCalledOnCreate && !mCalledOnDestroy) {
108             throw new IllegalStateException("onCreate called more than once with no onDestroy");
109         }
110         mCalledOnCreate = true;
111         checkState("onCreate", ATTACHED);
112         mState = CREATED;
113         onStateChanged(ATTACHED);
114     }
115 
116     @Override
onActivityCreated(Bundle savedInstanceState)117     public void onActivityCreated(Bundle savedInstanceState) {
118         super.onActivityCreated(savedInstanceState);
119         mCalledOnActivityCreated = true;
120         checkState("onActivityCreated", ATTACHED, CREATED);
121         int fromState = mState;
122         mState = ACTIVITY_CREATED;
123         onStateChanged(fromState);
124     }
125 
126     @Override
onStart()127     public void onStart() {
128         super.onStart();
129         mCalledOnStart = true;
130         checkState("onStart", ACTIVITY_CREATED);
131         mState = STARTED;
132         onStateChanged(ACTIVITY_CREATED);
133     }
134 
135     @Override
onResume()136     public void onResume() {
137         super.onResume();
138         mCalledOnResume = true;
139         checkState("onResume", STARTED);
140         mState = RESUMED;
141         onStateChanged(STARTED);
142     }
143 
144     @Override
onSaveInstanceState(Bundle outState)145     public void onSaveInstanceState(Bundle outState) {
146         super.onSaveInstanceState(outState);
147         mCalledOnSaveInstanceState = true;
148         checkGetActivity();
149         // FIXME: We should not allow onSaveInstanceState except when STARTED or greater.
150         // But FragmentManager currently does it in saveAllState for fragments on the
151         // back stack, so fragments may be in the CREATED state.
152         checkStateAtLeast("onSaveInstanceState", CREATED);
153     }
154 
155     @Override
onPause()156     public void onPause() {
157         super.onPause();
158         mCalledOnPause = true;
159         checkState("onPause", RESUMED);
160         mState = STARTED;
161         onStateChanged(RESUMED);
162     }
163 
164     @Override
onStop()165     public void onStop() {
166         super.onStop();
167         mCalledOnStop = true;
168         checkState("onStop", STARTED);
169         mState = CREATED;
170         onStateChanged(STARTED);
171     }
172 
173     @Override
onDestroy()174     public void onDestroy() {
175         super.onDestroy();
176         mCalledOnDestroy = true;
177         checkState("onDestroy", CREATED);
178         mState = ATTACHED;
179         onStateChanged(CREATED);
180     }
181 
182     @Override
onDetach()183     public void onDetach() {
184         super.onDetach();
185         mCalledOnDetach = true;
186         checkState("onDestroy", CREATED, ATTACHED);
187         int fromState = mState;
188         mState = DETACHED;
189         onStateChanged(fromState);
190     }
191 }
192