diff options
author | zachh <zachh@google.com> | 2017-12-05 17:42:58 -0800 |
---|---|---|
committer | Copybara-Service <copybara-piper@google.com> | 2017-12-07 18:26:26 -0800 |
commit | 5194036b423d455a517d06b38fd616a8bbfc4896 (patch) | |
tree | d9437bc5c8be48e7122c88b8c3e36edc94443b9d /java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java | |
parent | 6f7d6f1bc3f90d16f5b90c8424a5783f07bef1ec (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.java | 266 |
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); } } |