diff options
Diffstat (limited to 'java/com/android/dialer/common')
-rw-r--r-- | java/com/android/dialer/common/Assert.java | 4 | ||||
-rw-r--r-- | java/com/android/dialer/common/concurrent/AsyncTaskExecutors.java | 2 | ||||
-rw-r--r-- | java/com/android/dialer/common/concurrent/DefaultDialerExecutorFactory.java | 255 | ||||
-rw-r--r-- | java/com/android/dialer/common/concurrent/DialerExecutor.java | 100 | ||||
-rw-r--r-- | java/com/android/dialer/common/concurrent/DialerExecutorFactory.java | 63 | ||||
-rw-r--r-- | java/com/android/dialer/common/concurrent/DialerExecutors.java | 134 | ||||
-rw-r--r-- | java/com/android/dialer/common/concurrent/DialerUiTaskFragment.java (renamed from java/com/android/dialer/common/concurrent/DialerAsyncTaskHelper.java) | 142 | ||||
-rw-r--r-- | java/com/android/dialer/common/concurrent/ThreadUtil.java | 38 |
8 files changed, 633 insertions, 105 deletions
diff --git a/java/com/android/dialer/common/Assert.java b/java/com/android/dialer/common/Assert.java index 943e1ddcf..189d209c8 100644 --- a/java/com/android/dialer/common/Assert.java +++ b/java/com/android/dialer/common/Assert.java @@ -30,6 +30,10 @@ public class Assert { Assert.areThreadAssertsEnabled = areThreadAssertsEnabled; } + public static boolean areThreadAssertsEnabled() { + return areThreadAssertsEnabled; + } + /** * Called when a truly exceptional case occurs. * diff --git a/java/com/android/dialer/common/concurrent/AsyncTaskExecutors.java b/java/com/android/dialer/common/concurrent/AsyncTaskExecutors.java index fd358328e..a25e9fbf5 100644 --- a/java/com/android/dialer/common/concurrent/AsyncTaskExecutors.java +++ b/java/com/android/dialer/common/concurrent/AsyncTaskExecutors.java @@ -74,7 +74,7 @@ public final class AsyncTaskExecutors { AsyncTaskExecutor createAsyncTaskExeuctor(); } - private static class SimpleAsyncTaskExecutor implements AsyncTaskExecutor { + static class SimpleAsyncTaskExecutor implements AsyncTaskExecutor { private final Executor mExecutor; diff --git a/java/com/android/dialer/common/concurrent/DefaultDialerExecutorFactory.java b/java/com/android/dialer/common/concurrent/DefaultDialerExecutorFactory.java new file mode 100644 index 000000000..80ed725bc --- /dev/null +++ b/java/com/android/dialer/common/concurrent/DefaultDialerExecutorFactory.java @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2017 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.common.concurrent; + +import android.app.FragmentManager; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.DialerExecutor.Builder; +import com.android.dialer.common.concurrent.DialerExecutor.FailureListener; +import com.android.dialer.common.concurrent.DialerExecutor.SuccessListener; +import com.android.dialer.common.concurrent.DialerExecutor.Worker; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; + +/** The production {@link DialerExecutorFactory}. */ +public class DefaultDialerExecutorFactory implements DialerExecutorFactory { + + @Override + @NonNull + public <InputT, OutputT> DialerExecutor.Builder<InputT, OutputT> createUiTaskBuilder( + @NonNull FragmentManager fragmentManager, + @NonNull String taskId, + @NonNull Worker<InputT, OutputT> worker) { + return new UiTaskBuilder<>( + Assert.isNotNull(fragmentManager), Assert.isNotNull(taskId), Assert.isNotNull(worker)); + } + + @Override + @NonNull + public <InputT, OutputT> DialerExecutor.Builder<InputT, OutputT> createNonUiTaskBuilder( + @NonNull Worker<InputT, OutputT> worker) { + return new NonUiTaskBuilder<>(Assert.isNotNull(worker)); + } + + private abstract static class BaseTaskBuilder<InputT, OutputT> + implements DialerExecutor.Builder<InputT, OutputT> { + + private final Worker<InputT, OutputT> worker; + private SuccessListener<OutputT> successListener = output -> {}; + private FailureListener failureListener = + throwable -> { + throw new RuntimeException(throwable); + }; + @Nullable final ExecutorService serialExecutorService; + @Nullable final ExecutorService parallelExecutorService; + + BaseTaskBuilder( + Worker<InputT, OutputT> worker, + @Nullable ExecutorService serialExecutorService, + @Nullable ExecutorService parallelExecutorService) { + this.worker = worker; + this.serialExecutorService = serialExecutorService; + this.parallelExecutorService = parallelExecutorService; + } + + @NonNull + @Override + public Builder<InputT, OutputT> onSuccess(@NonNull SuccessListener<OutputT> successListener) { + this.successListener = Assert.isNotNull(successListener); + return this; + } + + @NonNull + @Override + public Builder<InputT, OutputT> onFailure(@NonNull FailureListener failureListener) { + this.failureListener = Assert.isNotNull(failureListener); + return this; + } + } + + /** Convenience class for use by {@link DialerExecutorFactory} implementations. */ + public static class UiTaskBuilder<InputT, OutputT> extends BaseTaskBuilder<InputT, OutputT> { + + private final FragmentManager fragmentManager; + private final String id; + + private DialerUiTaskFragment<InputT, OutputT> dialerUiTaskFragment; + + UiTaskBuilder(FragmentManager fragmentManager, String id, Worker<InputT, OutputT> worker) { + this( + fragmentManager, + id, + worker, + null /* serialExecutorService */, + null /* parallelExecutorService */); + } + + public UiTaskBuilder( + FragmentManager fragmentManager, + String id, + Worker<InputT, OutputT> worker, + ExecutorService serialExecutor, + ExecutorService parallelExecutor) { + super(worker, serialExecutor, parallelExecutor); + this.fragmentManager = fragmentManager; + this.id = id; + } + + @NonNull + @Override + public DialerExecutor<InputT> build() { + dialerUiTaskFragment = DialerUiTaskFragment.create( + fragmentManager, + id, + super.worker, + super.successListener, + super.failureListener, + serialExecutorService, + parallelExecutorService); + return new UiDialerExecutor<>(dialerUiTaskFragment); + } + } + + /** Convenience class for use by {@link DialerExecutorFactory} implementations. */ + public static class NonUiTaskBuilder<InputT, OutputT> extends BaseTaskBuilder<InputT, OutputT> { + private static final ExecutorService defaultSerialExecutorService = + Executors.newSingleThreadExecutor( + new ThreadFactory() { + @Override + public Thread newThread(Runnable runnable) { + LogUtil.i("NonUiTaskBuilder.newThread", "creating serial thread"); + Thread thread = new Thread(runnable, "NonUiTaskBuilder"); + thread.setPriority(4); // Corresponds to Process.THREAD_PRIORITY_BACKGROUND + return thread; + } + }); + + private static final ExecutorService defaultParallelExecutorService = + Executors.newFixedThreadPool( + 5, + new ThreadFactory() { + @Override + public Thread newThread(Runnable runnable) { + LogUtil.i("NonUiTaskBuilder.newThread", "creating parallel thread"); + Thread thread = new Thread(runnable, "NonUiTaskBuilder"); + thread.setPriority(4); // Corresponds to Process.THREAD_PRIORITY_BACKGROUND + return thread; + } + }); + + NonUiTaskBuilder(Worker<InputT, OutputT> worker) { + this(worker, defaultSerialExecutorService, defaultParallelExecutorService); + } + + public NonUiTaskBuilder( + Worker<InputT, OutputT> worker, + @NonNull ExecutorService serialExecutor, + @NonNull ExecutorService parallelExecutor) { + super(worker, Assert.isNotNull(serialExecutor), Assert.isNotNull(parallelExecutor)); + } + + @NonNull + @Override + public DialerExecutor<InputT> build() { + return new NonUiDialerExecutor<>( + super.worker, + super.successListener, + super.failureListener, + serialExecutorService, + parallelExecutorService); + } + } + + private static class UiDialerExecutor<InputT, OutputT> implements DialerExecutor<InputT> { + + private final DialerUiTaskFragment<InputT, OutputT> dialerUiTaskFragment; + + UiDialerExecutor(DialerUiTaskFragment<InputT, OutputT> dialerUiTaskFragment) { + this.dialerUiTaskFragment = dialerUiTaskFragment; + } + + @Override + public void executeSerial(@Nullable InputT input) { + dialerUiTaskFragment.executeSerial(input); + } + + @Override + public void executeParallel(@Nullable InputT input) { + dialerUiTaskFragment.executeParallel(input); + } + + @Override + public void executeOnCustomExecutorService( + @NonNull ExecutorService executorService, @Nullable InputT input) { + dialerUiTaskFragment.executeOnCustomExecutor(Assert.isNotNull(executorService), input); + } + } + + private static class NonUiDialerExecutor<InputT, OutputT> implements DialerExecutor<InputT> { + + private final Worker<InputT, OutputT> worker; + private final SuccessListener<OutputT> successListener; + private final FailureListener failureListener; + + private final ExecutorService serialExecutorService; + private final ExecutorService parallelExecutorService; + + NonUiDialerExecutor( + Worker<InputT, OutputT> worker, + SuccessListener<OutputT> successListener, + FailureListener failureListener, + ExecutorService serialExecutorService, + ExecutorService parallelExecutorService) { + this.worker = worker; + this.successListener = successListener; + this.failureListener = failureListener; + this.serialExecutorService = serialExecutorService; + this.parallelExecutorService = parallelExecutorService; + } + + @Override + public void executeSerial(@Nullable InputT input) { + executeOnCustomExecutorService(serialExecutorService, input); + } + + @Override + public void executeParallel(@Nullable InputT input) { + executeOnCustomExecutorService(parallelExecutorService, input); + } + + @Override + public void executeOnCustomExecutorService( + @NonNull ExecutorService executorService, @Nullable InputT input) { + Assert.isNotNull(executorService) + .execute( + () -> { + OutputT output; + try { + output = worker.doInBackground(input); + } catch (Throwable throwable) { + ThreadUtil.postOnUiThread(() -> failureListener.onFailure(throwable)); + return; + } + ThreadUtil.postOnUiThread(() -> successListener.onSuccess(output)); + }); + } + } +} diff --git a/java/com/android/dialer/common/concurrent/DialerExecutor.java b/java/com/android/dialer/common/concurrent/DialerExecutor.java new file mode 100644 index 000000000..b0d1eac66 --- /dev/null +++ b/java/com/android/dialer/common/concurrent/DialerExecutor.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2017 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.common.concurrent; + +import android.support.annotation.MainThread; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.WorkerThread; +import java.util.concurrent.ExecutorService; + +/** + * Provides a consistent interface for doing background work in either UI or non-UI contexts. + * + * <p>See {@link DialerExecutors} for usage examples. + */ +public interface DialerExecutor<InputT> { + + /** Functional interface for doing work in the background. */ + interface Worker<InputT, OutputT> { + @WorkerThread + @Nullable + OutputT doInBackground(@Nullable InputT input); + } + + /** Functional interface for handling the result of background work. */ + interface SuccessListener<OutputT> { + @MainThread + void onSuccess(@Nullable OutputT output); + } + + /** Functional interface for handling an error produced while performing background work. */ + interface FailureListener { + @MainThread + void onFailure(@NonNull Throwable throwable); + } + + /** Builder for {@link DialerExecutor}. */ + interface Builder<InputT, OutputT> { + + /** + * Optional. Default is no-op. + * + * @param successListener a function executed on the main thread upon task success. There are no + * restraints on this as it is executed on the main thread, so lambdas, anonymous, or inner + * classes of your activity or fragment are all fine. + */ + @NonNull + Builder<InputT, OutputT> onSuccess(@NonNull SuccessListener<OutputT> successListener); + + /** + * Optional. If this is not set and your worker throws an exception, the application will crash. + * + * @param failureListener a function executed on the main thread upon task failure. There are no + * restraints on this as it is executed on the main thread, so lambdas, anonymous, or inner + * classes of your activity or fragment are all fine. + */ + @NonNull + Builder<InputT, OutputT> onFailure(@NonNull FailureListener failureListener); + + /** + * Builds the {@link DialerExecutor} which can be used to execute your task (repeatedly with + * differing inputs if desired). + */ + @NonNull + DialerExecutor<InputT> build(); + } + + /** Executes the task such that repeated executions for this executor are serialized. */ + @MainThread + void executeSerial(@Nullable InputT input); + + /** + * Executes the task on a thread pool shared across the application. Multiple calls using this + * method may result in tasks being executed in parallel. + */ + @MainThread + void executeParallel(@Nullable InputT input); + + /** + * Executes the task on a custom executor service. This should rarely be used; instead prefer + * {@link #executeSerial(Object)} or {@link #executeParallel(Object)}. + */ + @MainThread + void executeOnCustomExecutorService( + @NonNull ExecutorService executorService, @Nullable InputT input); +} diff --git a/java/com/android/dialer/common/concurrent/DialerExecutorFactory.java b/java/com/android/dialer/common/concurrent/DialerExecutorFactory.java new file mode 100644 index 000000000..82f8c7c3d --- /dev/null +++ b/java/com/android/dialer/common/concurrent/DialerExecutorFactory.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2017 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.common.concurrent; + +import android.app.FragmentManager; +import android.support.annotation.NonNull; +import com.android.dialer.common.concurrent.DialerExecutor.Worker; + +/** + * Factory interface for creating {@link DialerExecutor} objects. + * + * <p>Factory instances may be used instead of the static methods in {@link DialerExecutors} in + * order to improve testability. + * + * @see DialerExecutors + */ +public interface DialerExecutorFactory { + + /** + * Must be called from onCreate of your activity or fragment. + * + * @param taskId used for the headless fragment ID and task ID + * @param worker a function executed on a worker thread which accepts an {@link InputT} and + * returns an {@link OutputT}. It should ideally not be an inner class of your (meaning it + * should not be a lambda, anonymous, or non-static) but it can be a static nested class. The + * static nested class should not contain any reference to UI, including any activity or + * fragment or activity context, though it may reference some threadsafe system objects such + * as the application context. + */ + @NonNull + <InputT, OutputT> DialerExecutor.Builder<InputT, OutputT> createUiTaskBuilder( + @NonNull FragmentManager fragmentManager, + @NonNull String taskId, + @NonNull Worker<InputT, OutputT> worker); + + /** + * Create a task from a non-UI context. + * + * @param worker a function executed on a worker thread which accepts an {@link InputT} and + * returns an {@link OutputT}. It should ideally not be an inner class of your (meaning it + * should not be a lambda, anonymous, or non-static) but it can be a static nested class. The + * static nested class should not contain any reference to UI, including any activity or + * fragment or activity context, though it may reference some threadsafe system objects such + * as the application context. + */ + @NonNull + <InputT, OutputT> DialerExecutor.Builder<InputT, OutputT> createNonUiTaskBuilder( + @NonNull Worker<InputT, OutputT> worker); +} diff --git a/java/com/android/dialer/common/concurrent/DialerExecutors.java b/java/com/android/dialer/common/concurrent/DialerExecutors.java new file mode 100644 index 000000000..148d8660c --- /dev/null +++ b/java/com/android/dialer/common/concurrent/DialerExecutors.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2017 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.common.concurrent; + +import android.app.FragmentManager; +import android.support.annotation.NonNull; +import com.android.dialer.common.Assert; +import com.android.dialer.common.concurrent.DialerExecutor.Worker; + +/** + * Factory methods for creating {@link DialerExecutor} objects for doing background work. + * + * <p>You may create an executor from a UI component (activity or fragment) or a non-UI component. + * Using this class provides a number of benefits: + * + * <ul> + * <li>Ensures that UI tasks keep running across configuration changes by using a headless + * fragment. + * <li>Forces exceptions to crash the application, unless the user implements their own onFailure + * method. + * <li>Checks for dead UI components which can be encountered if a UI task runs longer than its + * UI. If a dead UI component is encountered, onSuccess/onFailure are not called (because they + * can't be) but a message is logged. + * <li>Helps prevents memory leaks in UI tasks by ensuring that callbacks are nulled out when the + * headless fragment is detached. + * <li>UI and non-UI threads are shared across the application and run at reasonable priorities + * </ul> + * + * <p>Executors accept a single input and output parameter which should be immutable data objects. + * If you don't require an input or output, use Void and null as needed. + * + * <p>You may optionally specify onSuccess and onFailure listeners; the default behavior on success + * is a no-op and the default behavior on failure is to crash the application. + * + * <p>To use an executor from a UI component, you must create it in your onCreate method and then + * use it from anywhere: + * + * <pre><code> + * + * public class MyActivity extends Activity { + * + * private final DialerExecutor<MyInputType> myExecutor; + * + * public void onCreate(Bundle state) { + * super.onCreate(bundle); + * + * // Must be called in onCreate; don't use non-static or anonymous inner classes for worker! + * myExecutor = DialerExecutors.createUiTaskBuilder(fragmentManager, taskId, worker) + * .onSuccess(this::onSuccess) // Lambdas, anonymous, or non-static inner classes all fine + * .onFailure(this::onFailure) // Lambdas, anonymous, or non-static inner classes all fine + * .build(); + * ); + * } + * + * private static class MyWorker implements Worker<MyInputType, MyOutputType> { + * MyOutputType doInBackground(MyInputType input) { ... } + * } + * private void onSuccess(MyOutputType output) { ... } + * private void onFailure(Throwable throwable) { ... } + * + * private void userDidSomething() { myExecutor.executeParallel(input); } + * } + * </code></pre> + * + * <p>Usage for non-UI tasks is the same, except that tasks can be created from anywhere instead of + * in onCreate. Non-UI tasks use low-priority threads separate from the UI task threads so as not to + * compete with more critical UI tasks. + * + * <pre><code> + * + * public class MyManager { + * + * private final DialerExecutor<MyInputType> myExecutor; + * + * public void init() { + * // Don't use non-static or anonymous inner classes for worker! + * myExecutor = DialerExecutors.createNonUiTaskBuilder(worker) + * .onSuccess(this::onSuccess) // Lambdas, anonymous, or non-static inner classes all fine + * .onFailure(this::onFailure) // Lambdas, anonymous, or non-static inner classes all fine + * .build(); + * ); + * } + * + * private static class MyWorker implements Worker<MyInputType, MyOutputType> { + * MyOutputType doInBackground(MyInputType input) { ... } + * } + * private void onSuccess(MyOutputType output) { ... } + * private void onFailure(Throwable throwable) { ... } + * + * private void userDidSomething() { myExecutor.executeParallel(input); } + * } + * </code></pre> + * + * Note that non-UI tasks are intended to be relatively quick; for example reading/writing shared + * preferences or doing simple database work. If you submit long running non-UI tasks you may + * saturate the shared application threads and block other tasks. Also, this class does not create + * any wakelocks, so a long running task could be killed if the device goes to sleep while your task + * is still running. If you have to do long running or periodic work, consider using a job + * scheduler. + */ +public final class DialerExecutors { + + /** @see DialerExecutorFactory#createUiTaskBuilder(FragmentManager, String, Worker) */ + @NonNull + public static <InputT, OutputT> DialerExecutor.Builder<InputT, OutputT> createUiTaskBuilder( + @NonNull FragmentManager fragmentManager, + @NonNull String taskId, + @NonNull Worker<InputT, OutputT> worker) { + return new DefaultDialerExecutorFactory() + .createUiTaskBuilder( + Assert.isNotNull(fragmentManager), Assert.isNotNull(taskId), Assert.isNotNull(worker)); + } + + /** @see DialerExecutorFactory#createNonUiTaskBuilder(Worker) */ + @NonNull + public static <InputT, OutputT> DialerExecutor.Builder<InputT, OutputT> createNonUiTaskBuilder( + @NonNull Worker<InputT, OutputT> worker) { + return new DefaultDialerExecutorFactory().createNonUiTaskBuilder(Assert.isNotNull(worker)); + } +} diff --git a/java/com/android/dialer/common/concurrent/DialerAsyncTaskHelper.java b/java/com/android/dialer/common/concurrent/DialerUiTaskFragment.java index 65b408094..627336895 100644 --- a/java/com/android/dialer/common/concurrent/DialerAsyncTaskHelper.java +++ b/java/com/android/dialer/common/concurrent/DialerUiTaskFragment.java @@ -21,89 +21,33 @@ import android.app.FragmentManager; import android.os.AsyncTask; import android.os.Bundle; import android.support.annotation.MainThread; -import android.support.annotation.WorkerThread; +import android.support.annotation.Nullable; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.AsyncTaskExecutors.SimpleAsyncTaskExecutor; +import com.android.dialer.common.concurrent.DialerExecutor.FailureListener; +import com.android.dialer.common.concurrent.DialerExecutor.SuccessListener; +import com.android.dialer.common.concurrent.DialerExecutor.Worker; +import java.util.concurrent.ExecutorService; /** - * Helps use async task properly. - * - * <p>This provides a number of benefits over using AsyncTask directly: - * - * <ul> - * <li>Ensures that tasks keep running across configuration changes by using a headless fragment. - * <li>Propagates exceptions to users, who must implement both onSuccess and onFailure methods. - * <li>Checks for dead UI components which can be encountered if a task runs longer than its UI. - * If a dead UI component is encountered, onSuccess/onFailure are not called (because they - * can't be) but a message is logged. - * <li>Helps prevents memory leaks by ensuring that callbacks are nulled out when the headless - * fragment is detached. - * </ul> - * - * <p>In your activity or fragment: - * - * <pre><code> - * - * public class MyActivity extends Activity { - * - * private final DialerAsyncTaskHelper<MyInputType, MyOutputType> myTaskHelper; - * - * public void onCreate(Bundle state) { - * super.onCreate(bundle); - * - * // Must be called in onCreate - * myTaskHelper = DialerAsyncTaskHelper.create( - * fragmentManager, - * taskId, - * new MyWorker(), # Don't use non-static or anonymous inner classes! - * this::onSuccess, # Lambdas, anonymous, or non-static inner classes all fine - * this::onFailure # Lambdas, anonymous, or non-static inner classes all fine - * ); - * } - * - * private static class MyWorker implements Worker<MyInputType, MyOutputType> { - * MyOutputType doInBackground(MyInputType input) { ... } - * } - * private void onSuccess(MyOutputType output) { ... } - * private void onFailure(Throwable throwable) { ... } - * - * private void userDidSomething() { myTaskHelper.execute(executor, input); } - * } - * </code></pre> + * Do not use this class directly. Instead use {@link DialerExecutors}. * * @param <InputT> the type of the object sent to the task upon execution * @param <OutputT> the type of the result of the background computation */ -public final class DialerAsyncTaskHelper<InputT, OutputT> extends Fragment { +public final class DialerUiTaskFragment<InputT, OutputT> extends Fragment { private String taskId; private Worker<InputT, OutputT> worker; private SuccessListener<OutputT> successListener; private FailureListener failureListener; - private final AsyncTaskExecutor serialExecutor = AsyncTaskExecutors.createAsyncTaskExecutor(); - private final AsyncTaskExecutor parallelExecutor = AsyncTaskExecutors.createThreadPoolExecutor(); - - /** Functional interface for doing work in the background. */ - public interface Worker<InputT, OutputT> { - @WorkerThread - OutputT doInBackground(InputT input); - } - - /** Functional interface for handling the result of background work. */ - public interface SuccessListener<OutputT> { - @MainThread - void onSuccess(OutputT output); - } - - /** Functional interface for handling an error produced while performing background work. */ - public interface FailureListener { - @MainThread - void onFailure(Throwable throwable); - } + private AsyncTaskExecutor serialExecutor = AsyncTaskExecutors.createAsyncTaskExecutor(); + private AsyncTaskExecutor parallelExecutor = AsyncTaskExecutors.createThreadPoolExecutor(); /** - * Creates a new {@link DialerAsyncTaskHelper} or gets an existing one in the event that a + * Creates a new {@link DialerUiTaskFragment} or gets an existing one in the event that a * configuration change occurred while the previous activity's task was still running. Must be * called from onCreate of your activity or fragment. * @@ -123,30 +67,38 @@ public final class DialerAsyncTaskHelper<InputT, OutputT> extends Fragment { * fragment are all fine. * @param <InputT> the type of the object sent to the task upon execution * @param <OutputT> the type of the result of the background computation - * @return a {@link DialerAsyncTaskHelper} which may be used to call the "execute*" methods + * @return a {@link DialerUiTaskFragment} which may be used to call the "execute*" methods */ @MainThread - public static <InputT, OutputT> DialerAsyncTaskHelper<InputT, OutputT> create( + static <InputT, OutputT> DialerUiTaskFragment<InputT, OutputT> create( FragmentManager fragmentManager, String taskId, Worker<InputT, OutputT> worker, SuccessListener<OutputT> successListener, - FailureListener failureListener) { + FailureListener failureListener, + @Nullable ExecutorService serialExecutorService, + @Nullable ExecutorService parallelExecutorService) { Assert.isMainThread(); - DialerAsyncTaskHelper<InputT, OutputT> helperFragment = - (DialerAsyncTaskHelper<InputT, OutputT>) fragmentManager.findFragmentByTag(taskId); + DialerUiTaskFragment<InputT, OutputT> fragment = + (DialerUiTaskFragment<InputT, OutputT>) fragmentManager.findFragmentByTag(taskId); - if (helperFragment == null) { - LogUtil.i("DialerAsyncTaskHelper.create", "creating new helper fragment"); - helperFragment = new DialerAsyncTaskHelper<>(); - fragmentManager.beginTransaction().add(helperFragment, taskId).commit(); + if (fragment == null) { + LogUtil.i("DialerUiTaskFragment.create", "creating new DialerUiTaskFragment"); + fragment = new DialerUiTaskFragment<>(); + fragmentManager.beginTransaction().add(fragment, taskId).commit(); + } + fragment.taskId = taskId; + fragment.worker = worker; + fragment.successListener = successListener; + fragment.failureListener = failureListener; + if (serialExecutorService != null) { + fragment.serialExecutor = new SimpleAsyncTaskExecutor(serialExecutorService); } - helperFragment.taskId = taskId; - helperFragment.worker = worker; - helperFragment.successListener = successListener; - helperFragment.failureListener = failureListener; - return helperFragment; + if (parallelExecutorService != null) { + fragment.parallelExecutor = new SimpleAsyncTaskExecutor(parallelExecutorService); + } + return fragment; } @Override @@ -158,40 +110,22 @@ public final class DialerAsyncTaskHelper<InputT, OutputT> extends Fragment { @Override public void onDetach() { super.onDetach(); - LogUtil.enterBlock("DialerAsyncTaskHelper.onDetach"); + LogUtil.enterBlock("DialerUiTaskFragment.onDetach"); taskId = null; successListener = null; failureListener = null; } - /** - * Executes the task on a single thread global to the process. Multiple calls using this method - * will result in tasks being executed serially. - */ - @MainThread - public void executeSerial(InputT input) { - Assert.isMainThread(); + void executeSerial(InputT input) { serialExecutor.submit(taskId, new InternalTask(), input); } - /** - * Executes the task on a thread pool shared across the application. Multiple calls using this - * method may result in tasks being executed in parallel. - */ - @MainThread - public void executeParallel(InputT input) { - Assert.isMainThread(); + void executeParallel(InputT input) { parallelExecutor.submit(taskId, new InternalTask(), input); } - /** - * Executes the task on a custom executor. This should rarely be used; instead prefer {@link - * #executeSerial(Object)} or {@link #executeParallel(Object)}. - */ - @MainThread - public void executeOnCustomExecutor(AsyncTaskExecutor executor, InputT input) { - Assert.isMainThread(); - executor.submit(taskId, new InternalTask(), input); + void executeOnCustomExecutor(ExecutorService executor, InputT input) { + new SimpleAsyncTaskExecutor(executor).submit(taskId, new InternalTask(), input); } private final class InternalTask extends AsyncTask<InputT, Void, InternalTaskResult<OutputT>> { diff --git a/java/com/android/dialer/common/concurrent/ThreadUtil.java b/java/com/android/dialer/common/concurrent/ThreadUtil.java new file mode 100644 index 000000000..21cf4634e --- /dev/null +++ b/java/com/android/dialer/common/concurrent/ThreadUtil.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2017 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.common.concurrent; + +import android.os.Handler; +import android.os.Looper; + +/** Application-wide utility methods for working with threads. */ +public class ThreadUtil { + private static volatile Handler mainThreadHandler; + + /** Posts a runnable to the UI thread. */ + public static void postOnUiThread(Runnable runnable) { + getUiThreadHandler().post(runnable); + } + + /** Gets a handler which uses the main looper. */ + public static Handler getUiThreadHandler() { + if (mainThreadHandler == null) { + mainThreadHandler = new Handler(Looper.getMainLooper()); + } + return mainThreadHandler; + } +} |