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     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      */
    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 
    114     public LoaderManager getDelegate() {
    115         return mDelegate;
    116     }
    117 
    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
    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      */
    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
    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