diff options
Diffstat (limited to 'java/com/android/dialer/calllog')
27 files changed, 315 insertions, 1445 deletions
diff --git a/java/com/android/dialer/calllog/CallLogComponent.java b/java/com/android/dialer/calllog/CallLogComponent.java index c7db2a1b8..5cdd2b4d0 100644 --- a/java/com/android/dialer/calllog/CallLogComponent.java +++ b/java/com/android/dialer/calllog/CallLogComponent.java @@ -25,8 +25,6 @@ public abstract class CallLogComponent { public abstract CallLogFramework callLogFramework(); - public abstract RefreshAnnotatedCallLogWorker getRefreshAnnotatedCallLogWorker(); - public static CallLogComponent get(Context context) { return ((HasComponent) ((HasRootComponent) context.getApplicationContext()).component()) .callLogComponent(); diff --git a/java/com/android/dialer/calllog/CallLogFramework.java b/java/com/android/dialer/calllog/CallLogFramework.java index 55ef15b47..508413b14 100644 --- a/java/com/android/dialer/calllog/CallLogFramework.java +++ b/java/com/android/dialer/calllog/CallLogFramework.java @@ -22,7 +22,6 @@ import android.preference.PreferenceManager; import android.support.annotation.MainThread; import android.support.annotation.Nullable; import com.android.dialer.calllog.datasources.CallLogDataSource; -import com.android.dialer.calllog.datasources.DataSources; import com.android.dialer.common.Assert; import com.android.dialer.common.ConfigProviderBindings; import com.android.dialer.common.LogUtil; @@ -39,6 +38,7 @@ import javax.inject.Singleton; public final class CallLogFramework implements CallLogDataSource.ContentObserverCallbacks { static final String PREF_FORCE_REBUILD = "callLogFrameworkForceRebuild"; + static final String PREF_LAST_REBUILD_TIMESTAMP_MILLIS = "callLogFrameworkLastRebuild"; private final DataSources dataSources; @@ -58,7 +58,6 @@ public final class CallLogFramework implements CallLogDataSource.ContentObserver LogUtil.enterBlock("CallLogFramework.registerContentObservers"); if (!isNewCallLogEnabled(appContext)) { - LogUtil.i("CallLogFramework.registerContentObservers", "new call log not enabled"); return; } diff --git a/java/com/android/dialer/calllog/CallLogModule.java b/java/com/android/dialer/calllog/CallLogModule.java index 2f2f16d5b..d7473a75e 100644 --- a/java/com/android/dialer/calllog/CallLogModule.java +++ b/java/com/android/dialer/calllog/CallLogModule.java @@ -17,9 +17,11 @@ package com.android.dialer.calllog; import com.android.dialer.calllog.datasources.CallLogDataSource; -import com.android.dialer.calllog.datasources.DataSources; import com.android.dialer.calllog.datasources.contacts.ContactsDataSource; import com.android.dialer.calllog.datasources.systemcalllog.SystemCallLogDataSource; +import com.android.dialer.common.concurrent.DefaultDialerExecutorFactory; +import com.android.dialer.common.concurrent.DialerExecutorFactory; +import dagger.Binds; import dagger.Module; import dagger.Provides; import java.util.Arrays; @@ -30,6 +32,10 @@ import java.util.List; @Module public abstract class CallLogModule { + @Binds + abstract DialerExecutorFactory bindDialerExecutorFactory( + DefaultDialerExecutorFactory defaultDialerExecutorFactory); + @Provides static DataSources provideCallLogDataSources( SystemCallLogDataSource systemCallLogDataSource, ContactsDataSource contactsDataSource) { diff --git a/java/com/android/dialer/calllog/datasources/DataSources.java b/java/com/android/dialer/calllog/DataSources.java index 911ca3fa3..21d190167 100644 --- a/java/com/android/dialer/calllog/datasources/DataSources.java +++ b/java/com/android/dialer/calllog/DataSources.java @@ -14,13 +14,14 @@ * limitations under the License */ -package com.android.dialer.calllog.datasources; +package com.android.dialer.calllog; +import com.android.dialer.calllog.datasources.CallLogDataSource; 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 { +interface DataSources { SystemCallLogDataSource getSystemCallLogDataSource(); diff --git a/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java b/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java index d25ec5e65..f9f0c9935 100644 --- a/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java +++ b/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java @@ -18,16 +18,14 @@ package com.android.dialer.calllog; import android.annotation.TargetApi; import android.content.Context; -import android.content.OperationApplicationException; import android.content.SharedPreferences; +import android.database.sqlite.SQLiteDatabase; import android.os.Build; -import android.os.RemoteException; import android.preference.PreferenceManager; import android.support.annotation.WorkerThread; -import com.android.dialer.calllog.database.CallLogDatabaseComponent; +import com.android.dialer.calllog.database.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.DataSources; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.DialerExecutor.Worker; @@ -36,65 +34,75 @@ import javax.inject.Inject; /** * Worker which brings the annotated call log up to date, if necessary. * - * <p>Accepts a boolean which indicates if the dirty check should be skipped. + * <p>Accepts a boolean which indicates if the dirty check should be skipped, and returns true if + * the annotated call log was updated. */ -public class RefreshAnnotatedCallLogWorker implements Worker<Boolean, Void> { +public class RefreshAnnotatedCallLogWorker implements Worker<Boolean, Boolean> { private final Context appContext; private final DataSources dataSources; @Inject - RefreshAnnotatedCallLogWorker(Context appContext, DataSources dataSources) { + public RefreshAnnotatedCallLogWorker(Context appContext, DataSources dataSources) { this.appContext = appContext; this.dataSources = dataSources; } @Override - public Void doInBackground(Boolean skipDirtyCheck) - throws RemoteException, OperationApplicationException { - LogUtil.enterBlock("RefreshAnnotatedCallLogWorker.doInBackground"); + public Boolean doInBackground(Boolean skipDirtyCheck) { + LogUtil.enterBlock("RefreshAnnotatedCallLogWorker.doInBackgroundFallible"); long startTime = System.currentTimeMillis(); - checkDirtyAndRebuildIfNecessary(appContext, skipDirtyCheck); + boolean annotatedCallLogUpdated = checkDirtyAndRebuildIfNecessary(appContext, skipDirtyCheck); LogUtil.i( - "RefreshAnnotatedCallLogWorker.doInBackground", - "took %dms", + "RefreshAnnotatedCallLogWorker.doInBackgroundFallible", + "updated? %s, took %dms", + annotatedCallLogUpdated, System.currentTimeMillis() - startTime); - return null; + return annotatedCallLogUpdated; } @WorkerThread - private void checkDirtyAndRebuildIfNecessary(Context appContext, boolean skipDirtyCheck) - throws RemoteException, OperationApplicationException { + private boolean checkDirtyAndRebuildIfNecessary(Context appContext, boolean skipDirtyCheck) { Assert.isWorkerThread(); long startTime = System.currentTimeMillis(); SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(appContext); - // Default to true. If the pref doesn't exist, the annotated call log hasn't been created and - // we just skip isDirty checks and force a rebuild. + long lastRebuildTimeMillis = + sharedPreferences.getLong(CallLogFramework.PREF_LAST_REBUILD_TIMESTAMP_MILLIS, 0); + if (lastRebuildTimeMillis == 0) { + LogUtil.i( + "RefreshAnnotatedCallLogWorker.checkDirtyAndRebuildIfNecessary", + "annotated call log has never been built, marking it dirty"); + } boolean forceRebuildPrefValue = - sharedPreferences.getBoolean(CallLogFramework.PREF_FORCE_REBUILD, true); + sharedPreferences.getBoolean(CallLogFramework.PREF_FORCE_REBUILD, false); if (forceRebuildPrefValue) { LogUtil.i( "RefreshAnnotatedCallLogWorker.checkDirtyAndRebuildIfNecessary", - "annotated call log has been marked dirty or does not exist"); + "call log has been marked dirty"); } - boolean isDirty = skipDirtyCheck || forceRebuildPrefValue || isDirty(appContext); - + boolean isDirty = + lastRebuildTimeMillis == 0 + || skipDirtyCheck + || forceRebuildPrefValue + || isDirty(appContext); LogUtil.i( "RefreshAnnotatedCallLogWorker.checkDirtyAndRebuildIfNecessary", "isDirty took: %dms", System.currentTimeMillis() - startTime); if (isDirty) { startTime = System.currentTimeMillis(); - rebuild(appContext); + rebuild(appContext, lastRebuildTimeMillis); LogUtil.i( "RefreshAnnotatedCallLogWorker.checkDirtyAndRebuildIfNecessary", "rebuild took: %dms", System.currentTimeMillis() - startTime); + return true; // Annotated call log was updated. } + return false; // Annotated call log was not updated. } @WorkerThread @@ -121,48 +129,51 @@ public class RefreshAnnotatedCallLogWorker implements Worker<Boolean, Void> { @TargetApi(Build.VERSION_CODES.M) // Uses try-with-resources @WorkerThread - private void rebuild(Context appContext) throws RemoteException, OperationApplicationException { + private void rebuild(Context appContext, long lastRebuildTimeMillis) { Assert.isWorkerThread(); - CallLogMutations mutations = new CallLogMutations(); + // TODO: Start a transaction? + try (SQLiteDatabase database = AnnotatedCallLog.getWritableDatabase(appContext)) { - // System call log data source must go first! - CallLogDataSource systemCallLogDataSource = dataSources.getSystemCallLogDataSource(); - String dataSourceName = getName(systemCallLogDataSource); - LogUtil.i("RefreshAnnotatedCallLogWorker.rebuild", "filling %s", dataSourceName); - long startTime = System.currentTimeMillis(); - systemCallLogDataSource.fill(appContext, mutations); - LogUtil.i( - "RefreshAnnotatedCallLogWorker.rebuild", - "%s.fill took: %dms", - dataSourceName, - System.currentTimeMillis() - startTime); + CallLogMutations mutations = new CallLogMutations(); - for (CallLogDataSource dataSource : dataSources.getDataSourcesExcludingSystemCallLog()) { - dataSourceName = getName(dataSource); + // System call log data source must go first! + CallLogDataSource systemCallLogDataSource = dataSources.getSystemCallLogDataSource(); + String dataSourceName = getName(systemCallLogDataSource); LogUtil.i("RefreshAnnotatedCallLogWorker.rebuild", "filling %s", dataSourceName); - startTime = System.currentTimeMillis(); - dataSource.fill(appContext, mutations); + long startTime = System.currentTimeMillis(); + systemCallLogDataSource.fill(appContext, database, lastRebuildTimeMillis, mutations); LogUtil.i( - "CallLogFramework.rebuild", + "RefreshAnnotatedCallLogWorker.rebuild", "%s.fill took: %dms", dataSourceName, System.currentTimeMillis() - startTime); + + for (CallLogDataSource dataSource : dataSources.getDataSourcesExcludingSystemCallLog()) { + dataSourceName = getName(dataSource); + LogUtil.i("RefreshAnnotatedCallLogWorker.rebuild", "filling %s", dataSourceName); + startTime = System.currentTimeMillis(); + dataSource.fill(appContext, database, lastRebuildTimeMillis, mutations); + LogUtil.i( + "CallLogFramework.rebuild", + "%s.fill took: %dms", + dataSourceName, + System.currentTimeMillis() - startTime); + } + LogUtil.i("RefreshAnnotatedCallLogWorker.rebuild", "applying mutations to database"); + startTime = System.currentTimeMillis(); + mutations.applyToDatabase(database); + LogUtil.i( + "RefreshAnnotatedCallLogWorker.rebuild", + "applyToDatabase took: %dms", + System.currentTimeMillis() - startTime); } - LogUtil.i("RefreshAnnotatedCallLogWorker.rebuild", "applying mutations to database"); - startTime = System.currentTimeMillis(); - CallLogDatabaseComponent.get(appContext) - .mutationApplier() - .applyToDatabase(mutations, appContext); - LogUtil.i( - "RefreshAnnotatedCallLogWorker.rebuild", - "applyToDatabase took: %dms", - System.currentTimeMillis() - startTime); SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(appContext); sharedPreferences .edit() .putBoolean(CallLogFramework.PREF_FORCE_REBUILD, false) + .putLong(CallLogFramework.PREF_LAST_REBUILD_TIMESTAMP_MILLIS, System.currentTimeMillis()) .commit(); } diff --git a/java/com/android/dialer/calllog/database/AndroidManifest.xml b/java/com/android/dialer/calllog/database/AndroidManifest.xml deleted file mode 100644 index 396a6d9a1..000000000 --- a/java/com/android/dialer/calllog/database/AndroidManifest.xml +++ /dev/null @@ -1,28 +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 - --> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.dialer.calllog.database"> - - <application> - - <provider - android:authorities="com.android.dialer.annotatedcalllog" - android:exported="false" - android:multiprocess="false" - android:name=".AnnotatedCallLogContentProvider"/> - - </application> -</manifest> diff --git a/java/com/android/dialer/calllog/database/AnnotatedCallLog.java b/java/com/android/dialer/calllog/database/AnnotatedCallLog.java new file mode 100644 index 000000000..7dca44a60 --- /dev/null +++ b/java/com/android/dialer/calllog/database/AnnotatedCallLog.java @@ -0,0 +1,53 @@ +/* + * 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.database; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.support.annotation.WorkerThread; +import com.android.dialer.common.Assert; + +/** Static methods and constants for interacting with the annotated call log table. */ +public final class AnnotatedCallLog { + + private static final String DATABASE_NAME = "annotated_call_log.db"; + + public static final String TABLE_NAME = "AnnotatedCallLog"; + + /** Column names for the annotated call log table. */ + public static final class Columns { + public static final String ID = "_id"; + public static final String TIMESTAMP = "timestamp"; + public static final String CONTACT_NAME = "contact_name"; + } + + private AnnotatedCallLog() {} + + @WorkerThread + public static SQLiteDatabase getWritableDatabase(Context appContext) { + Assert.isWorkerThread(); + + return new AnnotatedCallLogDatabaseHelper(appContext, DATABASE_NAME).getWritableDatabase(); + } + + @WorkerThread + public static SQLiteDatabase getReadableDatabase(Context appContext) { + Assert.isWorkerThread(); + + return new AnnotatedCallLogDatabaseHelper(appContext, DATABASE_NAME).getReadableDatabase(); + } +} diff --git a/java/com/android/dialer/calllog/database/AnnotatedCallLogContentProvider.java b/java/com/android/dialer/calllog/database/AnnotatedCallLogContentProvider.java deleted file mode 100644 index a9c0d36b0..000000000 --- a/java/com/android/dialer/calllog/database/AnnotatedCallLogContentProvider.java +++ /dev/null @@ -1,310 +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.database; - -import android.annotation.TargetApi; -import android.content.ContentProvider; -import android.content.ContentProviderOperation; -import android.content.ContentProviderResult; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.OperationApplicationException; -import android.content.UriMatcher; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteQueryBuilder; -import android.net.Uri; -import android.os.Build; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract; -import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog; -import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.CoalescedAnnotatedCallLog; -import com.android.dialer.common.Assert; -import com.android.dialer.common.LogUtil; -import java.util.ArrayList; - -/** {@link ContentProvider} for the annotated call log. */ -public class AnnotatedCallLogContentProvider extends ContentProvider { - - private static final int ANNOTATED_CALL_LOG_TABLE_CODE = 1; - private static final int ANNOTATED_CALL_LOG_TABLE_ID_CODE = 2; - private static final int COALESCED_ANNOTATED_CALL_LOG_TABLE_CODE = 3; - - private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); - - static { - uriMatcher.addURI( - AnnotatedCallLogContract.AUTHORITY, AnnotatedCallLog.TABLE, ANNOTATED_CALL_LOG_TABLE_CODE); - uriMatcher.addURI( - AnnotatedCallLogContract.AUTHORITY, - AnnotatedCallLog.TABLE + "/#", - ANNOTATED_CALL_LOG_TABLE_ID_CODE); - uriMatcher.addURI( - AnnotatedCallLogContract.AUTHORITY, - CoalescedAnnotatedCallLog.TABLE, - COALESCED_ANNOTATED_CALL_LOG_TABLE_CODE); - } - - private AnnotatedCallLogDatabaseHelper databaseHelper; - private Coalescer coalescer; - - private final ThreadLocal<Boolean> applyingBatch = new ThreadLocal<>(); - - /** Ensures that only a single notification is generated from {@link #applyBatch(ArrayList)}. */ - private boolean isApplyingBatch() { - return applyingBatch.get() != null && applyingBatch.get(); - } - - @Override - public boolean onCreate() { - databaseHelper = new AnnotatedCallLogDatabaseHelper(getContext()); - coalescer = CallLogDatabaseComponent.get(getContext()).coalescer(); - return true; - } - - @TargetApi(Build.VERSION_CODES.M) // Uses try-with-resources - @Nullable - @Override - public Cursor query( - @NonNull Uri uri, - @Nullable String[] projection, - @Nullable String selection, - @Nullable String[] selectionArgs, - @Nullable String sortOrder) { - SQLiteDatabase db = databaseHelper.getReadableDatabase(); - SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); - queryBuilder.setTables(AnnotatedCallLog.TABLE); - int match = uriMatcher.match(uri); - switch (match) { - case ANNOTATED_CALL_LOG_TABLE_ID_CODE: - queryBuilder.appendWhere(AnnotatedCallLog._ID + "=" + ContentUris.parseId(uri)); - // fall through - case ANNOTATED_CALL_LOG_TABLE_CODE: - Cursor cursor = - queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder); - if (cursor != null) { - cursor.setNotificationUri( - getContext().getContentResolver(), AnnotatedCallLog.CONTENT_URI); - } else { - LogUtil.w("AnnotatedCallLogContentProvider.query", "cursor was null"); - } - return cursor; - case COALESCED_ANNOTATED_CALL_LOG_TABLE_CODE: - Assert.checkArgument(projection == null, "projection not supported for coalesced call log"); - Assert.checkArgument(selection == null, "selection not supported for coalesced call log"); - Assert.checkArgument( - selectionArgs == null, "selection args not supported for coalesced call log"); - Assert.checkArgument(sortOrder == null, "sort order not supported for coalesced call log"); - try (Cursor allAnnotatedCallLogRows = - queryBuilder.query( - db, null, null, null, null, null, AnnotatedCallLog.TIMESTAMP + " DESC")) { - Cursor coalescedRows = coalescer.coalesce(allAnnotatedCallLogRows); - coalescedRows.setNotificationUri( - getContext().getContentResolver(), CoalescedAnnotatedCallLog.CONTENT_URI); - return coalescedRows; - } - default: - throw new IllegalArgumentException("Unknown uri: " + uri); - } - } - - @Nullable - @Override - public String getType(@NonNull Uri uri) { - return AnnotatedCallLog.CONTENT_ITEM_TYPE; - } - - @Nullable - @Override - public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { - // Javadoc states values is not nullable, even though it is annotated as such (b/38123194)! - Assert.checkArgument(values != null); - - SQLiteDatabase database = databaseHelper.getWritableDatabase(); - int match = uriMatcher.match(uri); - switch (match) { - case ANNOTATED_CALL_LOG_TABLE_CODE: - Assert.checkArgument( - values.get(AnnotatedCallLog._ID) != null, "You must specify an _ID when inserting"); - break; - case ANNOTATED_CALL_LOG_TABLE_ID_CODE: - Long idFromUri = ContentUris.parseId(uri); - Long idFromValues = values.getAsLong(AnnotatedCallLog._ID); - Assert.checkArgument( - idFromValues == null || idFromValues.equals(idFromUri), - "_ID from values %d does not match ID from URI: %s", - idFromValues, - uri); - if (idFromValues == null) { - values.put(AnnotatedCallLog._ID, idFromUri); - } - break; - case COALESCED_ANNOTATED_CALL_LOG_TABLE_CODE: - throw new UnsupportedOperationException("coalesced call log does not support inserting"); - default: - throw new IllegalArgumentException("Unknown uri: " + uri); - } - long id = database.insert(AnnotatedCallLog.TABLE, null, values); - if (id < 0) { - LogUtil.w( - "AnnotatedCallLogContentProvider.insert", - "error inserting row with id: %d", - values.get(AnnotatedCallLog._ID)); - return null; - } - Uri insertedUri = ContentUris.withAppendedId(AnnotatedCallLog.CONTENT_URI, id); - if (!isApplyingBatch()) { - notifyChange(insertedUri); - } - return insertedUri; - } - - @Override - public int delete( - @NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) { - SQLiteDatabase database = databaseHelper.getWritableDatabase(); - final int match = uriMatcher.match(uri); - switch (match) { - case ANNOTATED_CALL_LOG_TABLE_CODE: - break; - case ANNOTATED_CALL_LOG_TABLE_ID_CODE: - Assert.checkArgument(selection == null, "Do not specify selection when deleting by ID"); - Assert.checkArgument( - selectionArgs == null, "Do not specify selection args when deleting by ID"); - long id = ContentUris.parseId(uri); - Assert.checkArgument(id != -1, "error parsing id from uri %s", uri); - selection = getSelectionWithId(id); - break; - case COALESCED_ANNOTATED_CALL_LOG_TABLE_CODE: - throw new UnsupportedOperationException("coalesced call log does not support deleting"); - default: - throw new IllegalArgumentException("Unknown uri: " + uri); - } - int rows = database.delete(AnnotatedCallLog.TABLE, selection, selectionArgs); - if (rows > 0) { - if (!isApplyingBatch()) { - notifyChange(uri); - } - } else { - LogUtil.w("AnnotatedCallLogContentProvider.delete", "no rows deleted"); - } - return rows; - } - - @Override - public int update( - @NonNull Uri uri, - @Nullable ContentValues values, - @Nullable String selection, - @Nullable String[] selectionArgs) { - // Javadoc states values is not nullable, even though it is annotated as such (b/38123194)! - Assert.checkArgument(values != null); - - SQLiteDatabase database = databaseHelper.getWritableDatabase(); - int match = uriMatcher.match(uri); - switch (match) { - case ANNOTATED_CALL_LOG_TABLE_CODE: - break; - case ANNOTATED_CALL_LOG_TABLE_ID_CODE: - Assert.checkArgument( - !values.containsKey(AnnotatedCallLog._ID), "Do not specify _ID when updating by ID"); - Assert.checkArgument(selection == null, "Do not specify selection when updating by ID"); - Assert.checkArgument( - selectionArgs == null, "Do not specify selection args when updating by ID"); - selection = getSelectionWithId(ContentUris.parseId(uri)); - break; - case COALESCED_ANNOTATED_CALL_LOG_TABLE_CODE: - throw new UnsupportedOperationException("coalesced call log does not support updating"); - default: - throw new IllegalArgumentException("Unknown uri: " + uri); - } - int rows = database.update(AnnotatedCallLog.TABLE, values, selection, selectionArgs); - if (rows > 0) { - if (!isApplyingBatch()) { - notifyChange(uri); - } - } else { - LogUtil.w("AnnotatedCallLogContentProvider.update", "no rows updated"); - } - return rows; - } - - /** - * {@inheritDoc} - * - * <p>Note: When applyBatch is used with the AnnotatedCallLog, only a single notification for the - * content URI is generated, not individual notifications for each affected URI. - */ - @NonNull - @Override - public ContentProviderResult[] applyBatch(@NonNull ArrayList<ContentProviderOperation> operations) - throws OperationApplicationException { - ContentProviderResult[] results = new ContentProviderResult[operations.size()]; - if (operations.isEmpty()) { - return results; - } - - SQLiteDatabase database = databaseHelper.getWritableDatabase(); - try { - applyingBatch.set(true); - database.beginTransaction(); - for (int i = 0; i < operations.size(); i++) { - ContentProviderOperation operation = operations.get(i); - int match = uriMatcher.match(operation.getUri()); - switch (match) { - case ANNOTATED_CALL_LOG_TABLE_CODE: - case ANNOTATED_CALL_LOG_TABLE_ID_CODE: - // These are allowed values, continue. - break; - case COALESCED_ANNOTATED_CALL_LOG_TABLE_CODE: - throw new UnsupportedOperationException( - "coalesced call log does not support applyBatch"); - default: - throw new IllegalArgumentException("Unknown uri: " + operation.getUri()); - } - ContentProviderResult result = operation.apply(this, results, i); - if (operations.get(i).isInsert()) { - if (result.uri == null) { - throw new OperationApplicationException("error inserting row"); - } - } else if (result.count == 0) { - throw new OperationApplicationException("error updating or deleting rows"); - } - results[i] = result; - } - database.setTransactionSuccessful(); - } finally { - applyingBatch.set(false); - database.endTransaction(); - } - notifyChange(AnnotatedCallLog.CONTENT_URI); - return results; - } - - private String getSelectionWithId(long id) { - return AnnotatedCallLog._ID + "=" + id; - } - - private void notifyChange(Uri uri) { - getContext().getContentResolver().notifyChange(uri, null); - // Any time the annotated call log changes, we need to also notify observers of the - // CoalescedAnnotatedCallLog, since that is just a massaged in-memory view of the real annotated - // call log table. - getContext().getContentResolver().notifyChange(CoalescedAnnotatedCallLog.CONTENT_URI, null); - } -} diff --git a/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java b/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java index 3cca639ff..7b28e5505 100644 --- a/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java +++ b/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java @@ -16,25 +16,28 @@ package com.android.dialer.calllog.database; +import static com.android.dialer.calllog.database.AnnotatedCallLog.Columns.CONTACT_NAME; +import static com.android.dialer.calllog.database.AnnotatedCallLog.Columns.ID; +import static com.android.dialer.calllog.database.AnnotatedCallLog.Columns.TIMESTAMP; + import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; -import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog; import com.android.dialer.common.LogUtil; /** {@link SQLiteOpenHelper} for the AnnotatedCallLog database. */ class AnnotatedCallLogDatabaseHelper extends SQLiteOpenHelper { - AnnotatedCallLogDatabaseHelper(Context appContext) { - super(appContext, "annotated_call_log.db", null, 1); + AnnotatedCallLogDatabaseHelper(Context appContext, String databaseName) { + super(appContext, databaseName, null, 1); } private static final String CREATE_SQL = new StringBuilder() - .append("create table if not exists " + AnnotatedCallLog.TABLE + " (") - .append(AnnotatedCallLog._ID + " integer primary key, ") - .append(AnnotatedCallLog.TIMESTAMP + " integer, ") - .append(AnnotatedCallLog.CONTACT_NAME + " string") + .append("create table if not exists " + AnnotatedCallLog.TABLE_NAME + " (") + .append(ID + " integer primary key, ") + .append(TIMESTAMP + " integer, ") + .append(CONTACT_NAME + " string") .append(");") .toString(); diff --git a/java/com/android/dialer/calllog/database/CallLogDatabaseComponent.java b/java/com/android/dialer/calllog/database/CallLogDatabaseComponent.java deleted file mode 100644 index ede46911c..000000000 --- a/java/com/android/dialer/calllog/database/CallLogDatabaseComponent.java +++ /dev/null @@ -1,40 +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.database; - -import android.content.Context; -import com.android.dialer.inject.HasRootComponent; -import dagger.Subcomponent; - -/** Dagger component for database package. */ -@Subcomponent -public abstract class CallLogDatabaseComponent { - - public abstract Coalescer coalescer(); - - public abstract MutationApplier mutationApplier(); - - public static CallLogDatabaseComponent get(Context context) { - return ((CallLogDatabaseComponent.HasComponent) - ((HasRootComponent) context.getApplicationContext()).component()) - .callLogDatabaseComponent(); - } - - /** Used to refer to the root application component. */ - public interface HasComponent { - CallLogDatabaseComponent callLogDatabaseComponent(); - } -} diff --git a/java/com/android/dialer/calllog/database/CallLogMutations.java b/java/com/android/dialer/calllog/database/CallLogMutations.java new file mode 100644 index 000000000..ec020c6af --- /dev/null +++ b/java/com/android/dialer/calllog/database/CallLogMutations.java @@ -0,0 +1,58 @@ +/* + * 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.database; + +import android.content.ContentValues; +import android.database.sqlite.SQLiteDatabase; +import android.support.annotation.WorkerThread; +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<Integer, ContentValues> inserts = new ArrayMap<>(); + private final ArrayMap<Integer, ContentValues> updates = new ArrayMap<>(); + private final ArraySet<Integer> deletes = new ArraySet<>(); + + /** @param contentValues an entire row not including the ID */ + public void insert(int id, ContentValues contentValues) { + inserts.put(id, contentValues); + } + + /** @param contentValues the specific columns to update, not including the ID. */ + public void update(int id, ContentValues contentValues) { + // TODO: Consider merging automatically. + updates.put(id, contentValues); + } + + public void delete(int id) { + deletes.add(id); + } + + public boolean isEmpty() { + return inserts.isEmpty() && updates.isEmpty() && deletes.isEmpty(); + } + + @WorkerThread + public void applyToDatabase(SQLiteDatabase writableDatabase) { + Assert.isWorkerThread(); + + // TODO: Implementation. + } +} diff --git a/java/com/android/dialer/calllog/database/Coalescer.java b/java/com/android/dialer/calllog/database/Coalescer.java deleted file mode 100644 index e3dfb7ece..000000000 --- a/java/com/android/dialer/calllog/database/Coalescer.java +++ /dev/null @@ -1,142 +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.database; - -import android.content.ContentValues; -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.database.MatrixCursor; -import android.support.annotation.NonNull; -import android.support.annotation.WorkerThread; -import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog; -import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.CoalescedAnnotatedCallLog; -import com.android.dialer.calllog.datasources.CallLogDataSource; -import com.android.dialer.calllog.datasources.DataSources; -import com.android.dialer.common.Assert; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import javax.inject.Inject; - -/** - * Coalesces call log rows by combining some adjacent rows. - * - * <p>Applies the business which logic which determines which adjacent rows should be coalasced, and - * then delegates to each data source to determine how individual columns should be aggregated. - */ -public class Coalescer { - - private final DataSources dataSources; - - @Inject - Coalescer(DataSources dataSources) { - this.dataSources = dataSources; - } - - /** - * Reads the entire {@link AnnotatedCallLog} database into memory from the provided {@code - * allAnnotatedCallLog} parameter and then builds and returns a new {@link MatrixCursor} which is - * the result of combining adjacent rows which should be collapsed for display purposes. - * - * @param allAnnotatedCallLogRowsSortedByTimestampDesc all {@link AnnotatedCallLog} rows, sorted - * by timestamp descending - * @return a new {@link MatrixCursor} containing the {@link CoalescedAnnotatedCallLog} rows to - * display - */ - @WorkerThread - @NonNull - Cursor coalesce(@NonNull Cursor allAnnotatedCallLogRowsSortedByTimestampDesc) { - Assert.isWorkerThread(); - - // Note: This method relies on rowsShouldBeCombined to determine which rows should be combined, - // but delegates to data sources to actually aggregate column values. - - MatrixCursor allCoalescedRowsMatrixCursor = - new MatrixCursor( - CoalescedAnnotatedCallLog.ALL_COLUMNS, - Assert.isNotNull(allAnnotatedCallLogRowsSortedByTimestampDesc).getCount()); - - if (allAnnotatedCallLogRowsSortedByTimestampDesc.moveToFirst()) { - int coalescedRowId = 0; - - List<ContentValues> currentRowGroup = new ArrayList<>(); - - do { - ContentValues currentRow = new ContentValues(); - DatabaseUtils.cursorRowToContentValues( - allAnnotatedCallLogRowsSortedByTimestampDesc, currentRow); - - if (currentRowGroup.isEmpty()) { - currentRowGroup.add(currentRow); - continue; - } - - ContentValues previousRow = currentRowGroup.get(currentRowGroup.size() - 1); - - if (!rowsShouldBeCombined(previousRow, currentRow)) { - ContentValues coalescedRow = coalesceRowsForAllDataSources(currentRowGroup); - coalescedRow.put(CoalescedAnnotatedCallLog.NUMBER_CALLS, currentRowGroup.size()); - addContentValuesToMatrixCursor( - coalescedRow, allCoalescedRowsMatrixCursor, coalescedRowId++); - currentRowGroup.clear(); - } - currentRowGroup.add(currentRow); - } while (allAnnotatedCallLogRowsSortedByTimestampDesc.moveToNext()); - - // Deal with leftover rows. - ContentValues coalescedRow = coalesceRowsForAllDataSources(currentRowGroup); - coalescedRow.put(CoalescedAnnotatedCallLog.NUMBER_CALLS, currentRowGroup.size()); - addContentValuesToMatrixCursor(coalescedRow, allCoalescedRowsMatrixCursor, coalescedRowId); - } - return allCoalescedRowsMatrixCursor; - } - - /** - * @param row1 a row from {@link AnnotatedCallLog} - * @param row2 a row from {@link AnnotatedCallLog} - */ - private static boolean rowsShouldBeCombined(ContentValues row1, ContentValues row2) { - // TODO: Real implementation. - return row1.get(AnnotatedCallLog.TIMESTAMP).equals(row2.get(AnnotatedCallLog.TIMESTAMP)); - } - - /** - * Delegates to data sources to aggregate individual columns to create a new coalesced row. - * - * @param individualRows {@link AnnotatedCallLog} rows sorted by timestamp descending - * @return a {@link CoalescedAnnotatedCallLog} row - */ - private ContentValues coalesceRowsForAllDataSources(List<ContentValues> individualRows) { - ContentValues coalescedValues = new ContentValues(); - for (CallLogDataSource dataSource : dataSources.getDataSourcesIncludingSystemCallLog()) { - coalescedValues.putAll(dataSource.coalesce(individualRows)); - } - return coalescedValues; - } - - /** - * @param contentValues a {@link CoalescedAnnotatedCallLog} row - * @param matrixCursor represents {@link CoalescedAnnotatedCallLog} - */ - private static void addContentValuesToMatrixCursor( - ContentValues contentValues, MatrixCursor matrixCursor, int rowId) { - MatrixCursor.RowBuilder rowBuilder = matrixCursor.newRow(); - rowBuilder.add(CoalescedAnnotatedCallLog._ID, rowId); - for (Map.Entry<String, Object> entry : contentValues.valueSet()) { - rowBuilder.add(entry.getKey(), entry.getValue()); - } - } -} diff --git a/java/com/android/dialer/calllog/database/MutationApplier.java b/java/com/android/dialer/calllog/database/MutationApplier.java deleted file mode 100644 index 21c8a507d..000000000 --- a/java/com/android/dialer/calllog/database/MutationApplier.java +++ /dev/null @@ -1,105 +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.database; - -import android.content.ContentProviderOperation; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.content.OperationApplicationException; -import android.os.RemoteException; -import android.support.annotation.WorkerThread; -import android.text.TextUtils; -import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract; -import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog; -import com.android.dialer.calllog.datasources.CallLogMutations; -import com.android.dialer.common.Assert; -import com.android.dialer.common.LogUtil; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Map.Entry; -import javax.inject.Inject; - -/** Applies {@link CallLogMutations} to the annotated call log. */ -public class MutationApplier { - - @Inject - MutationApplier() {} - - /** Applies the provided {@link CallLogMutations} to the annotated call log. */ - @WorkerThread - public void applyToDatabase(CallLogMutations mutations, Context appContext) - throws RemoteException, OperationApplicationException { - Assert.isWorkerThread(); - - if (mutations.isEmpty()) { - return; - } - - ArrayList<ContentProviderOperation> operations = new ArrayList<>(); - - if (!mutations.getInserts().isEmpty()) { - LogUtil.i( - "CallLogMutations.applyToDatabase", "inserting %d rows", mutations.getInserts().size()); - for (Entry<Long, ContentValues> entry : mutations.getInserts().entrySet()) { - long id = entry.getKey(); - ContentValues contentValues = entry.getValue(); - operations.add( - ContentProviderOperation.newInsert( - ContentUris.withAppendedId(AnnotatedCallLog.CONTENT_URI, id)) - .withValues(contentValues) - .build()); - } - } - - if (!mutations.getUpdates().isEmpty()) { - LogUtil.i( - "CallLogMutations.applyToDatabase", "updating %d rows", mutations.getUpdates().size()); - for (Entry<Long, ContentValues> entry : mutations.getUpdates().entrySet()) { - long id = entry.getKey(); - ContentValues contentValues = entry.getValue(); - operations.add( - ContentProviderOperation.newUpdate( - ContentUris.withAppendedId(AnnotatedCallLog.CONTENT_URI, id)) - .withValues(contentValues) - .build()); - } - } - - if (!mutations.getDeletes().isEmpty()) { - LogUtil.i( - "CallLogMutations.applyToDatabase", "deleting %d rows", mutations.getDeletes().size()); - String[] questionMarks = new String[mutations.getDeletes().size()]; - Arrays.fill(questionMarks, "?"); - - String whereClause = - (AnnotatedCallLog._ID + " in (") + TextUtils.join(",", questionMarks) + ")"; - - String[] whereArgs = new String[mutations.getDeletes().size()]; - int i = 0; - for (long id : mutations.getDeletes()) { - whereArgs[i++] = String.valueOf(id); - } - - operations.add( - ContentProviderOperation.newDelete(AnnotatedCallLog.CONTENT_URI) - .withSelection(whereClause, whereArgs) - .build()); - } - - appContext.getContentResolver().applyBatch(AnnotatedCallLogContract.AUTHORITY, operations); - } -} diff --git a/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java b/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java deleted file mode 100644 index 8b3b0a852..000000000 --- a/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java +++ /dev/null @@ -1,114 +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.database.contract; - -import android.net.Uri; -import android.provider.BaseColumns; -import com.android.dialer.constants.Constants; -import java.util.Arrays; - -/** Contract for the AnnotatedCallLog content provider. */ -public class AnnotatedCallLogContract { - public static final String AUTHORITY = Constants.get().getAnnotatedCallLogProviderAuthority(); - - public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY); - - /** - * Columns shared by {@link AnnotatedCallLog} and {@link CoalescedAnnotatedCallLog}. - * - * <p>When adding columns be sure to update {@link #ALL_COMMON_COLUMNS}. - */ - interface CommonColumns extends BaseColumns { - - /** - * Timestamp of the entry, in milliseconds. - * - * <p>Type: INTEGER (long) - */ - String TIMESTAMP = "timestamp"; - - /** - * Name to display for the entry. - * - * <p>Type: TEXT - */ - String CONTACT_NAME = "contact_name"; - - String[] ALL_COMMON_COLUMNS = new String[] {_ID, TIMESTAMP, CONTACT_NAME}; - } - - /** - * AnnotatedCallLog table. - * - * <p>This contains all of the non-coalesced call log entries. - */ - public static final class AnnotatedCallLog implements CommonColumns { - - public static final String TABLE = "AnnotatedCallLog"; - - /** The content URI for this table. */ - public static final Uri CONTENT_URI = - Uri.withAppendedPath(AnnotatedCallLogContract.CONTENT_URI, TABLE); - - /** The MIME type of a {@link android.content.ContentProvider#getType(Uri)} single entry. */ - public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/annotated_call_log"; - } - - /** - * Coalesced view of the AnnotatedCallLog table. - * - * <p>This is an in-memory view of the {@link AnnotatedCallLog} with some adjacent entries - * collapsed. - * - * <p>When adding columns be sure to update {@link #COLUMNS_ONLY_IN_COALESCED_CALL_LOG}. - */ - public static final class CoalescedAnnotatedCallLog implements CommonColumns { - - public static final String TABLE = "CoalescedAnnotatedCallLog"; - - /** The content URI for this table. */ - public static final Uri CONTENT_URI = - Uri.withAppendedPath(AnnotatedCallLogContract.CONTENT_URI, TABLE); - - /** The MIME type of a {@link android.content.ContentProvider#getType(Uri)} single entry. */ - public static final String CONTENT_ITEM_TYPE = - "vnd.android.cursor.item/coalesced_annotated_call_log"; - - /** - * Number of AnnotatedCallLog rows represented by this CoalescedAnnotatedCallLog row. - * - * <p>Type: INTEGER - */ - public static final String NUMBER_CALLS = "number_calls"; - - /** - * Columns that are only in the {@link CoalescedAnnotatedCallLog} but not the {@link - * AnnotatedCallLog}. - */ - private static final String[] COLUMNS_ONLY_IN_COALESCED_CALL_LOG = new String[] {NUMBER_CALLS}; - - /** All columns in the {@link CoalescedAnnotatedCallLog}. */ - public static final String[] ALL_COLUMNS = - concat(ALL_COMMON_COLUMNS, COLUMNS_ONLY_IN_COALESCED_CALL_LOG); - } - - private static String[] concat(String[] first, String[] second) { - String[] result = Arrays.copyOf(first, first.length + second.length); - System.arraycopy(second, 0, result, first.length, second.length); - return result; - } -} 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/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; - } -} diff --git a/java/com/android/dialer/calllog/testing/FakeCallLogApplication.java b/java/com/android/dialer/calllog/testing/FakeCallLogApplication.java deleted file mode 100644 index cb2240539..000000000 --- a/java/com/android/dialer/calllog/testing/FakeCallLogApplication.java +++ /dev/null @@ -1,39 +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.testing; - -import android.app.Application; -import com.android.dialer.calllog.CallLogModule; -import com.android.dialer.calllog.database.CallLogDatabaseComponent; -import com.android.dialer.inject.HasRootComponent; -import dagger.Component; -import javax.inject.Singleton; - -/** - * Fake application for call log robolectric tests which uses all real bindings but doesn't require - * tests to depend on and use all of DialerApplication. - */ -public final class FakeCallLogApplication extends Application implements HasRootComponent { - - @Override - public Object component() { - return DaggerFakeCallLogApplication_FakeComponent.create(); - } - - @Singleton - @Component(modules = CallLogModule.class) - interface FakeComponent extends CallLogDatabaseComponent.HasComponent {} -} diff --git a/java/com/android/dialer/calllog/ui/AndroidManifest.xml b/java/com/android/dialer/calllog/ui/AndroidManifest.xml index eaf71aba8..228167749 100644 --- a/java/com/android/dialer/calllog/ui/AndroidManifest.xml +++ b/java/com/android/dialer/calllog/ui/AndroidManifest.xml @@ -13,4 +13,4 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License --> -<manifest package="com.android.dialer.calllog.ui"/> +<manifest package="com.android.dialer.calllog"/> diff --git a/java/com/android/dialer/calllog/ui/AnnotatedCallLogCursorLoader.java b/java/com/android/dialer/calllog/ui/AnnotatedCallLogCursorLoader.java new file mode 100644 index 000000000..cd8622e80 --- /dev/null +++ b/java/com/android/dialer/calllog/ui/AnnotatedCallLogCursorLoader.java @@ -0,0 +1,48 @@ +/* + * 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.ui; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.CursorLoader; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.os.Build; +import com.android.dialer.calllog.database.AnnotatedCallLog; +import com.android.dialer.calllog.database.AnnotatedCallLog.Columns; + +/** CursorLoader which reads the annotated call log. */ +class AnnotatedCallLogCursorLoader extends CursorLoader { + + AnnotatedCallLogCursorLoader(Context context) { + super(context); + } + + @TargetApi(Build.VERSION_CODES.M) // Uses try-with-resources + @Override + public Cursor loadInBackground() { + try (SQLiteDatabase readableDatabase = AnnotatedCallLog.getReadableDatabase(getContext())) { + return readableDatabase.rawQuery( + "SELECT * FROM " + + AnnotatedCallLog.TABLE_NAME + + " ORDER BY " + + Columns.TIMESTAMP + + " DESC", + null /* selectionArgs */); + } + } +} diff --git a/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java b/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java deleted file mode 100644 index f9ab21cb3..000000000 --- a/java/com/android/dialer/calllog/ui/NewCallLogAdapter.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.ui; - -import android.database.Cursor; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.ViewGroup; -import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.CoalescedAnnotatedCallLog; - -/** {@link RecyclerView.Adapter} for the new call log fragment. */ -final class NewCallLogAdapter extends RecyclerView.Adapter<NewCallLogViewHolder> { - - private final Cursor cursor; - private final int timestampIndex; - - NewCallLogAdapter(Cursor cursor) { - this.cursor = cursor; - timestampIndex = cursor.getColumnIndexOrThrow(CoalescedAnnotatedCallLog.TIMESTAMP); - } - - @Override - public NewCallLogViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { - return new NewCallLogViewHolder( - LayoutInflater.from(viewGroup.getContext()) - .inflate(R.layout.new_call_log_entry, viewGroup, false)); - } - - @Override - public void onBindViewHolder(NewCallLogViewHolder viewHolder, int position) { - cursor.moveToPosition(position); - long timestamp = cursor.getLong(timestampIndex); - viewHolder.bind(timestamp); - } - - @Override - public int getItemCount() { - return cursor.getCount(); - } -} diff --git a/java/com/android/dialer/calllog/ui/NewCallLogFragment.java b/java/com/android/dialer/calllog/ui/NewCallLogFragment.java index 89ed52fd7..b8f2b1326 100644 --- a/java/com/android/dialer/calllog/ui/NewCallLogFragment.java +++ b/java/com/android/dialer/calllog/ui/NewCallLogFragment.java @@ -17,30 +17,30 @@ package com.android.dialer.calllog.ui; import android.app.Fragment; import android.app.LoaderManager.LoaderCallbacks; -import android.content.CursorLoader; +import android.content.Context; import android.content.Loader; import android.database.Cursor; import android.os.Bundle; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.CursorAdapter; +import android.widget.ListView; +import android.widget.SimpleCursorAdapter; +import android.widget.TextView; import com.android.dialer.calllog.CallLogComponent; import com.android.dialer.calllog.CallLogFramework; import com.android.dialer.calllog.CallLogFramework.CallLogUi; -import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.CoalescedAnnotatedCallLog; +import com.android.dialer.calllog.database.AnnotatedCallLog.Columns; import com.android.dialer.common.LogUtil; -import com.android.dialer.common.concurrent.DialerExecutor; -import com.android.dialer.common.concurrent.DialerExecutorComponent; -import com.android.dialer.common.concurrent.DialerExecutorFactory; +import java.text.SimpleDateFormat; +import java.util.Locale; /** The "new" call log fragment implementation, which is built on top of the annotated call log. */ public final class NewCallLogFragment extends Fragment implements CallLogUi, LoaderCallbacks<Cursor> { - private DialerExecutor<Boolean> refreshAnnotatedCallLogTask; - private RecyclerView recyclerView; + private CursorAdapter cursorAdapter; public NewCallLogFragment() { LogUtil.enterBlock("NewCallLogFragment.NewCallLogFragment"); @@ -52,27 +52,8 @@ public final class NewCallLogFragment extends Fragment LogUtil.enterBlock("NewCallLogFragment.onCreate"); - CallLogComponent component = CallLogComponent.get(getContext()); - CallLogFramework callLogFramework = component.callLogFramework(); + CallLogFramework callLogFramework = CallLogComponent.get(getContext()).callLogFramework(); callLogFramework.attachUi(this); - - DialerExecutorFactory dialerExecutorFactory = - DialerExecutorComponent.get(getContext()).dialerExecutorFactory(); - - refreshAnnotatedCallLogTask = - dialerExecutorFactory - .createUiTaskBuilder( - getFragmentManager(), - "NewCallLogFragment.refreshAnnotatedCallLog", - component.getRefreshAnnotatedCallLogWorker()) - .build(); - } - - @Override - public void onStart() { - super.onStart(); - - LogUtil.enterBlock("NewCallLogFragment.onStart"); } @Override @@ -83,9 +64,6 @@ public final class NewCallLogFragment extends Fragment CallLogFramework callLogFramework = CallLogComponent.get(getContext()).callLogFramework(); callLogFramework.attachUi(this); - - // TODO: Consider doing this when fragment becomes visible. - checkAnnotatedCallLogDirtyAndRefreshIfNecessary(); } @Override @@ -104,44 +82,57 @@ public final class NewCallLogFragment extends Fragment LogUtil.enterBlock("NewCallLogFragment.onCreateView"); View view = inflater.inflate(R.layout.new_call_log_fragment, container, false); - recyclerView = view.findViewById(R.id.new_call_log_recycler_view); + ListView listView = (ListView) view.findViewById(R.id.list); - getLoaderManager().restartLoader(0, null, this); + this.cursorAdapter = + new MyCursorAdapter( + getContext(), + R.layout.new_call_log_entry, + null /* cursor */, + new String[] {Columns.TIMESTAMP, Columns.CONTACT_NAME}, + new int[] {R.id.timestamp, R.id.contact_name}, + 0); + listView.setAdapter(cursorAdapter); - return view; - } + getLoaderManager().initLoader(0, null, this); - private void checkAnnotatedCallLogDirtyAndRefreshIfNecessary() { - LogUtil.enterBlock("NewCallLogFragment.checkAnnotatedCallLogDirtyAndRefreshIfNecessary"); - refreshAnnotatedCallLogTask.executeSerial(false /* skipDirtyCheck */); + return view; } @Override public void invalidateUi() { LogUtil.enterBlock("NewCallLogFragment.invalidateUi"); - refreshAnnotatedCallLogTask.executeSerial(true /* skipDirtyCheck */); + // TODO: Implementation. } @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { - LogUtil.enterBlock("NewCallLogFragment.onCreateLoader"); - // CoalescedAnnotatedCallLog requires that all params be null. - return new CursorLoader( - getContext(), CoalescedAnnotatedCallLog.CONTENT_URI, null, null, null, null); + // TODO: This is sort of weird, do we need to implement a content provider? + return new AnnotatedCallLogCursorLoader(getContext()); } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor newCursor) { - LogUtil.enterBlock("NewCallLogFragment.onLoadFinished"); - - // TODO: Handle empty cursor by showing empty view. - recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); - recyclerView.setAdapter(new NewCallLogAdapter(newCursor)); + cursorAdapter.swapCursor(newCursor); } @Override public void onLoaderReset(Loader<Cursor> loader) { - LogUtil.enterBlock("NewCallLogFragment.onLoaderReset"); - recyclerView.setAdapter(null); + cursorAdapter.swapCursor(null); + } + + private static class MyCursorAdapter extends SimpleCursorAdapter { + + MyCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to, int flags) { + super(context, layout, c, from, to, flags); + } + + @Override + public void setViewText(TextView view, String text) { + if (view.getId() == R.id.timestamp) { + text = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US).format(Long.valueOf(text)); + } + view.setText(text); + } } } diff --git a/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java b/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java deleted file mode 100644 index 4c459e123..000000000 --- a/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java +++ /dev/null @@ -1,43 +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.ui; - -import android.support.v7.widget.RecyclerView; -import android.view.View; -import android.widget.TextView; -import java.text.SimpleDateFormat; -import java.util.Locale; - -/** {@link RecyclerView.ViewHolder} for the new call log. */ -final class NewCallLogViewHolder extends RecyclerView.ViewHolder { - - // TODO: Format correctly using current locale. - private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US); - - private final TextView contactNameView; - private final TextView timestampView; - - NewCallLogViewHolder(View view) { - super(view); - contactNameView = view.findViewById(R.id.contact_name); - timestampView = view.findViewById(R.id.timestamp); - } - - void bind(long timestamp) { - contactNameView.setText("Contact Name Placeholder"); - timestampView.setText(dateFormat.format(timestamp)); - } -} diff --git a/java/com/android/dialer/calllog/ui/res/layout/new_call_log_entry.xml b/java/com/android/dialer/calllog/ui/res/layout/new_call_log_entry.xml index 99797fab4..ee3efd002 100644 --- a/java/com/android/dialer/calllog/ui/res/layout/new_call_log_entry.xml +++ b/java/com/android/dialer/calllog/ui/res/layout/new_call_log_entry.xml @@ -18,20 +18,16 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:padding="8dp" - android:orientation="vertical"> + android:layout_height="match_parent" + android:orientation="horizontal"> <TextView - android:id="@+id/contact_name" + android:id="@+id/timestamp" android:layout_width="wrap_content" - android:layout_height="wrap_content" - style="@style/PrimaryText"/> + android:layout_height="wrap_content"/> <TextView - android:id="@+id/timestamp" + android:id="@+id/contact_name" android:layout_width="wrap_content" - android:layout_height="wrap_content" - style="@style/SecondaryText"/> - + android:layout_height="wrap_content"/> </LinearLayout>
\ No newline at end of file diff --git a/java/com/android/dialer/calllog/ui/res/layout/new_call_log_fragment.xml b/java/com/android/dialer/calllog/ui/res/layout/new_call_log_fragment.xml index e1d8410b6..433dbdd0f 100644 --- a/java/com/android/dialer/calllog/ui/res/layout/new_call_log_fragment.xml +++ b/java/com/android/dialer/calllog/ui/res/layout/new_call_log_fragment.xml @@ -15,9 +15,8 @@ ~ limitations under the License --> -<android.support.v7.widget.RecyclerView +<ListView xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/new_call_log_recycler_view" + android:id="@+id/list" android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@color/background_dialer_light"/> + android:layout_height="match_parent"/> |