diff options
Diffstat (limited to 'java')
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 |