| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.dialer.util; |
| |
| import android.app.Instrumentation; |
| import android.os.AsyncTask; |
| |
| import com.google.common.collect.Lists; |
| |
| import junit.framework.Assert; |
| |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.Executor; |
| import java.util.concurrent.TimeUnit; |
| |
| import javax.annotation.concurrent.GuardedBy; |
| import javax.annotation.concurrent.ThreadSafe; |
| |
| /** |
| * Test implementation of AsyncTaskExecutor. |
| * <p> |
| * This class is thread-safe. As per the contract of the AsyncTaskExecutor, the submit methods must |
| * be called from the main ui thread, however the other public methods may be called from any thread |
| * (most commonly the test thread). |
| * <p> |
| * Tasks submitted to this executor will not be run immediately. Rather they will be stored in a |
| * list of submitted tasks, where they can be examined. They can also be run on-demand using the run |
| * methods, so that different ordering of AsyncTask execution can be simulated. |
| * <p> |
| * The onPreExecute method of the submitted AsyncTask will be called synchronously during the |
| * call to {@link #submit(Object, AsyncTask, Object...)}. |
| */ |
| @ThreadSafe |
| public class FakeAsyncTaskExecutor implements AsyncTaskExecutor { |
| private static final long DEFAULT_TIMEOUT_MS = 10000; |
| |
| /** The maximum length of time in ms to wait for tasks to execute during tests. */ |
| private final long mTimeoutMs = DEFAULT_TIMEOUT_MS; |
| |
| private final Object mLock = new Object(); |
| @GuardedBy("mLock") private final List<SubmittedTask> mSubmittedTasks = Lists.newArrayList(); |
| |
| private final DelayedExecutor mBlockingExecutor = new DelayedExecutor(); |
| private final Instrumentation mInstrumentation; |
| |
| /** Create a fake AsyncTaskExecutor for use in unit tests. */ |
| public FakeAsyncTaskExecutor(Instrumentation instrumentation) { |
| Assert.assertNotNull(instrumentation); |
| mInstrumentation = instrumentation; |
| } |
| |
| /** Encapsulates an async task with the params and identifier it was submitted with. */ |
| public interface SubmittedTask { |
| Runnable getRunnable(); |
| Object getIdentifier(); |
| AsyncTask<?, ?, ?> getAsyncTask(); |
| } |
| |
| private static final class SubmittedTaskImpl implements SubmittedTask { |
| private final Object mIdentifier; |
| private final Runnable mRunnable; |
| private final AsyncTask<?, ?, ?> mAsyncTask; |
| |
| public SubmittedTaskImpl(Object identifier, Runnable runnable, |
| AsyncTask<?, ?, ?> asyncTask) { |
| mIdentifier = identifier; |
| mRunnable = runnable; |
| mAsyncTask = asyncTask; |
| } |
| |
| @Override |
| public Object getIdentifier() { |
| return mIdentifier; |
| } |
| |
| @Override |
| public Runnable getRunnable() { |
| return mRunnable; |
| } |
| |
| @Override |
| public AsyncTask<?, ?, ?> getAsyncTask() { |
| return mAsyncTask; |
| } |
| |
| @Override |
| public String toString() { |
| return "SubmittedTaskImpl [mIdentifier=" + mIdentifier + "]"; |
| } |
| } |
| |
| private class DelayedExecutor implements Executor { |
| private final Object mNextLock = new Object(); |
| @GuardedBy("mNextLock") private Object mNextIdentifier; |
| @GuardedBy("mNextLock") private AsyncTask<?, ?, ?> mNextTask; |
| |
| @Override |
| public void execute(Runnable command) { |
| synchronized (mNextLock) { |
| Assert.assertNotNull(mNextTask); |
| mSubmittedTasks.add(new SubmittedTaskImpl(mNextIdentifier, |
| command, mNextTask)); |
| mNextIdentifier = null; |
| mNextTask = null; |
| } |
| } |
| |
| public <T> AsyncTask<T, ?, ?> submit(Object identifier, |
| AsyncTask<T, ?, ?> task, T... params) { |
| synchronized (mNextLock) { |
| Assert.assertNull(mNextIdentifier); |
| Assert.assertNull(mNextTask); |
| mNextIdentifier = identifier; |
| Assert.assertNotNull("Already had a valid task.\n" |
| + "Are you calling AsyncTaskExecutor.submit(...) from within the " |
| + "onPreExecute() method of another task being submitted?\n" |
| + "Sorry! Not that's not supported.", task); |
| mNextTask = task; |
| } |
| return task.executeOnExecutor(this, params); |
| } |
| } |
| |
| @Override |
| public <T> AsyncTask<T, ?, ?> submit(Object identifier, AsyncTask<T, ?, ?> task, T... params) { |
| AsyncTaskExecutors.checkCalledFromUiThread(); |
| return mBlockingExecutor.submit(identifier, task, params); |
| } |
| |
| /** |
| * Runs a single task matching the given identifier. |
| * <p> |
| * Removes the matching task from the list of submitted tasks, then runs it. The executor used |
| * to execute this async task will be a same-thread executor. |
| * <p> |
| * Fails if there was not exactly one task matching the given identifier. |
| * <p> |
| * This method blocks until the AsyncTask has completely finished executing. |
| */ |
| public void runTask(Object identifier) throws InterruptedException { |
| List<SubmittedTask> tasks = getSubmittedTasksByIdentifier(identifier, true); |
| Assert.assertEquals("Expected one task " + identifier + ", got " + tasks, 1, tasks.size()); |
| runTask(tasks.get(0)); |
| } |
| |
| /** |
| * Runs all tasks whose identifier matches the given identifier. |
| * <p> |
| * Removes all matching tasks from the list of submitted tasks, and runs them. The executor used |
| * to execute these async tasks will be a same-thread executor. |
| * <p> |
| * Fails if there were no tasks matching the given identifier. |
| * <p> |
| * This method blocks until the AsyncTask objects have completely finished executing. |
| */ |
| public void runAllTasks(Object identifier) throws InterruptedException { |
| List<SubmittedTask> tasks = getSubmittedTasksByIdentifier(identifier, true); |
| Assert.assertTrue("There were no tasks with identifier " + identifier, tasks.size() > 0); |
| for (SubmittedTask task : tasks) { |
| runTask(task); |
| } |
| } |
| |
| /** |
| * Executes a single {@link SubmittedTask}. |
| * <p> |
| * Blocks until the task has completed running. |
| */ |
| private <T> void runTask(final SubmittedTask submittedTask) throws InterruptedException { |
| submittedTask.getRunnable().run(); |
| // Block until the onPostExecute or onCancelled has finished. |
| // Unfortunately we can't be sure when the AsyncTask will have posted its result handling |
| // code to the main ui thread, the best we can do is wait for the Status to be FINISHED. |
| final CountDownLatch latch = new CountDownLatch(1); |
| class AsyncTaskHasFinishedRunnable implements Runnable { |
| @Override |
| public void run() { |
| if (submittedTask.getAsyncTask().getStatus() == AsyncTask.Status.FINISHED) { |
| latch.countDown(); |
| } else { |
| mInstrumentation.waitForIdle(this); |
| } |
| } |
| } |
| mInstrumentation.waitForIdle(new AsyncTaskHasFinishedRunnable()); |
| Assert.assertTrue(latch.await(mTimeoutMs, TimeUnit.MILLISECONDS)); |
| } |
| |
| private List<SubmittedTask> getSubmittedTasksByIdentifier( |
| Object identifier, boolean remove) { |
| Assert.assertNotNull(identifier); |
| List<SubmittedTask> results = Lists.newArrayList(); |
| synchronized (mLock) { |
| Iterator<SubmittedTask> iter = mSubmittedTasks.iterator(); |
| while (iter.hasNext()) { |
| SubmittedTask task = iter.next(); |
| if (identifier.equals(task.getIdentifier())) { |
| results.add(task); |
| iter.remove(); |
| } |
| } |
| } |
| return results; |
| } |
| |
| /** Get a factory that will return this instance - useful for testing. */ |
| public AsyncTaskExecutors.AsyncTaskExecutorFactory getFactory() { |
| return new AsyncTaskExecutors.AsyncTaskExecutorFactory() { |
| @Override |
| public AsyncTaskExecutor createAsyncTaskExeuctor() { |
| return FakeAsyncTaskExecutor.this; |
| } |
| }; |
| } |
| } |