diff options
Diffstat (limited to 'java/com/android/dialer/calllog/datasources')
6 files changed, 21 insertions, 470 deletions
diff --git a/java/com/android/dialer/calllog/datasources/CallLogDataSource.java b/java/com/android/dialer/calllog/datasources/CallLogDataSource.java index 3fff3ba53..13d0b842d 100644 --- a/java/com/android/dialer/calllog/datasources/CallLogDataSource.java +++ b/java/com/android/dialer/calllog/datasources/CallLogDataSource.java @@ -16,39 +16,13 @@ package com.android.dialer.calllog.datasources; -import android.content.ContentValues; import android.content.Context; +import android.database.sqlite.SQLiteDatabase; import android.support.annotation.MainThread; import android.support.annotation.WorkerThread; -import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract; -import java.util.List; +import com.android.dialer.calllog.database.CallLogMutations; -/** - * A source of data for one or more columns in the annotated call log. - * - * <p>Data sources have three lifecycle operations, which are always called on the same thread and - * in the same order for a particular "checkDirtyAndRebuild" cycle. However, not all operations are - * always invoked. - * - * <ol> - * <li>{@link #isDirty(Context)}: Invoked only if the framework doesn't yet know if a rebuild is - * necessary. - * <li>{@link #fill(Context, CallLogMutations)}: Invoked only if the framework determined a - * rebuild is necessary. - * <li>{@link #onSuccessfulFill(Context)}: Invoked if and only if fill was previously called and - * the mutations provided by the previous fill operation succeeded in being applied. - * </ol> - * - * <p>Because {@link #isDirty(Context)} is not always invoked, {@link #fill(Context, - * CallLogMutations)} shouldn't rely on any state saved during {@link #isDirty(Context)}. It - * <em>is</em> safe to assume that {@link #onSuccessfulFill(Context)} refers to the previous fill - * operation. - * - * <p>The same data source objects may be reused across multiple checkDirtyAndRebuild cycles, so - * implementors should take care to clear any internal state at the start of a new cycle. - * - * <p>{@link #coalesce(List)} may be called from any worker thread at any time. - */ +/** A source of data for one or more columns in the annotated call log. */ public interface CallLogDataSource { /** @@ -61,8 +35,6 @@ public interface CallLogDataSource { * <p>Most implementations of this method will rely on some sort of last modified timestamp. If it * is impossible for a data source to be modified without the dialer application being notified, * this method may immediately return false. - * - * @see CallLogDataSource class doc for complete lifecyle information */ @WorkerThread boolean isDirty(Context appContext); @@ -71,39 +43,16 @@ public interface CallLogDataSource { * Computes the set of mutations necessary to update the annotated call log with respect to this * data source. * - * @see CallLogDataSource class doc for complete lifecyle information * @param mutations the set of mutations which this method should contribute to. Note that it may * contain inserts from the system call log, and these inserts should be modified by each data * source. */ @WorkerThread - void fill(Context appContext, CallLogMutations mutations); - - /** - * Called after database mutations have been applied to all data sources. This is useful for - * saving state such as the timestamp of the last row processed in an underlying database. Note - * that all mutations across all data sources are applied in a single transaction. - * - * @see CallLogDataSource class doc for complete lifecyle information - */ - @WorkerThread - void onSuccessfulFill(Context appContext); - - /** - * Combines raw annotated call log rows into a single coalesced row. - * - * <p>May be called by any worker thread at any time so implementations should take care to be - * threadsafe. (Ideally no state should be required to implement this.) - * - * @param individualRowsSortedByTimestampDesc group of fully populated rows from {@link - * AnnotatedCallLogContract.AnnotatedCallLog} which need to be combined for display purposes. - * This method should not modify this list. - * @return a partial {@link AnnotatedCallLogContract.CoalescedAnnotatedCallLog} row containing - * only columns which this data source is responsible for, which is the result of aggregating - * {@code individualRowsSortedByTimestampDesc}. - */ - @WorkerThread - ContentValues coalesce(List<ContentValues> individualRowsSortedByTimestampDesc); + void fill( + Context appContext, + SQLiteDatabase readableDatabase, + long lastRebuildTimeMillis, + CallLogMutations mutations); @MainThread void registerContentObservers( diff --git a/java/com/android/dialer/calllog/datasources/CallLogMutations.java b/java/com/android/dialer/calllog/datasources/CallLogMutations.java deleted file mode 100644 index 148601d68..000000000 --- a/java/com/android/dialer/calllog/datasources/CallLogMutations.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.dialer.calllog.datasources; - -import android.content.ContentValues; -import android.util.ArrayMap; -import android.util.ArraySet; -import com.android.dialer.common.Assert; - -/** A collection of mutations to the annotated call log. */ -public final class CallLogMutations { - - private final ArrayMap<Long, ContentValues> inserts = new ArrayMap<>(); - private final ArrayMap<Long, ContentValues> updates = new ArrayMap<>(); - private final ArraySet<Long> deletes = new ArraySet<>(); - - /** - * @param contentValues an entire row not including the ID - * @throws IllegalStateException if this {@link CallLogMutations} already contains an insert, - * update, or delete with the provided id - */ - public void insert(long id, ContentValues contentValues) { - Assert.checkArgument(!inserts.containsKey(id), "Can't insert row already scheduled for insert"); - Assert.checkArgument(!updates.containsKey(id), "Can't insert row scheduled for update"); - Assert.checkArgument(!deletes.contains(id), "Can't insert row scheduled for delete"); - - inserts.put(id, contentValues); - } - - /** - * Stores a database update using the provided ID and content values. If this {@link - * CallLogMutations} object already contains an update with the specified ID, the existing content - * values are merged with the provided ones, with the provided ones overwriting the existing ones - * for values with the same key. - * - * @param contentValues the specific columns to update, not including the ID. - * @throws IllegalStateException if this {@link CallLogMutations} already contains an insert or - * delete with the provided id - */ - public void update(long id, ContentValues contentValues) { - Assert.checkArgument(!inserts.containsKey(id), "Can't update row scheduled for insert"); - Assert.checkArgument(!deletes.contains(id), "Can't delete row scheduled for delete"); - - ContentValues existingContentValues = updates.get(id); - if (existingContentValues != null) { - existingContentValues.putAll(contentValues); - } else { - updates.put(id, contentValues); - } - } - - /** - * @throws IllegalStateException if this {@link CallLogMutations} already contains an insert, - * update, or delete with the provided id - */ - public void delete(long id) { - Assert.checkArgument(!inserts.containsKey(id), "Can't delete row scheduled for insert"); - Assert.checkArgument(!updates.containsKey(id), "Can't delete row scheduled for update"); - Assert.checkArgument(!deletes.contains(id), "Can't delete row already scheduled for delete"); - - deletes.add(id); - } - - public boolean isEmpty() { - return inserts.isEmpty() && updates.isEmpty() && deletes.isEmpty(); - } - - /** - * Get the pending inserts. - * - * @return the pending inserts where the key is the annotated call log database ID and the values - * are values to be inserted (not including the ID) - */ - public ArrayMap<Long, ContentValues> getInserts() { - return inserts; - } - - /** - * Get the pending updates. - * - * @return the pending updates where the key is the annotated call log database ID and the values - * are values to be updated (not including the ID) - */ - public ArrayMap<Long, ContentValues> getUpdates() { - return updates; - } - - /** - * Get the pending deletes. - * - * @return the annotated call log database IDs corresponding to the rows to be deleted - */ - public ArraySet<Long> getDeletes() { - return deletes; - } -} diff --git a/java/com/android/dialer/calllog/datasources/DataSources.java b/java/com/android/dialer/calllog/datasources/DataSources.java deleted file mode 100644 index 911ca3fa3..000000000 --- a/java/com/android/dialer/calllog/datasources/DataSources.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.dialer.calllog.datasources; - -import com.android.dialer.calllog.datasources.systemcalllog.SystemCallLogDataSource; -import java.util.List; - -/** Immutable lists of data sources used to populate the annotated call log. */ -public interface DataSources { - - SystemCallLogDataSource getSystemCallLogDataSource(); - - List<CallLogDataSource> getDataSourcesIncludingSystemCallLog(); - - List<CallLogDataSource> getDataSourcesExcludingSystemCallLog(); -} diff --git a/java/com/android/dialer/calllog/datasources/contacts/ContactsDataSource.java b/java/com/android/dialer/calllog/datasources/contacts/ContactsDataSource.java index e9538daab..355940f6a 100644 --- a/java/com/android/dialer/calllog/datasources/contacts/ContactsDataSource.java +++ b/java/com/android/dialer/calllog/datasources/contacts/ContactsDataSource.java @@ -16,16 +16,13 @@ package com.android.dialer.calllog.datasources.contacts; -import android.content.ContentValues; import android.content.Context; +import android.database.sqlite.SQLiteDatabase; import android.support.annotation.MainThread; import android.support.annotation.WorkerThread; -import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog; +import com.android.dialer.calllog.database.CallLogMutations; import com.android.dialer.calllog.datasources.CallLogDataSource; -import com.android.dialer.calllog.datasources.CallLogMutations; -import com.android.dialer.calllog.datasources.util.RowCombiner; import com.android.dialer.common.Assert; -import java.util.List; import javax.inject.Inject; /** Responsible for maintaining the contacts related columns in the annotated call log. */ @@ -47,24 +44,13 @@ public final class ContactsDataSource implements CallLogDataSource { @Override public void fill( Context appContext, + SQLiteDatabase readableDatabase, + long lastRebuildTimeMillis, CallLogMutations mutations) { Assert.isWorkerThread(); // TODO: Implementation. } - @Override - public void onSuccessfulFill(Context appContext) { - // TODO: Implementation. - } - - @Override - public ContentValues coalesce(List<ContentValues> individualRowsSortedByTimestampDesc) { - // TODO: Implementation. - return new RowCombiner(individualRowsSortedByTimestampDesc) - .useSingleValueString(AnnotatedCallLog.CONTACT_NAME) - .combine(); - } - @MainThread @Override public void registerContentObservers( diff --git a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java index be2df6043..ea6663fbe 100644 --- a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java +++ b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java @@ -16,49 +16,28 @@ package com.android.dialer.calllog.datasources.systemcalllog; -import android.Manifest.permission; -import android.annotation.TargetApi; -import android.content.ContentValues; import android.content.Context; import android.database.ContentObserver; -import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; import android.net.Uri; -import android.os.Build; import android.os.Handler; -import android.preference.PreferenceManager; import android.provider.CallLog; -import android.provider.CallLog.Calls; import android.support.annotation.MainThread; -import android.support.annotation.Nullable; -import android.support.annotation.VisibleForTesting; import android.support.annotation.WorkerThread; -import android.text.TextUtils; -import android.util.ArraySet; -import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog; +import com.android.dialer.calllog.database.CallLogMutations; import com.android.dialer.calllog.datasources.CallLogDataSource; -import com.android.dialer.calllog.datasources.CallLogMutations; -import com.android.dialer.calllog.datasources.util.RowCombiner; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.ThreadUtil; import com.android.dialer.util.PermissionsUtil; -import java.util.Arrays; -import java.util.List; -import java.util.Set; import javax.inject.Inject; /** * Responsible for defining the rows in the annotated call log and maintaining the columns in it * which are derived from the system call log. */ -@SuppressWarnings("MissingPermission") public class SystemCallLogDataSource implements CallLogDataSource { - @VisibleForTesting - static final String PREF_LAST_TIMESTAMP_PROCESSED = "systemCallLogLastTimestampProcessed"; - - @Nullable private Long lastTimestampProcessed; - @Inject public SystemCallLogDataSource() {} @@ -68,8 +47,6 @@ public class SystemCallLogDataSource implements CallLogDataSource { Context appContext, ContentObserverCallbacks contentObserverCallbacks) { Assert.isMainThread(); - LogUtil.enterBlock("SystemCallLogDataSource.registerContentObservers"); - if (!PermissionsUtil.hasCallLogReadPermissions(appContext)) { LogUtil.i("SystemCallLogDataSource.registerContentObservers", "no call log permissions"); return; @@ -100,185 +77,17 @@ public class SystemCallLogDataSource implements CallLogDataSource { @WorkerThread @Override - public void fill(Context appContext, CallLogMutations mutations) { + public void fill( + Context appContext, + SQLiteDatabase readableDatabase, + long lastRebuildTimeMillis, + CallLogMutations mutations) { Assert.isWorkerThread(); - lastTimestampProcessed = null; - - if (!PermissionsUtil.hasPermission(appContext, permission.READ_CALL_LOG)) { - LogUtil.i("SystemCallLogDataSource.fill", "no call log permissions"); - return; - } - // This data source should always run first so the mutations should always be empty. - Assert.checkArgument(mutations.isEmpty()); - - Set<Long> annotatedCallLogIds = getAnnotatedCallLogIds(appContext); - - LogUtil.i( - "SystemCallLogDataSource.fill", - "found %d existing annotated call log ids", - annotatedCallLogIds.size()); - - handleInsertsAndUpdates(appContext, mutations, annotatedCallLogIds); - handleDeletes(appContext, annotatedCallLogIds, mutations); - } - - @WorkerThread - @Override - public void onSuccessfulFill(Context appContext) { - // If a fill operation was a no-op, lastTimestampProcessed could still be null. - if (lastTimestampProcessed != null) { - PreferenceManager.getDefaultSharedPreferences(appContext) - .edit() - .putLong(PREF_LAST_TIMESTAMP_PROCESSED, lastTimestampProcessed) - .commit(); - } - } - - @Override - public ContentValues coalesce(List<ContentValues> individualRowsSortedByTimestampDesc) { - // TODO: Complete implementation. - return new RowCombiner(individualRowsSortedByTimestampDesc) - .useMostRecentLong(AnnotatedCallLog.TIMESTAMP) - .combine(); - } - - @TargetApi(Build.VERSION_CODES.M) // Uses try-with-resources - private void handleInsertsAndUpdates( - Context appContext, CallLogMutations mutations, Set<Long> existingAnnotatedCallLogIds) { - long previousTimestampProcessed = - PreferenceManager.getDefaultSharedPreferences(appContext) - .getLong(PREF_LAST_TIMESTAMP_PROCESSED, 0L); - - try (Cursor cursor = - appContext - .getContentResolver() - .query( - Calls.CONTENT_URI, // Excludes voicemail - new String[] {Calls._ID, Calls.DATE, Calls.LAST_MODIFIED}, - Calls.LAST_MODIFIED + " > ?", - new String[] {String.valueOf(previousTimestampProcessed)}, - Calls.LAST_MODIFIED + " DESC LIMIT 1000")) { - - if (cursor == null) { - LogUtil.e("SystemCallLogDataSource.handleInsertsAndUpdates", "null cursor"); - return; - } - - LogUtil.i( - "SystemCallLogDataSource.handleInsertsAndUpdates", - "found %d entries to insert/update", - cursor.getCount()); + Assert.checkState(mutations.isEmpty()); - if (cursor.moveToFirst()) { - int idColumn = cursor.getColumnIndexOrThrow(Calls._ID); - int dateColumn = cursor.getColumnIndexOrThrow(Calls.DATE); - int lastModifiedColumn = cursor.getColumnIndexOrThrow(Calls.LAST_MODIFIED); - - // The cursor orders by LAST_MODIFIED DESC, so the first result is the most recent timestamp - // processed. - lastTimestampProcessed = cursor.getLong(lastModifiedColumn); - do { - long id = cursor.getLong(idColumn); - long date = cursor.getLong(dateColumn); - - ContentValues contentValues = new ContentValues(); - contentValues.put(AnnotatedCallLog.TIMESTAMP, date); - - if (existingAnnotatedCallLogIds.contains(id)) { - mutations.update(id, contentValues); - } else { - mutations.insert(id, contentValues); - } - } while (cursor.moveToNext()); - } // else no new results, do nothing. - } - } - - private static void handleDeletes( - Context appContext, Set<Long> existingAnnotatedCallLogIds, CallLogMutations mutations) { - Set<Long> systemCallLogIds = - getIdsFromSystemCallLogThatMatch(appContext, existingAnnotatedCallLogIds); - LogUtil.i( - "SystemCallLogDataSource.handleDeletes", - "found %d entries in system call log", - systemCallLogIds.size()); - Set<Long> idsInAnnotatedCallLogNoLongerInSystemCallLog = new ArraySet<>(); - idsInAnnotatedCallLogNoLongerInSystemCallLog.addAll(existingAnnotatedCallLogIds); - idsInAnnotatedCallLogNoLongerInSystemCallLog.removeAll(systemCallLogIds); - - LogUtil.i( - "SystemCallLogDataSource.handleDeletes", - "found %d call log entries to remove", - idsInAnnotatedCallLogNoLongerInSystemCallLog.size()); - - for (long id : idsInAnnotatedCallLogNoLongerInSystemCallLog) { - mutations.delete(id); - } - } - - @TargetApi(Build.VERSION_CODES.M) // Uses try-with-resources - private static Set<Long> getAnnotatedCallLogIds(Context appContext) { - ArraySet<Long> ids = new ArraySet<>(); - - try (Cursor cursor = - appContext - .getContentResolver() - .query( - AnnotatedCallLog.CONTENT_URI, - new String[] {AnnotatedCallLog._ID}, - null, - null, - null)) { - - if (cursor == null) { - LogUtil.e("SystemCallLogDataSource.getAnnotatedCallLogIds", "null cursor"); - return ids; - } - - if (cursor.moveToFirst()) { - int idColumn = cursor.getColumnIndexOrThrow(AnnotatedCallLog._ID); - do { - ids.add(cursor.getLong(idColumn)); - } while (cursor.moveToNext()); - } - } - return ids; - } - - @TargetApi(Build.VERSION_CODES.M) // Uses try-with-resources - private static Set<Long> getIdsFromSystemCallLogThatMatch( - Context appContext, Set<Long> matchingIds) { - ArraySet<Long> ids = new ArraySet<>(); - - String[] questionMarks = new String[matchingIds.size()]; - Arrays.fill(questionMarks, "?"); - String whereClause = (Calls._ID + " in (") + TextUtils.join(",", questionMarks) + ")"; - String[] whereArgs = new String[matchingIds.size()]; - int i = 0; - for (long id : matchingIds) { - whereArgs[i++] = String.valueOf(id); - } - - try (Cursor cursor = - appContext - .getContentResolver() - .query(Calls.CONTENT_URI, new String[] {Calls._ID}, whereClause, whereArgs, null)) { - - if (cursor == null) { - LogUtil.e("SystemCallLogDataSource.getIdsFromSystemCallLog", "null cursor"); - return ids; - } - - if (cursor.moveToFirst()) { - int idColumn = cursor.getColumnIndexOrThrow(Calls._ID); - do { - ids.add(cursor.getLong(idColumn)); - } while (cursor.moveToNext()); - } - return ids; - } + // TODO: Implementation. } private static class CallLogObserver extends ContentObserver { diff --git a/java/com/android/dialer/calllog/datasources/util/RowCombiner.java b/java/com/android/dialer/calllog/datasources/util/RowCombiner.java deleted file mode 100644 index 0c7be1e27..000000000 --- a/java/com/android/dialer/calllog/datasources/util/RowCombiner.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ -package com.android.dialer.calllog.datasources.util; - -import android.content.ContentValues; -import com.android.dialer.common.Assert; -import java.util.Iterator; -import java.util.List; - -/** Convenience class for aggregating row values. */ -public class RowCombiner { - private final List<ContentValues> individualRowsSortedByTimestampDesc; - private final ContentValues combinedRow = new ContentValues(); - - public RowCombiner(List<ContentValues> individualRowsSortedByTimestampDesc) { - Assert.checkArgument(!individualRowsSortedByTimestampDesc.isEmpty()); - this.individualRowsSortedByTimestampDesc = individualRowsSortedByTimestampDesc; - } - - /** Use the most recent value for the specified column. */ - public RowCombiner useMostRecentLong(String columnName) { - combinedRow.put(columnName, individualRowsSortedByTimestampDesc.get(0).getAsLong(columnName)); - return this; - } - - /** Asserts that all column values for the given column name are the same, and uses it. */ - public RowCombiner useSingleValueString(String columnName) { - Iterator<ContentValues> iterator = individualRowsSortedByTimestampDesc.iterator(); - String singleValue = iterator.next().getAsString(columnName); - while (iterator.hasNext()) { - Assert.checkState(iterator.next().getAsString(columnName).equals(singleValue)); - } - combinedRow.put(columnName, singleValue); - return this; - } - - public ContentValues combine() { - return combinedRow; - } -} |