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 TestLoaderManager()96 public TestLoaderManager() { 97 mFinishedLoaders = new HashSet<Integer>(); 98 } 99 100 /** 101 * Sets the object to which we delegate the actual work. 102 * <p> 103 * It can not be set to null. Once set, it cannot be changed (but it allows setting it to the 104 * same value again). 105 */ setDelegate(LoaderManager delegate)106 public void setDelegate(LoaderManager delegate) { 107 if (delegate == null || (mDelegate != null && mDelegate != delegate)) { 108 throw new IllegalArgumentException("TestLoaderManager cannot be shared"); 109 } 110 111 mDelegate = delegate; 112 } 113 getDelegate()114 public LoaderManager getDelegate() { 115 return mDelegate; 116 } 117 reset()118 public void reset() { 119 mFinishedLoaders.clear(); 120 } 121 122 /** 123 * Waits for the specified loaders to complete loading. 124 * <p> 125 * If one of the loaders has already completed since the last call to {@link #reset()}, it will 126 * not wait for it to complete again. 127 */ 128 @VisibleForTesting waitForLoaders(int... loaderIds)129 /*package*/ synchronized void waitForLoaders(int... loaderIds) { 130 List<Loader<?>> loaders = new ArrayList<Loader<?>>(loaderIds.length); 131 for (int loaderId : loaderIds) { 132 if (mFinishedLoaders.contains(loaderId)) { 133 // This loader has already completed since the last reset, do not wait for it. 134 continue; 135 } 136 137 final AsyncTaskLoader<?> loader = 138 (AsyncTaskLoader<?>) mDelegate.getLoader(loaderId); 139 if (loader == null) { 140 Assert.fail("Loader does not exist: " + loaderId); 141 return; 142 } 143 144 loaders.add(loader); 145 } 146 147 waitForLoaders(loaders.toArray(new Loader<?>[0])); 148 } 149 150 /** 151 * Waits for the specified loaders to complete loading. 152 */ waitForLoaders(Loader<?>.... loaders)153 public static void waitForLoaders(Loader<?>... loaders) { 154 // We want to wait for each loader using a separate thread, so that we can 155 // simulate race conditions. 156 Thread[] waitThreads = new Thread[loaders.length]; 157 for (int i = 0; i < loaders.length; i++) { 158 final AsyncTaskLoader<?> loader = (AsyncTaskLoader<?>) loaders[i]; 159 waitThreads[i] = new Thread("LoaderWaitingThread" + i) { 160 @Override 161 public void run() { 162 try { 163 loader.waitForLoader(); 164 } catch (Throwable e) { 165 Log.e(TAG, "Exception while waiting for loader: " + loader.getId(), e); 166 Assert.fail("Exception while waiting for loader: " + loader.getId()); 167 } 168 } 169 }; 170 waitThreads[i].start(); 171 } 172 173 // Now we wait for all these threads to finish 174 for (Thread thread : waitThreads) { 175 try { 176 thread.join(); 177 } catch (InterruptedException e) { 178 // Ignore 179 } 180 } 181 } 182 183 @Override initLoader(final int id, Bundle args, final LoaderCallbacks<D> callback)184 public <D> Loader<D> initLoader(final int id, Bundle args, final LoaderCallbacks<D> callback) { 185 return mDelegate.initLoader(id, args, new LoaderManager.LoaderCallbacks<D>() { 186 @Override 187 public Loader<D> onCreateLoader(int id, Bundle args) { 188 return callback.onCreateLoader(id, args); 189 } 190 191 @Override 192 public void onLoadFinished(Loader<D> loader, D data) { 193 callback.onLoadFinished(loader, data); 194 synchronized (this) { 195 mFinishedLoaders.add(id); 196 } 197 } 198 199 @Override 200 public void onLoaderReset(Loader<D> loader) { 201 callback.onLoaderReset(loader); 202 } 203 }); 204 } 205 206 @Override 207 public <D> Loader<D> restartLoader(int id, Bundle args, LoaderCallbacks<D> callback) { 208 return mDelegate.restartLoader(id, args, callback); 209 } 210 211 @Override 212 public void destroyLoader(int id) { 213 mDelegate.destroyLoader(id); 214 } 215 216 @Override 217 public <D> Loader<D> getLoader(int id) { 218 return mDelegate.getLoader(id); 219 } 220 221 @Override 222 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 223 mDelegate.dump(prefix, fd, writer, args); 224 } 225 } 226