Merge "Implement today and older for NUI Voicemail and update MediaPlayerView tests"
diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java b/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java
index 61fed52..955c7da 100644
--- a/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java
+++ b/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java
@@ -21,28 +21,49 @@
 import android.media.MediaPlayer.OnCompletionListener;
 import android.media.MediaPlayer.OnErrorListener;
 import android.media.MediaPlayer.OnPreparedListener;
+import android.support.annotation.IntDef;
 import android.support.annotation.Nullable;
 import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.ViewHolder;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import com.android.dialer.calllogutils.CallLogDates;
 import com.android.dialer.common.Assert;
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.common.concurrent.ThreadUtil;
 import com.android.dialer.time.Clock;
 import com.android.dialer.voicemail.listui.NewVoicemailViewHolder.NewVoicemailViewHolderListener;
 import com.android.dialer.voicemail.model.VoicemailEntry;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 import java.util.Set;
 
 /** {@link RecyclerView.Adapter} for the new voicemail call log fragment. */
-final class NewVoicemailAdapter extends RecyclerView.Adapter<NewVoicemailViewHolder>
+final class NewVoicemailAdapter extends RecyclerView.Adapter<ViewHolder>
     implements NewVoicemailViewHolderListener {
 
+  /** IntDef for the different types of rows that can be shown in the call log. */
+  @Retention(RetentionPolicy.SOURCE)
+  @IntDef({RowType.HEADER, RowType.VOICEMAIL_ENTRY})
+  @interface RowType {
+    /** Header that displays "Today" or "Older". */
+    int HEADER = 1;
+    /** A row representing a voicemail entry. */
+    int VOICEMAIL_ENTRY = 2;
+  }
+
   private final Cursor cursor;
   private final Clock clock;
+
+  /** {@link Integer#MAX_VALUE} when the "Today" header should not be displayed. */
+  private final int todayHeaderPosition;
+  /** {@link Integer#MAX_VALUE} when the "Older" header should not be displayed. */
+  private final int olderHeaderPosition;
+
   private final FragmentManager fragmentManager;
   /** A valid id for {@link VoicemailEntry} is greater than 0 */
   private int currentlyExpandedViewHolderId = -1;
@@ -73,6 +94,33 @@
     this.clock = clock;
     this.fragmentManager = fragmentManager;
     initializeMediaPlayerListeners();
+
+    // Calculate header adapter positions by reading cursor.
+    long currentTimeMillis = clock.currentTimeMillis();
+    if (cursor.moveToNext()) {
+      long firstTimestamp = VoicemailCursorLoader.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 = VoicemailCursorLoader.getTimestamp(cursor);
+
+          if (CallLogDates.isSameDay(currentTimeMillis, timestamp)) {
+            adapterPosition++;
+          } else {
+            this.olderHeaderPosition = adapterPosition;
+            return;
+          }
+        }
+        this.olderHeaderPosition = Integer.MAX_VALUE; // Didn't find any "Older" rows.
+      } else {
+        this.todayHeaderPosition = Integer.MAX_VALUE; // 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 = Integer.MAX_VALUE;
+      this.olderHeaderPosition = Integer.MAX_VALUE;
+    }
   }
 
   private void initializeMediaPlayerListeners() {
@@ -82,41 +130,123 @@
   }
 
   @Override
-  public NewVoicemailViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
+  public ViewHolder onCreateViewHolder(ViewGroup viewGroup, @RowType int viewType) {
+    LogUtil.enterBlock("NewVoicemailAdapter.onCreateViewHolder");
     LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());
-    View view = inflater.inflate(R.layout.new_voicemail_entry, viewGroup, false);
-    NewVoicemailViewHolder newVoicemailViewHolder = new NewVoicemailViewHolder(view, clock, this);
-    newVoicemailViewHolderSet.add(newVoicemailViewHolder);
-    return newVoicemailViewHolder;
+    View view;
+    switch (viewType) {
+      case RowType.HEADER:
+        view = inflater.inflate(R.layout.new_voicemail_entry_header, viewGroup, false);
+        return new NewVoicemailHeaderViewHolder(view);
+      case NewVoicemailAdapter.RowType.VOICEMAIL_ENTRY:
+        view = inflater.inflate(R.layout.new_voicemail_entry, viewGroup, false);
+        NewVoicemailViewHolder newVoicemailViewHolder =
+            new NewVoicemailViewHolder(view, clock, this);
+        newVoicemailViewHolderSet.add(newVoicemailViewHolder);
+        return newVoicemailViewHolder;
+      default:
+        throw Assert.createUnsupportedOperationFailException("Unsupported view type: " + viewType);
+    }
   }
 
+  // TODO(uabdullah): a bug - Clean up logging in this function, here for debugging during
+  // development.
   @Override
-  public void onBindViewHolder(NewVoicemailViewHolder viewHolder, int position) {
-    // Remove if the viewholder is being recycled.
-    if (newVoicemailViewHolderArrayMap.containsKey(viewHolder.getViewHolderId())) {
-      // TODO(uabdullah): Remove the logging, only here for debugging during development.
-      LogUtil.i(
-          "NewVoicemailAdapter.onBindViewHolder",
-          "Removing from hashset:%d, hashsetSize:%d",
-          viewHolder.getViewHolderId(),
-          newVoicemailViewHolderArrayMap.size());
+  public void onBindViewHolder(ViewHolder viewHolder, int position) {
+    LogUtil.enterBlock("NewVoicemailAdapter.onBindViewHolder, pos:" + position);
+    // TODO(uabdullah): a bug Remove logging, temporarily here for debugging.
+    printHashSet();
+    // TODO(uabdullah): a bug Remove logging, temporarily here for debugging.
+    printHashMap();
 
-      newVoicemailViewHolderArrayMap.remove(viewHolder.getViewHolderId());
+    if (viewHolder instanceof NewVoicemailHeaderViewHolder) {
+      LogUtil.i(
+          "NewVoicemailAdapter.onBindViewHolder", "view holder at pos:%d is a header", position);
+      NewVoicemailHeaderViewHolder headerViewHolder = (NewVoicemailHeaderViewHolder) viewHolder;
+      @RowType int viewType = getItemViewType(position);
+      if (position == todayHeaderPosition) {
+        headerViewHolder.setHeader(R.string.new_voicemail_header_today);
+      } else if (position == olderHeaderPosition) {
+        headerViewHolder.setHeader(R.string.new_voicemail_header_older);
+      } else {
+        throw Assert.createIllegalStateFailException(
+            "Unexpected view type " + viewType + " at position: " + position);
+      }
+      return;
     }
 
-    viewHolder.reset();
-    cursor.moveToPosition(position);
-    viewHolder.bindViewHolderValuesFromAdapter(
+    LogUtil.i(
+        "NewVoicemailAdapter.onBindViewHolder",
+        "view holder at pos:%d is a not a header",
+        position);
+
+    NewVoicemailViewHolder newVoicemailViewHolder = (NewVoicemailViewHolder) viewHolder;
+
+    int previousHeaders = 0;
+    if (todayHeaderPosition != Integer.MAX_VALUE && position > todayHeaderPosition) {
+      previousHeaders++;
+    }
+    if (olderHeaderPosition != Integer.MAX_VALUE && position > olderHeaderPosition) {
+      previousHeaders++;
+    }
+
+    LogUtil.i(
+        "NewVoicemailAdapter.onBindViewHolder",
+        "view holder at pos:%d, prevHeaderCount:%d",
+        position,
+        previousHeaders);
+
+    // Remove if the viewholder is being recycled.
+    if (newVoicemailViewHolderArrayMap.containsKey(newVoicemailViewHolder.getViewHolderId())) {
+      // TODO(uabdullah): a bug Remove logging, temporarily here for debugging.
+      LogUtil.i(
+          "NewVoicemailAdapter.onBindViewHolder",
+          "Removing from hashset:%d, hashsetSize:%d, currExpanded:%d",
+          newVoicemailViewHolder.getViewHolderId(),
+          newVoicemailViewHolderArrayMap.size(),
+          currentlyExpandedViewHolderId);
+
+      newVoicemailViewHolderArrayMap.remove(newVoicemailViewHolder.getViewHolderId());
+      printHashSet();
+      printHashMap();
+    }
+
+    newVoicemailViewHolder.reset();
+    cursor.moveToPosition(position - previousHeaders);
+    newVoicemailViewHolder.bindViewHolderValuesFromAdapter(
         cursor, fragmentManager, mediaPlayer, position, currentlyExpandedViewHolderId);
 
+    // TODO(uabdullah): a bug Remove logging, temporarily here for debugging.
+    LogUtil.i(
+        "NewVoicemailAdapter.onBindViewHolder",
+        "Adding to hashset:%d, hashsetSize:%d, pos:%d, currExpanded:%d",
+        newVoicemailViewHolder.getViewHolderId(),
+        newVoicemailViewHolderArrayMap.size(),
+        position,
+        currentlyExpandedViewHolderId);
+
     // Need this to ensure correct getCurrentlyExpandedViewHolder() value
-    newVoicemailViewHolderArrayMap.put(viewHolder.getViewHolderId(), viewHolder);
+    newVoicemailViewHolderArrayMap.put(
+        newVoicemailViewHolder.getViewHolderId(), newVoicemailViewHolder);
+
+    // TODO(uabdullah): a bug Remove logging, temporarily here for debugging.
+    printHashSet();
+    // TODO(uabdullah): a bug Remove logging, temporarily here for debugging.
+    printHashMap();
 
     // If the viewholder is playing the voicemail, keep updating its media player view (seekbar,
     // duration etc.)
-    if (viewHolder.isViewHolderExpanded() && mediaPlayer.isPlaying()) {
+    if (newVoicemailViewHolder.isViewHolderExpanded() && mediaPlayer.isPlaying()) {
+      LogUtil.i(
+          "NewVoicemailAdapter.onBindViewHolder",
+          "Adding to hashset:%d, hashsetSize:%d, pos:%d, currExpanded:%d",
+          newVoicemailViewHolderSet.size(),
+          newVoicemailViewHolderArrayMap.size(),
+          position,
+          currentlyExpandedViewHolderId);
+
       Assert.checkArgument(
-          viewHolder
+          newVoicemailViewHolder
               .getViewHolderVoicemailUri()
               .equals(mediaPlayer.getLastPlayedOrPlayingVoicemailUri()),
           "only the expanded view holder can be playing.");
@@ -126,10 +256,48 @@
               .getViewHolderVoicemailUri()
               .equals(mediaPlayer.getLastPlayedOrPlayingVoicemailUri()));
 
-      recursivelyUpdateMediaPlayerViewOfExpandedViewHolder(viewHolder);
+      recursivelyUpdateMediaPlayerViewOfExpandedViewHolder(newVoicemailViewHolder);
     }
     // Updates the hashmap with the most up-to-date state of the viewholder.
-    newVoicemailViewHolderArrayMap.put(viewHolder.getViewHolderId(), viewHolder);
+    newVoicemailViewHolderArrayMap.put(
+        newVoicemailViewHolder.getViewHolderId(), newVoicemailViewHolder);
+
+    // TODO(uabdullah): a bug Remove logging, temporarily here for debugging.
+    printHashSet();
+    // TODO(uabdullah): a bug Remove logging, temporarily here for debugging.
+    printHashMap();
+  }
+
+  private void printHashMap() {
+    LogUtil.i(
+        "NewVoicemailAdapter.printHashMap",
+        "hashMapSize: %d, currentlyExpandedViewHolderId:%d",
+        newVoicemailViewHolderArrayMap.size(),
+        currentlyExpandedViewHolderId);
+
+    if (!newVoicemailViewHolderArrayMap.isEmpty()) {
+      String ids = "";
+      for (int id : newVoicemailViewHolderArrayMap.keySet()) {
+        ids = id + String.valueOf(id) + " ";
+      }
+      LogUtil.i("NewVoicemailAdapter.printHashMap", "ids are " + ids);
+    }
+  }
+
+  private void printHashSet() {
+    LogUtil.i(
+        "NewVoicemailAdapter.printHashSet",
+        "hashSetSize: %d, currentlyExpandedViewHolderId:%d",
+        newVoicemailViewHolderSet.size(),
+        currentlyExpandedViewHolderId);
+
+    if (!newVoicemailViewHolderSet.isEmpty()) {
+      String viewHolderID = "";
+      for (NewVoicemailViewHolder vh : newVoicemailViewHolderSet) {
+        viewHolderID = viewHolderID + vh.getViewHolderId() + " ";
+      }
+      LogUtil.i("NewVoicemailAdapter.printHashSet", "ids are " + viewHolderID);
+    }
   }
 
   /**
@@ -316,6 +484,11 @@
    */
   private void recursivelyUpdateMediaPlayerViewOfExpandedViewHolder(
       NewVoicemailViewHolder expandedViewHolderPossiblyPlaying) {
+    // TODO(uabdullah): a bug Remove logging, temporarily here for debugging.
+    LogUtil.i(
+        "NewVoicemailAdapter.recursivelyUpdateMediaPlayerViewOfExpandedViewHolder",
+        "currentlyExpanded:%d",
+        currentlyExpandedViewHolderId);
 
     // It's possible that by the time this is run, the expanded view holder has been
     // scrolled out of view (and possibly recycled)
@@ -368,7 +541,7 @@
           mediaPlayer
               .getLastPlayedOrPlayingVoicemailUri()
               .equals(getCurrentlyExpandedViewHolder().getViewHolderVoicemailUri()));
-      // TODO(uabdullah): Remove this, here for debugging during development.
+      // TODO(uabdullah): a bug Remove logging, temporarily here for debugging.
       LogUtil.i(
           "NewVoicemailAdapter.recursivelyUpdateMediaPlayerViewOfExpandedViewHolder",
           "recursely update the player, currentlyExpanded:%d",
@@ -544,12 +717,36 @@
           "no view holder found in newVoicemailViewHolderArrayMap size:%d for %d",
           newVoicemailViewHolderArrayMap.size(),
           currentlyExpandedViewHolderId);
+      // TODO(uabdullah): a bug Remove logging, temporarily here for debugging.
+      printHashSet();
+      printHashMap();
       return null;
     }
   }
 
   @Override
   public int getItemCount() {
-    return cursor.getCount();
+    LogUtil.enterBlock("NewVoicemailAdapter.getItemCount");
+    int numberOfHeaders = 0;
+    if (todayHeaderPosition != Integer.MAX_VALUE) {
+      numberOfHeaders++;
+    }
+    if (olderHeaderPosition != Integer.MAX_VALUE) {
+      numberOfHeaders++;
+    }
+    return cursor.getCount() + numberOfHeaders;
+  }
+
+  @RowType
+  @Override
+  public int getItemViewType(int position) {
+    LogUtil.enterBlock("NewVoicemailAdapter.getItemViewType");
+    if (todayHeaderPosition != Integer.MAX_VALUE && position == todayHeaderPosition) {
+      return RowType.HEADER;
+    }
+    if (olderHeaderPosition != Integer.MAX_VALUE && position == olderHeaderPosition) {
+      return RowType.HEADER;
+    }
+    return RowType.VOICEMAIL_ENTRY;
   }
 }
diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailHeaderViewHolder.java b/java/com/android/dialer/voicemail/listui/NewVoicemailHeaderViewHolder.java
new file mode 100644
index 0000000..6bd8e86
--- /dev/null
+++ b/java/com/android/dialer/voicemail/listui/NewVoicemailHeaderViewHolder.java
@@ -0,0 +1,43 @@
+/*
+ * 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.voicemail.listui;
+
+import android.support.annotation.StringRes;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.widget.RecyclerView.ViewHolder;
+import android.view.View;
+import android.widget.TextView;
+
+/** ViewHolder for {@link NewVoicemailAdapter} to display "Today" or "Older" divider row. */
+final class NewVoicemailHeaderViewHolder extends ViewHolder {
+
+  private final TextView headerTextView;
+
+  NewVoicemailHeaderViewHolder(View view) {
+    super(view);
+    headerTextView = view.findViewById(R.id.new_voicemail_header_text);
+  }
+
+  void setHeader(@StringRes int header) {
+    headerTextView.setText(header);
+  }
+
+  @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+  String getHeaderText() {
+    return headerTextView.getText().toString();
+  }
+}
diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java b/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java
index 02b05db..d5b17a1 100644
--- a/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java
+++ b/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java
@@ -92,8 +92,18 @@
       int position,
       int currentlyExpandedViewHolderId) {
 
+    LogUtil.i(
+        "NewVoicemailViewHolder.bindViewHolderValuesFromAdapter",
+        "view holder at pos:%d, adapterPos:%d, cursorPos:%d, cursorSize:%d",
+        position,
+        getAdapterPosition(),
+        cursor.getPosition(),
+        cursor.getCount());
+
     voicemailEntryOfViewHolder = VoicemailCursorLoader.toVoicemailEntry(cursor);
     viewHolderId = voicemailEntryOfViewHolder.id();
+    LogUtil.i(
+        "NewVoicemailViewHolder.bindViewHolderValuesFromAdapter", "viewholderId:%d", viewHolderId);
     viewHolderVoicemailUri = Uri.parse(voicemailEntryOfViewHolder.voicemailUri());
     primaryTextView.setText(
         VoicemailEntryText.buildPrimaryVoicemailText(context, voicemailEntryOfViewHolder));
diff --git a/java/com/android/dialer/voicemail/listui/VoicemailCursorLoader.java b/java/com/android/dialer/voicemail/listui/VoicemailCursorLoader.java
index 3112d95..6a55483 100644
--- a/java/com/android/dialer/voicemail/listui/VoicemailCursorLoader.java
+++ b/java/com/android/dialer/voicemail/listui/VoicemailCursorLoader.java
@@ -97,4 +97,8 @@
         .setCallType(cursor.getInt(CALL_TYPE))
         .build();
   }
+
+  static long getTimestamp(Cursor cursor) {
+    return cursor.getLong(TIMESTAMP);
+  }
 }
diff --git a/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_entry_header.xml b/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_entry_header.xml
new file mode 100644
index 0000000..84fcc37
--- /dev/null
+++ b/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_entry_header.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:height="48dp"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+  <TextView
+      android:id="@+id/new_voicemail_header_text"
+      style="@style/SecondaryText"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_marginStart="@dimen/voicemail_header_margin_start"
+      android:layout_centerVertical="true"/>
+</RelativeLayout>
diff --git a/java/com/android/dialer/voicemail/listui/res/values/dimens.xml b/java/com/android/dialer/voicemail/listui/res/values/dimens.xml
index e37bc65..52fad49 100644
--- a/java/com/android/dialer/voicemail/listui/res/values/dimens.xml
+++ b/java/com/android/dialer/voicemail/listui/res/values/dimens.xml
@@ -36,4 +36,6 @@
   <!-- TODO(uabdullah): Work with UX on these values so that the touch target is not too small -->
   <dimen name="voicemail_media_player_height">56dp</dimen>
   <dimen name="voicemail_media_player_width">0dp</dimen>
+
+  <dimen name="voicemail_header_margin_start">16dp</dimen>
 </resources>
diff --git a/java/com/android/dialer/voicemail/listui/res/values/strings.xml b/java/com/android/dialer/voicemail/listui/res/values/strings.xml
index 508f674..53c5252 100644
--- a/java/com/android/dialer/voicemail/listui/res/values/strings.xml
+++ b/java/com/android/dialer/voicemail/listui/res/values/strings.xml
@@ -27,4 +27,11 @@
 
   <!-- String used to display the default staring point of a voicemail-->
   <string name="voicemail_media_player_inital_start_position" translatable="false">00:00</string>
+
+  <!-- Header in voicemail tab to group calls from the current day.  [CHAR LIMIT=30] -->
+  <string name="new_voicemail_header_today">Today</string>
+
+  <!-- Header in voicemail tab to group calls from before the current day.  [CHAR LIMIT=30] -->
+  <string name="new_voicemail_header_older">Older</string>
+
 </resources>
\ No newline at end of file