From 94b10b530c0fc297e2974e57e094c500d3ee6003 Mon Sep 17 00:00:00 2001 From: Chiao Cheng Date: Fri, 17 Aug 2012 16:59:12 -0700 Subject: Initial move of dialer features from contacts app. Bug: 6993891 Change-Id: I758ce359ca7e87a1d184303822979318be171921 --- .../android/dialer/util/FakeAsyncTaskExecutor.java | 233 +++++++++++++++++++++ 1 file changed, 233 insertions(+) create mode 100644 tests/src/com/android/dialer/util/FakeAsyncTaskExecutor.java (limited to 'tests/src/com/android/dialer/util/FakeAsyncTaskExecutor.java') diff --git a/tests/src/com/android/dialer/util/FakeAsyncTaskExecutor.java b/tests/src/com/android/dialer/util/FakeAsyncTaskExecutor.java new file mode 100644 index 000000000..064587e4b --- /dev/null +++ b/tests/src/com/android/dialer/util/FakeAsyncTaskExecutor.java @@ -0,0 +1,233 @@ +/* + * 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.android.contacts.util.AsyncTaskExecutor; +import com.android.contacts.util.AsyncTaskExecutors; +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. + *

+ * 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). + *

+ * 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. + *

+ * 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 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 AsyncTask submit(Object identifier, + AsyncTask 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 AsyncTask submit(Object identifier, AsyncTask task, T... params) { + AsyncTaskExecutors.checkCalledFromUiThread(); + return mBlockingExecutor.submit(identifier, task, params); + } + + /** + * Runs a single task matching the given identifier. + *

+ * 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. + *

+ * Fails if there was not exactly one task matching the given identifier. + *

+ * This method blocks until the AsyncTask has completely finished executing. + */ + public void runTask(Object identifier) throws InterruptedException { + List 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. + *

+ * 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. + *

+ * Fails if there were no tasks matching the given identifier. + *

+ * This method blocks until the AsyncTask objects have completely finished executing. + */ + public void runAllTasks(Object identifier) throws InterruptedException { + List 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}. + *

+ * Blocks until the task has completed running. + */ + private 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 getSubmittedTasksByIdentifier( + Object identifier, boolean remove) { + Assert.assertNotNull(identifier); + List results = Lists.newArrayList(); + synchronized (mLock) { + Iterator 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; + } + }; + } +} -- cgit v1.2.3