1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.dialer.calllog; 18 19 import android.content.Context; 20 import android.content.SharedPreferences; 21 import com.android.dialer.calllog.constants.SharedPrefKeys; 22 import com.android.dialer.calllog.database.MutationApplier; 23 import com.android.dialer.calllog.datasources.CallLogDataSource; 24 import com.android.dialer.calllog.datasources.CallLogMutations; 25 import com.android.dialer.calllog.datasources.DataSources; 26 import com.android.dialer.common.LogUtil; 27 import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor; 28 import com.android.dialer.common.concurrent.Annotations.LightweightExecutor; 29 import com.android.dialer.common.concurrent.DefaultFutureCallback; 30 import com.android.dialer.common.concurrent.DialerFutureSerializer; 31 import com.android.dialer.common.concurrent.DialerFutures; 32 import com.android.dialer.inject.ApplicationContext; 33 import com.android.dialer.metrics.FutureTimer; 34 import com.android.dialer.metrics.FutureTimer.LogCatMode; 35 import com.android.dialer.metrics.Metrics; 36 import com.android.dialer.storage.Unencrypted; 37 import com.google.common.base.Preconditions; 38 import com.google.common.util.concurrent.Futures; 39 import com.google.common.util.concurrent.ListenableFuture; 40 import com.google.common.util.concurrent.ListeningExecutorService; 41 import com.google.common.util.concurrent.MoreExecutors; 42 import java.util.ArrayList; 43 import java.util.List; 44 import javax.inject.Inject; 45 import javax.inject.Singleton; 46 47 /** Brings the annotated call log up to date, if necessary. */ 48 @Singleton 49 public class RefreshAnnotatedCallLogWorker { 50 51 private final Context appContext; 52 private final DataSources dataSources; 53 private final SharedPreferences sharedPreferences; 54 private final MutationApplier mutationApplier; 55 private final FutureTimer futureTimer; 56 private final CallLogState callLogState; 57 private final CallLogCacheUpdater callLogCacheUpdater; 58 private final ListeningExecutorService backgroundExecutorService; 59 private final ListeningExecutorService lightweightExecutorService; 60 // Used to ensure that only one refresh flow runs at a time. (Note that 61 // RefreshAnnotatedCallLogWorker is a @Singleton.) 62 private final DialerFutureSerializer dialerFutureSerializer = new DialerFutureSerializer(); 63 64 @Inject RefreshAnnotatedCallLogWorker( @pplicationContext Context appContext, DataSources dataSources, @Unencrypted SharedPreferences sharedPreferences, MutationApplier mutationApplier, FutureTimer futureTimer, CallLogState callLogState, CallLogCacheUpdater callLogCacheUpdater, @BackgroundExecutor ListeningExecutorService backgroundExecutorService, @LightweightExecutor ListeningExecutorService lightweightExecutorService)65 RefreshAnnotatedCallLogWorker( 66 @ApplicationContext Context appContext, 67 DataSources dataSources, 68 @Unencrypted SharedPreferences sharedPreferences, 69 MutationApplier mutationApplier, 70 FutureTimer futureTimer, 71 CallLogState callLogState, 72 CallLogCacheUpdater callLogCacheUpdater, 73 @BackgroundExecutor ListeningExecutorService backgroundExecutorService, 74 @LightweightExecutor ListeningExecutorService lightweightExecutorService) { 75 this.appContext = appContext; 76 this.dataSources = dataSources; 77 this.sharedPreferences = sharedPreferences; 78 this.mutationApplier = mutationApplier; 79 this.futureTimer = futureTimer; 80 this.callLogState = callLogState; 81 this.callLogCacheUpdater = callLogCacheUpdater; 82 this.backgroundExecutorService = backgroundExecutorService; 83 this.lightweightExecutorService = lightweightExecutorService; 84 } 85 86 /** Result of refreshing the annotated call log. */ 87 public enum RefreshResult { 88 NOT_DIRTY, 89 REBUILT_BUT_NO_CHANGES_NEEDED, 90 REBUILT_AND_CHANGES_NEEDED 91 } 92 93 /** Checks if the annotated call log is dirty and refreshes it if necessary. */ refreshWithDirtyCheck()94 ListenableFuture<RefreshResult> refreshWithDirtyCheck() { 95 return refresh(true); 96 } 97 98 /** Refreshes the annotated call log, bypassing dirty checks. */ refreshWithoutDirtyCheck()99 ListenableFuture<RefreshResult> refreshWithoutDirtyCheck() { 100 return refresh(false); 101 } 102 refresh(boolean checkDirty)103 private ListenableFuture<RefreshResult> refresh(boolean checkDirty) { 104 LogUtil.i("RefreshAnnotatedCallLogWorker.refresh", "submitting serialized refresh request"); 105 return dialerFutureSerializer.submitAsync( 106 () -> checkDirtyAndRebuildIfNecessary(checkDirty), lightweightExecutorService); 107 } 108 checkDirtyAndRebuildIfNecessary(boolean checkDirty)109 private ListenableFuture<RefreshResult> checkDirtyAndRebuildIfNecessary(boolean checkDirty) { 110 ListenableFuture<Boolean> forceRebuildFuture = 111 backgroundExecutorService.submit( 112 () -> { 113 LogUtil.i( 114 "RefreshAnnotatedCallLogWorker.checkDirtyAndRebuildIfNecessary", 115 "starting refresh flow"); 116 if (!checkDirty) { 117 return true; 118 } 119 // Default to true. If the pref doesn't exist, the annotated call log hasn't been 120 // created and we just skip isDirty checks and force a rebuild. 121 boolean forceRebuildPrefValue = 122 sharedPreferences.getBoolean(SharedPrefKeys.FORCE_REBUILD, true); 123 if (forceRebuildPrefValue) { 124 LogUtil.i( 125 "RefreshAnnotatedCallLogWorker.checkDirtyAndRebuildIfNecessary", 126 "annotated call log has been marked dirty or does not exist"); 127 } 128 return forceRebuildPrefValue; 129 }); 130 131 // After checking the "force rebuild" shared pref, conditionally call isDirty. 132 ListenableFuture<Boolean> isDirtyFuture = 133 Futures.transformAsync( 134 forceRebuildFuture, 135 forceRebuild -> 136 Preconditions.checkNotNull(forceRebuild) 137 ? Futures.immediateFuture(true) 138 : isDirty(), 139 lightweightExecutorService); 140 141 // After determining isDirty, conditionally call rebuild. 142 return Futures.transformAsync( 143 isDirtyFuture, 144 isDirty -> { 145 LogUtil.v( 146 "RefreshAnnotatedCallLogWorker.checkDirtyAndRebuildIfNecessary", 147 "isDirty: %b", 148 Preconditions.checkNotNull(isDirty)); 149 if (isDirty) { 150 return Futures.transformAsync( 151 callLogState.isBuilt(), this::rebuild, MoreExecutors.directExecutor()); 152 } 153 return Futures.immediateFuture(RefreshResult.NOT_DIRTY); 154 }, 155 lightweightExecutorService); 156 } 157 isDirty()158 private ListenableFuture<Boolean> isDirty() { 159 List<ListenableFuture<Boolean>> isDirtyFutures = new ArrayList<>(); 160 for (CallLogDataSource dataSource : dataSources.getDataSourcesIncludingSystemCallLog()) { 161 ListenableFuture<Boolean> dataSourceDirty = dataSource.isDirty(); 162 isDirtyFutures.add(dataSourceDirty); 163 String eventName = String.format(Metrics.IS_DIRTY_TEMPLATE, dataSource.getLoggingName()); 164 futureTimer.applyTiming(dataSourceDirty, eventName, LogCatMode.LOG_VALUES); 165 } 166 // Simultaneously invokes isDirty on all data sources, returning as soon as one returns true. 167 ListenableFuture<Boolean> isDirtyFuture = 168 DialerFutures.firstMatching(isDirtyFutures, Preconditions::checkNotNull, false); 169 futureTimer.applyTiming(isDirtyFuture, Metrics.IS_DIRTY_EVENT_NAME, LogCatMode.LOG_VALUES); 170 return isDirtyFuture; 171 } 172 rebuild(boolean isBuilt)173 private ListenableFuture<RefreshResult> rebuild(boolean isBuilt) { 174 CallLogMutations mutations = new CallLogMutations(); 175 176 // Start by filling the data sources--the system call log data source must go first! 177 CallLogDataSource systemCallLogDataSource = dataSources.getSystemCallLogDataSource(); 178 ListenableFuture<Void> fillFuture = systemCallLogDataSource.fill(mutations); 179 String systemEventName = eventNameForFill(systemCallLogDataSource, isBuilt); 180 futureTimer.applyTiming(fillFuture, systemEventName); 181 182 // After the system call log data source is filled, call fill sequentially on each remaining 183 // data source. This must be done sequentially because mutations are not threadsafe and are 184 // passed from source to source. 185 for (CallLogDataSource dataSource : dataSources.getDataSourcesExcludingSystemCallLog()) { 186 fillFuture = 187 Futures.transformAsync( 188 fillFuture, 189 unused -> { 190 ListenableFuture<Void> dataSourceFuture = dataSource.fill(mutations); 191 String eventName = eventNameForFill(dataSource, isBuilt); 192 futureTimer.applyTiming(dataSourceFuture, eventName); 193 return dataSourceFuture; 194 }, 195 lightweightExecutorService); 196 } 197 198 futureTimer.applyTiming(fillFuture, eventNameForOverallFill(isBuilt)); 199 200 // After all data sources are filled, apply mutations (at this point "fillFuture" is the result 201 // of filling the last data source). 202 ListenableFuture<Void> applyMutationsFuture = 203 Futures.transformAsync( 204 fillFuture, 205 unused -> { 206 ListenableFuture<Void> mutationApplierFuture = 207 mutationApplier.applyToDatabase(mutations, appContext); 208 futureTimer.applyTiming(mutationApplierFuture, eventNameForApplyMutations(isBuilt)); 209 return mutationApplierFuture; 210 }, 211 lightweightExecutorService); 212 213 Futures.addCallback( 214 Futures.transformAsync( 215 applyMutationsFuture, 216 unused -> callLogCacheUpdater.updateCache(mutations), 217 MoreExecutors.directExecutor()), 218 new DefaultFutureCallback<>(), 219 MoreExecutors.directExecutor()); 220 221 // After mutations applied, call onSuccessfulFill for each data source (in parallel). 222 ListenableFuture<List<Void>> onSuccessfulFillFuture = 223 Futures.transformAsync( 224 applyMutationsFuture, 225 unused -> { 226 List<ListenableFuture<Void>> onSuccessfulFillFutures = new ArrayList<>(); 227 for (CallLogDataSource dataSource : 228 dataSources.getDataSourcesIncludingSystemCallLog()) { 229 ListenableFuture<Void> dataSourceFuture = dataSource.onSuccessfulFill(); 230 onSuccessfulFillFutures.add(dataSourceFuture); 231 String eventName = eventNameForOnSuccessfulFill(dataSource, isBuilt); 232 futureTimer.applyTiming(dataSourceFuture, eventName); 233 } 234 ListenableFuture<List<Void>> allFutures = Futures.allAsList(onSuccessfulFillFutures); 235 futureTimer.applyTiming(allFutures, eventNameForOverallOnSuccessfulFill(isBuilt)); 236 return allFutures; 237 }, 238 lightweightExecutorService); 239 240 // After onSuccessfulFill is called for every data source, write the shared pref. 241 return Futures.transform( 242 onSuccessfulFillFuture, 243 unused -> { 244 sharedPreferences.edit().putBoolean(SharedPrefKeys.FORCE_REBUILD, false).apply(); 245 callLogState.markBuilt(); 246 return mutations.isEmpty() 247 ? RefreshResult.REBUILT_BUT_NO_CHANGES_NEEDED 248 : RefreshResult.REBUILT_AND_CHANGES_NEEDED; 249 }, 250 backgroundExecutorService); 251 } 252 253 private static String eventNameForFill(CallLogDataSource dataSource, boolean isBuilt) { 254 return String.format( 255 !isBuilt ? Metrics.INITIAL_FILL_TEMPLATE : Metrics.FILL_TEMPLATE, 256 dataSource.getLoggingName()); 257 } 258 259 private static String eventNameForOverallFill(boolean isBuilt) { 260 return !isBuilt ? Metrics.INITIAL_FILL_EVENT_NAME : Metrics.FILL_EVENT_NAME; 261 } 262 263 private static String eventNameForOnSuccessfulFill( 264 CallLogDataSource dataSource, boolean isBuilt) { 265 return String.format( 266 !isBuilt 267 ? Metrics.INITIAL_ON_SUCCESSFUL_FILL_TEMPLATE 268 : Metrics.ON_SUCCESSFUL_FILL_TEMPLATE, 269 dataSource.getLoggingName()); 270 } 271 272 private static String eventNameForOverallOnSuccessfulFill(boolean isBuilt) { 273 return !isBuilt 274 ? Metrics.INITIAL_ON_SUCCESSFUL_FILL_EVENT_NAME 275 : Metrics.ON_SUCCESSFUL_FILL_EVENT_NAME; 276 } 277 278 private static String eventNameForApplyMutations(boolean isBuilt) { 279 return !isBuilt 280 ? Metrics.INITIAL_APPLY_MUTATIONS_EVENT_NAME 281 : Metrics.APPLY_MUTATIONS_EVENT_NAME; 282 } 283 } 284