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