• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
RefreshAnnotatedCallLogWorker( @pplicationContext Context appContext, DataSources dataSources, @Unencrypted SharedPreferences sharedPreferences, MutationApplier mutationApplier, FutureTimer futureTimer, CallLogState callLogState, @BackgroundExecutor ListeningExecutorService backgroundExecutorService, @LightweightExecutor ListeningExecutorService lightweightExecutorService)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. */
refreshWithDirtyCheck()90   ListenableFuture<RefreshResult> refreshWithDirtyCheck() {
91     return refresh(true);
92   }
93 
94   /** Refreshes the annotated call log, bypassing dirty checks. */
refreshWithoutDirtyCheck()95   ListenableFuture<RefreshResult> refreshWithoutDirtyCheck() {
96     return refresh(false);
97   }
98 
refresh(boolean checkDirty)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 
checkDirtyAndRebuildIfNecessary(boolean checkDirty)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 
isDirty()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 
rebuild(boolean isBuilt)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