summaryrefslogtreecommitdiff
path: root/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java
diff options
context:
space:
mode:
authorzachh <zachh@google.com>2017-12-05 17:42:58 -0800
committerCopybara-Service <copybara-piper@google.com>2017-12-07 18:26:26 -0800
commit5194036b423d455a517d06b38fd616a8bbfc4896 (patch)
treed9437bc5c8be48e7122c88b8c3e36edc94443b9d /java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java
parent6f7d6f1bc3f90d16f5b90c8424a5783f07bef1ec (diff)
Switched CallLogDataSource interface to be Future based.
Bug: 34672501 Test: existing PiperOrigin-RevId: 178038086 Change-Id: I1230992ad04bb4415f5a29bd15802d23dff88012
Diffstat (limited to 'java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java')
-rw-r--r--java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java266
1 files changed, 119 insertions, 147 deletions
diff --git a/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java b/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java
index d9924b23f..de8905db8 100644
--- a/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java
+++ b/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java
@@ -16,204 +16,176 @@
package com.android.dialer.calllog;
-import android.annotation.TargetApi;
import android.content.Context;
-import android.content.OperationApplicationException;
import android.content.SharedPreferences;
-import android.os.Build;
-import android.os.RemoteException;
-import android.support.annotation.WorkerThread;
import com.android.dialer.calllog.database.CallLogDatabaseComponent;
import com.android.dialer.calllog.datasources.CallLogDataSource;
import com.android.dialer.calllog.datasources.CallLogMutations;
import com.android.dialer.calllog.datasources.DataSources;
-import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
-import com.android.dialer.common.concurrent.Annotations.UiSerial;
+import com.android.dialer.common.concurrent.Annotations.NonUiParallel;
+import com.android.dialer.common.concurrent.DialerFutureSerializer;
+import com.android.dialer.common.concurrent.DialerFutures;
import com.android.dialer.inject.ApplicationContext;
import com.android.dialer.storage.Unencrypted;
-import com.google.common.util.concurrent.ListenableScheduledFuture;
-import com.google.common.util.concurrent.ListeningScheduledExecutorService;
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
import javax.inject.Inject;
+import javax.inject.Singleton;
/** Brings the annotated call log up to date, if necessary. */
+@Singleton
public class RefreshAnnotatedCallLogWorker {
- /*
- * This is a reasonable time that it might take between related call log writes, that also
- * shouldn't slow down single-writes too much. For example, when populating the database using
- * the simulator, using this value results in ~6 refresh cycles (on a release build) to write 120
- * call log entries.
- */
- private static final long WAIT_MILLIS = 100L;
-
private final Context appContext;
private final DataSources dataSources;
private final SharedPreferences sharedPreferences;
- private final ListeningScheduledExecutorService listeningScheduledExecutorService;
- private ListenableScheduledFuture<Void> scheduledFuture;
+ private final ListeningExecutorService parallelUiListeningExecutorService;
+ // Used to ensure that only one refresh flow runs at a time. (Note that
+ // RefreshAnnotatedCallLogWorker is a @Singleton.)
+ private final DialerFutureSerializer dialerFutureSerializer = new DialerFutureSerializer();
@Inject
RefreshAnnotatedCallLogWorker(
@ApplicationContext Context appContext,
DataSources dataSources,
@Unencrypted SharedPreferences sharedPreferences,
- @UiSerial ScheduledExecutorService serialUiExecutorService) {
+ @NonUiParallel ExecutorService parallelUiExecutorService) {
this.appContext = appContext;
this.dataSources = dataSources;
this.sharedPreferences = sharedPreferences;
- this.listeningScheduledExecutorService =
- MoreExecutors.listeningDecorator(serialUiExecutorService);
+
+ // TODO(zachh): Create and use bindings for ListeningExecutorServices.
+ this.parallelUiListeningExecutorService =
+ MoreExecutors.listeningDecorator(parallelUiExecutorService);
}
/** Checks if the annotated call log is dirty and refreshes it if necessary. */
- public ListenableScheduledFuture<Void> refreshWithDirtyCheck() {
+ public ListenableFuture<Void> refreshWithDirtyCheck() {
return refresh(true);
}
/** Refreshes the annotated call log, bypassing dirty checks. */
- public ListenableScheduledFuture<Void> refreshWithoutDirtyCheck() {
+ public ListenableFuture<Void> refreshWithoutDirtyCheck() {
return refresh(false);
}
- private ListenableScheduledFuture<Void> refresh(boolean checkDirty) {
- if (scheduledFuture != null) {
- LogUtil.i("RefreshAnnotatedCallLogWorker.refresh", "cancelling waiting task");
- scheduledFuture.cancel(false /* mayInterrupt */);
- }
- scheduledFuture =
- listeningScheduledExecutorService.schedule(
- () -> doInBackground(checkDirty), WAIT_MILLIS, TimeUnit.MILLISECONDS);
- return scheduledFuture;
+ private ListenableFuture<Void> refresh(boolean checkDirty) {
+ LogUtil.i("RefreshAnnotatedCallLogWorker.refresh", "submitting serialized refresh request");
+ // Note: directExecutor is safe to use here and throughout because all methods are async.
+ return dialerFutureSerializer.submitAsync(
+ () -> checkDirtyAndRebuildIfNecessary(appContext, checkDirty),
+ MoreExecutors.directExecutor());
}
- @WorkerThread
- private Void doInBackground(boolean checkDirty)
- throws RemoteException, OperationApplicationException {
- LogUtil.enterBlock("RefreshAnnotatedCallLogWorker.doInBackground");
-
- long startTime = System.currentTimeMillis();
- checkDirtyAndRebuildIfNecessary(appContext, checkDirty);
- LogUtil.i(
- "RefreshAnnotatedCallLogWorker.doInBackground",
- "took %dms",
- System.currentTimeMillis() - startTime);
- return null;
- }
-
- @WorkerThread
- private void checkDirtyAndRebuildIfNecessary(Context appContext, boolean checkDirty)
- throws RemoteException, OperationApplicationException {
- Assert.isWorkerThread();
-
- long startTime = System.currentTimeMillis();
-
- // Default to true. If the pref doesn't exist, the annotated call log hasn't been created and
- // we just skip isDirty checks and force a rebuild.
- boolean forceRebuildPrefValue =
- sharedPreferences.getBoolean(CallLogFramework.PREF_FORCE_REBUILD, true);
- if (forceRebuildPrefValue) {
- LogUtil.i(
- "RefreshAnnotatedCallLogWorker.checkDirtyAndRebuildIfNecessary",
- "annotated call log has been marked dirty or does not exist");
- }
-
- boolean isDirty = !checkDirty || forceRebuildPrefValue || isDirty(appContext);
-
- LogUtil.i(
- "RefreshAnnotatedCallLogWorker.checkDirtyAndRebuildIfNecessary",
- "isDirty took: %dms",
- System.currentTimeMillis() - startTime);
- if (isDirty) {
- startTime = System.currentTimeMillis();
- rebuild(appContext);
- LogUtil.i(
- "RefreshAnnotatedCallLogWorker.checkDirtyAndRebuildIfNecessary",
- "rebuild took: %dms",
- System.currentTimeMillis() - startTime);
- }
+ private ListenableFuture<Void> checkDirtyAndRebuildIfNecessary(
+ Context appContext, boolean checkDirty) {
+ ListenableFuture<Boolean> forceRebuildFuture =
+ parallelUiListeningExecutorService.submit(
+ () -> {
+ LogUtil.i(
+ "RefreshAnnotatedCallLogWorker.checkDirtyAndRebuildIfNecessary",
+ "starting refresh flow");
+ if (!checkDirty) {
+ return true;
+ }
+ // Default to true. If the pref doesn't exist, the annotated call log hasn't been
+ // created and we just skip isDirty checks and force a rebuild.
+ boolean forceRebuildPrefValue =
+ sharedPreferences.getBoolean(CallLogFramework.PREF_FORCE_REBUILD, true);
+ if (forceRebuildPrefValue) {
+ LogUtil.i(
+ "RefreshAnnotatedCallLogWorker.checkDirtyAndRebuildIfNecessary",
+ "annotated call log has been marked dirty or does not exist");
+ }
+ return forceRebuildPrefValue;
+ });
+
+ // After checking the "force rebuild" shared pref, conditionally call isDirty.
+ ListenableFuture<Boolean> isDirtyFuture =
+ Futures.transformAsync(
+ forceRebuildFuture,
+ forceRebuild ->
+ Preconditions.checkNotNull(forceRebuild)
+ ? Futures.immediateFuture(true)
+ : isDirty(appContext),
+ MoreExecutors.directExecutor());
+
+ // After determining isDirty, conditionally call rebuild.
+ return Futures.transformAsync(
+ isDirtyFuture,
+ isDirty ->
+ Preconditions.checkNotNull(isDirty)
+ ? rebuild(appContext)
+ : Futures.immediateFuture(null),
+ MoreExecutors.directExecutor());
}
- @WorkerThread
- private boolean isDirty(Context appContext) {
- Assert.isWorkerThread();
-
+ private ListenableFuture<Boolean> isDirty(Context appContext) {
+ List<ListenableFuture<Boolean>> isDirtyFutures = new ArrayList<>();
for (CallLogDataSource dataSource : dataSources.getDataSourcesIncludingSystemCallLog()) {
- String dataSourceName = getName(dataSource);
- long startTime = System.currentTimeMillis();
- LogUtil.i("RefreshAnnotatedCallLogWorker.isDirty", "running isDirty for %s", dataSourceName);
- boolean isDirty = dataSource.isDirty(appContext);
- LogUtil.i(
- "RefreshAnnotatedCallLogWorker.isDirty",
- "%s.isDirty returned %b in %dms",
- dataSourceName,
- isDirty,
- System.currentTimeMillis() - startTime);
- if (isDirty) {
- return true;
- }
+ isDirtyFutures.add(dataSource.isDirty(appContext));
}
- return false;
+ // Simultaneously invokes isDirty on all data sources, returning as soon as one returns true.
+ return DialerFutures.firstMatching(isDirtyFutures, Preconditions::checkNotNull, false);
}
- @TargetApi(Build.VERSION_CODES.M) // Uses try-with-resources
- @WorkerThread
- private void rebuild(Context appContext) throws RemoteException, OperationApplicationException {
- Assert.isWorkerThread();
-
+ private ListenableFuture<Void> rebuild(Context appContext) {
CallLogMutations mutations = new CallLogMutations();
- // System call log data source must go first!
+ // Start by filling the data sources--the system call log data source must go first!
CallLogDataSource systemCallLogDataSource = dataSources.getSystemCallLogDataSource();
- String dataSourceName = getName(systemCallLogDataSource);
- LogUtil.i("RefreshAnnotatedCallLogWorker.rebuild", "filling %s", dataSourceName);
- long startTime = System.currentTimeMillis();
- systemCallLogDataSource.fill(appContext, mutations);
- LogUtil.i(
- "RefreshAnnotatedCallLogWorker.rebuild",
- "%s.fill took: %dms",
- dataSourceName,
- System.currentTimeMillis() - startTime);
+ ListenableFuture<Void> fillFuture = systemCallLogDataSource.fill(appContext, mutations);
+ // After the system call log data source is filled, call fill sequentially on each remaining
+ // data source. This must be done sequentially because mutations are not threadsafe and are
+ // passed from source to source.
for (CallLogDataSource dataSource : dataSources.getDataSourcesExcludingSystemCallLog()) {
- dataSourceName = getName(dataSource);
- LogUtil.i("RefreshAnnotatedCallLogWorker.rebuild", "filling %s", dataSourceName);
- startTime = System.currentTimeMillis();
- dataSource.fill(appContext, mutations);
- LogUtil.i(
- "CallLogFramework.rebuild",
- "%s.fill took: %dms",
- dataSourceName,
- System.currentTimeMillis() - startTime);
+ fillFuture =
+ Futures.transformAsync(
+ fillFuture,
+ unused -> dataSource.fill(appContext, mutations),
+ MoreExecutors.directExecutor());
}
- LogUtil.i("RefreshAnnotatedCallLogWorker.rebuild", "applying mutations to database");
- startTime = System.currentTimeMillis();
- CallLogDatabaseComponent.get(appContext)
- .mutationApplier()
- .applyToDatabase(mutations, appContext);
- LogUtil.i(
- "RefreshAnnotatedCallLogWorker.rebuild",
- "applyToDatabase took: %dms",
- System.currentTimeMillis() - startTime);
-
- for (CallLogDataSource dataSource : dataSources.getDataSourcesIncludingSystemCallLog()) {
- dataSourceName = getName(dataSource);
- LogUtil.i("RefreshAnnotatedCallLogWorker.rebuild", "onSuccessfulFill'ing %s", dataSourceName);
- startTime = System.currentTimeMillis();
- dataSource.onSuccessfulFill(appContext);
- LogUtil.i(
- "CallLogFramework.rebuild",
- "%s.onSuccessfulFill took: %dms",
- dataSourceName,
- System.currentTimeMillis() - startTime);
- }
- sharedPreferences.edit().putBoolean(CallLogFramework.PREF_FORCE_REBUILD, false).apply();
- }
- private static String getName(CallLogDataSource dataSource) {
- return dataSource.getClass().getSimpleName();
+ // After all data sources are filled, apply mutations (at this point "fillFuture" is the result
+ // of filling the last data source).
+ ListenableFuture<Void> applyMutationsFuture =
+ Futures.transformAsync(
+ fillFuture,
+ unused ->
+ CallLogDatabaseComponent.get(appContext)
+ .mutationApplier()
+ .applyToDatabase(mutations, appContext),
+ MoreExecutors.directExecutor());
+
+ // After mutations applied, call onSuccessfulFill for each data source (in parallel).
+ ListenableFuture<List<Void>> onSuccessfulFillFuture =
+ Futures.transformAsync(
+ applyMutationsFuture,
+ unused -> {
+ List<ListenableFuture<Void>> onSuccessfulFillFutures = new ArrayList<>();
+ for (CallLogDataSource dataSource :
+ dataSources.getDataSourcesIncludingSystemCallLog()) {
+ onSuccessfulFillFutures.add(dataSource.onSuccessfulFill(appContext));
+ }
+ return Futures.allAsList(onSuccessfulFillFutures);
+ },
+ MoreExecutors.directExecutor());
+
+ // After onSuccessfulFill is called for every data source, write the shared pref.
+ return Futures.transform(
+ onSuccessfulFillFuture,
+ unused -> {
+ sharedPreferences.edit().putBoolean(CallLogFramework.PREF_FORCE_REBUILD, false).apply();
+ return null;
+ },
+ parallelUiListeningExecutorService);
}
}