diff options
author | Eric Erfanian <erfanian@google.com> | 2017-08-31 06:57:16 -0700 |
---|---|---|
committer | Eric Erfanian <erfanian@google.com> | 2017-08-31 16:13:53 +0000 |
commit | 2ca4318cc1ee57dda907ba2069bd61d162b1baef (patch) | |
tree | e282668a9587cf6c1ec7b604dea860400c75c6c7 /java/com/android/dialer/common/concurrent | |
parent | 68038172793ee0e2ab3e2e56ddfbeb82879d1f58 (diff) |
Update Dialer source to latest internal Google revision.
Previously, Android's Dialer app was developed in an internal Google
source control system and only exported to public during AOSP drops.
The Dialer team is now switching to a public development model similar
to the telephony team.
This CL represents all internal Google changes that were committed to
Dialer between the public O release and today's tip of tree on internal
master. This CL squashes those changes into a single commit.
In subsequent changes, changes will be exported on a per-commit basis.
Test: make, flash install, run
Merged-In: I45270eaa8ce732d71a1bd84b08c7fa0e99af3160
Change-Id: I529aaeb88535b9533c0ae4ef4e6c1222d4e0f1c8
PiperOrigin-RevId: 167068436
Diffstat (limited to 'java/com/android/dialer/common/concurrent')
8 files changed, 251 insertions, 101 deletions
diff --git a/java/com/android/dialer/common/concurrent/DefaultDialerExecutorFactory.java b/java/com/android/dialer/common/concurrent/DefaultDialerExecutorFactory.java index 4b6d81afd..a87bbceb4 100644 --- a/java/com/android/dialer/common/concurrent/DefaultDialerExecutorFactory.java +++ b/java/com/android/dialer/common/concurrent/DefaultDialerExecutorFactory.java @@ -17,6 +17,7 @@ package com.android.dialer.common.concurrent; import android.app.FragmentManager; +import android.os.AsyncTask; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.android.dialer.common.Assert; @@ -25,9 +26,13 @@ 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.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; import javax.inject.Inject; /** The production {@link DialerExecutorFactory}. */ @@ -62,16 +67,16 @@ public class DefaultDialerExecutorFactory implements DialerExecutorFactory { throwable -> { throw new RuntimeException(throwable); }; - @Nullable final ExecutorService serialExecutorService; - @Nullable final ExecutorService parallelExecutorService; + @Nullable final ScheduledExecutorService serialExecutorService; + @Nullable final Executor parallelExecutor; BaseTaskBuilder( Worker<InputT, OutputT> worker, - @Nullable ExecutorService serialExecutorService, - @Nullable ExecutorService parallelExecutorService) { + @Nullable ScheduledExecutorService serialExecutorService, + @Nullable Executor parallelExecutor) { this.worker = worker; this.serialExecutorService = serialExecutorService; - this.parallelExecutorService = parallelExecutorService; + this.parallelExecutor = parallelExecutor; } @NonNull @@ -91,6 +96,19 @@ public class DefaultDialerExecutorFactory implements DialerExecutorFactory { /** Convenience class for use by {@link DialerExecutorFactory} implementations. */ public static class UiTaskBuilder<InputT, OutputT> extends BaseTaskBuilder<InputT, OutputT> { + private static final ScheduledExecutorService defaultSerialExecutorService = + Executors.newSingleThreadScheduledExecutor( + new ThreadFactory() { + @Override + public Thread newThread(Runnable runnable) { + LogUtil.i("UiTaskBuilder.newThread", "creating serial thread"); + Thread thread = new Thread(runnable, "UiTaskBuilder-Serial"); + thread.setPriority(5); // Corresponds to Process.THREAD_PRIORITY_DEFAULT + return thread; + } + }); + + private static final Executor defaultParallelExecutorService = AsyncTask.THREAD_POOL_EXECUTOR; private final FragmentManager fragmentManager; private final String id; @@ -102,16 +120,16 @@ public class DefaultDialerExecutorFactory implements DialerExecutorFactory { fragmentManager, id, worker, - null /* serialExecutorService */, - null /* parallelExecutorService */); + defaultSerialExecutorService, + defaultParallelExecutorService); } public UiTaskBuilder( FragmentManager fragmentManager, String id, Worker<InputT, OutputT> worker, - ExecutorService serialExecutor, - ExecutorService parallelExecutor) { + ScheduledExecutorService serialExecutor, + Executor parallelExecutor) { super(worker, serialExecutor, parallelExecutor); this.fragmentManager = fragmentManager; this.id = id; @@ -128,46 +146,36 @@ public class DefaultDialerExecutorFactory implements DialerExecutorFactory { super.successListener, super.failureListener, serialExecutorService, - parallelExecutorService); + parallelExecutor); 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( + private static final ScheduledExecutorService defaultSerialExecutorService = + Executors.newSingleThreadScheduledExecutor( new ThreadFactory() { @Override public Thread newThread(Runnable runnable) { LogUtil.i("NonUiTaskBuilder.newThread", "creating serial thread"); - Thread thread = new Thread(runnable, "NonUiTaskBuilder"); + Thread thread = new Thread(runnable, "NonUiTaskBuilder-Serial"); 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; - } - }); + private static final Executor defaultParallelExecutor = + DialerExecutors.getLowPriorityThreadPool(); NonUiTaskBuilder(Worker<InputT, OutputT> worker) { - this(worker, defaultSerialExecutorService, defaultParallelExecutorService); + this(worker, defaultSerialExecutorService, defaultParallelExecutor); } public NonUiTaskBuilder( Worker<InputT, OutputT> worker, - @NonNull ExecutorService serialExecutor, - @NonNull ExecutorService parallelExecutor) { + @NonNull ScheduledExecutorService serialExecutor, + @NonNull Executor parallelExecutor) { super(worker, Assert.isNotNull(serialExecutor), Assert.isNotNull(parallelExecutor)); } @@ -179,7 +187,7 @@ public class DefaultDialerExecutorFactory implements DialerExecutorFactory { super.successListener, super.failureListener, serialExecutorService, - parallelExecutorService); + parallelExecutor); } } @@ -197,6 +205,11 @@ public class DefaultDialerExecutorFactory implements DialerExecutorFactory { } @Override + public void executeSerialWithWait(@Nullable InputT input, long waitMillis) { + dialerUiTaskFragment.executeSerialWithWait(input, waitMillis); + } + + @Override public void executeParallel(@Nullable InputT input) { dialerUiTaskFragment.executeParallel(input); } @@ -214,47 +227,59 @@ public class DefaultDialerExecutorFactory implements DialerExecutorFactory { private final SuccessListener<OutputT> successListener; private final FailureListener failureListener; - private final ExecutorService serialExecutorService; - private final ExecutorService parallelExecutorService; + private final ScheduledExecutorService serialExecutorService; + private final Executor parallelExecutor; + + private ScheduledFuture<?> scheduledFuture; NonUiDialerExecutor( Worker<InputT, OutputT> worker, SuccessListener<OutputT> successListener, FailureListener failureListener, - ExecutorService serialExecutorService, - ExecutorService parallelExecutorService) { + ScheduledExecutorService serialExecutorService, + Executor parallelExecutor) { this.worker = worker; this.successListener = successListener; this.failureListener = failureListener; this.serialExecutorService = serialExecutorService; - this.parallelExecutorService = parallelExecutorService; + this.parallelExecutor = parallelExecutor; } @Override public void executeSerial(@Nullable InputT input) { - executeOnCustomExecutorService(serialExecutorService, input); + serialExecutorService.execute(() -> run(input)); + } + + @Override + public void executeSerialWithWait(@Nullable InputT input, long waitMillis) { + if (scheduledFuture != null) { + LogUtil.i("NonUiDialerExecutor.executeSerialWithWait", "cancelling waiting task"); + scheduledFuture.cancel(false /* mayInterrupt */); + } + scheduledFuture = + serialExecutorService.schedule(() -> run(input), waitMillis, TimeUnit.MILLISECONDS); } @Override public void executeParallel(@Nullable InputT input) { - executeOnCustomExecutorService(parallelExecutorService, input); + parallelExecutor.execute(() -> run(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)); - }); + Assert.isNotNull(executorService).execute(() -> run(input)); + } + + private void run(@Nullable InputT input) { + 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 index 0237e3a05..0414581d4 100644 --- a/java/com/android/dialer/common/concurrent/DialerExecutor.java +++ b/java/com/android/dialer/common/concurrent/DialerExecutor.java @@ -84,6 +84,16 @@ public interface DialerExecutor<InputT> { void executeSerial(@Nullable InputT input); /** + * Executes the task after waiting {@code waitMillis}. If called while the previous invocation is + * still waiting to be started, the original invocation is cancelled. + * + * <p>This is useful for tasks which might get scheduled many times in very quick succession, but + * it is only the last one that actually needs to be executed. + */ + @MainThread + void executeSerialWithWait(@Nullable InputT input, long waitMillis); + + /** * Executes the task on a thread pool shared across the application. Multiple calls using this * method may result in tasks being executed in parallel. */ diff --git a/java/com/android/dialer/common/concurrent/DialerExecutorComponent.java b/java/com/android/dialer/common/concurrent/DialerExecutorComponent.java new file mode 100644 index 000000000..25033251f --- /dev/null +++ b/java/com/android/dialer/common/concurrent/DialerExecutorComponent.java @@ -0,0 +1,39 @@ +/* + * 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.content.Context; +import com.android.dialer.inject.HasRootComponent; +import dagger.Subcomponent; + +/** Dagger component which provides a {@link DialerExecutorFactory}. */ +@Subcomponent +public abstract class DialerExecutorComponent { + + public abstract DialerExecutorFactory dialerExecutorFactory(); + + public static DialerExecutorComponent get(Context context) { + return ((DialerExecutorComponent.HasComponent) + ((HasRootComponent) context.getApplicationContext()).component()) + .dialerExecutorComponent(); + } + + /** Used to refer to the root application component. */ + public interface HasComponent { + DialerExecutorComponent dialerExecutorComponent(); + } +} diff --git a/java/com/android/dialer/common/concurrent/DialerExecutorModule.java b/java/com/android/dialer/common/concurrent/DialerExecutorModule.java new file mode 100644 index 000000000..281f88c15 --- /dev/null +++ b/java/com/android/dialer/common/concurrent/DialerExecutorModule.java @@ -0,0 +1,28 @@ +/* + * 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 dagger.Binds; +import dagger.Module; + +/** Module which binds the production {@link DialerExecutorFactory}. */ +@Module +public abstract class DialerExecutorModule { + + @Binds + abstract DialerExecutorFactory bindDialerExecutorFactory( + DefaultDialerExecutorFactory defaultDialerExecutorFactory); +} diff --git a/java/com/android/dialer/common/concurrent/DialerExecutors.java b/java/com/android/dialer/common/concurrent/DialerExecutors.java index 148d8660c..81b3c5cb3 100644 --- a/java/com/android/dialer/common/concurrent/DialerExecutors.java +++ b/java/com/android/dialer/common/concurrent/DialerExecutors.java @@ -19,7 +19,11 @@ 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.LogUtil; import com.android.dialer.common.concurrent.DialerExecutor.Worker; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; /** * Factory methods for creating {@link DialerExecutor} objects for doing background work. @@ -131,4 +135,27 @@ public final class DialerExecutors { @NonNull Worker<InputT, OutputT> worker) { return new DefaultDialerExecutorFactory().createNonUiTaskBuilder(Assert.isNotNull(worker)); } + + private static final Executor lowPriorityThreadPool = + Executors.newFixedThreadPool( + 5, + new ThreadFactory() { + @Override + public Thread newThread(Runnable runnable) { + LogUtil.i("DialerExecutors.newThread", "creating low priority thread"); + Thread thread = new Thread(runnable, "DialerExecutors-LowPriority"); + thread.setPriority(4); // Corresponds to Process.THREAD_PRIORITY_BACKGROUND + return thread; + } + }); + + /** + * An application-wide thread pool used for low priority (non-UI) tasks. + * + * <p>This exists to prevent each individual dialer component from having to create its own + * threads/pools, which would result in the application having more threads than really necessary. + */ + public static Executor getLowPriorityThreadPool() { + return lowPriorityThreadPool; + } } diff --git a/java/com/android/dialer/common/concurrent/DialerUiTaskFragment.java b/java/com/android/dialer/common/concurrent/DialerUiTaskFragment.java index 627336895..f0b753758 100644 --- a/java/com/android/dialer/common/concurrent/DialerUiTaskFragment.java +++ b/java/com/android/dialer/common/concurrent/DialerUiTaskFragment.java @@ -18,17 +18,21 @@ package com.android.dialer.common.concurrent; import android.app.Fragment; import android.app.FragmentManager; -import android.os.AsyncTask; import android.os.Bundle; import android.support.annotation.MainThread; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.WorkerThread; 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.Executor; import java.util.concurrent.ExecutorService; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; /** * Do not use this class directly. Instead use {@link DialerExecutors}. @@ -38,13 +42,13 @@ import java.util.concurrent.ExecutorService; */ public final class DialerUiTaskFragment<InputT, OutputT> extends Fragment { - private String taskId; private Worker<InputT, OutputT> worker; private SuccessListener<OutputT> successListener; private FailureListener failureListener; - private AsyncTaskExecutor serialExecutor = AsyncTaskExecutors.createAsyncTaskExecutor(); - private AsyncTaskExecutor parallelExecutor = AsyncTaskExecutors.createThreadPoolExecutor(); + private ScheduledExecutorService serialExecutor; + private Executor parallelExecutor; + private ScheduledFuture<?> scheduledFuture; /** * Creates a new {@link DialerUiTaskFragment} or gets an existing one in the event that a @@ -76,28 +80,23 @@ public final class DialerUiTaskFragment<InputT, OutputT> extends Fragment { Worker<InputT, OutputT> worker, SuccessListener<OutputT> successListener, FailureListener failureListener, - @Nullable ExecutorService serialExecutorService, - @Nullable ExecutorService parallelExecutorService) { + @NonNull ScheduledExecutorService serialExecutorService, + @NonNull Executor parallelExecutor) { Assert.isMainThread(); DialerUiTaskFragment<InputT, OutputT> fragment = (DialerUiTaskFragment<InputT, OutputT>) fragmentManager.findFragmentByTag(taskId); if (fragment == null) { - LogUtil.i("DialerUiTaskFragment.create", "creating new DialerUiTaskFragment"); + LogUtil.i("DialerUiTaskFragment.create", "creating new DialerUiTaskFragment for " + taskId); 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); - } - if (parallelExecutorService != null) { - fragment.parallelExecutor = new SimpleAsyncTaskExecutor(parallelExecutorService); - } + fragment.serialExecutor = Assert.isNotNull(serialExecutorService); + fragment.parallelExecutor = Assert.isNotNull(parallelExecutor); return fragment; } @@ -111,60 +110,75 @@ public final class DialerUiTaskFragment<InputT, OutputT> extends Fragment { public void onDetach() { super.onDetach(); LogUtil.enterBlock("DialerUiTaskFragment.onDetach"); - taskId = null; successListener = null; failureListener = null; + if (scheduledFuture != null) { + scheduledFuture.cancel(false /* mayInterrupt */); + scheduledFuture = null; + } } void executeSerial(InputT input) { - serialExecutor.submit(taskId, new InternalTask(), input); + serialExecutor.execute(() -> runTask(input)); + } + + void executeSerialWithWait(InputT input, long waitMillis) { + if (scheduledFuture != null) { + LogUtil.i("DialerUiTaskFragment.executeSerialWithWait", "cancelling waiting task"); + scheduledFuture.cancel(false /* mayInterrupt */); + } + scheduledFuture = + serialExecutor.schedule(() -> runTask(input), waitMillis, TimeUnit.MILLISECONDS); } void executeParallel(InputT input) { - parallelExecutor.submit(taskId, new InternalTask(), input); + parallelExecutor.execute(() -> runTask(input)); } void executeOnCustomExecutor(ExecutorService executor, InputT input) { - new SimpleAsyncTaskExecutor(executor).submit(taskId, new InternalTask(), input); + executor.execute(() -> runTask(input)); } - private final class InternalTask extends AsyncTask<InputT, Void, InternalTaskResult<OutputT>> { - - @SafeVarargs - @Override - protected final InternalTaskResult<OutputT> doInBackground(InputT... params) { - try { - return new InternalTaskResult<>(null, worker.doInBackground(params[0])); - } catch (Throwable throwable) { - LogUtil.e("InternalTask.doInBackground", "task failed", throwable); - return new InternalTaskResult<>(throwable, null); + @WorkerThread + private void runTask(@Nullable InputT input) { + try { + OutputT output = worker.doInBackground(input); + if (successListener == null) { + LogUtil.i("DialerUiTaskFragment.runTask", "task succeeded but UI is dead"); + } else { + ThreadUtil.postOnUiThread( + () -> { + // Even though there is a null check above, it is possible for the activity/fragment + // to be finished between the time the runnable is posted and the time it executes. Do + // an additional check here. + if (successListener == null) { + LogUtil.i( + "DialerUiTaskFragment.runTask", + "task succeeded but UI died after success runnable posted"); + } else { + successListener.onSuccess(output); + } + }); } - } - - @Override - protected void onPostExecute(InternalTaskResult<OutputT> result) { - if (result.throwable != null) { - if (failureListener == null) { - LogUtil.i("InternalTask.onPostExecute", "task failed but UI is dead"); - } else { - failureListener.onFailure(result.throwable); - } - } else if (successListener == null) { - LogUtil.i("InternalTask.onPostExecute", "task succeeded but UI is dead"); + } catch (Throwable throwable) { + LogUtil.e("DialerUiTaskFragment.runTask", "task failed", throwable); + if (failureListener == null) { + LogUtil.i("DialerUiTaskFragment.runTask", "task failed but UI is dead"); } else { - successListener.onSuccess(result.result); + ThreadUtil.postOnUiThread( + () -> { + // Even though there is a null check above, it is possible for the activity/fragment + // to be finished between the time the runnable is posted and the time it executes. Do + // an additional check here. + if (failureListener == null) { + LogUtil.i( + "DialerUiTaskFragment.runTask", + "task failed but UI died after failure runnable posted"); + } else { + failureListener.onFailure(throwable); + } + }); } } } - - private static class InternalTaskResult<OutputT> { - - private final Throwable throwable; - private final OutputT result; - - InternalTaskResult(Throwable throwable, OutputT result) { - this.throwable = throwable; - this.result = result; - } - } } diff --git a/java/com/android/dialer/common/concurrent/FallibleAsyncTask.java b/java/com/android/dialer/common/concurrent/FallibleAsyncTask.java index 6d02fe67f..c7a7f36a6 100644 --- a/java/com/android/dialer/common/concurrent/FallibleAsyncTask.java +++ b/java/com/android/dialer/common/concurrent/FallibleAsyncTask.java @@ -30,7 +30,9 @@ import com.google.auto.value.AutoValue; * @param <ParamsT> the type of the parameters sent to the task upon execution * @param <ProgressT> the type of the progress units published during the background computation * @param <ResultT> the type of the result of the background computation + * @deprecated Please use {@link DialerExecutors}. */ +@Deprecated public abstract class FallibleAsyncTask<ParamsT, ProgressT, ResultT> extends AsyncTask<ParamsT, ProgressT, FallibleTaskResult<ResultT>> { diff --git a/java/com/android/dialer/common/concurrent/ThreadUtil.java b/java/com/android/dialer/common/concurrent/ThreadUtil.java index 21cf4634e..39b6cceff 100644 --- a/java/com/android/dialer/common/concurrent/ThreadUtil.java +++ b/java/com/android/dialer/common/concurrent/ThreadUtil.java @@ -28,6 +28,11 @@ public class ThreadUtil { getUiThreadHandler().post(runnable); } + /** Posts a runnable to the UI thread, to be run after the specified amount of time elapses. */ + public static void postDelayedOnUiThread(Runnable runnable, long delayMillis) { + getUiThreadHandler().postDelayed(runnable, delayMillis); + } + /** Gets a handler which uses the main looper. */ public static Handler getUiThreadHandler() { if (mainThreadHandler == null) { |