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