diff options
author | zachh <zachh@google.com> | 2017-08-15 18:13:03 -0700 |
---|---|---|
committer | Eric Erfanian <erfanian@google.com> | 2017-08-30 03:45:26 +0000 |
commit | 4e9a7930756e2e1dbfba20355f8f716987835a3a (patch) | |
tree | a4434a38478029d4f8c44396b0a2b82c20d90622 /java/com/android | |
parent | f1fa776384ceab6682ad86348e3d08e3ca2b983b (diff) |
Added a few more columns to annotated call log.
These are mostly columns that are just copied from the system call log.
Also refactored and implemented new logic for displaying call log durations and dates (not used yet).
Bug: 34672501
Test: existing and new
PiperOrigin-RevId: 165387731
Change-Id: I2bc736d848b5c10e42562e62beea64efdeed9c12
Diffstat (limited to 'java/com/android')
15 files changed, 518 insertions, 85 deletions
diff --git a/java/com/android/dialer/calldetails/CallDetailsEntryViewHolder.java b/java/com/android/dialer/calldetails/CallDetailsEntryViewHolder.java index 9c592fc5f..084bd667c 100644 --- a/java/com/android/dialer/calldetails/CallDetailsEntryViewHolder.java +++ b/java/com/android/dialer/calldetails/CallDetailsEntryViewHolder.java @@ -28,7 +28,8 @@ import android.view.View; import android.widget.ImageView; import android.widget.TextView; import com.android.dialer.calldetails.CallDetailsEntries.CallDetailsEntry; -import com.android.dialer.calllogutils.CallEntryFormatter; +import com.android.dialer.calllogutils.CallLogDates; +import com.android.dialer.calllogutils.CallLogDurations; import com.android.dialer.calllogutils.CallTypeHelper; import com.android.dialer.calllogutils.CallTypeIconsView; import com.android.dialer.common.LogUtil; @@ -103,16 +104,17 @@ public class CallDetailsEntryViewHolder extends ViewHolder { callTypeText.setText( callTypeHelper.getCallTypeText(callType, isVideoCall, isPulledCall, isLightbringerCall)); - callTime.setText(CallEntryFormatter.formatDate(context, entry.getDate())); + callTime.setText(CallLogDates.formatDate(context, entry.getDate())); + if (CallTypeHelper.isMissedCallType(callType)) { callDuration.setVisibility(View.GONE); } else { callDuration.setVisibility(View.VISIBLE); callDuration.setText( - CallEntryFormatter.formatDurationAndDataUsage( + CallLogDurations.formatDurationAndDataUsage( context, entry.getDuration(), entry.getDataUsage())); callDuration.setContentDescription( - CallEntryFormatter.formatDurationAndDataUsageA11y( + CallLogDurations.formatDurationAndDataUsageA11y( context, entry.getDuration(), entry.getDataUsage())); } setMultimediaDetails(number, entry, showMultimediaDivider); diff --git a/java/com/android/dialer/calllog/database/AnnotatedCallLogContentProvider.java b/java/com/android/dialer/calllog/database/AnnotatedCallLogContentProvider.java index 30aa2bff5..9a3d2e20f 100644 --- a/java/com/android/dialer/calllog/database/AnnotatedCallLogContentProvider.java +++ b/java/com/android/dialer/calllog/database/AnnotatedCallLogContentProvider.java @@ -112,7 +112,9 @@ public class AnnotatedCallLogContentProvider extends ContentProvider { } return cursor; case COALESCED_ANNOTATED_CALL_LOG_TABLE_CODE: - Assert.checkArgument(projection == null, "projection not supported for coalesced call log"); + Assert.checkArgument( + projection == CoalescedAnnotatedCallLog.ALL_COLUMNS, + "only ALL_COLUMNS projection 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"); diff --git a/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java b/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java index 5f48d7b1f..ebfd3c79b 100644 --- a/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java +++ b/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java @@ -35,9 +35,20 @@ class AnnotatedCallLogDatabaseHelper extends SQLiteOpenHelper { private static final String CREATE_TABLE_SQL = new StringBuilder() .append("create table if not exists " + AnnotatedCallLog.TABLE + " (") + // Common columns. .append(AnnotatedCallLog._ID + " integer primary key, ") .append(AnnotatedCallLog.TIMESTAMP + " integer, ") - .append(AnnotatedCallLog.CONTACT_NAME + " string, ") + .append(AnnotatedCallLog.PRIMARY_TEXT + " string, ") + .append(AnnotatedCallLog.CONTACT_PHOTO_URI + " string, ") + .append(AnnotatedCallLog.NUMBER_TYPE_LABEL + " string, ") + .append(AnnotatedCallLog.IS_READ + " integer, ") + .append(AnnotatedCallLog.GEOCODED_LOCATION + " string, ") + .append(AnnotatedCallLog.PHONE_ACCOUNT_LABEL + " string, ") + .append(AnnotatedCallLog.PHONE_ACCOUNT_COLOR + " integer, ") + .append(AnnotatedCallLog.FEATURES + " integer, ") + .append(AnnotatedCallLog.IS_BUSINESS + " integer, ") + .append(AnnotatedCallLog.IS_VOICEMAIL + " integer, ") + // Columns only in AnnotatedCallLog .append(AnnotatedCallLog.NUMBER + " blob") .append(");") .toString(); diff --git a/java/com/android/dialer/calllog/database/annotated_call_log.proto b/java/com/android/dialer/calllog/database/annotated_call_log.proto new file mode 100644 index 000000000..eb0ee52ce --- /dev/null +++ b/java/com/android/dialer/calllog/database/annotated_call_log.proto @@ -0,0 +1,14 @@ +syntax = "proto2"; + +option java_package = "com.android.dialer"; +option java_multiple_files = true; +option optimize_for = LITE_RUNTIME; + +// DIALER_SCRUB.UNCOMMENT_IN_OPEN_SOURCE option optimize_for = LITE_RUNTIME; + +package com.android.dialer; + +// A list of android.provider.CallLog.Calls.TYPE values. +message CallTypes { + repeated int32 type = 1; +} diff --git a/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java b/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java index 7f314e37c..172006878 100644 --- a/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java +++ b/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java @@ -42,13 +42,103 @@ public class AnnotatedCallLogContract { String TIMESTAMP = "timestamp"; /** - * Name to display for the entry. + * Primary text to display for the entry. This could be a name from a local contact or caller ID + * data source, or it could just be a phone number, for example. + * + * <p>This is exactly how it should appear to the user. If the user's locale or name display + * preferences change, this column should be rewritten. * * <p>Type: TEXT */ - String CONTACT_NAME = "contact_name"; + String PRIMARY_TEXT = "primary_text"; + + /** + * Local photo URI for the contact associated with the phone number, if it exists. + * + * <p>Photos currently only come from local contacts database and not caller ID sources. If + * there is no photo for a contact then an appropriate letter tile should be drawn. + * + * <p>TYPE: TEXT + */ + String CONTACT_PHOTO_URI = "contact_photo_uri"; + + // TODO(zachh): If we need to support photos other than local contacts', add a (blob?) column. + + /** + * The number type as a string to be displayed to the user, for example "Home" or "Mobile". + * + * <p>This column should be updated for the appropriate language when the locale changes. + * + * <p>TYPE: TEXT + */ + String NUMBER_TYPE_LABEL = "number_type_label"; + + /** + * See CallLog.Calls.IS_READ. + * + * <p>TYPE: INTEGER (boolean) + */ + String IS_READ = "is_read"; - String[] ALL_COMMON_COLUMNS = new String[] {_ID, TIMESTAMP, CONTACT_NAME}; + /** + * See CallLog.Calls.GEOCODED_LOCATION. + * + * <p>TYPE: TEXT + */ + String GEOCODED_LOCATION = "geocoded_location"; + + /** + * String suitable for display which indicates the phone account used to make the call. + * + * <p>TYPE: TEXT + */ + String PHONE_ACCOUNT_LABEL = "phone_account_label"; + + /** + * The color int for the phone account. + * + * <p>TYPE: INTEGER (int) + */ + String PHONE_ACCOUNT_COLOR = "phone_account_color"; + + /** + * See CallLog.Calls.FEATURES. + * + * <p>TYPE: INTEGER (int) + */ + String FEATURES = "features"; + + /** + * True if a caller ID data source informed us that this is a business number. This is used to + * determine if a generic business avatar should be shown vs. a generic person avatar. + * + * <p>TYPE: INTEGER (boolean) + */ + String IS_BUSINESS = "is_business"; + + /** + * True if this was a call to voicemail. This is used to determine if the voicemail avatar + * should be displayed. + * + * <p>TYPE: INTEGER (boolean) + */ + String IS_VOICEMAIL = "is_voicemail"; + + String[] ALL_COMMON_COLUMNS = + new String[] { + _ID, + TIMESTAMP, + PRIMARY_TEXT, + CONTACT_PHOTO_URI, + NUMBER_TYPE_LABEL, + IS_READ, + GEOCODED_LOCATION, + PHONE_ACCOUNT_LABEL, + PHONE_ACCOUNT_COLOR, + FEATURES, + IS_BUSINESS, + IS_VOICEMAIL + }; } /** @@ -116,11 +206,18 @@ public class AnnotatedCallLogContract { public static final String FORMATTED_NUMBER = "formatted_number"; /** + * The call types of the most recent 3 calls, encoded as a CallTypes proto. + * + * <p>TYPE: BLOB + */ + public static final String CALL_TYPES = "call_types"; + + /** * 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, FORMATTED_NUMBER}; + new String[] {NUMBER_CALLS, FORMATTED_NUMBER, CALL_TYPES}; /** All columns in the {@link CoalescedAnnotatedCallLog}. */ public static final String[] ALL_COLUMNS = diff --git a/java/com/android/dialer/calllog/datasources/contacts/ContactsDataSource.java b/java/com/android/dialer/calllog/datasources/contacts/ContactsDataSource.java index db7421515..ad824274e 100644 --- a/java/com/android/dialer/calllog/datasources/contacts/ContactsDataSource.java +++ b/java/com/android/dialer/calllog/datasources/contacts/ContactsDataSource.java @@ -51,7 +51,7 @@ public final class ContactsDataSource implements CallLogDataSource { Assert.isWorkerThread(); // TODO(zachh): Implementation. for (ContentValues contentValues : mutations.getInserts().values()) { - contentValues.put(AnnotatedCallLog.CONTACT_NAME, "Placeholder name"); + contentValues.put(AnnotatedCallLog.PRIMARY_TEXT, "Placeholder name"); } } @@ -64,7 +64,7 @@ public final class ContactsDataSource implements CallLogDataSource { public ContentValues coalesce(List<ContentValues> individualRowsSortedByTimestampDesc) { // TODO(zachh): Implementation. return new RowCombiner(individualRowsSortedByTimestampDesc) - .useSingleValueString(AnnotatedCallLog.CONTACT_NAME) + .useSingleValueString(AnnotatedCallLog.PRIMARY_TEXT) .combine(); } diff --git a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java index 86145a95b..94b908f8f 100644 --- a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java +++ b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java @@ -28,10 +28,13 @@ import android.os.Handler; import android.preference.PreferenceManager; import android.provider.CallLog; import android.provider.CallLog.Calls; +import android.provider.ContactsContract.CommonDataKinds.Phone; import android.support.annotation.MainThread; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.support.annotation.WorkerThread; +import android.telecom.PhoneAccount; +import android.telecom.PhoneAccountHandle; import android.text.TextUtils; import android.util.ArraySet; import com.android.dialer.DialerPhoneNumber; @@ -40,10 +43,12 @@ import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.Coa 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.calllogutils.PhoneAccountUtils; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.ThreadUtil; import com.android.dialer.phonenumberproto.DialerPhoneNumberUtil; +import com.android.dialer.theme.R; import com.android.dialer.util.PermissionsUtil; import com.google.i18n.phonenumbers.PhoneNumberUtil; import com.google.protobuf.InvalidProtocolBufferException; @@ -189,7 +194,18 @@ public class SystemCallLogDataSource implements CallLogDataSource { .query( Calls.CONTENT_URI, // Excludes voicemail new String[] { - Calls._ID, Calls.DATE, Calls.LAST_MODIFIED, Calls.NUMBER, Calls.COUNTRY_ISO + Calls._ID, + Calls.DATE, + Calls.LAST_MODIFIED, + Calls.NUMBER, + Calls.COUNTRY_ISO, + Calls.CACHED_NUMBER_TYPE, + Calls.CACHED_NUMBER_LABEL, + Calls.IS_READ, + Calls.GEOCODED_LOCATION, + Calls.PHONE_ACCOUNT_COMPONENT_NAME, + Calls.PHONE_ACCOUNT_ID, + Calls.FEATURES }, Calls.LAST_MODIFIED + " > ?", new String[] {String.valueOf(previousTimestampProcessed)}, @@ -211,6 +227,14 @@ public class SystemCallLogDataSource implements CallLogDataSource { int lastModifiedColumn = cursor.getColumnIndexOrThrow(Calls.LAST_MODIFIED); int numberColumn = cursor.getColumnIndexOrThrow(Calls.NUMBER); int countryIsoColumn = cursor.getColumnIndexOrThrow(Calls.COUNTRY_ISO); + int cachedNumberTypeColumn = cursor.getColumnIndexOrThrow(Calls.CACHED_NUMBER_TYPE); + int cachedNumberLabelColumn = cursor.getColumnIndexOrThrow(Calls.CACHED_NUMBER_LABEL); + int isReadColumn = cursor.getColumnIndexOrThrow(Calls.IS_READ); + int geocodedLocationColumn = cursor.getColumnIndexOrThrow(Calls.GEOCODED_LOCATION); + int phoneAccountComponentColumn = + cursor.getColumnIndexOrThrow(Calls.PHONE_ACCOUNT_COMPONENT_NAME); + int phoneAccountIdColumn = cursor.getColumnIndexOrThrow(Calls.PHONE_ACCOUNT_ID); + int featuresColumn = cursor.getColumnIndexOrThrow(Calls.FEATURES); // The cursor orders by LAST_MODIFIED DESC, so the first result is the most recent timestamp // processed. @@ -220,14 +244,34 @@ public class SystemCallLogDataSource implements CallLogDataSource { long date = cursor.getLong(dateColumn); String numberAsStr = cursor.getString(numberColumn); String countryIso = cursor.getString(countryIsoColumn); + // TODO(zachh): Decide if should use "cached" columns from call log or recompute. + int cachedNumberType = cursor.getInt(cachedNumberTypeColumn); + String cachedNumberLabel = cursor.getString(cachedNumberLabelColumn); + int isRead = cursor.getInt(isReadColumn); + String geocodedLocation = cursor.getString(geocodedLocationColumn); + String phoneAccountComponentName = cursor.getString(phoneAccountComponentColumn); + String phoneAccountId = cursor.getString(phoneAccountIdColumn); + int features = cursor.getInt(featuresColumn); byte[] numberAsProtoBytes = dialerPhoneNumberUtil.parse(numberAsStr, countryIso).toByteArray(); ContentValues contentValues = new ContentValues(); contentValues.put(AnnotatedCallLog.TIMESTAMP, date); + // TODO(zachh): Need to handle post-dial digits; different on N and M. contentValues.put(AnnotatedCallLog.NUMBER, numberAsProtoBytes); + // TODO(zachh): Test this for locales. + contentValues.put( + AnnotatedCallLog.NUMBER_TYPE_LABEL, + Phone.getTypeLabel(appContext.getResources(), cachedNumberType, cachedNumberLabel) + .toString()); + contentValues.put(AnnotatedCallLog.IS_READ, isRead); + contentValues.put(AnnotatedCallLog.GEOCODED_LOCATION, geocodedLocation); + populatePhoneAccountLabelAndColor( + appContext, contentValues, phoneAccountComponentName, phoneAccountId); + contentValues.put(AnnotatedCallLog.FEATURES, features); + if (existingAnnotatedCallLogIds.contains(id)) { mutations.update(id, contentValues); } else { @@ -238,6 +282,29 @@ public class SystemCallLogDataSource implements CallLogDataSource { } } + private void populatePhoneAccountLabelAndColor( + Context appContext, + ContentValues contentValues, + String phoneAccountComponentName, + String phoneAccountId) { + PhoneAccountHandle phoneAccountHandle = + PhoneAccountUtils.getAccount(phoneAccountComponentName, phoneAccountId); + if (phoneAccountHandle == null) { + return; + } + String label = PhoneAccountUtils.getAccountLabel(appContext, phoneAccountHandle); + if (TextUtils.isEmpty(label)) { + return; + } + contentValues.put(AnnotatedCallLog.PHONE_ACCOUNT_LABEL, label); + + int color = PhoneAccountUtils.getAccountColor(appContext, phoneAccountHandle); + if (color == PhoneAccount.NO_HIGHLIGHT_COLOR) { + color = R.color.dialer_secondary_text_color; + } + contentValues.put(AnnotatedCallLog.PHONE_ACCOUNT_COLOR, color); + } + private static void handleDeletes( Context appContext, Set<Long> existingAnnotatedCallLogIds, CallLogMutations mutations) { Set<Long> systemCallLogIds = diff --git a/java/com/android/dialer/calllog/ui/CoalescedAnnotatedCallLogCursorLoader.java b/java/com/android/dialer/calllog/ui/CoalescedAnnotatedCallLogCursorLoader.java new file mode 100644 index 000000000..e993816bf --- /dev/null +++ b/java/com/android/dialer/calllog/ui/CoalescedAnnotatedCallLogCursorLoader.java @@ -0,0 +1,127 @@ +/* + * 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.content.Context; +import android.content.CursorLoader; +import android.database.Cursor; +import com.android.dialer.CallTypes; +import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.CoalescedAnnotatedCallLog; +import com.google.protobuf.InvalidProtocolBufferException; + +/** CursorLoader for the coalesced annotated call log. */ +final class CoalescedAnnotatedCallLogCursorLoader extends CursorLoader { + + /** Indexes for CoalescedAnnotatedCallLog.ALL_COLUMNS */ + private static final int ID = 0; + + private static final int TIMESTAMP = 1; + private static final int PRIMARY_TEXT = 2; + private static final int CONTACT_PHOTO_URI = 3; + private static final int NUMBER_TYPE_LABEL = 4; + private static final int IS_READ = 5; + private static final int GEOCODED_LOCATION = 6; + private static final int PHONE_ACCOUNT_LABEL = 7; + private static final int PHONE_ACCOUNT_COLOR = 8; + private static final int FEATURES = 9; + private static final int IS_BUSINESS = 10; + private static final int IS_VOICEMAIL = 11; + private static final int NUMBER_CALLS = 12; + private static final int FORMATTED_NUMBER = 13; + private static final int CALL_TYPES = 14; + + /** Convenience class for accessing values using an abbreviated syntax. */ + static final class Row { + private final Cursor cursor; + + Row(Cursor cursor) { + this.cursor = cursor; + } + + long id() { + return cursor.getInt(ID); + } + + long timestamp() { + return cursor.getLong(TIMESTAMP); + } + + String primaryText() { + return cursor.getString(PRIMARY_TEXT); + } + + String contactPhotoUri() { + return cursor.getString(CONTACT_PHOTO_URI); + } + + String numberTypeLabel() { + return cursor.getString(NUMBER_TYPE_LABEL); + } + + boolean isRead() { + return cursor.getInt(IS_READ) == 1; + } + + String geocodedLocation() { + return cursor.getString(GEOCODED_LOCATION); + } + + String phoneAccountLabel() { + return cursor.getString(PHONE_ACCOUNT_LABEL); + } + + int phoneAccountColor() { + return cursor.getInt(PHONE_ACCOUNT_COLOR); + } + + int features() { + return cursor.getInt(FEATURES); + } + + boolean isBusiness() { + return cursor.getInt(IS_BUSINESS) == 1; + } + + boolean isVoicemail() { + return cursor.getInt(IS_VOICEMAIL) == 1; + } + + int numberCalls() { + return cursor.getInt(NUMBER_CALLS); + } + + String formattedNumber() { + return cursor.getString(FORMATTED_NUMBER); + } + + CallTypes callTypes() throws InvalidProtocolBufferException { + return CallTypes.parseFrom(cursor.getBlob(CALL_TYPES)); + } + } + + CoalescedAnnotatedCallLogCursorLoader(Context context) { + // CoalescedAnnotatedCallLog requires that PROJECTION be ALL_COLUMNS and the following params be + // null. + super( + context, + CoalescedAnnotatedCallLog.CONTENT_URI, + CoalescedAnnotatedCallLog.ALL_COLUMNS, + null, + null, + null); + } +} diff --git a/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java b/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java index f9ab21cb3..4655b0982 100644 --- a/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java +++ b/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java @@ -19,17 +19,14 @@ 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 @@ -42,8 +39,7 @@ final class NewCallLogAdapter extends RecyclerView.Adapter<NewCallLogViewHolder> @Override public void onBindViewHolder(NewCallLogViewHolder viewHolder, int position) { cursor.moveToPosition(position); - long timestamp = cursor.getLong(timestampIndex); - viewHolder.bind(timestamp); + viewHolder.bind(cursor); } @Override diff --git a/java/com/android/dialer/calllog/ui/NewCallLogFragment.java b/java/com/android/dialer/calllog/ui/NewCallLogFragment.java index 17fcf1939..c4bcb766b 100644 --- a/java/com/android/dialer/calllog/ui/NewCallLogFragment.java +++ b/java/com/android/dialer/calllog/ui/NewCallLogFragment.java @@ -17,7 +17,6 @@ package com.android.dialer.calllog.ui; import android.app.Fragment; import android.app.LoaderManager.LoaderCallbacks; -import android.content.CursorLoader; import android.content.Loader; import android.database.Cursor; import android.os.Bundle; @@ -29,7 +28,6 @@ import android.view.ViewGroup; 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.common.LogUtil; import com.android.dialer.common.concurrent.DialerExecutor; import com.android.dialer.common.concurrent.DialerExecutorComponent; @@ -133,9 +131,7 @@ public final class NewCallLogFragment extends Fragment @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); + return new CoalescedAnnotatedCallLogCursorLoader(getContext()); } @Override diff --git a/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java b/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java index 9521a032c..72ea17b03 100644 --- a/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java +++ b/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java @@ -15,6 +15,7 @@ */ package com.android.dialer.calllog.ui; +import android.database.Cursor; import android.support.v7.widget.RecyclerView; import android.view.View; import android.widget.TextView; @@ -27,17 +28,20 @@ final class NewCallLogViewHolder extends RecyclerView.ViewHolder { // TODO(zachh): 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; + private final TextView primaryTextView; + private final TextView secondaryTextView; NewCallLogViewHolder(View view) { super(view); - contactNameView = view.findViewById(R.id.contact_name); - timestampView = view.findViewById(R.id.timestamp); + primaryTextView = view.findViewById(R.id.primary_text); + secondaryTextView = view.findViewById(R.id.secondary_text); } - void bind(long timestamp) { - contactNameView.setText("Contact Name Placeholder"); - timestampView.setText(dateFormat.format(timestamp)); + /** @param cursor a cursor from {@link CoalescedAnnotatedCallLogCursorLoader}. */ + void bind(Cursor cursor) { + CoalescedAnnotatedCallLogCursorLoader.Row row = + new CoalescedAnnotatedCallLogCursorLoader.Row(cursor); + primaryTextView.setText(row.primaryText()); + secondaryTextView.setText(dateFormat.format(row.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..38e07becf 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 @@ -23,13 +23,13 @@ android:orientation="vertical"> <TextView - android:id="@+id/contact_name" + android:id="@+id/primary_text" android:layout_width="wrap_content" android:layout_height="wrap_content" style="@style/PrimaryText"/> <TextView - android:id="@+id/timestamp" + android:id="@+id/secondary_text" android:layout_width="wrap_content" android:layout_height="wrap_content" style="@style/SecondaryText"/> diff --git a/java/com/android/dialer/calllogutils/CallLogDates.java b/java/com/android/dialer/calllogutils/CallLogDates.java new file mode 100644 index 000000000..2d4bd8bf5 --- /dev/null +++ b/java/com/android/dialer/calllogutils/CallLogDates.java @@ -0,0 +1,165 @@ +/* + * 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.calllogutils; + +import android.content.Context; +import android.icu.lang.UCharacter; +import android.icu.text.BreakIterator; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; +import android.text.format.DateUtils; +import java.util.Calendar; +import java.util.Locale; +import java.util.concurrent.TimeUnit; + +/** Static methods for formatting dates in the call log. */ +public final class CallLogDates { + + /** + * Uses the new date formatting rules to format dates in the new call log. + * + * <p>Rules: + * + * <pre> + * if < 1 minute ago: "Now"; + * else if today: "12:15 PM" + * else if < 3 days ago: "Wednesday"; + * else: "Jan 15" + * </pre> + */ + public static CharSequence newCallLogTimestampLabel( + Context context, long nowMillis, long timestampMillis) { + if (nowMillis - timestampMillis < TimeUnit.MINUTES.toMillis(1)) { + return context.getString(R.string.now); + } + if (isSameDay(nowMillis, timestampMillis)) { + return DateUtils.formatDateTime( + context, timestampMillis, DateUtils.FORMAT_SHOW_TIME); // e.g. 12:15 PM + } + if (isWithin3Days(nowMillis, timestampMillis)) { + return formatDayOfWeek(context, timestampMillis); // e.g. "Wednesday" + } + return formatAbbreviatedMonthAndDay(context, timestampMillis); // e.g. "Jan 15" + } + + /** + * Formats the provided date into a value suitable for display in the current locale. + * + * <p>For example, returns a string like "Wednesday, May 25, 2016, 8:02PM" or "Chorshanba, 2016 + * may 25,20:02". + * + * <p>For pre-N devices, the returned value may not start with a capital if the local convention + * is to not capitalize day names. On N+ devices, the returned value is always capitalized. + */ + public static CharSequence formatDate(Context context, long callDateMillis) { + return toTitleCase( + DateUtils.formatDateTime( + context, + callDateMillis, + DateUtils.FORMAT_SHOW_TIME + | DateUtils.FORMAT_SHOW_DATE + | DateUtils.FORMAT_SHOW_WEEKDAY + | DateUtils.FORMAT_SHOW_YEAR)); + } + + /** + * Formats the provided date into the day of week. + * + * <p>For example, returns a string like "Wednesday" or "Chorshanba". + * + * <p>For pre-N devices, the returned value may not start with a capital if the local convention + * is to not capitalize day names. On N+ devices, the returned value is always capitalized. + */ + private static CharSequence formatDayOfWeek(Context context, long callDateMillis) { + return toTitleCase( + DateUtils.formatDateTime(context, callDateMillis, DateUtils.FORMAT_SHOW_WEEKDAY)); + } + + /** + * Formats the provided date into the month abbreviation and day. + * + * <p>For example, returns a string like "Jan 15". + * + * <p>For pre-N devices, the returned value may not start with a capital if the local convention + * is to not capitalize day names. On N+ devices, the returned value is always capitalized. + */ + private static CharSequence formatAbbreviatedMonthAndDay(Context context, long callDateMillis) { + return toTitleCase( + DateUtils.formatDateTime( + context, callDateMillis, DateUtils.FORMAT_ABBREV_MONTH | DateUtils.FORMAT_NO_YEAR)); + } + + private static CharSequence toTitleCase(CharSequence value) { + // We want the beginning of the date string to be capitalized, even if the word at the beginning + // of the string is not usually capitalized. For example, "Wednesdsay" in Uzbek is "chorshanba” + // (not capitalized). To handle this issue we apply title casing to the start of the sentence so + // that "chorshanba, 2016 may 25,20:02" becomes "Chorshanba, 2016 may 25,20:02". + // + // The ICU library was not available in Android until N, so we can only do this in N+ devices. + // Pre-N devices will still see incorrect capitalization in some languages. + if (VERSION.SDK_INT < VERSION_CODES.N) { + return value; + } + + // Using the ICU library is safer than just applying toUpperCase() on the first letter of the + // word because in some languages, there can be multiple starting characters which should be + // upper-cased together. For example in Dutch "ij" is a digraph in which both letters should be + // capitalized together. + + // TITLECASE_NO_LOWERCASE is necessary so that things that are already capitalized are not + // lower-cased as part of the conversion. + return UCharacter.toTitleCase( + Locale.getDefault(), + value.toString(), + BreakIterator.getSentenceInstance(), + UCharacter.TITLECASE_NO_LOWERCASE); + } + + private static boolean isWithin3Days(long nowMillis, long timestampMillis) { + Calendar threeDaysAgoStartOfDay = Calendar.getInstance(); + threeDaysAgoStartOfDay.setTimeInMillis(nowMillis); + + // This is attempting to find the start of the current day, but it's not quite right due to + // daylight savings. Unfortunately there doesn't seem to be a way to get the correct start of + // the day without using Joda or Java8, both of which are disallowed. This means that the wrong + // formatting may be applied on days with time changes (though the displayed values will be + // correct). + threeDaysAgoStartOfDay.add( + Calendar.HOUR_OF_DAY, -threeDaysAgoStartOfDay.get(Calendar.HOUR_OF_DAY)); + threeDaysAgoStartOfDay.add(Calendar.MINUTE, -threeDaysAgoStartOfDay.get(Calendar.MINUTE)); + threeDaysAgoStartOfDay.add(Calendar.SECOND, -threeDaysAgoStartOfDay.get(Calendar.SECOND)); + + threeDaysAgoStartOfDay.add(Calendar.DATE, -2); + + Calendar then = Calendar.getInstance(); + then.setTimeInMillis(timestampMillis); + + return then.equals(threeDaysAgoStartOfDay) || then.after(threeDaysAgoStartOfDay); + } + + private static boolean isSameDay(long firstMillis, long secondMillis) { + Calendar first = Calendar.getInstance(); + first.setTimeInMillis(firstMillis); + + Calendar second = Calendar.getInstance(); + second.setTimeInMillis(secondMillis); + + return first.get(Calendar.YEAR) == second.get(Calendar.YEAR) + && first.get(Calendar.MONTH) == second.get(Calendar.MONTH) + && first.get(Calendar.DAY_OF_MONTH) == second.get(Calendar.DAY_OF_MONTH); + } +} diff --git a/java/com/android/dialer/calllogutils/CallEntryFormatter.java b/java/com/android/dialer/calllogutils/CallLogDurations.java index c5ec15748..20998deb4 100644 --- a/java/com/android/dialer/calllogutils/CallEntryFormatter.java +++ b/java/com/android/dialer/calllogutils/CallLogDurations.java @@ -18,67 +18,16 @@ package com.android.dialer.calllogutils; import android.content.Context; import android.content.res.Resources; -import android.icu.lang.UCharacter; -import android.icu.text.BreakIterator; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; -import android.text.format.DateUtils; import android.text.format.Formatter; import com.android.dialer.util.DialerUtils; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; -import java.util.Locale; import java.util.concurrent.TimeUnit; -/** Utility class for formatting data and data usage in call log entries. */ -public class CallEntryFormatter { - - /** - * Formats the provided date into a value suitable for display in the current locale. - * - * <p>For example, returns a string like "Wednesday, May 25, 2016, 8:02PM" or "Chorshanba, 2016 - * may 25,20:02". - * - * <p>For pre-N devices, the returned value may not start with a capital if the local convention - * is to not capitalize day names. On N+ devices, the returned value is always capitalized. - */ - public static CharSequence formatDate(Context context, long callDateMillis) { - CharSequence dateValue = - DateUtils.formatDateRange( - context, - callDateMillis /* startDate */, - callDateMillis /* endDate */, - DateUtils.FORMAT_SHOW_TIME - | DateUtils.FORMAT_SHOW_DATE - | DateUtils.FORMAT_SHOW_WEEKDAY - | DateUtils.FORMAT_SHOW_YEAR); - - // We want the beginning of the date string to be capitalized, even if the word at the beginning - // of the string is not usually capitalized. For example, "Wednesdsay" in Uzbek is "chorshanba” - // (not capitalized). To handle this issue we apply title casing to the start of the sentence so - // that "chorshanba, 2016 may 25,20:02" becomes "Chorshanba, 2016 may 25,20:02". - // - // The ICU library was not available in Android until N, so we can only do this in N+ devices. - // Pre-N devices will still see incorrect capitalization in some languages. - if (VERSION.SDK_INT < VERSION_CODES.N) { - return dateValue; - } - - // Using the ICU library is safer than just applying toUpperCase() on the first letter of the - // word because in some languages, there can be multiple starting characters which should be - // upper-cased together. For example in Dutch "ij" is a digraph in which both letters should be - // capitalized together. - - // TITLECASE_NO_LOWERCASE is necessary so that things that are already capitalized like the - // month ("May") are not lower-cased as part of the conversion. - return UCharacter.toTitleCase( - Locale.getDefault(), - dateValue.toString(), - BreakIterator.getSentenceInstance(), - UCharacter.TITLECASE_NO_LOWERCASE); - } +/** Utility class for formatting duration and data usage in call log entries. */ +public class CallLogDurations { private static CharSequence formatDuration(Context context, long elapsedSeconds) { Resources res = context.getResources(); diff --git a/java/com/android/dialer/calllogutils/res/values/strings.xml b/java/com/android/dialer/calllogutils/res/values/strings.xml index 255990399..56cd94a9e 100644 --- a/java/com/android/dialer/calllogutils/res/values/strings.xml +++ b/java/com/android/dialer/calllogutils/res/values/strings.xml @@ -127,4 +127,7 @@ <!-- String used for displaying calls to the voicemail number in the call log --> <string name="voicemail_string">Voicemail</string> + + <!-- String to be displayed to indicate in the call log that a call just now occurred. --> + <string name="now">Now</string> </resources>
\ No newline at end of file |