From fdaa46618ce61344bc83a66590863d126c47b05f Mon Sep 17 00:00:00 2001 From: linyuh Date: Fri, 19 Jan 2018 11:55:16 -0800 Subject: Add the "Yesterday" header in the new call log Bug: 70989598 Test: NewCallLogAdapterTest, CallLogDatesTest PiperOrigin-RevId: 182567571 Change-Id: Ieabbe709668d843334bc3bf4a128834fddb57cb8 --- .../dialer/calllog/ui/NewCallLogAdapter.java | 129 +++++++++++++-------- .../dialer/calllog/ui/res/values/strings.xml | 5 +- .../android/dialer/calllogutils/CallLogDates.java | 53 ++++++--- 3 files changed, 126 insertions(+), 61 deletions(-) (limited to 'java/com/android') diff --git a/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java b/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java index 5618c4d16..05a339978 100644 --- a/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java +++ b/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java @@ -34,14 +34,21 @@ final class NewCallLogAdapter extends RecyclerView.Adapter { /** IntDef for the different types of rows that can be shown in the call log. */ @Retention(RetentionPolicy.SOURCE) - @IntDef({RowType.HEADER_TODAY, RowType.HEADER_OLDER, RowType.CALL_LOG_ENTRY}) + @IntDef({ + RowType.HEADER_TODAY, + RowType.HEADER_YESTERDAY, + RowType.HEADER_OLDER, + RowType.CALL_LOG_ENTRY + }) @interface RowType { /** Header that displays "Today". */ int HEADER_TODAY = 1; + /** Header that displays "Yesterday". */ + int HEADER_YESTERDAY = 2; /** Header that displays "Older". */ - int HEADER_OLDER = 2; + int HEADER_OLDER = 3; /** A row representing a call log entry (which could represent one or more calls). */ - int CALL_LOG_ENTRY = 3; + int CALL_LOG_ENTRY = 4; } private final Clock clock; @@ -49,9 +56,13 @@ final class NewCallLogAdapter extends RecyclerView.Adapter { private Cursor cursor; - /** Null when the "Today" header should not be displayed. */ + /** Position of the "Today" header. Null when it should not be displayed. */ @Nullable private Integer todayHeaderPosition; - /** Null when the "Older" header should not be displayed. */ + + /** Position of the "Yesterday" header. Null when it should not be displayed. */ + @Nullable private Integer yesterdayHeaderPosition; + + /** Position of the "Older" header. Null when it should not be displayed. */ @Nullable private Integer olderHeaderPosition; NewCallLogAdapter(Context context, Cursor cursor, Clock clock) { @@ -75,38 +86,49 @@ final class NewCallLogAdapter extends RecyclerView.Adapter { } private void setHeaderPositions() { - // Calculate header adapter positions by reading cursor. + // If there are no rows to display, set all header positions to null. + if (!cursor.moveToFirst()) { + todayHeaderPosition = null; + yesterdayHeaderPosition = null; + olderHeaderPosition = null; + return; + } + long currentTimeMillis = clock.currentTimeMillis(); - if (cursor.moveToFirst()) { - long firstTimestamp = CoalescedAnnotatedCallLogCursorLoader.getTimestamp(cursor); - if (CallLogDates.isSameDay(currentTimeMillis, firstTimestamp)) { - this.todayHeaderPosition = 0; - int adapterPosition = 2; // Accounted for "Today" header and first row. - while (cursor.moveToNext()) { - long timestamp = CoalescedAnnotatedCallLogCursorLoader.getTimestamp(cursor); - - if (CallLogDates.isSameDay(currentTimeMillis, timestamp)) { - adapterPosition++; - } else { - this.olderHeaderPosition = adapterPosition; - return; - } - } - this.olderHeaderPosition = null; // Didn't find any "Older" rows. + + int numItemsInToday = 0; + int numItemsInYesterday = 0; + do { + long timestamp = CoalescedAnnotatedCallLogCursorLoader.getTimestamp(cursor); + long dayDifference = CallLogDates.getDayDifference(currentTimeMillis, timestamp); + if (dayDifference == 0) { + numItemsInToday++; + } else if (dayDifference == 1) { + numItemsInYesterday++; } else { - this.todayHeaderPosition = null; // Didn't find any "Today" rows. - this.olderHeaderPosition = 0; + break; } - } else { // There are no rows, just need to set these because they are final. - this.todayHeaderPosition = null; - this.olderHeaderPosition = null; + } while (cursor.moveToNext()); + + if (numItemsInToday > 0) { + numItemsInToday++; // including the "Today" header; + } + if (numItemsInYesterday > 0) { + numItemsInYesterday++; // including the "Yesterday" header; } + + // Set all header positions. + // A header position will be null if there is no item to be displayed under that header. + todayHeaderPosition = numItemsInToday > 0 ? 0 : null; + yesterdayHeaderPosition = numItemsInYesterday > 0 ? numItemsInToday : null; + olderHeaderPosition = !cursor.isAfterLast() ? numItemsInToday + numItemsInYesterday : null; } @Override public ViewHolder onCreateViewHolder(ViewGroup viewGroup, @RowType int viewType) { switch (viewType) { case RowType.HEADER_TODAY: + case RowType.HEADER_YESTERDAY: case RowType.HEADER_OLDER: return new HeaderViewHolder( LayoutInflater.from(viewGroup.getContext()) @@ -124,29 +146,36 @@ final class NewCallLogAdapter extends RecyclerView.Adapter { @Override public void onBindViewHolder(ViewHolder viewHolder, int position) { - if (viewHolder instanceof HeaderViewHolder) { - HeaderViewHolder headerViewHolder = (HeaderViewHolder) viewHolder; - @RowType int viewType = getItemViewType(position); - if (viewType == RowType.HEADER_OLDER) { - headerViewHolder.setHeader(R.string.new_call_log_header_older); - } else if (viewType == RowType.HEADER_TODAY) { - headerViewHolder.setHeader(R.string.new_call_log_header_today); - } else { + @RowType int viewType = getItemViewType(position); + switch (viewType) { + case RowType.HEADER_TODAY: + ((HeaderViewHolder) viewHolder).setHeader(R.string.new_call_log_header_today); + break; + case RowType.HEADER_YESTERDAY: + ((HeaderViewHolder) viewHolder).setHeader(R.string.new_call_log_header_yesterday); + break; + case RowType.HEADER_OLDER: + ((HeaderViewHolder) viewHolder).setHeader(R.string.new_call_log_header_older); + break; + case RowType.CALL_LOG_ENTRY: + NewCallLogViewHolder newCallLogViewHolder = (NewCallLogViewHolder) viewHolder; + int previousHeaders = 0; + if (todayHeaderPosition != null && position > todayHeaderPosition) { + previousHeaders++; + } + if (yesterdayHeaderPosition != null && position > yesterdayHeaderPosition) { + previousHeaders++; + } + if (olderHeaderPosition != null && position > olderHeaderPosition) { + previousHeaders++; + } + cursor.moveToPosition(position - previousHeaders); + newCallLogViewHolder.bind(cursor); + break; + default: throw Assert.createIllegalStateFailException( "Unexpected view type " + viewType + " at position: " + position); - } - return; - } - NewCallLogViewHolder newCallLogViewHolder = (NewCallLogViewHolder) viewHolder; - int previousHeaders = 0; - if (todayHeaderPosition != null && position > todayHeaderPosition) { - previousHeaders++; - } - if (olderHeaderPosition != null && position > olderHeaderPosition) { - previousHeaders++; } - cursor.moveToPosition(position - previousHeaders); - newCallLogViewHolder.bind(cursor); } @Override @@ -155,6 +184,9 @@ final class NewCallLogAdapter extends RecyclerView.Adapter { if (todayHeaderPosition != null && position == todayHeaderPosition) { return RowType.HEADER_TODAY; } + if (yesterdayHeaderPosition != null && position == yesterdayHeaderPosition) { + return RowType.HEADER_YESTERDAY; + } if (olderHeaderPosition != null && position == olderHeaderPosition) { return RowType.HEADER_OLDER; } @@ -167,6 +199,9 @@ final class NewCallLogAdapter extends RecyclerView.Adapter { if (todayHeaderPosition != null) { numberOfHeaders++; } + if (yesterdayHeaderPosition != null) { + numberOfHeaders++; + } if (olderHeaderPosition != null) { numberOfHeaders++; } diff --git a/java/com/android/dialer/calllog/ui/res/values/strings.xml b/java/com/android/dialer/calllog/ui/res/values/strings.xml index 0ef0eaf26..ebddc3578 100644 --- a/java/com/android/dialer/calllog/ui/res/values/strings.xml +++ b/java/com/android/dialer/calllog/ui/res/values/strings.xml @@ -20,7 +20,10 @@ Today - + + Yesterday + + Older \ No newline at end of file diff --git a/java/com/android/dialer/calllogutils/CallLogDates.java b/java/com/android/dialer/calllogutils/CallLogDates.java index 82e8e404e..84e52df14 100644 --- a/java/com/android/dialer/calllogutils/CallLogDates.java +++ b/java/com/android/dialer/calllogutils/CallLogDates.java @@ -50,7 +50,7 @@ public final class CallLogDates { return DateUtils.formatDateTime( context, timestampMillis, DateUtils.FORMAT_SHOW_TIME); // e.g. 12:15 PM } - if (isWithin3Days(nowMillis, timestampMillis)) { + if (getDayDifference(nowMillis, timestampMillis) < 3) { return formatDayOfWeek(context, timestampMillis); // e.g. "Wednesday" } return formatAbbreviatedMonthAndDay(context, timestampMillis); // e.g. "Jan 15" @@ -129,26 +129,53 @@ public final class CallLogDates { UCharacter.TITLECASE_NO_LOWERCASE); } - private static boolean isWithin3Days(long nowMillis, long timestampMillis) { - Calendar threeDaysAgoStartOfDay = Calendar.getInstance(); - threeDaysAgoStartOfDay.setTimeInMillis(nowMillis); + /** + * Returns the absolute difference in days between two timestamps. It is the caller's + * responsibility to ensure both timestamps are in milliseconds. Failure to do so will result in + * undefined behavior. + * + *

Note that the difference is based on day boundaries, not 24-hour periods. + * + *

Examples: + * + *

    + *
  • The difference between 01/19/2018 00:00 and 01/19/2018 23:59 is 0. + *
  • The difference between 01/18/2018 23:59 and 01/19/2018 23:59 is 1. + *
  • The difference between 01/18/2018 00:00 and 01/19/2018 23:59 is 1. + *
  • The difference between 01/17/2018 23:59 and 01/19/2018 00:00 is 2. + *
+ */ + public static int getDayDifference(long firstTimestamp, long secondTimestamp) { + // Ensure secondMillis is no less than firstMillis + if (secondTimestamp < firstTimestamp) { + long t = firstTimestamp; + firstTimestamp = secondTimestamp; + secondTimestamp = t; + } - // This is attempting to find the start of the current day, but it's not quite right due to + // Use secondTimestamp as reference + Calendar startOfReferenceDay = Calendar.getInstance(); + startOfReferenceDay.setTimeInMillis(secondTimestamp); + + // This is attempting to find the start of the reference 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)); + startOfReferenceDay.add(Calendar.HOUR_OF_DAY, -startOfReferenceDay.get(Calendar.HOUR_OF_DAY)); + startOfReferenceDay.add(Calendar.MINUTE, -startOfReferenceDay.get(Calendar.MINUTE)); + startOfReferenceDay.add(Calendar.SECOND, -startOfReferenceDay.get(Calendar.SECOND)); - threeDaysAgoStartOfDay.add(Calendar.DATE, -2); + Calendar other = Calendar.getInstance(); + other.setTimeInMillis(firstTimestamp); - Calendar then = Calendar.getInstance(); - then.setTimeInMillis(timestampMillis); + int dayDifference = 0; + while (other.before(startOfReferenceDay)) { + startOfReferenceDay.add(Calendar.DATE, -1); + dayDifference++; + } - return then.equals(threeDaysAgoStartOfDay) || then.after(threeDaysAgoStartOfDay); + return dayDifference; } /** Returns true if the provided timestamps are from the same day in the default time zone. */ -- cgit v1.2.3