• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 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 package com.android.contacts.interactions;
18 
19 import android.app.Activity;
20 import android.app.LoaderManager;
21 import android.content.AsyncTaskLoader;
22 import android.content.Loader;
23 import android.os.Bundle;
24 import android.util.Log;
25 
26 import com.google.common.annotations.VisibleForTesting;
27 
28 import junit.framework.Assert;
29 
30 import java.io.FileDescriptor;
31 import java.io.PrintWriter;
32 import java.util.ArrayList;
33 import java.util.HashSet;
34 import java.util.List;
35 
36 /**
37  * A {@link LoaderManager} that records which loaders have been completed.
38  * <p>
39  * You should wrap the existing LoaderManager with an instance of this class, which will then
40  * delegate to the original object.
41  * <p>
42  * Typically, one would override {@link Activity#getLoaderManager()} to return the
43  * TestLoaderManager and ensuring it wraps the {@link LoaderManager} for this object, e.g.:
44  * <pre>
45  *   private TestLoaderManager mTestLoaderManager;
46  *
47  *   public LoaderManager getLoaderManager() {
48  *     LoaderManager loaderManager = super.getLoaderManager();
49  *     if (mTestLoaderManager != null) {
50  *       mTestLoaderManager.setDelegate(loaderManager);
51  *       return mTestLoaderManager;
52  *     } else {
53  *       return loaderManager;
54  *     }
55  *   }
56  *
57  *   void setTestLoaderManager(TestLoaderManager testLoaderManager) {
58  *     mTestLoaderManager = testLoaderManager;
59  *   }
60  * </pre>
61  * In the tests, one would set the TestLoaderManager upon creating the activity, and then wait for
62  * the loader to complete.
63  * <pre>
64  *   public void testLoadedCorrect() {
65  *     TestLoaderManager testLoaderManager = new TestLoaderManager();
66  *     getActivity().setTestLoaderManager(testLoaderManager);
67  *     runOnUiThread(new Runnable() { public void run() { getActivity().startLoading(); } });
68  *     testLoaderManager.waitForLoader(R.id.test_loader_id);
69  *   }
70  * </pre>
71  * If the loader completes before the call to {@link #waitForLoaders(int...)}, the TestLoaderManager
72  * will have stored the fact that the loader has completed and correctly terminate immediately.
73  * <p>
74  * It one needs to wait for the same loader multiple times, call {@link #reset()} between the them
75  * as in:
76  * <pre>
77  *   public void testLoadedCorrect() {
78  *     TestLoaderManager testLoaderManager = new TestLoaderManager();
79  *     getActivity().setTestLoaderManager(testLoaderManager);
80  *     runOnUiThread(new Runnable() { public void run() { getActivity().startLoading(); } });
81  *     testLoaderManager.waitForLoader(R.id.test_loader_id);
82  *     testLoaderManager.reset();
83  *     // Load and wait again.
84  *     runOnUiThread(new Runnable() { public void run() { getActivity().startLoading(); } });
85  *     testLoaderManager.waitForLoader(R.id.test_loader_id);
86  *   }
87  * </pre>
88  */
89 public class TestLoaderManager extends LoaderManager {
90     private static final String TAG = "TestLoaderManager";
91 
92     private final HashSet<Integer> mFinishedLoaders;
93 
94     private LoaderManager mDelegate;
95 
96     @VisibleForTesting
TestLoaderManager()97     public TestLoaderManager() {
98         mFinishedLoaders = new HashSet<Integer>();
99     }
100 
101     /**
102      * Sets the object to which we delegate the actual work.
103      * <p>
104      * It can not be set to null. Once set, it cannot be changed (but it allows setting it to the
105      * same value again).
106      */
setDelegate(LoaderManager delegate)107     public void setDelegate(LoaderManager delegate) {
108         if (delegate == null || (mDelegate != null && mDelegate != delegate)) {
109             throw new IllegalArgumentException("TestLoaderManager cannot be shared");
110         }
111 
112         mDelegate = delegate;
113     }
114 
getDelegate()115     public LoaderManager getDelegate() {
116         return mDelegate;
117     }
118 
reset()119     public void reset() {
120         mFinishedLoaders.clear();
121     }
122 
123     /**
124      * Waits for the specified loaders to complete loading.
125      * <p>
126      * If one of the loaders has already completed since the last call to {@link #reset()}, it will
127      * not wait for it to complete again.
128      */
129     @VisibleForTesting
waitForLoaders(int... loaderIds)130     /*package*/ synchronized void waitForLoaders(int... loaderIds) {
131         List<Loader<?>> loaders = new ArrayList<Loader<?>>(loaderIds.length);
132         for (int loaderId : loaderIds) {
133             if (mFinishedLoaders.contains(loaderId)) {
134                 // This loader has already completed since the last reset, do not wait for it.
135                 continue;
136             }
137 
138             final AsyncTaskLoader<?> loader =
139                     (AsyncTaskLoader<?>) mDelegate.getLoader(loaderId);
140             if (loader == null) {
141                 Assert.fail("Loader does not exist: " + loaderId);
142                 return;
143             }
144 
145             loaders.add(loader);
146         }
147 
148         waitForLoaders(loaders.toArray(new Loader<?>[0]));
149     }
150 
151     /**
152      * Waits for the specified loaders to complete loading.
153      */
waitForLoaders(Loader<?>.... loaders)154     public static void waitForLoaders(Loader<?>... loaders) {
155         // We want to wait for each loader using a separate thread, so that we can
156         // simulate race conditions.
157         Thread[] waitThreads = new Thread[loaders.length];
158         for (int i = 0; i < loaders.length; i++) {
159             final AsyncTaskLoader<?> loader = (AsyncTaskLoader<?>) loaders[i];
160             waitThreads[i] = new Thread("LoaderWaitingThread" + i) {
161                 @Override
162                 public void run() {
163                     try {
164                         loader.waitForLoader();
165                     } catch (Throwable e) {
166                         Log.e(TAG, "Exception while waiting for loader: " + loader.getId(), e);
167                         Assert.fail("Exception while waiting for loader: " + loader.getId());
168                     }
169                 }
170             };
171             waitThreads[i].start();
172         }
173 
174         // Now we wait for all these threads to finish
175         for (Thread thread : waitThreads) {
176             try {
177                 thread.join();
178             } catch (InterruptedException e) {
179                 // Ignore
180             }
181         }
182     }
183 
184     @Override
initLoader(final int id, Bundle args, final LoaderCallbacks<D> callback)185     public <D> Loader<D> initLoader(final int id, Bundle args, final LoaderCallbacks<D> callback) {
186         return mDelegate.initLoader(id, args, new LoaderManager.LoaderCallbacks<D>() {
187             @Override
188             public Loader<D> onCreateLoader(int id, Bundle args) {
189                 return callback.onCreateLoader(id, args);
190             }
191 
192             @Override
193             public void onLoadFinished(Loader<D> loader, D data) {
194                 callback.onLoadFinished(loader, data);
195                 synchronized (this) {
196                     mFinishedLoaders.add(id);
197                 }
198             }
199 
200             @Override
201             public void onLoaderReset(Loader<D> loader) {
202                 callback.onLoaderReset(loader);
203             }
204         });
205     }
206 
207     @Override
208     public <D> Loader<D> restartLoader(int id, Bundle args, LoaderCallbacks<D> callback) {
209         return mDelegate.restartLoader(id, args, callback);
210     }
211 
212     @Override
213     public void destroyLoader(int id) {
214         mDelegate.destroyLoader(id);
215     }
216 
217     @Override
218     public <D> Loader<D> getLoader(int id) {
219         return mDelegate.getLoader(id);
220     }
221 
222     @Override
223     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
224         mDelegate.dump(prefix, fd, writer, args);
225     }
226 }
227