From 8369df095a73a77b3715f8ae7ba06089cebca4ce Mon Sep 17 00:00:00 2001 From: Eric Erfanian Date: Wed, 3 May 2017 10:27:13 -0700 Subject: This change reflects the Dialer V10 RC00 branch. RC00 is based on: branch: dialer-android_release_branch/153304843.1 synced to: 153304843 following the instructions at go/dialer-aosp-release. In this release: * Removes final apache sources. * Uses native lite compilation. More drops will follow with subsequent release candidates until we reach our final v10 release, in cadence with our prebuilt drops. Test: TreeHugger, on device Change-Id: Ic9684057230f9b579c777820c746cd21bf45ec0f --- .../android/dialer/calllog/CallLogComponent.java | 37 +++++ .../android/dialer/calllog/CallLogFramework.java | 117 +++++++++++++ java/com/android/dialer/calllog/CallLogModule.java | 62 +++++++ java/com/android/dialer/calllog/DataSources.java | 31 ++++ .../calllog/RefreshAnnotatedCallLogWorker.java | 183 +++++++++++++++++++++ .../dialer/calllog/database/AnnotatedCallLog.java | 53 ++++++ .../database/AnnotatedCallLogDatabaseHelper.java | 58 +++++++ .../dialer/calllog/database/CallLogMutations.java | 58 +++++++ .../calllog/datasources/CallLogDataSource.java | 68 ++++++++ .../datasources/contacts/ContactsDataSource.java | 58 +++++++ .../systemcalllog/SystemCallLogDataSource.java | 114 +++++++++++++ .../android/dialer/calllog/ui/AndroidManifest.xml | 16 ++ .../calllog/ui/AnnotatedCallLogCursorLoader.java | 48 ++++++ .../dialer/calllog/ui/NewCallLogFragment.java | 138 ++++++++++++++++ .../calllog/ui/res/layout/new_call_log_entry.xml | 33 ++++ .../ui/res/layout/new_call_log_fragment.xml | 22 +++ 16 files changed, 1096 insertions(+) create mode 100644 java/com/android/dialer/calllog/CallLogComponent.java create mode 100644 java/com/android/dialer/calllog/CallLogFramework.java create mode 100644 java/com/android/dialer/calllog/CallLogModule.java create mode 100644 java/com/android/dialer/calllog/DataSources.java create mode 100644 java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java create mode 100644 java/com/android/dialer/calllog/database/AnnotatedCallLog.java create mode 100644 java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java create mode 100644 java/com/android/dialer/calllog/database/CallLogMutations.java create mode 100644 java/com/android/dialer/calllog/datasources/CallLogDataSource.java create mode 100644 java/com/android/dialer/calllog/datasources/contacts/ContactsDataSource.java create mode 100644 java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java create mode 100644 java/com/android/dialer/calllog/ui/AndroidManifest.xml create mode 100644 java/com/android/dialer/calllog/ui/AnnotatedCallLogCursorLoader.java create mode 100644 java/com/android/dialer/calllog/ui/NewCallLogFragment.java create mode 100644 java/com/android/dialer/calllog/ui/res/layout/new_call_log_entry.xml create mode 100644 java/com/android/dialer/calllog/ui/res/layout/new_call_log_fragment.xml (limited to 'java/com/android/dialer/calllog') diff --git a/java/com/android/dialer/calllog/CallLogComponent.java b/java/com/android/dialer/calllog/CallLogComponent.java new file mode 100644 index 000000000..5cdd2b4d0 --- /dev/null +++ b/java/com/android/dialer/calllog/CallLogComponent.java @@ -0,0 +1,37 @@ +/* + * 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; + +import android.content.Context; +import com.android.dialer.inject.HasRootComponent; +import dagger.Subcomponent; + +/** Dagger component for the call log package. */ +@Subcomponent +public abstract class CallLogComponent { + + public abstract CallLogFramework callLogFramework(); + + public static CallLogComponent get(Context context) { + return ((HasComponent) ((HasRootComponent) context.getApplicationContext()).component()) + .callLogComponent(); + } + + /** Used to refer to the root application component. */ + public interface HasComponent { + CallLogComponent callLogComponent(); + } +} diff --git a/java/com/android/dialer/calllog/CallLogFramework.java b/java/com/android/dialer/calllog/CallLogFramework.java new file mode 100644 index 000000000..508413b14 --- /dev/null +++ b/java/com/android/dialer/calllog/CallLogFramework.java @@ -0,0 +1,117 @@ +/* + * 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; + +import android.content.Context; +import android.content.SharedPreferences; +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.common.Assert; +import com.android.dialer.common.ConfigProviderBindings; +import com.android.dialer.common.LogUtil; +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Coordinates work across CallLog data sources to detect if the annotated call log is out of date + * ("dirty") and update it if necessary. + * + *

All methods should be called on the main thread. + */ +@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; + + @Nullable private CallLogUi ui; + + @Inject + CallLogFramework(DataSources dataSources) { + this.dataSources = dataSources; + } + + public boolean isNewCallLogEnabled(Context context) { + return ConfigProviderBindings.get(context).getBoolean("enable_new_call_log_tab", false); + } + + /** Registers the content observers for all data sources. */ + public void registerContentObservers(Context appContext) { + LogUtil.enterBlock("CallLogFramework.registerContentObservers"); + + if (!isNewCallLogEnabled(appContext)) { + return; + } + + for (CallLogDataSource dataSource : dataSources.getDataSourcesIncludingSystemCallLog()) { + dataSource.registerContentObservers(appContext, this); + } + } + + /** + * Attach a UI component to the framework so that it may be notified of changes to the annotated + * call log. + */ + public void attachUi(CallLogUi ui) { + LogUtil.enterBlock("CallLogFramework.attachUi"); + this.ui = ui; + } + + /** + * Detaches the UI from the framework. This should be called when the UI is hidden or destroyed + * and no longer needs to be notified of changes to the annotated call log. + */ + public void detachUi() { + LogUtil.enterBlock("CallLogFramework.detachUi"); + this.ui = null; + } + + /** + * Marks the call log as dirty and notifies any attached UI components. If there are no UI + * components currently attached, this is an efficient operation since it is just writing a shared + * pref. + * + *

We don't want to actually force a rebuild when there is no UI running because we don't want + * to be constantly rebuilding the database when the device is sitting on a desk and receiving a + * lot of calls, for example. + */ + @Override + @MainThread + public void markDirtyAndNotify(Context appContext) { + Assert.isMainThread(); + LogUtil.enterBlock("CallLogFramework.markDirtyAndNotify"); + + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(appContext); + sharedPreferences.edit().putBoolean(PREF_FORCE_REBUILD, true).apply(); + + if (ui != null) { + ui.invalidateUi(); + } + } + + /** Callbacks invoked on listening UI components. */ + public interface CallLogUi { + + /** Notifies the call log UI that the annotated call log is out of date. */ + @MainThread + void invalidateUi(); + } +} diff --git a/java/com/android/dialer/calllog/CallLogModule.java b/java/com/android/dialer/calllog/CallLogModule.java new file mode 100644 index 000000000..d7473a75e --- /dev/null +++ b/java/com/android/dialer/calllog/CallLogModule.java @@ -0,0 +1,62 @@ +/* + * 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; + +import com.android.dialer.calllog.datasources.CallLogDataSource; +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; +import java.util.Collections; +import java.util.List; + +/** Dagger module which satisfies call log dependencies. */ +@Module +public abstract class CallLogModule { + + @Binds + abstract DialerExecutorFactory bindDialerExecutorFactory( + DefaultDialerExecutorFactory defaultDialerExecutorFactory); + + @Provides + static DataSources provideCallLogDataSources( + SystemCallLogDataSource systemCallLogDataSource, ContactsDataSource contactsDataSource) { + // System call log must be first, see getDataSourcesExcludingSystemCallLog below. + List allDataSources = + Collections.unmodifiableList(Arrays.asList(systemCallLogDataSource, contactsDataSource)); + return new DataSources() { + @Override + public SystemCallLogDataSource getSystemCallLogDataSource() { + return systemCallLogDataSource; + } + + @Override + public List getDataSourcesIncludingSystemCallLog() { + return allDataSources; + } + + @Override + public List getDataSourcesExcludingSystemCallLog() { + return allDataSources.subList(1, allDataSources.size()); + } + }; + } +} diff --git a/java/com/android/dialer/calllog/DataSources.java b/java/com/android/dialer/calllog/DataSources.java new file mode 100644 index 000000000..21d190167 --- /dev/null +++ b/java/com/android/dialer/calllog/DataSources.java @@ -0,0 +1,31 @@ +/* + * 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; + +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. */ +interface DataSources { + + SystemCallLogDataSource getSystemCallLogDataSource(); + + List getDataSourcesIncludingSystemCallLog(); + + List getDataSourcesExcludingSystemCallLog(); +} diff --git a/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java b/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java new file mode 100644 index 000000000..f9f0c9935 --- /dev/null +++ b/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java @@ -0,0 +1,183 @@ +/* + * 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; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.SharedPreferences; +import android.database.sqlite.SQLiteDatabase; +import android.os.Build; +import android.preference.PreferenceManager; +import android.support.annotation.WorkerThread; +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.common.Assert; +import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.DialerExecutor.Worker; +import javax.inject.Inject; + +/** + * Worker which brings the annotated call log up to date, if necessary. + * + *

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 { + + private final Context appContext; + private final DataSources dataSources; + + @Inject + public RefreshAnnotatedCallLogWorker(Context appContext, DataSources dataSources) { + this.appContext = appContext; + this.dataSources = dataSources; + } + + @Override + public Boolean doInBackground(Boolean skipDirtyCheck) { + LogUtil.enterBlock("RefreshAnnotatedCallLogWorker.doInBackgroundFallible"); + + long startTime = System.currentTimeMillis(); + boolean annotatedCallLogUpdated = checkDirtyAndRebuildIfNecessary(appContext, skipDirtyCheck); + LogUtil.i( + "RefreshAnnotatedCallLogWorker.doInBackgroundFallible", + "updated? %s, took %dms", + annotatedCallLogUpdated, + System.currentTimeMillis() - startTime); + return annotatedCallLogUpdated; + } + + @WorkerThread + private boolean checkDirtyAndRebuildIfNecessary(Context appContext, boolean skipDirtyCheck) { + Assert.isWorkerThread(); + + long startTime = System.currentTimeMillis(); + + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(appContext); + 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, false); + if (forceRebuildPrefValue) { + LogUtil.i( + "RefreshAnnotatedCallLogWorker.checkDirtyAndRebuildIfNecessary", + "call log has been marked dirty"); + } + + 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, 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 + private boolean isDirty(Context appContext) { + Assert.isWorkerThread(); + + for (CallLogDataSource dataSource : dataSources.getDataSourcesIncludingSystemCallLog()) { + String dataSourceName = getName(dataSource); + long startTime = System.currentTimeMillis(); + LogUtil.i("RefreshAnnotatedCallLogWorker.isDirty", "running isDirty for %s", dataSourceName); + boolean isDirty = dataSource.isDirty(appContext); + LogUtil.i( + "RefreshAnnotatedCallLogWorker.isDirty", + "%s.isDirty returned %b in %dms", + dataSourceName, + isDirty, + System.currentTimeMillis() - startTime); + if (isDirty) { + return true; + } + } + return false; + } + + @TargetApi(Build.VERSION_CODES.M) // Uses try-with-resources + @WorkerThread + private void rebuild(Context appContext, long lastRebuildTimeMillis) { + Assert.isWorkerThread(); + + // TODO: Start a transaction? + try (SQLiteDatabase database = AnnotatedCallLog.getWritableDatabase(appContext)) { + + CallLogMutations mutations = new CallLogMutations(); + + // 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, database, lastRebuildTimeMillis, mutations); + LogUtil.i( + "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); + } + + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(appContext); + sharedPreferences + .edit() + .putBoolean(CallLogFramework.PREF_FORCE_REBUILD, false) + .putLong(CallLogFramework.PREF_LAST_REBUILD_TIMESTAMP_MILLIS, System.currentTimeMillis()) + .commit(); + } + + private static String getName(CallLogDataSource dataSource) { + return dataSource.getClass().getSimpleName(); + } +} 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/AnnotatedCallLogDatabaseHelper.java b/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java new file mode 100644 index 000000000..7b28e5505 --- /dev/null +++ b/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.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 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.common.LogUtil; + +/** {@link SQLiteOpenHelper} for the AnnotatedCallLog database. */ +class AnnotatedCallLogDatabaseHelper extends SQLiteOpenHelper { + + 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_NAME + " (") + .append(ID + " integer primary key, ") + .append(TIMESTAMP + " integer, ") + .append(CONTACT_NAME + " string") + .append(");") + .toString(); + + @Override + public void onCreate(SQLiteDatabase db) { + LogUtil.enterBlock("AnnotatedCallLogDatabaseHelper.onCreate"); + long startTime = System.currentTimeMillis(); + db.execSQL(CREATE_SQL); + // TODO: Consider logging impression. + LogUtil.i( + "AnnotatedCallLogDatabaseHelper.onCreate", + "took: %dms", + System.currentTimeMillis() - startTime); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {} +} 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 inserts = new ArrayMap<>(); + private final ArrayMap updates = new ArrayMap<>(); + private final ArraySet 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/datasources/CallLogDataSource.java b/java/com/android/dialer/calllog/datasources/CallLogDataSource.java new file mode 100644 index 000000000..13d0b842d --- /dev/null +++ b/java/com/android/dialer/calllog/datasources/CallLogDataSource.java @@ -0,0 +1,68 @@ +/* + * 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.Context; +import android.database.sqlite.SQLiteDatabase; +import android.support.annotation.MainThread; +import android.support.annotation.WorkerThread; +import com.android.dialer.calllog.database.CallLogMutations; + +/** A source of data for one or more columns in the annotated call log. */ +public interface CallLogDataSource { + + /** + * A lightweight check which runs frequently to detect if the annotated call log is out of date + * with respect to this data source. + * + *

This is typically used to detect external changes to the underlying data source which have + * been made in such a way that the dialer application was not notified. + * + *

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. + */ + @WorkerThread + boolean isDirty(Context appContext); + + /** + * Computes the set of mutations necessary to update the annotated call log with respect to this + * data source. + * + * @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, + SQLiteDatabase readableDatabase, + long lastRebuildTimeMillis, + CallLogMutations mutations); + + @MainThread + void registerContentObservers( + Context appContext, ContentObserverCallbacks contentObserverCallbacks); + + /** + * Methods which may optionally be called as a result of a data source's content observer firing. + */ + interface ContentObserverCallbacks { + @MainThread + void markDirtyAndNotify(Context appContext); + } +} diff --git a/java/com/android/dialer/calllog/datasources/contacts/ContactsDataSource.java b/java/com/android/dialer/calllog/datasources/contacts/ContactsDataSource.java new file mode 100644 index 000000000..241be5d71 --- /dev/null +++ b/java/com/android/dialer/calllog/datasources/contacts/ContactsDataSource.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.datasources.contacts; + +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.CallLogMutations; +import com.android.dialer.calllog.datasources.CallLogDataSource; +import com.android.dialer.common.Assert; +import javax.inject.Inject; + +/** Responsible for maintaining the contacts related columns in the annotated call log. */ +public final class ContactsDataSource implements CallLogDataSource { + + @Inject + public ContactsDataSource() {} + + @WorkerThread + @Override + public boolean isDirty(Context appContext) { + Assert.isWorkerThread(); + + // TODO: Implementation. + return false; + } + + @WorkerThread + @Override + public void fill( + Context appContext, + SQLiteDatabase readableDatabase, + long lastRebuildTimeMillis, + CallLogMutations mutations) { + Assert.isWorkerThread(); + // TODO: Implementation. + } + + @MainThread + @Override + public void registerContentObservers( + Context appContext, ContentObserverCallbacks contentObserverCallbacks) {} +} diff --git a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java new file mode 100644 index 000000000..1cc51ee99 --- /dev/null +++ b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java @@ -0,0 +1,114 @@ +/* + * 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.systemcalllog; + +import android.content.Context; +import android.database.ContentObserver; +import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; +import android.os.Handler; +import android.provider.CallLog; +import android.support.annotation.MainThread; +import android.support.annotation.WorkerThread; +import com.android.dialer.calllog.database.CallLogMutations; +import com.android.dialer.calllog.datasources.CallLogDataSource; +import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.ThreadUtil; +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. + */ +public class SystemCallLogDataSource implements CallLogDataSource { + + @Inject + public SystemCallLogDataSource() {} + + @MainThread + @Override + public void registerContentObservers( + Context appContext, ContentObserverCallbacks contentObserverCallbacks) { + Assert.isMainThread(); + + appContext + .getContentResolver() + .registerContentObserver( + CallLog.Calls.CONTENT_URI, + true, + new CallLogObserver( + ThreadUtil.getUiThreadHandler(), appContext, contentObserverCallbacks)); + } + + @WorkerThread + @Override + public boolean isDirty(Context appContext) { + Assert.isWorkerThread(); + + /* + * The system call log has a last updated timestamp, but deletes are physical (the "deleted" + * column is unused). This means that we can't detect deletes without scanning the entire table, + * which would be too slow. So, we just rely on content observers to trigger rebuilds when any + * change is made to the system call log. + */ + return false; + } + + @WorkerThread + @Override + public void fill( + Context appContext, + SQLiteDatabase readableDatabase, + long lastRebuildTimeMillis, + CallLogMutations mutations) { + Assert.isWorkerThread(); + + // This data source should always run first so the mutations should always be empty. + Assert.checkState(mutations.isEmpty()); + + // TODO: Implementation. + } + + private static class CallLogObserver extends ContentObserver { + private final Context appContext; + private final ContentObserverCallbacks contentObserverCallbacks; + + CallLogObserver( + Handler handler, Context appContext, ContentObserverCallbacks contentObserverCallbacks) { + super(handler); + this.appContext = appContext; + this.contentObserverCallbacks = contentObserverCallbacks; + } + + @MainThread + @Override + public void onChange(boolean selfChange, Uri uri) { + Assert.isMainThread(); + LogUtil.enterBlock("SystemCallLogDataSource.CallLogObserver.onChange"); + super.onChange(selfChange, uri); + + /* + * The system call log has a last updated timestamp, but deletes are physical (the "deleted" + * column is unused). This means that we can't detect deletes without scanning the entire + * table, which would be too slow. So, we just rely on content observers to trigger rebuilds + * when any change is made to the system call log. + */ + contentObserverCallbacks.markDirtyAndNotify(appContext); + } + } +} diff --git a/java/com/android/dialer/calllog/ui/AndroidManifest.xml b/java/com/android/dialer/calllog/ui/AndroidManifest.xml new file mode 100644 index 000000000..228167749 --- /dev/null +++ b/java/com/android/dialer/calllog/ui/AndroidManifest.xml @@ -0,0 +1,16 @@ + + 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/NewCallLogFragment.java b/java/com/android/dialer/calllog/ui/NewCallLogFragment.java new file mode 100644 index 000000000..b8f2b1326 --- /dev/null +++ b/java/com/android/dialer/calllog/ui/NewCallLogFragment.java @@ -0,0 +1,138 @@ +/* + * 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.app.Fragment; +import android.app.LoaderManager.LoaderCallbacks; +import android.content.Context; +import android.content.Loader; +import android.database.Cursor; +import android.os.Bundle; +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.AnnotatedCallLog.Columns; +import com.android.dialer.common.LogUtil; +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 { + + private CursorAdapter cursorAdapter; + + public NewCallLogFragment() { + LogUtil.enterBlock("NewCallLogFragment.NewCallLogFragment"); + } + + @Override + public void onCreate(Bundle state) { + super.onCreate(state); + + LogUtil.enterBlock("NewCallLogFragment.onCreate"); + + CallLogFramework callLogFramework = CallLogComponent.get(getContext()).callLogFramework(); + callLogFramework.attachUi(this); + } + + @Override + public void onResume() { + super.onResume(); + + LogUtil.enterBlock("NewCallLogFragment.onResume"); + + CallLogFramework callLogFramework = CallLogComponent.get(getContext()).callLogFramework(); + callLogFramework.attachUi(this); + } + + @Override + public void onPause() { + super.onPause(); + + LogUtil.enterBlock("NewCallLogFragment.onPause"); + + CallLogFramework callLogFramework = CallLogComponent.get(getContext()).callLogFramework(); + callLogFramework.detachUi(); + } + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + LogUtil.enterBlock("NewCallLogFragment.onCreateView"); + + View view = inflater.inflate(R.layout.new_call_log_fragment, container, false); + ListView listView = (ListView) view.findViewById(R.id.list); + + 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); + + getLoaderManager().initLoader(0, null, this); + + return view; + } + + @Override + public void invalidateUi() { + LogUtil.enterBlock("NewCallLogFragment.invalidateUi"); + // TODO: Implementation. + } + + @Override + public Loader onCreateLoader(int id, Bundle args) { + // TODO: This is sort of weird, do we need to implement a content provider? + return new AnnotatedCallLogCursorLoader(getContext()); + } + + @Override + public void onLoadFinished(Loader loader, Cursor newCursor) { + cursorAdapter.swapCursor(newCursor); + } + + @Override + public void onLoaderReset(Loader loader) { + 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/res/layout/new_call_log_entry.xml b/java/com/android/dialer/calllog/ui/res/layout/new_call_log_entry.xml new file mode 100644 index 000000000..ee3efd002 --- /dev/null +++ b/java/com/android/dialer/calllog/ui/res/layout/new_call_log_entry.xml @@ -0,0 +1,33 @@ + + + + + + + + + \ 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 new file mode 100644 index 000000000..433dbdd0f --- /dev/null +++ b/java/com/android/dialer/calllog/ui/res/layout/new_call_log_fragment.xml @@ -0,0 +1,22 @@ + + + + -- cgit v1.2.3