Home | History | Annotate | Download | only in calllog
      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