Home | History | Annotate | Download | only in interactions
      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
     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      */
    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 
    115     public LoaderManager getDelegate() {
    116         return mDelegate;
    117     }
    118 
    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
    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      */
    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
    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