Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fdaa4661 authored by linyuh's avatar linyuh Committed by Copybara-Service
Browse files

Add the "Yesterday" header in the new call log

Bug: 70989598
Test: NewCallLogAdapterTest, CallLogDatesTest
PiperOrigin-RevId: 182567571
Change-Id: Ieabbe709668d843334bc3bf4a128834fddb57cb8
parent 7e421825
Loading
Loading
Loading
Loading
+82 −47
Original line number Diff line number Diff line
@@ -34,14 +34,21 @@ final class NewCallLogAdapter extends RecyclerView.Adapter<ViewHolder> {

  /** 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<ViewHolder> {

  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<ViewHolder> {
  }

  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++;
    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.olderHeaderPosition = adapterPosition;
            return;
        break;
      }
    } while (cursor.moveToNext());

    if (numItemsInToday > 0) {
      numItemsInToday++; // including the "Today" header;
    }
        this.olderHeaderPosition = null; // Didn't find any "Older" rows.
      } else {
        this.todayHeaderPosition = null; // Didn't find any "Today" rows.
        this.olderHeaderPosition = 0;
      }
    } else { // There are no rows, just need to set these because they are final.
      this.todayHeaderPosition = null;
      this.olderHeaderPosition = null;
    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<ViewHolder> {

  @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 {
        throw Assert.createIllegalStateFailException(
            "Unexpected view type " + viewType + " at position: " + position);
      }
      return;
    }
    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);
    }
  }

  @Override
@@ -155,6 +184,9 @@ final class NewCallLogAdapter extends RecyclerView.Adapter<ViewHolder> {
    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<ViewHolder> {
    if (todayHeaderPosition != null) {
      numberOfHeaders++;
    }
    if (yesterdayHeaderPosition != null) {
      numberOfHeaders++;
    }
    if (olderHeaderPosition != null) {
      numberOfHeaders++;
    }
+4 −1
Original line number Diff line number Diff line
@@ -20,7 +20,10 @@
  <!-- Header in call log to group calls from the current day.  [CHAR LIMIT=30] -->
  <string name="new_call_log_header_today">Today</string>

  <!-- Header in call log to group calls from before the current day.  [CHAR LIMIT=30] -->
  <!-- Header in call log to group calls from the previous day.  [CHAR LIMIT=30] -->
  <string name="new_call_log_header_yesterday">Yesterday</string>

  <!-- Header in call log to group calls from before yesterday.  [CHAR LIMIT=30] -->
  <string name="new_call_log_header_older">Older</string>

</resources>
 No newline at end of file
+40 −13
Original line number Diff line number Diff line
@@ -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.
   *
   * <p>Note that the difference is based on day boundaries, not 24-hour periods.
   *
   * <p>Examples:
   *
   * <ul>
   *   <li>The difference between 01/19/2018 00:00 and 01/19/2018 23:59 is 0.
   *   <li>The difference between 01/18/2018 23:59 and 01/19/2018 23:59 is 1.
   *   <li>The difference between 01/18/2018 00:00 and 01/19/2018 23:59 is 1.
   *   <li>The difference between 01/17/2018 23:59 and 01/19/2018 00:00 is 2.
   * </ul>
   */
  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. */