Added partial UI to new call log.

UI notes:

-Updated view holder to show number and basic secondary text
-Updated adapter to divide "Today" and "Older" entries
-Photo is just an anonymous avatar for now
-Clicking anywhere is a no-op

Other things done in this CL:

-Plumbed a few more columns through the call log framework
-Tweaked some column names in the data model (contract)
-Cleaned up some existing tests and added some new ones

Screenshot:
https://screenshot.googleplex.com/DiMscW47AYb

This is the complete spec I am working from:
https://screenshot.googleplex.com/XLquTek1oHk

Bug: 34672501
Test: existing and new
Change-Id: Ice0e538e23e59b7d752f47125a5f9da96bf91430
PiperOrigin-RevId: 166508997
diff --git a/assets/quantum/res/drawable/quantum_ic_more_vert_vd_theme_24.xml b/assets/quantum/res/drawable/quantum_ic_more_vert_vd_theme_24.xml
new file mode 100644
index 0000000..1a3a684
--- /dev/null
+++ b/assets/quantum/res/drawable/quantum_ic_more_vert_vd_theme_24.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"
+        android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
+</vector>
diff --git a/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java b/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java
index ebfd3c7..507a51a 100644
--- a/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java
+++ b/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java
@@ -38,7 +38,9 @@
           // Common columns.
           .append(AnnotatedCallLog._ID + " integer primary key, ")
           .append(AnnotatedCallLog.TIMESTAMP + " integer, ")
-          .append(AnnotatedCallLog.PRIMARY_TEXT + " string, ")
+          .append(AnnotatedCallLog.NAME + " string, ")
+          .append(AnnotatedCallLog.NEW + " integer, ")
+          .append(AnnotatedCallLog.TYPE + " integer, ")
           .append(AnnotatedCallLog.CONTACT_PHOTO_URI + " string, ")
           .append(AnnotatedCallLog.NUMBER_TYPE_LABEL + " string, ")
           .append(AnnotatedCallLog.IS_READ + " integer, ")
diff --git a/java/com/android/dialer/calllog/database/Coalescer.java b/java/com/android/dialer/calllog/database/Coalescer.java
index 23ddc9c..55bed3e 100644
--- a/java/com/android/dialer/calllog/database/Coalescer.java
+++ b/java/com/android/dialer/calllog/database/Coalescer.java
@@ -133,8 +133,16 @@
     DialerPhoneNumber number1;
     DialerPhoneNumber number2;
     try {
-      number1 = DialerPhoneNumber.parseFrom(row1.getAsByteArray(AnnotatedCallLog.NUMBER));
-      number2 = DialerPhoneNumber.parseFrom(row2.getAsByteArray(AnnotatedCallLog.NUMBER));
+      byte[] number1Bytes = row1.getAsByteArray(AnnotatedCallLog.NUMBER);
+      byte[] number2Bytes = row2.getAsByteArray(AnnotatedCallLog.NUMBER);
+
+      if (number1Bytes == null || number2Bytes == null) {
+        // Empty numbers should not be combined.
+        return false;
+      }
+
+      number1 = DialerPhoneNumber.parseFrom(number1Bytes);
+      number2 = DialerPhoneNumber.parseFrom(number2Bytes);
     } catch (InvalidProtocolBufferException e) {
       throw Assert.createAssertionFailException("error parsing DialerPhoneNumber proto", e);
     }
diff --git a/java/com/android/dialer/calllog/database/annotated_call_log.proto b/java/com/android/dialer/calllog/database/annotated_call_log.proto
index eb0ee52..de2bc5f 100644
--- a/java/com/android/dialer/calllog/database/annotated_call_log.proto
+++ b/java/com/android/dialer/calllog/database/annotated_call_log.proto
@@ -8,7 +8,8 @@
 
 package com.android.dialer;
 
-// A list of android.provider.CallLog.Calls.TYPE values.
+// A list of android.provider.CallLog.Calls.TYPE values ordered from newest to
+// oldest.
 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 1720068..c669bda 100644
--- a/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java
+++ b/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java
@@ -42,15 +42,15 @@
     String TIMESTAMP = "timestamp";
 
     /**
-     * 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.
+     * Name of the caller if available. This could be a name from a local contact or caller ID data
+     * source, 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 PRIMARY_TEXT = "primary_text";
+    String NAME = "name";
 
     /**
      * Local photo URI for the contact associated with the phone number, if it exists.
@@ -74,14 +74,21 @@
     String NUMBER_TYPE_LABEL = "number_type_label";
 
     /**
-     * See CallLog.Calls.IS_READ.
+     * See {@link android.provider.CallLog.Calls#IS_READ}.
      *
      * <p>TYPE: INTEGER (boolean)
      */
     String IS_READ = "is_read";
 
     /**
-     * See CallLog.Calls.GEOCODED_LOCATION.
+     * See {@link android.provider.CallLog.Calls#NEW}.
+     *
+     * <p>Type: INTEGER (boolean)
+     */
+    String NEW = "new";
+
+    /**
+     * See {@link android.provider.CallLog.Calls#GEOCODED_LOCATION}.
      *
      * <p>TYPE: TEXT
      */
@@ -102,7 +109,7 @@
     String PHONE_ACCOUNT_COLOR = "phone_account_color";
 
     /**
-     * See CallLog.Calls.FEATURES.
+     * See {@link android.provider.CallLog.Calls#FEATURES}.
      *
      * <p>TYPE: INTEGER (int)
      */
@@ -128,10 +135,11 @@
         new String[] {
           _ID,
           TIMESTAMP,
-          PRIMARY_TEXT,
+          NAME,
           CONTACT_PHOTO_URI,
           NUMBER_TYPE_LABEL,
           IS_READ,
+          NEW,
           GEOCODED_LOCATION,
           PHONE_ACCOUNT_LABEL,
           PHONE_ACCOUNT_COLOR,
@@ -168,6 +176,13 @@
      * <p>Type: BLOB
      */
     public static final String NUMBER = "number";
+
+    /**
+     * Copied from {@link android.provider.CallLog.Calls#TYPE}.
+     *
+     * <p>Type: INTEGER (int)
+     */
+    public static final String TYPE = "type";
   }
 
   /**
diff --git a/java/com/android/dialer/calllog/datasources/contacts/ContactsDataSource.java b/java/com/android/dialer/calllog/datasources/contacts/ContactsDataSource.java
index ad82427..8090cbc 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 @@
     Assert.isWorkerThread();
     // TODO(zachh): Implementation.
     for (ContentValues contentValues : mutations.getInserts().values()) {
-      contentValues.put(AnnotatedCallLog.PRIMARY_TEXT, "Placeholder name");
+      contentValues.put(AnnotatedCallLog.NAME, "Placeholder name");
     }
   }
 
@@ -64,7 +64,7 @@
   public ContentValues coalesce(List<ContentValues> individualRowsSortedByTimestampDesc) {
     // TODO(zachh): Implementation.
     return new RowCombiner(individualRowsSortedByTimestampDesc)
-        .useSingleValueString(AnnotatedCallLog.PRIMARY_TEXT)
+        .useSingleValueString(AnnotatedCallLog.NAME)
         .combine();
   }
 
diff --git a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java
index 94b908f..7bf2972 100644
--- a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java
+++ b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java
@@ -37,6 +37,7 @@
 import android.telecom.PhoneAccountHandle;
 import android.text.TextUtils;
 import android.util.ArraySet;
+import com.android.dialer.CallTypes;
 import com.android.dialer.DialerPhoneNumber;
 import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog;
 import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.CoalescedAnnotatedCallLog;
@@ -84,6 +85,7 @@
       LogUtil.i("SystemCallLogDataSource.registerContentObservers", "no call log permissions");
       return;
     }
+    // TODO(zachh): Need to somehow register observers if user enables permission after launch?
 
     appContext
         .getContentResolver()
@@ -104,8 +106,11 @@
      * column is unused). This means that we can't detect deletes without scanning the entire table,
      * which would be too slow. So, we just rely on content observers to trigger rebuilds when any
      * change is made to the system call log.
+     *
+     * Just return false unless the table has never been written to.
      */
-    return false;
+    return !PreferenceManager.getDefaultSharedPreferences(appContext)
+        .contains(PREF_LAST_TIMESTAMP_PROCESSED);
   }
 
   @WorkerThread
@@ -152,23 +157,41 @@
     ContentValues coalescedValues =
         new RowCombiner(individualRowsSortedByTimestampDesc)
             .useMostRecentLong(AnnotatedCallLog.TIMESTAMP)
+            .useMostRecentLong(AnnotatedCallLog.NEW)
+            .useMostRecentString(AnnotatedCallLog.NUMBER_TYPE_LABEL)
+            .useMostRecentString(AnnotatedCallLog.GEOCODED_LOCATION)
             .combine();
 
     // All phone numbers in the provided group should be equivalent (but could be formatted
     // differently). Arbitrarily show the raw phone number of the most recent call.
     DialerPhoneNumber mostRecentPhoneNumber =
         getMostRecentPhoneNumber(individualRowsSortedByTimestampDesc);
-    coalescedValues.put(
-        CoalescedAnnotatedCallLog.FORMATTED_NUMBER,
-        mostRecentPhoneNumber.getRawInput().getNumber());
+    if (mostRecentPhoneNumber != null) {
+      coalescedValues.put(
+          CoalescedAnnotatedCallLog.FORMATTED_NUMBER,
+          mostRecentPhoneNumber.getRawInput().getNumber());
+    }
+
+    CallTypes.Builder callTypes = CallTypes.newBuilder();
+    // Store a maximum of 3 call types since that's all we show to users via icons.
+    for (int i = 0; i < 3 && i < individualRowsSortedByTimestampDesc.size(); i++) {
+      callTypes.addType(
+          individualRowsSortedByTimestampDesc.get(i).getAsInteger(AnnotatedCallLog.TYPE));
+    }
+    coalescedValues.put(CoalescedAnnotatedCallLog.CALL_TYPES, callTypes.build().toByteArray());
+
     return coalescedValues;
   }
 
+  @Nullable
   private static DialerPhoneNumber getMostRecentPhoneNumber(
       List<ContentValues> individualRowsSortedByTimestampDesc) {
-    DialerPhoneNumber dialerPhoneNumber;
     byte[] protoBytes =
         individualRowsSortedByTimestampDesc.get(0).getAsByteArray(AnnotatedCallLog.NUMBER);
+    if (protoBytes == null) {
+      return null;
+    }
+    DialerPhoneNumber dialerPhoneNumber;
     try {
       dialerPhoneNumber = DialerPhoneNumber.parseFrom(protoBytes);
     } catch (InvalidProtocolBufferException e) {
@@ -198,10 +221,12 @@
                   Calls.DATE,
                   Calls.LAST_MODIFIED,
                   Calls.NUMBER,
+                  Calls.TYPE,
                   Calls.COUNTRY_ISO,
                   Calls.CACHED_NUMBER_TYPE,
                   Calls.CACHED_NUMBER_LABEL,
                   Calls.IS_READ,
+                  Calls.NEW,
                   Calls.GEOCODED_LOCATION,
                   Calls.PHONE_ACCOUNT_COMPONENT_NAME,
                   Calls.PHONE_ACCOUNT_ID,
@@ -226,10 +251,12 @@
         int dateColumn = cursor.getColumnIndexOrThrow(Calls.DATE);
         int lastModifiedColumn = cursor.getColumnIndexOrThrow(Calls.LAST_MODIFIED);
         int numberColumn = cursor.getColumnIndexOrThrow(Calls.NUMBER);
+        int typeColumn = cursor.getColumnIndexOrThrow(Calls.TYPE);
         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 newColumn = cursor.getColumnIndexOrThrow(Calls.NEW);
         int geocodedLocationColumn = cursor.getColumnIndexOrThrow(Calls.GEOCODED_LOCATION);
         int phoneAccountComponentColumn =
             cursor.getColumnIndexOrThrow(Calls.PHONE_ACCOUNT_COMPONENT_NAME);
@@ -243,30 +270,40 @@
           long id = cursor.getLong(idColumn);
           long date = cursor.getLong(dateColumn);
           String numberAsStr = cursor.getString(numberColumn);
+          long type = cursor.getType(typeColumn);
           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);
+          int isNew = cursor.getInt(newColumn);
           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());
+          if (!TextUtils.isEmpty(numberAsStr)) {
+            byte[] numberAsProtoBytes =
+                dialerPhoneNumberUtil.parse(numberAsStr, countryIso).toByteArray();
+            // TODO(zachh): Need to handle post-dial digits; different on N and M.
+            contentValues.put(AnnotatedCallLog.NUMBER, numberAsProtoBytes);
+          }
+
+          contentValues.put(AnnotatedCallLog.TYPE, type);
+
+          // Phone.getTypeLabel returns "Custom" if given (0, null) which is not of any use. Just
+          // omit setting the label if there's no information for it.
+          if (cachedNumberType != 0 || cachedNumberLabel != null) {
+            contentValues.put(
+                AnnotatedCallLog.NUMBER_TYPE_LABEL,
+                Phone.getTypeLabel(appContext.getResources(), cachedNumberType, cachedNumberLabel)
+                    .toString());
+          }
           contentValues.put(AnnotatedCallLog.IS_READ, isRead);
+          contentValues.put(AnnotatedCallLog.NEW, isNew);
           contentValues.put(AnnotatedCallLog.GEOCODED_LOCATION, geocodedLocation);
           populatePhoneAccountLabelAndColor(
               appContext, contentValues, phoneAccountComponentName, phoneAccountId);
diff --git a/java/com/android/dialer/calllog/datasources/util/RowCombiner.java b/java/com/android/dialer/calllog/datasources/util/RowCombiner.java
index 0c7be1e..3595a05 100644
--- a/java/com/android/dialer/calllog/datasources/util/RowCombiner.java
+++ b/java/com/android/dialer/calllog/datasources/util/RowCombiner.java
@@ -36,12 +36,23 @@
     return this;
   }
 
+  /** Use the most recent value for the specified column. */
+  public RowCombiner useMostRecentString(String columnName) {
+    combinedRow.put(columnName, individualRowsSortedByTimestampDesc.get(0).getAsString(columnName));
+    return this;
+  }
+
   /** Asserts that all column values for the given column name are the same, and uses it. */
   public RowCombiner useSingleValueString(String columnName) {
     Iterator<ContentValues> iterator = individualRowsSortedByTimestampDesc.iterator();
     String singleValue = iterator.next().getAsString(columnName);
     while (iterator.hasNext()) {
-      Assert.checkState(iterator.next().getAsString(columnName).equals(singleValue));
+      String current = iterator.next().getAsString(columnName);
+      if (current == null) {
+        Assert.checkState(singleValue == null);
+      } else {
+        Assert.checkState(current.equals(singleValue));
+      }
     }
     combinedRow.put(columnName, singleValue);
     return this;
diff --git a/java/com/android/dialer/calllog/ui/CoalescedAnnotatedCallLogCursorLoader.java b/java/com/android/dialer/calllog/ui/CoalescedAnnotatedCallLogCursorLoader.java
index e993816..6545916 100644
--- a/java/com/android/dialer/calllog/ui/CoalescedAnnotatedCallLogCursorLoader.java
+++ b/java/com/android/dialer/calllog/ui/CoalescedAnnotatedCallLogCursorLoader.java
@@ -30,19 +30,20 @@
   private static final int ID = 0;
 
   private static final int TIMESTAMP = 1;
-  private static final int PRIMARY_TEXT = 2;
+  private static final int NAME = 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;
+  private static final int NEW = 6;
+  private static final int GEOCODED_LOCATION = 7;
+  private static final int PHONE_ACCOUNT_LABEL = 8;
+  private static final int PHONE_ACCOUNT_COLOR = 9;
+  private static final int FEATURES = 10;
+  private static final int IS_BUSINESS = 11;
+  private static final int IS_VOICEMAIL = 12;
+  private static final int NUMBER_CALLS = 13;
+  private static final int FORMATTED_NUMBER = 14;
+  private static final int CALL_TYPES = 15;
 
   /** Convenience class for accessing values using an abbreviated syntax. */
   static final class Row {
@@ -60,8 +61,8 @@
       return cursor.getLong(TIMESTAMP);
     }
 
-    String primaryText() {
-      return cursor.getString(PRIMARY_TEXT);
+    String name() {
+      return cursor.getString(NAME);
     }
 
     String contactPhotoUri() {
@@ -76,6 +77,10 @@
       return cursor.getInt(IS_READ) == 1;
     }
 
+    boolean isNew() {
+      return cursor.getInt(NEW) == 1;
+    }
+
     String geocodedLocation() {
       return cursor.getString(GEOCODED_LOCATION);
     }
diff --git a/java/com/android/dialer/calllog/ui/HeaderViewHolder.java b/java/com/android/dialer/calllog/ui/HeaderViewHolder.java
new file mode 100644
index 0000000..e4fe029
--- /dev/null
+++ b/java/com/android/dialer/calllog/ui/HeaderViewHolder.java
@@ -0,0 +1,36 @@
+/*
+ * 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.support.annotation.StringRes;
+import android.support.v7.widget.RecyclerView.ViewHolder;
+import android.view.View;
+import android.widget.TextView;
+
+/** ViewHolder for {@link NewCallLogAdapter} to display "Today" or "Older" divider row. */
+final class HeaderViewHolder extends ViewHolder {
+
+  private TextView headerTextView;
+
+  HeaderViewHolder(View view) {
+    super(view);
+    headerTextView = view.findViewById(R.id.new_call_log_header_text);
+  }
+
+  void setHeader(@StringRes int header) {
+    headerTextView.setText(header);
+  }
+}
diff --git a/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java b/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java
index 4655b09..b922a6e 100644
--- a/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java
+++ b/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java
@@ -16,34 +16,140 @@
 package com.android.dialer.calllog.ui;
 
 import android.database.Cursor;
+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.view.LayoutInflater;
 import android.view.ViewGroup;
+import com.android.dialer.calllogutils.CallLogDates;
+import com.android.dialer.common.Assert;
+import com.android.dialer.time.Clock;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /** {@link RecyclerView.Adapter} for the new call log fragment. */
-final class NewCallLogAdapter extends RecyclerView.Adapter<NewCallLogViewHolder> {
+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})
+  @interface RowType {
+    /** Header that displays "Today". */
+    int HEADER_TODAY = 1;
+    /** Header that displays "Older". */
+    int HEADER_OLDER = 2;
+    /** A row representing a call log entry (which could represent one or more calls). */
+    int CALL_LOG_ENTRY = 3;
+  }
 
   private final Cursor cursor;
+  private final Clock clock;
 
-  NewCallLogAdapter(Cursor cursor) {
+  /** Null when the "Today" header should not be displayed. */
+  @Nullable private final Integer todayHeaderPosition;
+  /** Null when the "Older" header should not be displayed. */
+  @Nullable private final Integer olderHeaderPosition;
+
+  NewCallLogAdapter(Cursor cursor, Clock clock) {
     this.cursor = cursor;
+    this.clock = clock;
+
+    // Calculate header adapter positions by reading cursor.
+    long currentTimeMillis = clock.currentTimeMillis();
+    if (cursor.moveToNext()) {
+      CoalescedAnnotatedCallLogCursorLoader.Row firstRow =
+          new CoalescedAnnotatedCallLogCursorLoader.Row(cursor);
+      if (CallLogDates.isSameDay(currentTimeMillis, firstRow.timestamp())) {
+        this.todayHeaderPosition = 0;
+        int adapterPosition = 2; // Accounted for "Today" header and first row.
+        while (cursor.moveToNext()) {
+          CoalescedAnnotatedCallLogCursorLoader.Row row =
+              new CoalescedAnnotatedCallLogCursorLoader.Row(cursor);
+          if (CallLogDates.isSameDay(currentTimeMillis, row.timestamp())) {
+            adapterPosition++;
+          } else {
+            this.olderHeaderPosition = adapterPosition;
+            return;
+          }
+        }
+        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;
+    }
   }
 
   @Override
-  public NewCallLogViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
-    return new NewCallLogViewHolder(
-        LayoutInflater.from(viewGroup.getContext())
-            .inflate(R.layout.new_call_log_entry, viewGroup, false));
+  public ViewHolder onCreateViewHolder(ViewGroup viewGroup, @RowType int viewType) {
+    switch (viewType) {
+      case RowType.HEADER_TODAY:
+      case RowType.HEADER_OLDER:
+        return new HeaderViewHolder(
+            LayoutInflater.from(viewGroup.getContext())
+                .inflate(R.layout.new_call_log_header, viewGroup, false));
+      case RowType.CALL_LOG_ENTRY:
+        return new NewCallLogViewHolder(
+            LayoutInflater.from(viewGroup.getContext())
+                .inflate(R.layout.new_call_log_entry, viewGroup, false),
+            clock);
+      default:
+        throw Assert.createUnsupportedOperationFailException("Unsupported view type: " + viewType);
+    }
   }
 
   @Override
-  public void onBindViewHolder(NewCallLogViewHolder viewHolder, int position) {
-    cursor.moveToPosition(position);
-    viewHolder.bind(cursor);
+  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;
+    }
+    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
+  @RowType
+  public int getItemViewType(int position) {
+    if (todayHeaderPosition != null && position == todayHeaderPosition) {
+      return RowType.HEADER_TODAY;
+    }
+    if (olderHeaderPosition != null && position == olderHeaderPosition) {
+      return RowType.HEADER_OLDER;
+    }
+    return RowType.CALL_LOG_ENTRY;
   }
 
   @Override
   public int getItemCount() {
-    return cursor.getCount();
+    int numberOfHeaders = 0;
+    if (todayHeaderPosition != null) {
+      numberOfHeaders++;
+    }
+    if (olderHeaderPosition != null) {
+      numberOfHeaders++;
+    }
+    return cursor.getCount() + numberOfHeaders;
   }
 }
diff --git a/java/com/android/dialer/calllog/ui/NewCallLogFragment.java b/java/com/android/dialer/calllog/ui/NewCallLogFragment.java
index c4bcb76..9227678 100644
--- a/java/com/android/dialer/calllog/ui/NewCallLogFragment.java
+++ b/java/com/android/dialer/calllog/ui/NewCallLogFragment.java
@@ -68,7 +68,7 @@
     refreshAnnotatedCallLogTask =
         dialerExecutorFactory
             .createUiTaskBuilder(
-                getFragmentManager(),
+                getActivity().getFragmentManager(),
                 "NewCallLogFragment.refreshAnnotatedCallLog",
                 component.getRefreshAnnotatedCallLogWorker())
             .build();
@@ -140,7 +140,7 @@
 
     // TODO(zachh): Handle empty cursor by showing empty view.
     recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
-    recyclerView.setAdapter(new NewCallLogAdapter(newCursor));
+    recyclerView.setAdapter(new NewCallLogAdapter(newCursor, System::currentTimeMillis));
   }
 
   @Override
diff --git a/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java b/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java
index 72ea17b..b6b658f 100644
--- a/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java
+++ b/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java
@@ -15,33 +15,119 @@
  */
 package com.android.dialer.calllog.ui;
 
+import android.content.Context;
 import android.database.Cursor;
+import android.provider.CallLog.Calls;
 import android.support.v7.widget.RecyclerView;
+import android.text.TextUtils;
 import android.view.View;
+import android.widget.QuickContactBadge;
 import android.widget.TextView;
-import java.text.SimpleDateFormat;
+import com.android.dialer.calllogutils.CallLogDates;
+import com.android.dialer.contactphoto.ContactPhotoManager;
+import com.android.dialer.lettertile.LetterTileDrawable;
+import com.android.dialer.time.Clock;
 import java.util.Locale;
 
 /** {@link RecyclerView.ViewHolder} for the new call log. */
 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 Context context;
   private final TextView primaryTextView;
   private final TextView secondaryTextView;
+  private final QuickContactBadge quickContactBadge;
+  private final Clock clock;
 
-  NewCallLogViewHolder(View view) {
+  NewCallLogViewHolder(View view, Clock clock) {
     super(view);
+    this.context = view.getContext();
     primaryTextView = view.findViewById(R.id.primary_text);
     secondaryTextView = view.findViewById(R.id.secondary_text);
+    quickContactBadge = view.findViewById(R.id.quick_contact_photo);
+    this.clock = clock;
   }
 
   /** @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()));
+
+    // TODO(zachh): Add HD icon and Wifi icon after primary text.
+    // TODO(zachh): Call type icons for last 3 calls.
+    // TODO(zachh): Use name for primary text if available.
+    // TODO(zachh): Handle CallLog.Calls.PRESENTATION_*, including Verizon restricted numbers.
+    // TODO(zachh): Handle RTL properly.
+    primaryTextView.setText(buildPrimaryText(row));
+    secondaryTextView.setText(buildSecondaryText(row));
+
+    if (row.isNew()) {
+      // TODO(zachh): Figure out correct styling for new/missed/unread calls.
+      primaryTextView.setTextAppearance(R.style.primary_textview_new_call);
+      // TODO(zachh): Styling for call type icons when the call is new.
+      secondaryTextView.setTextAppearance(R.style.secondary_textview_new_call);
+    }
+
+    setPhoto();
+  }
+
+  private String buildPrimaryText(CoalescedAnnotatedCallLogCursorLoader.Row row) {
+    StringBuilder primaryText =
+        new StringBuilder(
+            TextUtils.isEmpty(row.formattedNumber())
+                ? context.getText(R.string.new_call_log_unknown)
+                : row.formattedNumber());
+    if (row.numberCalls() > 1) {
+      primaryText.append(String.format(Locale.getDefault(), " (%d)", row.numberCalls()));
+    }
+    return primaryText.toString();
+  }
+
+  private String buildSecondaryText(CoalescedAnnotatedCallLogCursorLoader.Row row) {
+    /*
+     * Rules: (Duo video, )?$Label|$Location • Date
+     *
+     * Examples:
+     *   Duo Video, Mobile • Now
+     *   Duo Video • 11:45pm
+     *   Mobile • 11:45pm
+     *   Mobile • Sunday
+     *   Brooklyn, NJ • Jan 15
+     *
+     * Date rules:
+     *   if < 1 minute ago: "Now"; else if today: HH:MM(am|pm); else if < 3 days: day; else: MON D
+     */
+    StringBuilder secondaryText = new StringBuilder();
+    if ((row.features() & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO) {
+      // TODO(zachh): Add "Duo" prefix?
+      secondaryText.append(context.getText(R.string.new_call_log_video));
+    }
+    String numberTypeLabel = row.numberTypeLabel();
+    if (!TextUtils.isEmpty(numberTypeLabel)) {
+      if (secondaryText.length() > 0) {
+        secondaryText.append(", ");
+      }
+      secondaryText.append(numberTypeLabel);
+    } else { // If there's a number type label, don't show the location.
+      String location = row.geocodedLocation();
+      if (!TextUtils.isEmpty(location)) {
+        if (secondaryText.length() > 0) {
+          secondaryText.append(", ");
+        }
+        secondaryText.append(location);
+      }
+    }
+    if (secondaryText.length() > 0) {
+      secondaryText.append(" • ");
+    }
+    secondaryText.append(
+        CallLogDates.newCallLogTimestampLabel(context, clock.currentTimeMillis(), row.timestamp()));
+    return secondaryText.toString();
+  }
+
+  private void setPhoto() {
+    // TODO(zachh): Set photo/icon appropriately. (This just uses the anonymous avatar.)
+    ContactPhotoManager.getInstance(context)
+        .loadDialerThumbnailOrPhoto(
+            quickContactBadge, null, 0, null, null, LetterTileDrawable.TYPE_DEFAULT);
   }
 }
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 38e07be..568b351 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
@@ -15,23 +15,64 @@
   ~ limitations under the License
   -->
 
-<LinearLayout
+<RelativeLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:padding="8dp"
-    android:orientation="vertical">
+    android:layout_marginTop="@dimen/call_log_entry_top_margin"
+    android:paddingTop="@dimen/call_log_entry_padding_top_start"
+    android:paddingBottom="@dimen/call_log_entry_padding_bottom_end"
+    android:paddingStart="@dimen/call_log_entry_padding_top_start"
+    android:paddingEnd="@dimen/call_log_entry_padding_bottom_end"
+    android:gravity="center_vertical">
 
-  <TextView
-      android:id="@+id/primary_text"
+  <QuickContactBadge
+      android:id="@+id/quick_contact_photo"
+      android:layout_width="@dimen/call_log_entry_photo_size"
+      android:layout_height="@dimen/call_log_entry_photo_size"
+      android:layout_centerVertical="true"
+      android:padding="@dimen/call_log_entry_photo_padding"
+      android:focusable="true"/>
+
+  <LinearLayout
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
-      style="@style/PrimaryText"/>
+      android:layout_centerVertical="true"
+      android:layout_toEndOf="@+id/quick_contact_photo"
+      android:layout_toStartOf="@+id/menu_button"
+      android:orientation="vertical">
 
-  <TextView
-      android:id="@+id/secondary_text"
-      android:layout_width="wrap_content"
-      android:layout_height="wrap_content"
-      style="@style/SecondaryText"/>
+    <TextView
+        android:id="@+id/primary_text"
+        style="@style/PrimaryText"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/call_log_entry_photo_text_margin"/>
 
-</LinearLayout>
\ No newline at end of file
+    <TextView
+        android:id="@+id/secondary_text"
+        style="@style/SecondaryText"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/call_log_entry_photo_text_margin"/>
+
+    <TextView
+        android:id="@+id/phone_account"
+        style="@style/SecondaryText"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/call_log_entry_photo_text_margin"
+        android:visibility="gone"/>
+  </LinearLayout>
+
+  <ImageView
+      android:id="@+id/menu_button"
+      android:layout_width="@dimen/call_log_entry_menu_button_size"
+      android:layout_height="@dimen/call_log_entry_menu_button_size"
+      android:layout_alignParentEnd="true"
+      android:layout_centerVertical="true"
+      android:background="?android:attr/selectableItemBackgroundBorderless"
+      android:scaleType="center"
+      android:src="@drawable/quantum_ic_more_vert_vd_theme_24"
+      android:tint="@color/dialer_secondary_text_color"/>
+</RelativeLayout>
diff --git a/java/com/android/dialer/calllog/ui/res/layout/new_call_log_header.xml b/java/com/android/dialer/calllog/ui/res/layout/new_call_log_header.xml
new file mode 100644
index 0000000..13575db
--- /dev/null
+++ b/java/com/android/dialer/calllog/ui/res/layout/new_call_log_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:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:height="48dp">
+  <TextView
+      android:id="@+id/new_call_log_header_text"
+      style="@style/SecondaryText"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_marginStart="@dimen/call_log_action_icon_margin_start"
+      android:layout_centerVertical="true"/>
+</RelativeLayout>
diff --git a/java/com/android/dialer/calllog/ui/res/values/dimens.xml b/java/com/android/dialer/calllog/ui/res/values/dimens.xml
new file mode 100644
index 0000000..bfb4c99
--- /dev/null
+++ b/java/com/android/dialer/calllog/ui/res/values/dimens.xml
@@ -0,0 +1,28 @@
+<?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
+  -->
+<resources>
+
+  <!-- call log entries -->
+  <dimen name="call_log_entry_top_margin">6dp</dimen>
+  <dimen name="call_log_entry_padding_bottom_end">16dp</dimen>
+  <dimen name="call_log_entry_padding_top_start">12dp</dimen>
+  <dimen name="call_log_entry_photo_size">48dp</dimen>
+  <dimen name="call_log_entry_photo_padding">4dp</dimen>
+  <dimen name="call_log_entry_photo_text_margin">8dp</dimen>
+  <dimen name="call_log_entry_menu_button_size">48dp</dimen>
+
+</resources>
diff --git a/java/com/android/dialer/calllog/ui/res/values/strings.xml b/java/com/android/dialer/calllog/ui/res/values/strings.xml
new file mode 100644
index 0000000..9b044ca
--- /dev/null
+++ b/java/com/android/dialer/calllog/ui/res/values/strings.xml
@@ -0,0 +1,32 @@
+<?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
+  -->
+
+<resources>
+
+  <!-- Text to show in call log for a video call. [CHAR LIMIT=16] -->
+  <string name="new_call_log_video">Video</string>
+
+  <!-- String used to display calls from unknown numbers in the call log.  [CHAR LIMIT=30] -->
+  <string name="new_call_log_unknown">Unknown</string>
+
+  <!-- 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] -->
+  <string name="new_call_log_header_older">Older</string>
+
+</resources>
\ No newline at end of file
diff --git a/java/com/android/dialer/calllog/ui/res/values/styles.xml b/java/com/android/dialer/calllog/ui/res/values/styles.xml
new file mode 100644
index 0000000..23cb93e
--- /dev/null
+++ b/java/com/android/dialer/calllog/ui/res/values/styles.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2016 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
+  -->
+<resources>
+
+  <style name="primary_textview_new_call">
+    <item name="android:fontFamily">sans-serif-medium</item>
+  </style>
+
+  <style name="secondary_textview_new_call">
+    <item name="android:textColor">@color/missed_call</item>
+    <item name="android:fontFamily">sans-serif-medium</item>
+  </style>
+
+</resources>
\ 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 2d4bd8b..82e8e40 100644
--- a/java/com/android/dialer/calllogutils/CallLogDates.java
+++ b/java/com/android/dialer/calllogutils/CallLogDates.java
@@ -151,7 +151,8 @@
     return then.equals(threeDaysAgoStartOfDay) || then.after(threeDaysAgoStartOfDay);
   }
 
-  private static boolean isSameDay(long firstMillis, long secondMillis) {
+  /** Returns true if the provided timestamps are from the same day in the default time zone. */
+  public static boolean isSameDay(long firstMillis, long secondMillis) {
     Calendar first = Calendar.getInstance();
     first.setTimeInMillis(firstMillis);
 
diff --git a/java/com/android/dialer/function/Supplier.java b/java/com/android/dialer/function/Supplier.java
new file mode 100644
index 0000000..1a16183
--- /dev/null
+++ b/java/com/android/dialer/function/Supplier.java
@@ -0,0 +1,23 @@
+/*
+ * 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.function;
+
+/** Functional interface for supplying generic values. */
+public interface Supplier<T> {
+
+  /** Supplies a value. */
+  T get();
+}
diff --git a/java/com/android/dialer/strictmode/DialerStrictMode.java b/java/com/android/dialer/strictmode/DialerStrictMode.java
index 4a0336e..c7d0b3f 100644
--- a/java/com/android/dialer/strictmode/DialerStrictMode.java
+++ b/java/com/android/dialer/strictmode/DialerStrictMode.java
@@ -28,6 +28,7 @@
 import android.support.annotation.MainThread;
 import android.support.v4.os.UserManagerCompat;
 import com.android.dialer.buildtype.BuildType;
+import com.android.dialer.function.Supplier;
 import com.android.dialer.util.DialerUtils;
 
 /** Enables strict mode for the application, and provides means of temporarily disabling it. */
@@ -94,11 +95,6 @@
     return Looper.getMainLooper().equals(Looper.myLooper());
   }
 
-  /** Functional interface intended to be used with {@link #bypass(Provider)}. */
-  public interface Provider<T> {
-    T get();
-  }
-
   /**
    * Convenience method for disabling and enabling the thread policy death penalty using lambdas.
    *
@@ -111,17 +107,17 @@
    * <p>The thread policy is only mutated if this is called from the main thread.
    */
   @AnyThread
-  public static <T> T bypass(Provider<T> provider) {
+  public static <T> T bypass(Supplier<T> supplier) {
     if (isStrictModeAllowed() && onMainThread()) {
       ThreadPolicy originalPolicy = StrictMode.getThreadPolicy();
       StrictModeUtils.setRecommendedMainThreadPolicy(THREAD_LOG_PENALTY);
       try {
-        return provider.get();
+        return supplier.get();
       } finally {
         StrictMode.setThreadPolicy(originalPolicy);
       }
     }
-    return provider.get();
+    return supplier.get();
   }
 
   /**
@@ -149,3 +145,5 @@
     runnable.run();
   }
 }
+
+
diff --git a/java/com/android/dialer/time/Clock.java b/java/com/android/dialer/time/Clock.java
new file mode 100644
index 0000000..4b7edc6
--- /dev/null
+++ b/java/com/android/dialer/time/Clock.java
@@ -0,0 +1,23 @@
+/*
+ * 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.time;
+
+/** Functional interface for providing time since epoch. */
+public interface Clock {
+  /** Returns milliseconds since epoch. */
+  long currentTimeMillis();
+}