RefreshAnnotatedCallLogWorker.java revision 8369df095a73a77b3715f8ae7ba06089cebca4ce
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
17package com.android.dialer.calllog;
18
19import android.annotation.TargetApi;
20import android.content.Context;
21import android.content.SharedPreferences;
22import android.database.sqlite.SQLiteDatabase;
23import android.os.Build;
24import android.preference.PreferenceManager;
25import android.support.annotation.WorkerThread;
26import com.android.dialer.calllog.database.AnnotatedCallLog;
27import com.android.dialer.calllog.database.CallLogMutations;
28import com.android.dialer.calllog.datasources.CallLogDataSource;
29import com.android.dialer.common.Assert;
30import com.android.dialer.common.LogUtil;
31import com.android.dialer.common.concurrent.DialerExecutor.Worker;
32import javax.inject.Inject;
33
34/**
35 * Worker which brings the annotated call log up to date, if necessary.
36 *
37 * <p>Accepts a boolean which indicates if the dirty check should be skipped, and returns true if
38 * the annotated call log was updated.
39 */
40public class RefreshAnnotatedCallLogWorker implements Worker<Boolean, Boolean> {
41
42  private final Context appContext;
43  private final DataSources dataSources;
44
45  @Inject
46  public RefreshAnnotatedCallLogWorker(Context appContext, DataSources dataSources) {
47    this.appContext = appContext;
48    this.dataSources = dataSources;
49  }
50
51  @Override
52  public Boolean doInBackground(Boolean skipDirtyCheck) {
53    LogUtil.enterBlock("RefreshAnnotatedCallLogWorker.doInBackgroundFallible");
54
55    long startTime = System.currentTimeMillis();
56    boolean annotatedCallLogUpdated = checkDirtyAndRebuildIfNecessary(appContext, skipDirtyCheck);
57    LogUtil.i(
58        "RefreshAnnotatedCallLogWorker.doInBackgroundFallible",
59        "updated? %s, took %dms",
60        annotatedCallLogUpdated,
61        System.currentTimeMillis() - startTime);
62    return annotatedCallLogUpdated;
63  }
64
65  @WorkerThread
66  private boolean checkDirtyAndRebuildIfNecessary(Context appContext, boolean skipDirtyCheck) {
67    Assert.isWorkerThread();
68
69    long startTime = System.currentTimeMillis();
70
71    SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(appContext);
72    long lastRebuildTimeMillis =
73        sharedPreferences.getLong(CallLogFramework.PREF_LAST_REBUILD_TIMESTAMP_MILLIS, 0);
74    if (lastRebuildTimeMillis == 0) {
75      LogUtil.i(
76          "RefreshAnnotatedCallLogWorker.checkDirtyAndRebuildIfNecessary",
77          "annotated call log has never been built, marking it dirty");
78    }
79    boolean forceRebuildPrefValue =
80        sharedPreferences.getBoolean(CallLogFramework.PREF_FORCE_REBUILD, false);
81    if (forceRebuildPrefValue) {
82      LogUtil.i(
83          "RefreshAnnotatedCallLogWorker.checkDirtyAndRebuildIfNecessary",
84          "call log has been marked dirty");
85    }
86
87    boolean isDirty =
88        lastRebuildTimeMillis == 0
89            || skipDirtyCheck
90            || forceRebuildPrefValue
91            || isDirty(appContext);
92    LogUtil.i(
93        "RefreshAnnotatedCallLogWorker.checkDirtyAndRebuildIfNecessary",
94        "isDirty took: %dms",
95        System.currentTimeMillis() - startTime);
96    if (isDirty) {
97      startTime = System.currentTimeMillis();
98      rebuild(appContext, lastRebuildTimeMillis);
99      LogUtil.i(
100          "RefreshAnnotatedCallLogWorker.checkDirtyAndRebuildIfNecessary",
101          "rebuild took: %dms",
102          System.currentTimeMillis() - startTime);
103      return true; // Annotated call log was updated.
104    }
105    return false; // Annotated call log was not updated.
106  }
107
108  @WorkerThread
109  private boolean isDirty(Context appContext) {
110    Assert.isWorkerThread();
111
112    for (CallLogDataSource dataSource : dataSources.getDataSourcesIncludingSystemCallLog()) {
113      String dataSourceName = getName(dataSource);
114      long startTime = System.currentTimeMillis();
115      LogUtil.i("RefreshAnnotatedCallLogWorker.isDirty", "running isDirty for %s", dataSourceName);
116      boolean isDirty = dataSource.isDirty(appContext);
117      LogUtil.i(
118          "RefreshAnnotatedCallLogWorker.isDirty",
119          "%s.isDirty returned %b in %dms",
120          dataSourceName,
121          isDirty,
122          System.currentTimeMillis() - startTime);
123      if (isDirty) {
124        return true;
125      }
126    }
127    return false;
128  }
129
130  @TargetApi(Build.VERSION_CODES.M) // Uses try-with-resources
131  @WorkerThread
132  private void rebuild(Context appContext, long lastRebuildTimeMillis) {
133    Assert.isWorkerThread();
134
135    // TODO: Start a transaction?
136    try (SQLiteDatabase database = AnnotatedCallLog.getWritableDatabase(appContext)) {
137
138      CallLogMutations mutations = new CallLogMutations();
139
140      // System call log data source must go first!
141      CallLogDataSource systemCallLogDataSource = dataSources.getSystemCallLogDataSource();
142      String dataSourceName = getName(systemCallLogDataSource);
143      LogUtil.i("RefreshAnnotatedCallLogWorker.rebuild", "filling %s", dataSourceName);
144      long startTime = System.currentTimeMillis();
145      systemCallLogDataSource.fill(appContext, database, lastRebuildTimeMillis, mutations);
146      LogUtil.i(
147          "RefreshAnnotatedCallLogWorker.rebuild",
148          "%s.fill took: %dms",
149          dataSourceName,
150          System.currentTimeMillis() - startTime);
151
152      for (CallLogDataSource dataSource : dataSources.getDataSourcesExcludingSystemCallLog()) {
153        dataSourceName = getName(dataSource);
154        LogUtil.i("RefreshAnnotatedCallLogWorker.rebuild", "filling %s", dataSourceName);
155        startTime = System.currentTimeMillis();
156        dataSource.fill(appContext, database, lastRebuildTimeMillis, mutations);
157        LogUtil.i(
158            "CallLogFramework.rebuild",
159            "%s.fill took: %dms",
160            dataSourceName,
161            System.currentTimeMillis() - startTime);
162      }
163      LogUtil.i("RefreshAnnotatedCallLogWorker.rebuild", "applying mutations to database");
164      startTime = System.currentTimeMillis();
165      mutations.applyToDatabase(database);
166      LogUtil.i(
167          "RefreshAnnotatedCallLogWorker.rebuild",
168          "applyToDatabase took: %dms",
169          System.currentTimeMillis() - startTime);
170    }
171
172    SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(appContext);
173    sharedPreferences
174        .edit()
175        .putBoolean(CallLogFramework.PREF_FORCE_REBUILD, false)
176        .putLong(CallLogFramework.PREF_LAST_REBUILD_TIMESTAMP_MILLIS, System.currentTimeMillis())
177        .commit();
178  }
179
180  private static String getName(CallLogDataSource dataSource) {
181    return dataSource.getClass().getSimpleName();
182  }
183}
184