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.annotation.TargetApi; 20 import android.content.Context; 21 import android.content.SharedPreferences; 22 import android.database.sqlite.SQLiteDatabase; 23 import android.os.Build; 24 import android.preference.PreferenceManager; 25 import android.support.annotation.WorkerThread; 26 import com.android.dialer.calllog.database.AnnotatedCallLog; 27 import com.android.dialer.calllog.database.CallLogMutations; 28 import com.android.dialer.calllog.datasources.CallLogDataSource; 29 import com.android.dialer.common.Assert; 30 import com.android.dialer.common.LogUtil; 31 import com.android.dialer.common.concurrent.DialerExecutor.Worker; 32 import 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 */ 40 public class RefreshAnnotatedCallLogWorker implements Worker<Boolean, Boolean> { 41 42 private final Context appContext; 43 private final DataSources dataSources; 44 45 @Inject RefreshAnnotatedCallLogWorker(Context appContext, DataSources dataSources)46 public RefreshAnnotatedCallLogWorker(Context appContext, DataSources dataSources) { 47 this.appContext = appContext; 48 this.dataSources = dataSources; 49 } 50 51 @Override doInBackground(Boolean skipDirtyCheck)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 checkDirtyAndRebuildIfNecessary(Context appContext, boolean skipDirtyCheck)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 isDirty(Context appContext)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 rebuild(Context appContext, long lastRebuildTimeMillis)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 getName(CallLogDataSource dataSource)180 private static String getName(CallLogDataSource dataSource) { 181 return dataSource.getClass().getSimpleName(); 182 } 183 } 184