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
17package com.android.contacts.interactions;
18
19import android.app.LoaderManager;
20import android.content.AsyncTaskLoader;
21import android.content.Loader;
22import android.os.Bundle;
23import android.util.Log;
24
25import com.google.common.annotations.VisibleForTesting;
26
27import junit.framework.Assert;
28
29import java.io.FileDescriptor;
30import java.io.PrintWriter;
31import java.util.ArrayList;
32import java.util.HashSet;
33import java.util.List;
34
35/**
36 * This implementation of TestLoaderManagerBase uses hidden APIs and must therefore
37 * be kept outside of the main Contacts apk.
38 */
39public class TestLoaderManager extends TestLoaderManagerBase {
40    private static final String TAG = "TestLoaderManager";
41
42    private final HashSet<Integer> mFinishedLoaders;
43
44    private LoaderManager mDelegate;
45
46    @VisibleForTesting
47    public TestLoaderManager() {
48        mFinishedLoaders = new HashSet<Integer>();
49    }
50
51    /**
52     * Sets the object to which we delegate the actual work.
53     * <p>
54     * It can not be set to null. Once set, it cannot be changed (but it allows setting it to the
55     * same value again).
56     */
57    public void setDelegate(LoaderManager delegate) {
58        if (delegate == null || (mDelegate != null && mDelegate != delegate)) {
59            throw new IllegalArgumentException("TestLoaderManager cannot be shared");
60        }
61
62        mDelegate = delegate;
63    }
64
65    public LoaderManager getDelegate() {
66        return mDelegate;
67    }
68
69    public void reset() {
70        mFinishedLoaders.clear();
71    }
72
73    /**
74     * Waits for the specified loaders to complete loading.
75     * <p>
76     * If one of the loaders has already completed since the last call to {@link #reset()}, it will
77     * not wait for it to complete again.
78     */
79    @VisibleForTesting
80    public synchronized void waitForLoaders(int... loaderIds) {
81        List<Loader<?>> loaders = new ArrayList<Loader<?>>(loaderIds.length);
82        for (int loaderId : loaderIds) {
83            if (mFinishedLoaders.contains(loaderId)) {
84                // This loader has already completed since the last reset, do not wait for it.
85                continue;
86            }
87
88            final AsyncTaskLoader<?> loader =
89                    (AsyncTaskLoader<?>) mDelegate.getLoader(loaderId);
90            if (loader == null) {
91                Assert.fail("Loader does not exist: " + loaderId);
92                return;
93            }
94
95            loaders.add(loader);
96        }
97
98        waitForLoaders(loaders.toArray(new Loader<?>[0]));
99    }
100
101    /**
102     * Waits for the specified loaders to complete loading.
103     */
104    public static void waitForLoaders(Loader<?>... loaders) {
105        // We want to wait for each loader using a separate thread, so that we can
106        // simulate race conditions.
107        Thread[] waitThreads = new Thread[loaders.length];
108        for (int i = 0; i < loaders.length; i++) {
109            final AsyncTaskLoader<?> loader = (AsyncTaskLoader<?>) loaders[i];
110            waitThreads[i] = new Thread("LoaderWaitingThread" + i) {
111                @Override
112                public void run() {
113                    try {
114                        AsyncTaskLoader.class.getMethod("waitForLoader").invoke(loader, null);
115                    } catch (Throwable e) {
116                        Log.e(TAG, "Exception while waiting for loader: " + loader.getId(), e);
117                        Assert.fail("Exception while waiting for loader: " + loader.getId());
118                    }
119                }
120            };
121            waitThreads[i].start();
122        }
123
124        // Now we wait for all these threads to finish
125        for (Thread thread : waitThreads) {
126            try {
127                thread.join();
128            } catch (InterruptedException e) {
129                // Ignore
130            }
131        }
132    }
133
134    @Override
135    public <D> Loader<D> initLoader(final int id, Bundle args, final LoaderCallbacks<D> callback) {
136        return mDelegate.initLoader(id, args, new LoaderManager.LoaderCallbacks<D>() {
137            @Override
138            public Loader<D> onCreateLoader(int id, Bundle args) {
139                return callback.onCreateLoader(id, args);
140            }
141
142            @Override
143            public void onLoadFinished(Loader<D> loader, D data) {
144                callback.onLoadFinished(loader, data);
145                synchronized (this) {
146                    mFinishedLoaders.add(id);
147                }
148            }
149
150            @Override
151            public void onLoaderReset(Loader<D> loader) {
152                callback.onLoaderReset(loader);
153            }
154        });
155    }
156
157    @Override
158    public <D> Loader<D> restartLoader(int id, Bundle args, LoaderCallbacks<D> callback) {
159        return mDelegate.restartLoader(id, args, callback);
160    }
161
162    @Override
163    public void destroyLoader(int id) {
164        mDelegate.destroyLoader(id);
165    }
166
167    @Override
168    public <D> Loader<D> getLoader(int id) {
169        return mDelegate.getLoader(id);
170    }
171
172    @Override
173    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
174        mDelegate.dump(prefix, fd, writer, args);
175    }
176}
177