Merge "Remove redundant long-click listener."
diff --git a/res/layout/call_log_fragment.xml b/res/layout/call_log_fragment.xml
index b4714a3..5cddbe7 100644
--- a/res/layout/call_log_fragment.xml
+++ b/res/layout/call_log_fragment.xml
@@ -61,13 +61,17 @@
     <FrameLayout
         android:layout_width="match_parent"
         android:layout_height="match_parent">
+        <!-- clipChildren=true is required to ensure shadows on elevated call log entries are not
+             clipped.-->
         <ListView android:id="@android:id/list"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:fadingEdge="none"
             android:scrollbarStyle="outsideOverlay"
             android:divider="@null"
-            android:nestedScrollingEnabled="true" />
+            android:nestedScrollingEnabled="true"
+            android:clipChildren="false"
+        />
         <TextView android:id="@android:id/empty"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
diff --git a/res/layout/call_log_list_item.xml b/res/layout/call_log_list_item.xml
index 6068bd8..b36101e 100644
--- a/res/layout/call_log_list_item.xml
+++ b/res/layout/call_log_list_item.xml
@@ -22,20 +22,23 @@
     android:id="@+id/call_log_list_item"
     android:orientation="vertical"
 >
-    <!--
-        This layout may represent either a call log item or one of the
-        headers in the call log.
-
-        The former will make the @id/call_log_item visible and the
-        @id/call_log_header gone.
-
-        The latter will make the @id/call_log_header visible and the
-        @id/call_log_item gone
-    -->
-
+    <!-- Day group heading. Used to show a "today", "yesterday", "last week" or "other" heading
+         above a group of call log entries. -->
+    <TextView
+        android:id="@+id/call_log_day_group_label"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/call_log_outer_margin"
+        android:layout_marginEnd="@dimen/call_log_outer_margin"
+        android:textColor="?attr/call_log_secondary_text_color"
+        android:textSize="@dimen/call_log_secondary_text_size"
+        android:paddingTop="@dimen/call_log_day_group_padding"
+        android:paddingBottom="0dp"
+        />
     <!-- Linear layout to separate the primary area containing the contact badge and caller
          information and the secondary action (call details / play voicemail). -->
     <LinearLayout
+        android:id="@+id/call_log_row"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:baselineAligned="false"
@@ -127,14 +130,6 @@
                   android:layout_width="match_parent"
                   android:layout_height="wrap_content"/>
     </LinearLayout>
-    <TextView
-        android:id="@+id/call_log_header"
-        style="@style/ContactListSeparatorTextViewStyle"
-        android:layout_marginStart="@dimen/call_log_outer_margin"
-        android:layout_marginEnd="@dimen/call_log_outer_margin"
-        android:paddingTop="@dimen/call_log_inner_margin"
-        android:paddingBottom="@dimen/call_log_inner_margin" />
-
     <!-- Displays the extra link section -->
     <ViewStub android:id="@+id/link_stub"
               android:layout="@layout/call_log_list_item_extra"
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 89bd592..ae65330 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -130,4 +130,6 @@
     <dimen name="call_log_action_height">48dp</dimen>
     <!-- Elevation of expanded call log items. -->
     <dimen name="call_log_expanded_elevation">4dp</dimen>
+    <!-- Padding above call log day group headers. -->
+    <dimen name="call_log_day_group_padding">16dp</dimen>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d6d4766..4215f53 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -718,6 +718,15 @@
          [CHAR LIMIT=NONE] -->
     <string name="description_delete_action">Delete call log entry for <xliff:g id="nameOrNumber" example="John Smith">%1$s</xliff:g></string>
 
-    <!-- Toast message which appears when a call log entry is deleted. -->
+    <!-- Toast message which appears when a call log entry is deleted.
+         [CHAR LIMIT=NONE] -->
     <string name="toast_entry_removed">Call log entry deleted.</string>
+
+    <!-- String used as a header in the call log above calls which occurred last week.
+         [CHAR LIMIT=65] -->
+    <string name="call_log_header_last_week">Last week</string>
+
+    <!-- String used as a header in the call log above calls which ocurred more than a week ago.
+         [CHAR LIMIT=65] -->
+    <string name="call_log_header_other">Other</string>
 </resources>
diff --git a/src/com/android/dialer/DialtactsActivity.java b/src/com/android/dialer/DialtactsActivity.java
index ed92add..daeaf2c 100644
--- a/src/com/android/dialer/DialtactsActivity.java
+++ b/src/com/android/dialer/DialtactsActivity.java
@@ -992,12 +992,12 @@
             mFloatingActionButton.setImageResource(R.drawable.fab_ic_call);
             mFloatingActionButton.setContentDescription(
                     getResources().getString(R.string.description_dial_button));
-            alignFloatingActionButtonByTab(mCurrentTabPosition);
+            alignFloatingActionButtonMiddle();
         } else {
             mFloatingActionButton.setImageResource(R.drawable.fab_ic_dial);
             mFloatingActionButton.setContentDescription(
                     getResources().getString(R.string.action_menu_dialpad_button));
-            alignFloatingActionButtonMiddle();
+            alignFloatingActionButtonByTab(mCurrentTabPosition);
         }
     }
 
diff --git a/src/com/android/dialer/calllog/CallLogAdapter.java b/src/com/android/dialer/calllog/CallLogAdapter.java
index 0aca913..c4389ad 100644
--- a/src/com/android/dialer/calllog/CallLogAdapter.java
+++ b/src/com/android/dialer/calllog/CallLogAdapter.java
@@ -40,6 +40,7 @@
 import com.android.common.widget.GroupingListAdapter;
 import com.android.contacts.common.ContactPhotoManager;
 import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
+import com.android.contacts.common.util.DateUtils;
 import com.android.contacts.common.util.UriUtils;
 import com.android.dialer.PhoneCallDetails;
 import com.android.dialer.PhoneCallDetailsHelper;
@@ -60,7 +61,6 @@
 public class CallLogAdapter extends GroupingListAdapter
         implements ViewTreeObserver.OnPreDrawListener, CallLogGroupBuilder.GroupCreator {
 
-
     /** The enumeration of {@link android.os.AsyncTask} objects used in this class. */
     public enum Tasks {
         REMOVE_CALL_LOG_ENTRIES,
@@ -117,6 +117,12 @@
     /** The size of the cache of contact info. */
     private static final int CONTACT_INFO_CACHE_SIZE = 100;
 
+    /** Localized string representing the word "Today". */
+    private static final CharSequence TODAY_LABEL = DateUtils.getTodayString();
+
+    /** Localized string representing the word "Yesterday". */
+    private static final CharSequence YESTERDAY_LABEL = DateUtils.getYesterdayString();
+
     protected final Context mContext;
     private final ContactInfoHelper mContactInfoHelper;
     private final CallFetcher mCallFetcher;
@@ -139,6 +145,20 @@
     private HashMap<Long,Boolean> mIsExpanded = new HashMap<Long,Boolean>();
 
     /**
+     *  Hashmap, keyed by call Id, used to track the day group for a call.  As call log entries are
+     *  put into the primary call groups in {@link com.android.dialer.calllog.CallLogGroupBuilder},
+     *  they are also assigned a secondary "day group".  This hashmap tracks the day group assigned
+     *  to all calls in the call log.  This information is used to trigger the display of a day
+     *  group header above the call log entry at the start of a day group.
+     *  Note: Multiple calls are grouped into a single primary "call group" in the call log, and
+     *  the cursor used to bind rows includes all of these calls.  When determining if a day group
+     *  change has occurred it is necessary to look at the last entry in the call log to determine
+     *  its day group.  This hashmap provides a means of determining the previous day group without
+     *  having to reverse the cursor to the start of the previous day call log entry.
+     */
+    private HashMap<Long,Integer> mDayGroups = new HashMap<Long, Integer>();
+
+    /**
      * A request for contact details for the given number.
      */
     private static final class ContactInfoRequest {
@@ -589,7 +609,6 @@
 
         // Default case: an item in the call log.
         views.primaryActionView.setVisibility(View.VISIBLE);
-        views.listHeaderTextView.setVisibility(View.GONE);
 
         final String number = c.getString(CallLogQuery.NUMBER);
         final int numberPresentation = c.getInt(CallLogQuery.NUMBER_PRESENTATION);
@@ -600,6 +619,21 @@
         final long rowId = c.getLong(CallLogQuery.ID);
         views.rowId = rowId;
 
+        // For entries in the call log, check if the day group has changed and display a header
+        // if necessary.
+        if (mIsCallLog) {
+            int currentGroup = getDayGroupForCall(rowId);
+            int previousGroup = getPreviousDayGroup(c);
+            if (currentGroup != previousGroup) {
+                views.dayGroupHeader.setVisibility(View.VISIBLE);
+                views.dayGroupHeader.setText(getGroupDescription(currentGroup));
+            } else {
+                views.dayGroupHeader.setVisibility(View.GONE);
+            }
+        } else {
+            views.dayGroupHeader.setVisibility(View.GONE);
+        }
+
         // Store some values used when the actions ViewStub is inflated on expansion of the actions
         // section.
         views.number = number;
@@ -738,6 +772,38 @@
     }
 
     /**
+     * Retrieves the day group of the previous call in the call log.  Used to determine if the day
+     * group has changed and to trigger display of the day group text.
+     *
+     * @param cursor The call log cursor.
+     * @return The previous day group, or DAY_GROUP_NONE if this is the first call.
+     */
+    private int getPreviousDayGroup(Cursor cursor) {
+        // We want to restore the position in the cursor at the end.
+        int startingPosition = cursor.getPosition();
+        int dayGroup = CallLogGroupBuilder.DAY_GROUP_NONE;
+        if (cursor.moveToPrevious()) {
+            long previousRowId = cursor.getLong(CallLogQuery.ID);
+            dayGroup = getDayGroupForCall(previousRowId);
+        }
+        cursor.moveToPosition(startingPosition);
+        return dayGroup;
+    }
+
+    /**
+     * Given a call Id, look up the day group that the call belongs to.  The day group data is
+     * populated in {@link com.android.dialer.calllog.CallLogGroupBuilder}.
+     *
+     * @param callId The call to retrieve the day group for.
+     * @return The day group for the call.
+     */
+    private int getDayGroupForCall(long callId) {
+        if (mDayGroups.containsKey(callId)) {
+            return mDayGroups.get(callId);
+        }
+        return CallLogGroupBuilder.DAY_GROUP_NONE;
+    }
+    /**
      * Determines if a call log row with the given Id is expanded to show the action buttons or
      * not. If the row Id is not yet tracked, add a new entry assuming the row is collapsed.
      * @param rowId
@@ -779,9 +845,9 @@
             inflateActionViewStub(callLogItem);
 
             views.actionsView.setVisibility(View.VISIBLE);
-            callLogItem.setBackgroundColor(
+            views.callLogEntryView.setBackgroundColor(
                     callLogItem.getResources().getColor(R.color.background_dialer_light));
-            callLogItem.setElevation(
+            views.callLogEntryView.setElevation(
                     callLogItem.getResources().getDimension(R.dimen.call_log_expanded_elevation));
 
             // Attempt to give accessibility focus to one of the action buttons.
@@ -799,9 +865,9 @@
                 views.actionsView.setVisibility(View.GONE);
             }
 
-            callLogItem.setBackgroundColor(
+            views.callLogEntryView.setBackgroundColor(
                     callLogItem.getResources().getColor(R.color.background_dialer_list_items));
-            callLogItem.setElevation(0);
+            views.callLogEntryView.setElevation(0);
         }
     }
 
@@ -1138,6 +1204,27 @@
         super.addGroup(cursorPosition, size, expanded);
     }
 
+    /**
+     * Stores the day group associated with a call in the call log.
+     *
+     * @param rowId The row Id of the current call.
+     * @param dayGroup The day group the call belongs in.
+     */
+    @Override
+    public void setDayGroup(long rowId, int dayGroup) {
+        if (!mDayGroups.containsKey(rowId)) {
+            mDayGroups.put(rowId, dayGroup);
+        }
+    }
+
+    /**
+     * Clears the day group associations on re-bind of the call log.
+     */
+    @Override
+    public void clearDayGroups() {
+        mDayGroups.clear();
+    }
+
     /*
      * Get the number from the Contacts, if available, since sometimes
      * the number provided by caller id may not be formatted properly
@@ -1199,6 +1286,24 @@
     }
 
     /**
+     * Determines the description for a day group.
+     *
+     * @param group The day group to retrieve the description for.
+     * @return The day group description.
+     */
+    private CharSequence getGroupDescription(int group) {
+       if (group == CallLogGroupBuilder.DAY_GROUP_TODAY) {
+           return TODAY_LABEL;
+       } else if (group == CallLogGroupBuilder.DAY_GROUP_YESTERDAY) {
+           return YESTERDAY_LABEL;
+       } else if (group == CallLogGroupBuilder.DAY_GROUP_LAST_WEEK) {
+           return mContext.getResources().getString(R.string.call_log_header_last_week);
+       } else {
+           return mContext.getResources().getString(R.string.call_log_header_other);
+       }
+    }
+
+    /**
      * Retrieves an instance of the asynchronous task executor, creating one if required.
      * @return The {@link com.android.dialer.util.AsyncTaskExecutor}
      */
diff --git a/src/com/android/dialer/calllog/CallLogGroupBuilder.java b/src/com/android/dialer/calllog/CallLogGroupBuilder.java
index 0b2edf0..50cf054 100644
--- a/src/com/android/dialer/calllog/CallLogGroupBuilder.java
+++ b/src/com/android/dialer/calllog/CallLogGroupBuilder.java
@@ -19,22 +19,74 @@
 import android.database.Cursor;
 import android.provider.CallLog.Calls;
 import android.telephony.PhoneNumberUtils;
+import android.text.format.Time;
 
 import com.android.common.widget.GroupingListAdapter;
+import com.android.contacts.common.util.DateUtils;
 import com.android.contacts.common.util.PhoneNumberHelper;
 
 import com.google.common.annotations.VisibleForTesting;
 
 /**
- * Groups together calls in the call log.
+ * Groups together calls in the call log.  The primary grouping attempts to group together calls
+ * to and from the same number into a single row on the call log.
+ * A secondary grouping assigns calls, grouped via the primary grouping, to "day groups".  The day
+ * groups provide a means of identifying the calls which occurred "Today", "Yesterday", "Last week",
+ * or "Other".
  * <p>
  * This class is meant to be used in conjunction with {@link GroupingListAdapter}.
  */
 public class CallLogGroupBuilder {
     public interface GroupCreator {
+
+        /**
+         * Defines the interface for adding a group to the call log.
+         * The primary group for a call log groups the calls together based on the number which was
+         * dialed.
+         * @param cursorPosition The starting position of the group in the cursor.
+         * @param size The size of the group.
+         * @param expanded Whether the group is expanded; always false for the call log.
+         */
         public void addGroup(int cursorPosition, int size, boolean expanded);
+
+        /**
+         * Defines the interface for tracking the day group each call belongs to.  Calls in a call
+         * group are assigned the same day group as the first call in the group.  The day group
+         * assigns calls to the buckets: Today, Yesterday, Last week, and Other
+         *
+         * @param rowId The row Id of the current call.
+         * @param dayGroup The day group the call belongs in.
+         */
+        public void setDayGroup(long rowId, int dayGroup);
+
+        /**
+         * Defines the interface for clearing the day groupings information on rebind/regroup.
+         */
+        public void clearDayGroups();
     }
 
+    /**
+     * Day grouping for call log entries used to represent no associated day group.  Used primarily
+     * when retrieving the previous day group, but there is no previous day group (i.e. we are at
+     * the start of the list).
+     */
+    public static final int DAY_GROUP_NONE = -1;
+
+    /** Day grouping for calls which occurred today. */
+    public static final int DAY_GROUP_TODAY = 0;
+
+    /** Day grouping for calls which occurred yesterday. */
+    public static final int DAY_GROUP_YESTERDAY = 1;
+
+    /** Day grouping for calls which occurred last week. */
+    public static final int DAY_GROUP_LAST_WEEK = 2;
+
+    /** Day grouping for calls which occurred before last week. */
+    public static final int DAY_GROUP_OTHER = 3;
+
+    /** Instance of the time object used for time calculations. */
+    private static final Time TIME = new Time();
+
     /** The object on which the groups are created. */
     private final GroupCreator mGroupCreator;
 
@@ -59,18 +111,33 @@
             return;
         }
 
+        // Clear any previous day grouping information.
+        mGroupCreator.clearDayGroups();
+
+        // Get current system time, used for calculating which day group calls belong to.
+        long currentTime = System.currentTimeMillis();
+
         int currentGroupSize = 1;
         cursor.moveToFirst();
         // The number of the first entry in the group.
         String firstNumber = cursor.getString(CallLogQuery.NUMBER);
         // This is the type of the first call in the group.
         int firstCallType = cursor.getInt(CallLogQuery.CALL_TYPE);
+
+        // Determine the day group for the first call in the cursor.
+        final long firstDate = cursor.getLong(CallLogQuery.DATE);
+        final long firstRowId = cursor.getLong(CallLogQuery.ID);
+        int currentGroupDayGroup = getDayGroup(firstDate, currentTime);
+        mGroupCreator.setDayGroup(firstRowId, currentGroupDayGroup);
+
         while (cursor.moveToNext()) {
             // The number of the current row in the cursor.
             final String currentNumber = cursor.getString(CallLogQuery.NUMBER);
             final int callType = cursor.getInt(CallLogQuery.CALL_TYPE);
             final boolean sameNumber = equalNumbers(firstNumber, currentNumber);
             final boolean shouldGroup;
+            final long currentCallId = cursor.getLong(CallLogQuery.ID);
+            final long date = cursor.getLong(CallLogQuery.DATE);
 
             if (!sameNumber) {
                 // Should only group with calls from the same number.
@@ -88,6 +155,11 @@
                 // the group until we find a call that does not match.
                 currentGroupSize++;
             } else {
+                // The call group has changed, so determine the day group for the new call group.
+                // This ensures all calls grouped together in the call log are assigned the same
+                // day group.
+                currentGroupDayGroup = getDayGroup(date, currentTime);
+
                 // Create a group for the previous set of calls, excluding the current one, but do
                 // not create a group for a single call.
                 if (currentGroupSize > 1) {
@@ -99,6 +171,9 @@
                 firstNumber = currentNumber;
                 firstCallType = callType;
             }
+
+            // Save the day group associated with the current call.
+            mGroupCreator.setDayGroup(currentCallId, currentGroupDayGroup);
         }
         // If the last set of calls at the end of the call log was itself a group, create it now.
         if (currentGroupSize > 1) {
@@ -154,4 +229,25 @@
 
         return userinfo1.equals(userinfo2) && rest1.equalsIgnoreCase(rest2);
     }
+
+    /**
+     * Given a call date and the current date, determine which date group the call belongs in.
+     *
+     * @param date The call date.
+     * @param now The current date.
+     * @return The date group the call belongs in.
+     */
+    private int getDayGroup(long date, long now) {
+        int days = DateUtils.getDayDifference(TIME, date, now);
+
+        if (days == 0) {
+            return DAY_GROUP_TODAY;
+        } else if (days == 1) {
+            return DAY_GROUP_YESTERDAY;
+        } else if (days > 1 && days <=7) {
+            return DAY_GROUP_LAST_WEEK;
+        } else {
+            return DAY_GROUP_OTHER;
+        }
+    }
 }
diff --git a/src/com/android/dialer/calllog/CallLogListItemViews.java b/src/com/android/dialer/calllog/CallLogListItemViews.java
index 333769d..648362e 100644
--- a/src/com/android/dialer/calllog/CallLogListItemViews.java
+++ b/src/com/android/dialer/calllog/CallLogListItemViews.java
@@ -36,8 +36,10 @@
     public final View primaryActionView;
     /** The details of the phone call. */
     public final PhoneCallDetailsViews phoneCallDetailsViews;
-    /** The text of the header of a section. */
-    public final TextView listHeaderTextView;
+    /** The text of the header for a day grouping. */
+    public final TextView dayGroupHeader;
+    /** The view containing the details for the call log row, including the action buttons. */
+    public final View callLogEntryView;
     /** The view containing call log item actions.  Null until the ViewStub is inflated. */
     public View actionsView;
     /** The "call back" action button - assigned only when the action section is expanded. */
@@ -91,12 +93,13 @@
     public CharSequence nameOrNumber;
 
     private CallLogListItemViews(QuickContactBadge quickContactView, View primaryActionView,
-            PhoneCallDetailsViews phoneCallDetailsViews,
-            TextView listHeaderTextView) {
+            PhoneCallDetailsViews phoneCallDetailsViews, View callLogEntryView,
+            TextView dayGroupHeader) {
         this.quickContactView = quickContactView;
         this.primaryActionView = primaryActionView;
         this.phoneCallDetailsViews = phoneCallDetailsViews;
-        this.listHeaderTextView = listHeaderTextView;
+        this.callLogEntryView = callLogEntryView;
+        this.dayGroupHeader = dayGroupHeader;
     }
 
     public static CallLogListItemViews fromView(View view) {
@@ -104,7 +107,8 @@
                 (QuickContactBadge) view.findViewById(R.id.quick_contact_photo),
                 view.findViewById(R.id.primary_action_view),
                 PhoneCallDetailsViews.fromView(view),
-                (TextView) view.findViewById(R.id.call_log_header));
+                view.findViewById(R.id.call_log_row),
+                (TextView) view.findViewById(R.id.call_log_day_group_label));
     }
 
     @NeededForTesting
@@ -113,6 +117,7 @@
                 new QuickContactBadge(context),
                 new View(context),
                 PhoneCallDetailsViews.createForTest(context),
+                new View(context),
                 new TextView(context));
         views.callBackButtonView = new TextView(context);
         views.deleteButtonView = new TextView(context);