diff options
Diffstat (limited to 'java')
10 files changed, 246 insertions, 111 deletions
diff --git a/java/com/android/dialer/calllog/AnnotatedCallLogMigrator.java b/java/com/android/dialer/calllog/AnnotatedCallLogMigrator.java index f8c6fcef1..e2e112cd0 100644 --- a/java/com/android/dialer/calllog/AnnotatedCallLogMigrator.java +++ b/java/com/android/dialer/calllog/AnnotatedCallLogMigrator.java @@ -18,6 +18,7 @@ package com.android.dialer.calllog; import android.content.Context; import android.content.SharedPreferences; +import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor; import com.android.dialer.configprovider.ConfigProviderBindings; import com.android.dialer.inject.ApplicationContext; @@ -39,7 +40,7 @@ public final class AnnotatedCallLogMigrator { private final Context appContext; private final SharedPreferences sharedPreferences; private final RefreshAnnotatedCallLogWorker refreshAnnotatedCallLogWorker; - private final ListeningExecutorService backgorundExecutor; + private final ListeningExecutorService backgroundExecutor; @Inject AnnotatedCallLogMigrator( @@ -49,7 +50,7 @@ public final class AnnotatedCallLogMigrator { RefreshAnnotatedCallLogWorker refreshAnnotatedCallLogWorker) { this.appContext = appContext; this.sharedPreferences = sharedPreferences; - this.backgorundExecutor = backgroundExecutor; + this.backgroundExecutor = backgroundExecutor; this.refreshAnnotatedCallLogWorker = refreshAnnotatedCallLogWorker; } @@ -58,13 +59,13 @@ public final class AnnotatedCallLogMigrator { * the latency the first time call log is shown. */ public ListenableFuture<Void> migrate() { - return Futures.transformAsync( shouldMigrate(), (shouldMigrate) -> { if (!shouldMigrate) { return Futures.immediateFuture(null); } + LogUtil.i("AnnotatedCallLogMigrator.migrate", "migrating annotated call log"); return Futures.transform( refreshAnnotatedCallLogWorker.refreshWithoutDirtyCheck(), (unused) -> { @@ -77,7 +78,7 @@ public final class AnnotatedCallLogMigrator { } private ListenableFuture<Boolean> shouldMigrate() { - return backgorundExecutor.submit( + return backgroundExecutor.submit( () -> { if (!(ConfigProviderBindings.get(appContext) .getBoolean("is_nui_shortcut_enabled", false))) { diff --git a/java/com/android/dialer/calllog/CallLogState.java b/java/com/android/dialer/calllog/CallLogState.java new file mode 100644 index 000000000..bdb30a769 --- /dev/null +++ b/java/com/android/dialer/calllog/CallLogState.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2018 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.calllog; + +import android.content.SharedPreferences; +import android.support.annotation.AnyThread; +import android.support.annotation.VisibleForTesting; +import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor; +import com.android.dialer.storage.Unencrypted; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import javax.annotation.concurrent.ThreadSafe; +import javax.inject.Inject; + +/** Provides information about the state of the annotated call log. */ +@ThreadSafe +public final class CallLogState { + + private static final String ANNOTATED_CALL_LOG_BUILT_PREF = "annotated_call_log_built"; + + private final SharedPreferences sharedPreferences; + private final ListeningExecutorService backgroundExecutor; + + @VisibleForTesting + @Inject + public CallLogState( + @Unencrypted SharedPreferences sharedPreferences, + @BackgroundExecutor ListeningExecutorService backgroundExecutor) { + this.sharedPreferences = sharedPreferences; + this.backgroundExecutor = backgroundExecutor; + } + + /** + * Mark the call log as having been built. This is written to disk the first time the annotated + * call log has been built and shouldn't ever be reset unless the user clears data. + */ + @AnyThread + public void markBuilt() { + sharedPreferences.edit().putBoolean(ANNOTATED_CALL_LOG_BUILT_PREF, true).apply(); + } + + /** + * Returns true if the annotated call log has been built at least once. + * + * <p>It may not yet have been built if the user was just upgraded to the new call log, or they + * just cleared data. + */ + @AnyThread + public ListenableFuture<Boolean> isBuilt() { + return backgroundExecutor.submit( + () -> sharedPreferences.getBoolean(ANNOTATED_CALL_LOG_BUILT_PREF, false)); + } +} diff --git a/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java b/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java index 3765831ae..e05b77268 100644 --- a/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java +++ b/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java @@ -37,6 +37,7 @@ 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.ArrayList; import java.util.List; import javax.inject.Inject; @@ -51,6 +52,7 @@ public class RefreshAnnotatedCallLogWorker { private final SharedPreferences sharedPreferences; private final MutationApplier mutationApplier; private final FutureTimer futureTimer; + private final CallLogState callLogState; private final ListeningExecutorService backgroundExecutorService; private final ListeningExecutorService lightweightExecutorService; // Used to ensure that only one refresh flow runs at a time. (Note that @@ -64,6 +66,7 @@ public class RefreshAnnotatedCallLogWorker { @Unencrypted SharedPreferences sharedPreferences, MutationApplier mutationApplier, FutureTimer futureTimer, + CallLogState callLogState, @BackgroundExecutor ListeningExecutorService backgroundExecutorService, @LightweightExecutor ListeningExecutorService lightweightExecutorService) { this.appContext = appContext; @@ -71,17 +74,18 @@ public class RefreshAnnotatedCallLogWorker { this.sharedPreferences = sharedPreferences; this.mutationApplier = mutationApplier; this.futureTimer = futureTimer; + this.callLogState = callLogState; this.backgroundExecutorService = backgroundExecutorService; this.lightweightExecutorService = lightweightExecutorService; } /** Checks if the annotated call log is dirty and refreshes it if necessary. */ - public ListenableFuture<Void> refreshWithDirtyCheck() { + ListenableFuture<Void> refreshWithDirtyCheck() { return refresh(true); } /** Refreshes the annotated call log, bypassing dirty checks. */ - public ListenableFuture<Void> refreshWithoutDirtyCheck() { + ListenableFuture<Void> refreshWithoutDirtyCheck() { return refresh(false); } @@ -131,7 +135,11 @@ public class RefreshAnnotatedCallLogWorker { "RefreshAnnotatedCallLogWorker.checkDirtyAndRebuildIfNecessary", "isDirty: %b", Preconditions.checkNotNull(isDirty)); - return isDirty ? rebuild() : Futures.immediateFuture(null); + if (isDirty) { + return Futures.transformAsync( + callLogState.isBuilt(), this::rebuild, MoreExecutors.directExecutor()); + } + return Futures.immediateFuture(null); }, lightweightExecutorService); } @@ -152,14 +160,13 @@ public class RefreshAnnotatedCallLogWorker { return isDirtyFuture; } - private ListenableFuture<Void> rebuild() { + private ListenableFuture<Void> rebuild(boolean isBuilt) { CallLogMutations mutations = new CallLogMutations(); // Start by filling the data sources--the system call log data source must go first! CallLogDataSource systemCallLogDataSource = dataSources.getSystemCallLogDataSource(); ListenableFuture<Void> fillFuture = systemCallLogDataSource.fill(appContext, mutations); - String systemEventName = - String.format(Metrics.FILL_TEMPLATE, systemCallLogDataSource.getClass().getSimpleName()); + String systemEventName = eventNameForFill(systemCallLogDataSource, isBuilt); futureTimer.applyTiming(fillFuture, systemEventName); // After the system call log data source is filled, call fill sequentially on each remaining @@ -171,14 +178,14 @@ public class RefreshAnnotatedCallLogWorker { fillFuture, unused -> { ListenableFuture<Void> dataSourceFuture = dataSource.fill(appContext, mutations); - String eventName = - String.format(Metrics.FILL_TEMPLATE, dataSource.getClass().getSimpleName()); + String eventName = eventNameForFill(dataSource, isBuilt); futureTimer.applyTiming(dataSourceFuture, eventName); return dataSourceFuture; }, lightweightExecutorService); } - futureTimer.applyTiming(fillFuture, Metrics.FILL_EVENT_NAME); + + futureTimer.applyTiming(fillFuture, eventNameForOverallFill(isBuilt)); // After all data sources are filled, apply mutations (at this point "fillFuture" is the result // of filling the last data source). @@ -188,7 +195,7 @@ public class RefreshAnnotatedCallLogWorker { unused -> { ListenableFuture<Void> mutationApplierFuture = mutationApplier.applyToDatabase(mutations, appContext); - futureTimer.applyTiming(mutationApplierFuture, Metrics.APPLY_MUTATIONS_EVENT_NAME); + futureTimer.applyTiming(mutationApplierFuture, eventNameForApplyMutations(isBuilt)); return mutationApplierFuture; }, lightweightExecutorService); @@ -203,13 +210,11 @@ public class RefreshAnnotatedCallLogWorker { dataSources.getDataSourcesIncludingSystemCallLog()) { ListenableFuture<Void> dataSourceFuture = dataSource.onSuccessfulFill(appContext); onSuccessfulFillFutures.add(dataSourceFuture); - String eventName = - String.format( - Metrics.ON_SUCCESSFUL_FILL_TEMPLATE, dataSource.getClass().getSimpleName()); + String eventName = eventNameForOnSuccessfulFill(dataSource, isBuilt); futureTimer.applyTiming(dataSourceFuture, eventName); } ListenableFuture<List<Void>> allFutures = Futures.allAsList(onSuccessfulFillFutures); - futureTimer.applyTiming(allFutures, Metrics.ON_SUCCESSFUL_FILL_EVENT_NAME); + futureTimer.applyTiming(allFutures, eventNameForOverallOnSuccessfulFill(isBuilt)); return allFutures; }, lightweightExecutorService); @@ -219,8 +224,40 @@ public class RefreshAnnotatedCallLogWorker { onSuccessfulFillFuture, unused -> { sharedPreferences.edit().putBoolean(SharedPrefKeys.FORCE_REBUILD, false).apply(); + callLogState.markBuilt(); return null; }, backgroundExecutorService); } + + private static String eventNameForFill(CallLogDataSource dataSource, boolean isBuilt) { + return String.format( + !isBuilt ? Metrics.INITIAL_FILL_TEMPLATE : Metrics.FILL_TEMPLATE, + dataSource.getClass().getSimpleName()); + } + + private static String eventNameForOverallFill(boolean isBuilt) { + return !isBuilt ? Metrics.INITIAL_FILL_EVENT_NAME : Metrics.FILL_EVENT_NAME; + } + + private static String eventNameForOnSuccessfulFill( + CallLogDataSource dataSource, boolean isBuilt) { + return String.format( + !isBuilt + ? Metrics.INITIAL_ON_SUCCESSFUL_FILL_TEMPLATE + : Metrics.ON_SUCCESSFUL_FILL_TEMPLATE, + dataSource.getClass().getSimpleName()); + } + + private static String eventNameForOverallOnSuccessfulFill(boolean isBuilt) { + return !isBuilt + ? Metrics.INITIAL_ON_SUCCESSFUL_FILL_EVENT_NAME + : Metrics.ON_SUCCESSFUL_FILL_EVENT_NAME; + } + + private static String eventNameForApplyMutations(boolean isBuilt) { + return !isBuilt + ? Metrics.INITIAL_APPLY_MUTATIONS_EVENT_NAME + : Metrics.APPLY_MUTATIONS_EVENT_NAME; + } } diff --git a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java index 40788f42a..ff8c931ad 100644 --- a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java +++ b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java @@ -39,6 +39,7 @@ import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor; import com.android.dialer.common.concurrent.Annotations.LightweightExecutor; import com.android.dialer.phonelookup.PhoneLookup; import com.android.dialer.phonelookup.PhoneLookupInfo; +import com.android.dialer.phonelookup.composite.CompositePhoneLookup; import com.android.dialer.phonelookup.database.contract.PhoneLookupHistoryContract; import com.android.dialer.phonelookup.database.contract.PhoneLookupHistoryContract.PhoneLookupHistory; import com.google.common.collect.ImmutableMap; @@ -63,7 +64,7 @@ import javax.inject.Inject; */ public final class PhoneLookupDataSource implements CallLogDataSource { - private final PhoneLookup<PhoneLookupInfo> phoneLookup; + private final CompositePhoneLookup compositePhoneLookup; private final ListeningExecutorService backgroundExecutorService; private final ListeningExecutorService lightweightExecutorService; @@ -86,10 +87,10 @@ public final class PhoneLookupDataSource implements CallLogDataSource { @Inject PhoneLookupDataSource( - PhoneLookup<PhoneLookupInfo> phoneLookup, + CompositePhoneLookup compositePhoneLookup, @BackgroundExecutor ListeningExecutorService backgroundExecutorService, @LightweightExecutor ListeningExecutorService lightweightExecutorService) { - this.phoneLookup = phoneLookup; + this.compositePhoneLookup = compositePhoneLookup; this.backgroundExecutorService = backgroundExecutorService; this.lightweightExecutorService = lightweightExecutorService; } @@ -99,7 +100,8 @@ public final class PhoneLookupDataSource implements CallLogDataSource { ListenableFuture<ImmutableSet<DialerPhoneNumber>> phoneNumbers = backgroundExecutorService.submit( () -> queryDistinctDialerPhoneNumbersFromAnnotatedCallLog(appContext)); - return Futures.transformAsync(phoneNumbers, phoneLookup::isDirty, lightweightExecutorService); + return Futures.transformAsync( + phoneNumbers, compositePhoneLookup::isDirty, lightweightExecutorService); } /** @@ -157,10 +159,13 @@ public final class PhoneLookupDataSource implements CallLogDataSource { queryPhoneLookupHistoryForNumbers(appContext, annotatedCallLogIdsByNumber.keySet()), backgroundExecutorService); - // Use the original info map to generate the updated info map by delegating to phoneLookup. + // Use the original info map to generate the updated info map by delegating to + // compositePhoneLookup. ListenableFuture<ImmutableMap<DialerPhoneNumber, PhoneLookupInfo>> updatedInfoMapFuture = Futures.transformAsync( - originalInfoMapFuture, phoneLookup::getMostRecentInfo, lightweightExecutorService); + originalInfoMapFuture, + compositePhoneLookup::getMostRecentInfo, + lightweightExecutorService); // This is the computation that will use the result of all of the above. Callable<ImmutableMap<Long, PhoneLookupInfo>> computeRowsToUpdate = @@ -241,7 +246,7 @@ public final class PhoneLookupDataSource implements CallLogDataSource { // the AnnotatedCallLog and PhoneLookupHistory have been successfully updated. return Futures.transformAsync( writePhoneLookupHistory, - unused -> phoneLookup.onSuccessfulBulkUpdate(), + unused -> compositePhoneLookup.onSuccessfulBulkUpdate(), lightweightExecutorService); } @@ -286,7 +291,7 @@ public final class PhoneLookupDataSource implements CallLogDataSource { @MainThread @Override public void registerContentObservers(Context appContext) { - phoneLookup.registerContentObservers(appContext); + compositePhoneLookup.registerContentObservers(appContext); } private static ImmutableSet<DialerPhoneNumber> diff --git a/java/com/android/dialer/calllog/ui/RealtimeRowProcessor.java b/java/com/android/dialer/calllog/ui/RealtimeRowProcessor.java index 69c431953..22c3a3c11 100644 --- a/java/com/android/dialer/calllog/ui/RealtimeRowProcessor.java +++ b/java/com/android/dialer/calllog/ui/RealtimeRowProcessor.java @@ -31,8 +31,8 @@ import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor; import com.android.dialer.common.concurrent.Annotations.Ui; import com.android.dialer.common.concurrent.ThreadUtil; import com.android.dialer.inject.ApplicationContext; -import com.android.dialer.phonelookup.PhoneLookup; import com.android.dialer.phonelookup.PhoneLookupInfo; +import com.android.dialer.phonelookup.composite.CompositePhoneLookup; import com.android.dialer.phonelookup.database.contract.PhoneLookupHistoryContract; import com.android.dialer.phonelookup.database.contract.PhoneLookupHistoryContract.PhoneLookupHistory; import com.google.common.collect.ImmutableMap; @@ -68,7 +68,7 @@ public final class RealtimeRowProcessor { @VisibleForTesting static final long BATCH_WAIT_MILLIS = TimeUnit.SECONDS.toMillis(3); private final Context appContext; - private final PhoneLookup<PhoneLookupInfo> phoneLookup; + private final CompositePhoneLookup compositePhoneLookup; private final ListeningExecutorService uiExecutor; private final ListeningExecutorService backgroundExecutor; @@ -83,11 +83,11 @@ public final class RealtimeRowProcessor { @ApplicationContext Context appContext, @Ui ListeningExecutorService uiExecutor, @BackgroundExecutor ListeningExecutorService backgroundExecutor, - PhoneLookup<PhoneLookupInfo> phoneLookup) { + CompositePhoneLookup compositePhoneLookup) { this.appContext = appContext; this.uiExecutor = uiExecutor; this.backgroundExecutor = backgroundExecutor; - this.phoneLookup = phoneLookup; + this.compositePhoneLookup = compositePhoneLookup; } /** @@ -106,7 +106,8 @@ public final class RealtimeRowProcessor { return Futures.immediateFuture(applyPhoneLookupInfoToRow(cachedPhoneLookupInfo, row)); } - ListenableFuture<PhoneLookupInfo> phoneLookupInfoFuture = phoneLookup.lookup(row.number()); + ListenableFuture<PhoneLookupInfo> phoneLookupInfoFuture = + compositePhoneLookup.lookup(row.number()); return Futures.transform( phoneLookupInfoFuture, phoneLookupInfo -> { diff --git a/java/com/android/dialer/metrics/Metrics.java b/java/com/android/dialer/metrics/Metrics.java index 8c18ac942..383b3a3f3 100644 --- a/java/com/android/dialer/metrics/Metrics.java +++ b/java/com/android/dialer/metrics/Metrics.java @@ -34,12 +34,21 @@ public interface Metrics { String NEW_CALL_LOG_JANK_EVENT_NAME = "NewCallLog.Jank"; // Events related to refreshing the annotated call log. + String INITIAL_FILL_EVENT_NAME = "RefreshAnnotatedCallLog.Initial.Fill"; + String INITIAL_ON_SUCCESSFUL_FILL_EVENT_NAME = "RefreshAnnotatedCallLog.Initial.OnSuccessfulFill"; + String INITIAL_APPLY_MUTATIONS_EVENT_NAME = "RefreshAnnotatedCallLog.Initial.ApplyMutations"; + String IS_DIRTY_EVENT_NAME = "RefreshAnnotatedCallLog.IsDirty"; String FILL_EVENT_NAME = "RefreshAnnotatedCallLog.Fill"; String ON_SUCCESSFUL_FILL_EVENT_NAME = "RefreshAnnotatedCallLog.OnSuccessfulFill"; String APPLY_MUTATIONS_EVENT_NAME = "RefreshAnnotatedCallLog.ApplyMutations"; // These templates are prefixed with a CallLogDataSource or PhoneLookup simple class name. + String INITIAL_FILL_TEMPLATE = "%s.Initial.Fill"; + String INITIAL_GET_MOST_RECENT_INFO_TEMPLATE = "%s.Initial.GetMostRecentInfo"; + String INITIAL_ON_SUCCESSFUL_FILL_TEMPLATE = "%s.Initial.OnSuccessfulFill"; + String INITIAL_ON_SUCCESSFUL_BULK_UPDATE_TEMPLATE = "%s.Initial.OnSuccessfulBulkUpdate"; + String IS_DIRTY_TEMPLATE = "%s.IsDirty"; String FILL_TEMPLATE = "%s.Fill"; String GET_MOST_RECENT_INFO_TEMPLATE = "%s.GetMostRecentInfo"; diff --git a/java/com/android/dialer/phonelookup/PhoneLookupComponent.java b/java/com/android/dialer/phonelookup/PhoneLookupComponent.java index f59886bcd..832587c81 100644 --- a/java/com/android/dialer/phonelookup/PhoneLookupComponent.java +++ b/java/com/android/dialer/phonelookup/PhoneLookupComponent.java @@ -17,13 +17,14 @@ package com.android.dialer.phonelookup; import android.content.Context; import com.android.dialer.inject.HasRootComponent; +import com.android.dialer.phonelookup.composite.CompositePhoneLookup; import dagger.Subcomponent; /** Dagger component for the PhoneLookup package. */ @Subcomponent public abstract class PhoneLookupComponent { - public abstract PhoneLookup<PhoneLookupInfo> phoneLookup(); + public abstract CompositePhoneLookup compositePhoneLookup(); public static PhoneLookupComponent get(Context context) { return ((HasComponent) ((HasRootComponent) context.getApplicationContext()).component()) diff --git a/java/com/android/dialer/phonelookup/PhoneLookupModule.java b/java/com/android/dialer/phonelookup/PhoneLookupModule.java index d4cd60a04..3e21e7c77 100644 --- a/java/com/android/dialer/phonelookup/PhoneLookupModule.java +++ b/java/com/android/dialer/phonelookup/PhoneLookupModule.java @@ -18,7 +18,6 @@ package com.android.dialer.phonelookup; import com.android.dialer.phonelookup.blockednumber.DialerBlockedNumberPhoneLookup; import com.android.dialer.phonelookup.blockednumber.SystemBlockedNumberPhoneLookup; -import com.android.dialer.phonelookup.composite.CompositePhoneLookup; import com.android.dialer.phonelookup.cp2.Cp2LocalPhoneLookup; import com.android.dialer.phonelookup.cp2.Cp2RemotePhoneLookup; import com.android.dialer.phonelookup.spam.SpamPhoneLookup; @@ -45,10 +44,4 @@ public abstract class PhoneLookupModule { systemBlockedNumberPhoneLookup, spamPhoneLookup); } - - @Provides - static PhoneLookup<PhoneLookupInfo> providePhoneLookup( - CompositePhoneLookup compositePhoneLookup) { - return compositePhoneLookup; - } } diff --git a/java/com/android/dialer/phonelookup/composite/CompositePhoneLookup.java b/java/com/android/dialer/phonelookup/composite/CompositePhoneLookup.java index 7be7732e3..0d84a2eda 100644 --- a/java/com/android/dialer/phonelookup/composite/CompositePhoneLookup.java +++ b/java/com/android/dialer/phonelookup/composite/CompositePhoneLookup.java @@ -18,7 +18,9 @@ package com.android.dialer.phonelookup.composite; import android.content.Context; import android.support.annotation.MainThread; +import android.support.annotation.VisibleForTesting; import com.android.dialer.DialerPhoneNumber; +import com.android.dialer.calllog.CallLogState; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.Annotations.LightweightExecutor; import com.android.dialer.common.concurrent.DialerFutures; @@ -36,6 +38,7 @@ import com.google.common.collect.Maps; 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.ArrayList; import java.util.List; import java.util.Map; @@ -44,20 +47,26 @@ import javax.inject.Inject; /** * {@link PhoneLookup} which delegates to a configured set of {@link PhoneLookup PhoneLookups}, * iterating, prioritizing, and coalescing data as necessary. + * + * <p>TODO(zachh): Consider renaming and moving this file since it does not implement PhoneLookup. */ -public final class CompositePhoneLookup implements PhoneLookup<PhoneLookupInfo> { +public final class CompositePhoneLookup { private final ImmutableList<PhoneLookup> phoneLookups; private final FutureTimer futureTimer; + private final CallLogState callLogState; private final ListeningExecutorService lightweightExecutorService; + @VisibleForTesting @Inject - CompositePhoneLookup( + public CompositePhoneLookup( ImmutableList<PhoneLookup> phoneLookups, FutureTimer futureTimer, + CallLogState callLogState, @LightweightExecutor ListeningExecutorService lightweightExecutorService) { this.phoneLookups = phoneLookups; this.futureTimer = futureTimer; + this.callLogState = callLogState; this.lightweightExecutorService = lightweightExecutorService; } @@ -68,7 +77,6 @@ public final class CompositePhoneLookup implements PhoneLookup<PhoneLookupInfo> * the dependent lookups does not complete, the returned future will also not complete. */ @SuppressWarnings({"unchecked", "rawtype"}) - @Override public ListenableFuture<PhoneLookupInfo> lookup(DialerPhoneNumber dialerPhoneNumber) { // TODO(zachh): Add short-circuiting logic so that this call is not blocked on low-priority // lookups finishing when a higher-priority one has already finished. @@ -98,7 +106,10 @@ public final class CompositePhoneLookup implements PhoneLookup<PhoneLookupInfo> return combinedFuture; } - @Override + /** + * Delegates to sub-lookups' {@link PhoneLookup#isDirty(ImmutableSet)} completing when the first + * sub-lookup which returns true completes. + */ public ListenableFuture<Boolean> isDirty(ImmutableSet<DialerPhoneNumber> phoneNumbers) { List<ListenableFuture<Boolean>> futures = new ArrayList<>(); for (PhoneLookup<?> phoneLookup : phoneLookups) { @@ -125,46 +136,50 @@ public final class CompositePhoneLookup implements PhoneLookup<PhoneLookupInfo> * the dependent lookups does not complete, the returned future will also not complete. */ @SuppressWarnings("unchecked") - @Override public ListenableFuture<ImmutableMap<DialerPhoneNumber, PhoneLookupInfo>> getMostRecentInfo( ImmutableMap<DialerPhoneNumber, PhoneLookupInfo> existingInfoMap) { - List<ListenableFuture<ImmutableMap<DialerPhoneNumber, ?>>> futures = new ArrayList<>(); - for (PhoneLookup phoneLookup : phoneLookups) { - futures.add(buildSubmapAndGetMostRecentInfo(existingInfoMap, phoneLookup)); - } - ListenableFuture<ImmutableMap<DialerPhoneNumber, PhoneLookupInfo>> combinedFuture = - Futures.transform( - Futures.allAsList(futures), - (allMaps) -> { - ImmutableMap.Builder<DialerPhoneNumber, PhoneLookupInfo> combinedMap = - ImmutableMap.builder(); - for (DialerPhoneNumber dialerPhoneNumber : existingInfoMap.keySet()) { - PhoneLookupInfo.Builder combinedInfo = PhoneLookupInfo.newBuilder(); - for (int i = 0; i < allMaps.size(); i++) { - ImmutableMap<DialerPhoneNumber, ?> map = allMaps.get(i); - Object subInfo = map.get(dialerPhoneNumber); - if (subInfo == null) { - throw new IllegalStateException( - "A sublookup didn't return an info for number: " - + LogUtil.sanitizePhoneNumber(dialerPhoneNumber.getNormalizedNumber())); - } - phoneLookups.get(i).setSubMessage(combinedInfo, subInfo); - } - combinedMap.put(dialerPhoneNumber, combinedInfo.build()); - } - return combinedMap.build(); - }, - lightweightExecutorService); - String eventName = - String.format( - Metrics.GET_MOST_RECENT_INFO_TEMPLATE, CompositePhoneLookup.class.getSimpleName()); - futureTimer.applyTiming(combinedFuture, eventName); - return combinedFuture; + return Futures.transformAsync( + callLogState.isBuilt(), + isBuilt -> { + List<ListenableFuture<ImmutableMap<DialerPhoneNumber, ?>>> futures = new ArrayList<>(); + for (PhoneLookup phoneLookup : phoneLookups) { + futures.add(buildSubmapAndGetMostRecentInfo(existingInfoMap, phoneLookup, isBuilt)); + } + ListenableFuture<ImmutableMap<DialerPhoneNumber, PhoneLookupInfo>> combinedFuture = + Futures.transform( + Futures.allAsList(futures), + (allMaps) -> { + ImmutableMap.Builder<DialerPhoneNumber, PhoneLookupInfo> combinedMap = + ImmutableMap.builder(); + for (DialerPhoneNumber dialerPhoneNumber : existingInfoMap.keySet()) { + PhoneLookupInfo.Builder combinedInfo = PhoneLookupInfo.newBuilder(); + for (int i = 0; i < allMaps.size(); i++) { + ImmutableMap<DialerPhoneNumber, ?> map = allMaps.get(i); + Object subInfo = map.get(dialerPhoneNumber); + if (subInfo == null) { + throw new IllegalStateException( + "A sublookup didn't return an info for number: " + + LogUtil.sanitizePhoneNumber( + dialerPhoneNumber.getNormalizedNumber())); + } + phoneLookups.get(i).setSubMessage(combinedInfo, subInfo); + } + combinedMap.put(dialerPhoneNumber, combinedInfo.build()); + } + return combinedMap.build(); + }, + lightweightExecutorService); + String eventName = getMostRecentInfoEventName(this, isBuilt); + futureTimer.applyTiming(combinedFuture, eventName); + return combinedFuture; + }, + MoreExecutors.directExecutor()); } private <T> ListenableFuture<ImmutableMap<DialerPhoneNumber, T>> buildSubmapAndGetMostRecentInfo( ImmutableMap<DialerPhoneNumber, PhoneLookupInfo> existingInfoMap, - PhoneLookup<T> phoneLookup) { + PhoneLookup<T> phoneLookup, + boolean isBuilt) { Map<DialerPhoneNumber, T> submap = Maps.transformEntries( existingInfoMap, @@ -172,50 +187,54 @@ public final class CompositePhoneLookup implements PhoneLookup<PhoneLookupInfo> phoneLookup.getSubMessage(existingInfoMap.get(dialerPhoneNumber))); ListenableFuture<ImmutableMap<DialerPhoneNumber, T>> mostRecentInfoFuture = phoneLookup.getMostRecentInfo(ImmutableMap.copyOf(submap)); - String eventName = - String.format( - Metrics.GET_MOST_RECENT_INFO_TEMPLATE, phoneLookup.getClass().getSimpleName()); + String eventName = getMostRecentInfoEventName(phoneLookup, isBuilt); futureTimer.applyTiming(mostRecentInfoFuture, eventName); return mostRecentInfoFuture; } - @Override - public void setSubMessage(PhoneLookupInfo.Builder destination, PhoneLookupInfo source) { - throw new UnsupportedOperationException( - "This method is only expected to be called by CompositePhoneLookup itself"); - } - - @Override - public PhoneLookupInfo getSubMessage(PhoneLookupInfo phoneLookupInfo) { - throw new UnsupportedOperationException( - "This method is only expected to be called by CompositePhoneLookup itself"); - } - - @Override + /** Delegates to sub-lookups' {@link PhoneLookup#onSuccessfulBulkUpdate()}. */ public ListenableFuture<Void> onSuccessfulBulkUpdate() { - List<ListenableFuture<Void>> futures = new ArrayList<>(); - for (PhoneLookup<?> phoneLookup : phoneLookups) { - ListenableFuture<Void> phoneLookupFuture = phoneLookup.onSuccessfulBulkUpdate(); - futures.add(phoneLookupFuture); - String eventName = - String.format( - Metrics.ON_SUCCESSFUL_BULK_UPDATE_TEMPLATE, phoneLookup.getClass().getSimpleName()); - futureTimer.applyTiming(phoneLookupFuture, eventName); - } - ListenableFuture<Void> combinedFuture = - Futures.transform(Futures.allAsList(futures), unused -> null, lightweightExecutorService); - String eventName = - String.format( - Metrics.ON_SUCCESSFUL_BULK_UPDATE_TEMPLATE, CompositePhoneLookup.class.getSimpleName()); - futureTimer.applyTiming(combinedFuture, eventName); - return combinedFuture; + return Futures.transformAsync( + callLogState.isBuilt(), + isBuilt -> { + List<ListenableFuture<Void>> futures = new ArrayList<>(); + for (PhoneLookup<?> phoneLookup : phoneLookups) { + ListenableFuture<Void> phoneLookupFuture = phoneLookup.onSuccessfulBulkUpdate(); + futures.add(phoneLookupFuture); + String eventName = onSuccessfulBulkUpdatedEventName(phoneLookup, isBuilt); + futureTimer.applyTiming(phoneLookupFuture, eventName); + } + ListenableFuture<Void> combinedFuture = + Futures.transform( + Futures.allAsList(futures), unused -> null, lightweightExecutorService); + String eventName = onSuccessfulBulkUpdatedEventName(this, isBuilt); + futureTimer.applyTiming(combinedFuture, eventName); + return combinedFuture; + }, + MoreExecutors.directExecutor()); } - @Override + /** Delegates to sub-lookups' {@link PhoneLookup#registerContentObservers(Context)}. */ @MainThread public void registerContentObservers(Context appContext) { for (PhoneLookup phoneLookup : phoneLookups) { phoneLookup.registerContentObservers(appContext); } } + + private static String getMostRecentInfoEventName(Object classNameSource, boolean isBuilt) { + return String.format( + !isBuilt + ? Metrics.INITIAL_GET_MOST_RECENT_INFO_TEMPLATE + : Metrics.GET_MOST_RECENT_INFO_TEMPLATE, + classNameSource.getClass().getSimpleName()); + } + + private static String onSuccessfulBulkUpdatedEventName(Object classNameSource, boolean isBuilt) { + return String.format( + !isBuilt + ? Metrics.INITIAL_ON_SUCCESSFUL_BULK_UPDATE_TEMPLATE + : Metrics.ON_SUCCESSFUL_BULK_UPDATE_TEMPLATE, + classNameSource.getClass().getSimpleName()); + } } diff --git a/java/com/android/incallui/PhoneLookupHistoryRecorder.java b/java/com/android/incallui/PhoneLookupHistoryRecorder.java index 017e6f43c..802bf63cb 100644 --- a/java/com/android/incallui/PhoneLookupHistoryRecorder.java +++ b/java/com/android/incallui/PhoneLookupHistoryRecorder.java @@ -69,7 +69,9 @@ final class PhoneLookupHistoryRecorder { Futures.transformAsync( numberFuture, dialerPhoneNumber -> - PhoneLookupComponent.get(appContext).phoneLookup().lookup(dialerPhoneNumber), + PhoneLookupComponent.get(appContext) + .compositePhoneLookup() + .lookup(dialerPhoneNumber), MoreExecutors.directExecutor()); Futures.addCallback( |