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