1 /* 2 * Copyright (C) 2008 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 android.fragment.cts; 17 18 import static org.junit.Assert.assertEquals; 19 import static org.junit.Assert.assertFalse; 20 import static org.junit.Assert.assertNull; 21 import static org.junit.Assert.assertTrue; 22 23 import android.app.Fragment; 24 import android.app.FragmentManager; 25 import android.app.LoaderManager; 26 import android.content.AsyncTaskLoader; 27 import android.content.Context; 28 import android.content.Loader; 29 import android.os.Bundle; 30 import android.os.SystemClock; 31 import android.support.test.InstrumentationRegistry; 32 import android.support.test.filters.MediumTest; 33 import android.support.test.rule.ActivityTestRule; 34 import android.support.test.runner.AndroidJUnit4; 35 36 import org.junit.Rule; 37 import org.junit.Test; 38 import org.junit.runner.RunWith; 39 40 import java.lang.ref.WeakReference; 41 import java.util.concurrent.CountDownLatch; 42 import java.util.concurrent.TimeUnit; 43 44 @MediumTest 45 @RunWith(AndroidJUnit4.class) 46 public class LoaderTest { 47 private static final int DELAY_LOADER = 10; 48 49 @Rule 50 public ActivityTestRule<LoaderActivity> mActivityRule = 51 new ActivityTestRule<>(LoaderActivity.class); 52 53 /** 54 * Test to ensure that there is no Activity leak due to Loader 55 */ 56 @Test 57 public void testLeak() throws Throwable { 58 // Restart the activity because mActivityRule keeps a strong reference to the 59 // old activity. 60 LoaderActivity activity = FragmentTestUtil.recreateActivity(mActivityRule, 61 mActivityRule.getActivity()); 62 LoaderFragment fragment = new LoaderFragment(); 63 FragmentManager fm = activity.getFragmentManager(); 64 65 fm.beginTransaction() 66 .add(fragment, "1") 67 .commit(); 68 69 FragmentTestUtil.executePendingTransactions(mActivityRule, fm); 70 71 fm.beginTransaction() 72 .remove(fragment) 73 .addToBackStack(null) 74 .commit(); 75 76 FragmentTestUtil.executePendingTransactions(mActivityRule, fm); 77 fm = null; // clear it so that it can be released 78 79 WeakReference<LoaderActivity> weakActivity = new WeakReference(LoaderActivity.sActivity); 80 81 activity = FragmentTestUtil.recreateActivity(mActivityRule, activity); 82 83 // Wait for everything to settle. We have to make sure that the old Activity 84 // is ready to be collected. 85 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 86 FragmentTestUtil.waitForExecution(mActivityRule); 87 88 // Now force a garbage collection. 89 FragmentTestUtil.forceGC(); 90 assertNull(weakActivity.get()); 91 } 92 93 /** 94 * When a LoaderManager is reused, it should notify in onResume 95 */ 96 @Test 97 public void startWhenReused() throws Throwable { 98 LoaderActivity activity = mActivityRule.getActivity(); 99 activity.waitForResume(mActivityRule); 100 assertTrue(activity.loadFinished.await(1, TimeUnit.SECONDS)); 101 102 assertEquals("Loaded!", activity.textView.getText().toString()); 103 104 activity = FragmentTestUtil.recreateActivity(mActivityRule, activity); 105 assertTrue(activity.loadFinished.await(1, TimeUnit.SECONDS)); 106 107 // After orientation change, the text should still be loaded properly 108 assertEquals("Loaded!", activity.textView.getText().toString()); 109 } 110 111 /** 112 * When a change is interrupted with stop, the data in the LoaderManager remains stale. 113 */ 114 @Test 115 public void noStaleData() throws Throwable { 116 final LoaderActivity activity = mActivityRule.getActivity(); 117 final String[] value = new String[] { "First Value" }; 118 119 final CountDownLatch[] loadedLatch = new CountDownLatch[] { new CountDownLatch(1) }; 120 final Loader<String>[] loaders = new Loader[1]; 121 mActivityRule.runOnUiThread(new Runnable() { 122 @Override 123 public void run() { 124 final Loader<String> loader = 125 activity.getLoaderManager().initLoader(DELAY_LOADER, null, 126 new LoaderManager.LoaderCallbacks<String>() { 127 @Override 128 public Loader<String> onCreateLoader(int id, Bundle args) { 129 return new AsyncTaskLoader<String>(activity) { 130 @Override 131 protected void onStopLoading() { 132 cancelLoad(); 133 } 134 135 @Override 136 public String loadInBackground() { 137 SystemClock.sleep(50); 138 return value[0]; 139 } 140 141 @Override 142 protected void onStartLoading() { 143 if (takeContentChanged()) { 144 forceLoad(); 145 } 146 super.onStartLoading(); 147 } 148 }; 149 } 150 151 @Override 152 public void onLoadFinished(Loader<String> loader, String data) { 153 activity.textViewB.setText(data); 154 loadedLatch[0].countDown(); 155 } 156 157 @Override 158 public void onLoaderReset(Loader<String> loader) { 159 } 160 }); 161 loader.forceLoad(); 162 loaders[0] = loader; 163 } 164 }); 165 166 assertTrue(loadedLatch[0].await(1, TimeUnit.SECONDS)); 167 assertEquals("First Value", activity.textViewB.getText().toString()); 168 169 loadedLatch[0] = new CountDownLatch(1); 170 mActivityRule.runOnUiThread(new Runnable() { 171 @Override 172 public void run() { 173 value[0] = "Second Value"; 174 loaders[0].onContentChanged(); 175 loaders[0].stopLoading(); 176 } 177 }); 178 179 // Since the loader was stopped (and canceled), it shouldn't notify the change 180 assertFalse(loadedLatch[0].await(300, TimeUnit.MILLISECONDS)); 181 182 mActivityRule.runOnUiThread(new Runnable() { 183 @Override 184 public void run() { 185 loaders[0].startLoading(); 186 } 187 }); 188 189 // Since the loader was stopped (and canceled), it shouldn't notify the change 190 assertTrue(loadedLatch[0].await(1, TimeUnit.SECONDS)); 191 assertEquals("Second Value", activity.textViewB.getText().toString()); 192 } 193 194 public static class LoaderFragment extends Fragment { 195 private static final int LOADER_ID = 1; 196 private final LoaderManager.LoaderCallbacks<Boolean> mLoaderCallbacks = 197 new LoaderManager.LoaderCallbacks<Boolean>() { 198 @Override 199 public Loader<Boolean> onCreateLoader(int id, Bundle args) { 200 return new DummyLoader(getContext()); 201 } 202 203 @Override 204 public void onLoadFinished(Loader<Boolean> loader, Boolean data) { 205 206 } 207 208 @Override 209 public void onLoaderReset(Loader<Boolean> loader) { 210 211 } 212 }; 213 214 @Override 215 public void onActivityCreated(Bundle savedInstanceState) { 216 super.onActivityCreated(savedInstanceState); 217 218 getLoaderManager().initLoader(LOADER_ID, null, mLoaderCallbacks); 219 } 220 } 221 222 static class DummyLoader extends Loader<Boolean> { 223 DummyLoader(Context context) { 224 super(context); 225 } 226 227 @Override 228 protected void onStartLoading() { 229 deliverResult(true); 230 } 231 } 232 } 233