Merge changes Ib035855d,I15bc2c45,I079aca18,I9b90aaa0,I452ca635, ...

* changes:
  Don't override contactExists if there is no update from Cequint Caller ID.
  Make default lastModified timestamp configurable
  Added old call log to NUI.
  Add old speed dial fragment to NUI.
  Added badge count feature to bottom nav.
  Allow delete of Restricted number entries
  Update FragUtils to encourage better readability in Activities.
diff --git a/java/com/android/dialer/app/DialtactsActivity.java b/java/com/android/dialer/app/DialtactsActivity.java
index 23f4d40..293ebed 100644
--- a/java/com/android/dialer/app/DialtactsActivity.java
+++ b/java/com/android/dialer/app/DialtactsActivity.java
@@ -1508,6 +1508,11 @@
   @Override
   public void onDroppedOnRemove() {}
 
+  @Override
+  public ImageView getDragShadowOverlay() {
+    return findViewById(R.id.contact_tile_drag_shadow_overlay);
+  }
+
   /**
    * Allows the SpeedDialFragment to attach the drag controller to mRemoveViewContainer once it has
    * been attached to the activity.
diff --git a/java/com/android/dialer/app/calllog/CallLogFragment.java b/java/com/android/dialer/app/calllog/CallLogFragment.java
index 4f5035f..7f635db 100644
--- a/java/com/android/dialer/app/calllog/CallLogFragment.java
+++ b/java/com/android/dialer/app/calllog/CallLogFragment.java
@@ -53,10 +53,10 @@
 import com.android.dialer.app.contactinfo.ContactInfoCache;
 import com.android.dialer.app.contactinfo.ContactInfoCache.OnContactInfoChangedListener;
 import com.android.dialer.app.contactinfo.ExpirableCacheHeadlessFragment;
-import com.android.dialer.app.list.ListsFragment;
 import com.android.dialer.app.voicemail.VoicemailPlaybackPresenter;
 import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler;
 import com.android.dialer.common.Assert;
+import com.android.dialer.common.FragmentUtils;
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.database.CallLogQueryHandler;
 import com.android.dialer.database.CallLogQueryHandler.Listener;
@@ -344,9 +344,10 @@
                 recyclerView,
                 this,
                 this,
-                activityType == CallLogAdapter.ACTIVITY_TYPE_DIALTACTS
-                    ? (CallLogAdapter.OnActionModeStateChangedListener) getActivity()
-                    : null,
+                // We aren't calling getParentUnsafe because CallLogActivity doesn't need to
+                // implement this listener
+                FragmentUtils.getParent(
+                    this, CallLogAdapter.OnActionModeStateChangedListener.class),
                 new CallLogCache(getActivity()),
                 contactInfoCache,
                 getVoicemailPlaybackPresenter(),
@@ -479,7 +480,7 @@
   public void fetchCalls() {
     callLogQueryHandler.fetchCalls(callTypeFilter, dateLimit);
     if (!isCallLogActivity) {
-      ((ListsFragment) getParentFragment()).updateTabUnreadCounts();
+      FragmentUtils.getParentUnsafe(this, CallLogFragmentListener.class).updateTabUnreadCounts();
     }
   }
 
@@ -616,7 +617,8 @@
   public void onVisible() {
     LogUtil.enterBlock("CallLogFragment.onPageSelected");
     if (getActivity() != null && getActivity() instanceof HostInterface) {
-      ((HostInterface) getActivity()).enableFloatingButton(!isModalAlertVisible());
+      FragmentUtils.getParentUnsafe(this, HostInterface.class)
+          .enableFloatingButton(!isModalAlertVisible());
     }
   }
 
@@ -638,7 +640,7 @@
         this,
         getUserVisibleHint());
     getAdapter().notifyDataSetChanged();
-    HostInterface hostInterface = (HostInterface) getActivity();
+    HostInterface hostInterface = FragmentUtils.getParent(this, HostInterface.class);
     if (show) {
       recyclerView.setVisibility(View.GONE);
       modalAlertView.setVisibility(View.VISIBLE);
@@ -659,7 +661,8 @@
     multiSelectUnSelectAllViewContent.setVisibility(show ? View.VISIBLE : View.GONE);
     multiSelectUnSelectAllViewContent.setAlpha(show ? 0 : 1);
     multiSelectUnSelectAllViewContent.animate().alpha(show ? 1 : 0).start();
-    ((ListsFragment) getParentFragment()).showMultiSelectRemoveView(show);
+    FragmentUtils.getParentUnsafe(this, CallLogFragmentListener.class)
+        .showMultiSelectRemoveView(show);
   }
 
   @Override
@@ -717,4 +720,16 @@
       refreshDataRequired = true;
     }
   }
+
+  /** Useful callback for ListsFragment children to use to call into ListsFragment. */
+  public interface CallLogFragmentListener {
+
+    /**
+     * External method to update unread count because the unread count changes when the user expands
+     * a voicemail in the call log or when the user expands an unread call in the call history tab.
+     */
+    void updateTabUnreadCounts();
+
+    void showMultiSelectRemoveView(boolean show);
+  }
 }
diff --git a/java/com/android/dialer/app/list/ListsFragment.java b/java/com/android/dialer/app/list/ListsFragment.java
index bbbf056..d314917 100644
--- a/java/com/android/dialer/app/list/ListsFragment.java
+++ b/java/com/android/dialer/app/list/ListsFragment.java
@@ -34,6 +34,7 @@
 import com.android.contacts.common.list.ViewPagerTabs;
 import com.android.dialer.app.R;
 import com.android.dialer.app.calllog.CallLogFragment;
+import com.android.dialer.app.calllog.CallLogFragment.CallLogFragmentListener;
 import com.android.dialer.app.calllog.CallLogNotificationsService;
 import com.android.dialer.app.calllog.VisualVoicemailCallLogFragment;
 import com.android.dialer.common.LogUtil;
@@ -59,7 +60,8 @@
  * Contacts list. This will also eventually contain the logic that allows sliding the ViewPager
  * containing the lists up above the search bar and pin it against the top of the screen.
  */
-public class ListsFragment extends Fragment implements OnPageChangeListener, Listener {
+public class ListsFragment extends Fragment
+    implements OnPageChangeListener, Listener, CallLogFragmentListener {
 
   private static final String TAG = "ListsFragment";
 
@@ -423,10 +425,7 @@
     return true;
   }
 
-  /**
-   * External method to update unread count because the unread count changes when the user expands a
-   * voicemail in the call log or when the user expands an unread call in the call history tab.
-   */
+  @Override
   public void updateTabUnreadCounts() {
     if (callLogQueryHandler != null) {
       callLogQueryHandler.fetchMissedCallsUnreadCount();
@@ -450,6 +449,7 @@
     removeView.animate().alpha(show ? 1 : 0).start();
   }
 
+  @Override
   public void showMultiSelectRemoveView(boolean show) {
     viewPagerTabs.setVisibility(show ? View.GONE : View.VISIBLE);
     viewPager.setEnableSwipingPages(!show);
diff --git a/java/com/android/dialer/app/list/OldSpeedDialFragment.java b/java/com/android/dialer/app/list/OldSpeedDialFragment.java
index 1b366c1..caa5e91 100644
--- a/java/com/android/dialer/app/list/OldSpeedDialFragment.java
+++ b/java/com/android/dialer/app/list/OldSpeedDialFragment.java
@@ -20,7 +20,6 @@
 import android.animation.Animator;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
-import android.app.Activity;
 import android.app.Fragment;
 import android.app.LoaderManager;
 import android.content.CursorLoader;
@@ -50,6 +49,7 @@
 import com.android.contacts.common.list.OnPhoneNumberPickerActionListener;
 import com.android.dialer.app.R;
 import com.android.dialer.callintent.CallSpecificAppData;
+import com.android.dialer.common.FragmentUtils;
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.contactphoto.ContactPhotoManager;
 import com.android.dialer.util.PermissionsUtil;
@@ -77,22 +77,17 @@
   private static final long KEY_REMOVED_ITEM_HEIGHT = Long.MAX_VALUE;
 
   private static final String TAG = "OldSpeedDialFragment";
-  private static final boolean DEBUG = false;
   /** Used with LoaderManager. */
   private static final int LOADER_ID_CONTACT_TILE = 1;
 
   private final LongSparseArray<Integer> itemIdTopMap = new LongSparseArray<>();
   private final LongSparseArray<Integer> itemIdLeftMap = new LongSparseArray<>();
   private final ContactTileView.Listener contactTileAdapterListener =
-      new ContactTileAdapterListener();
-  private final LoaderManager.LoaderCallbacks<Cursor> contactTileLoaderListener =
-      new ContactTileLoaderListener();
-  private final ScrollListener scrollListener = new ScrollListener();
+      new ContactTileAdapterListener(this);
+  private final ScrollListener scrollListener = new ScrollListener(this);
+  private LoaderManager.LoaderCallbacks<Cursor> contactTileLoaderListener;
   private int animationDuration;
-  private OnPhoneNumberPickerActionListener phoneNumberPickerActionListener;
-  private OnListFragmentScrolledListener activityScrollListener;
   private PhoneFavoritesTileAdapter contactTileAdapter;
-  private View parentView;
   private PhoneFavoriteListView listView;
   private View contactTileFrame;
   /** Layout used when there are no favorites. */
@@ -100,9 +95,6 @@
 
   @Override
   public void onCreate(Bundle savedState) {
-    if (DEBUG) {
-      LogUtil.d("OldSpeedDialFragment.onCreate", null);
-    }
     Trace.beginSection(TAG + " onCreate");
     super.onCreate(savedState);
 
@@ -110,8 +102,9 @@
     // We don't construct the resultant adapter at this moment since it requires LayoutInflater
     // that will be available on onCreateView().
     contactTileAdapter =
-        new PhoneFavoritesTileAdapter(getActivity(), contactTileAdapterListener, this);
-    contactTileAdapter.setPhotoLoader(ContactPhotoManager.getInstance(getActivity()));
+        new PhoneFavoritesTileAdapter(getContext(), contactTileAdapterListener, this);
+    contactTileAdapter.setPhotoLoader(ContactPhotoManager.getInstance(getContext()));
+    contactTileLoaderListener = new ContactTileLoaderListener(this, contactTileAdapter);
     animationDuration = getResources().getInteger(R.integer.fade_duration);
     Trace.endSection();
   }
@@ -123,7 +116,7 @@
     if (contactTileAdapter != null) {
       contactTileAdapter.refreshContactsPreferences();
     }
-    if (PermissionsUtil.hasContactsReadPermissions(getActivity())) {
+    if (PermissionsUtil.hasContactsReadPermissions(getContext())) {
       if (getLoaderManager().getLoader(LOADER_ID_CONTACT_TILE) == null) {
         getLoaderManager().initLoader(LOADER_ID_CONTACT_TILE, null, contactTileLoaderListener);
 
@@ -144,7 +137,7 @@
   public View onCreateView(
       LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
     Trace.beginSection(TAG + " onCreateView");
-    parentView = inflater.inflate(R.layout.speed_dial_fragment, container, false);
+    View parentView = inflater.inflate(R.layout.speed_dial_fragment, container, false);
 
     listView = (PhoneFavoriteListView) parentView.findViewById(R.id.contact_tile_list);
     listView.setOnItemClickListener(this);
@@ -152,10 +145,8 @@
     listView.setVerticalScrollbarPosition(View.SCROLLBAR_POSITION_RIGHT);
     listView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY);
     listView.getDragDropController().addOnDragDropListener(contactTileAdapter);
-
-    final ImageView dragShadowOverlay =
-        (ImageView) getActivity().findViewById(R.id.contact_tile_drag_shadow_overlay);
-    listView.setDragShadowOverlay(dragShadowOverlay);
+    listView.setDragShadowOverlay(
+        FragmentUtils.getParentUnsafe(this, HostInterface.class).getDragShadowOverlay());
 
     emptyView = (EmptyContentView) parentView.findViewById(R.id.empty_list_view);
     emptyView.setImage(R.drawable.empty_speed_dial);
@@ -165,7 +156,7 @@
 
     final LayoutAnimationController controller =
         new LayoutAnimationController(
-            AnimationUtils.loadAnimation(getActivity(), android.R.anim.fade_in));
+            AnimationUtils.loadAnimation(getContext(), android.R.anim.fade_in));
     controller.setDelay(0);
     listView.setLayoutAnimation(controller);
     listView.setAdapter(contactTileAdapter);
@@ -206,36 +197,16 @@
   @Override
   public void onStart() {
     super.onStart();
-
-    final Activity activity = getActivity();
-
-    try {
-      activityScrollListener = (OnListFragmentScrolledListener) activity;
-    } catch (ClassCastException e) {
-      throw new ClassCastException(
-          activity.toString() + " must implement OnListFragmentScrolledListener");
-    }
-
-    try {
-      OnDragDropListener listener = (OnDragDropListener) activity;
-      listView.getDragDropController().addOnDragDropListener(listener);
-      ((HostInterface) activity).setDragDropController(listView.getDragDropController());
-    } catch (ClassCastException e) {
-      throw new ClassCastException(
-          activity.toString() + " must implement OnDragDropListener and HostInterface");
-    }
-
-    try {
-      phoneNumberPickerActionListener = (OnPhoneNumberPickerActionListener) activity;
-    } catch (ClassCastException e) {
-      throw new ClassCastException(
-          activity.toString() + " must implement PhoneFavoritesFragment.listener");
-    }
+    listView
+        .getDragDropController()
+        .addOnDragDropListener(FragmentUtils.getParentUnsafe(this, OnDragDropListener.class));
+    FragmentUtils.getParentUnsafe(this, HostInterface.class)
+        .setDragDropController(listView.getDragDropController());
 
     // Use initLoader() instead of restartLoader() to refraining unnecessary reload.
     // This method call implicitly assures ContactTileLoaderListener's onLoadFinished() will
     // be called, on which we'll check if "all" contacts should be reloaded again or not.
-    if (PermissionsUtil.hasContactsReadPermissions(activity)) {
+    if (PermissionsUtil.hasContactsReadPermissions(getContext())) {
       getLoaderManager().initLoader(LOADER_ID_CONTACT_TILE, null, contactTileLoaderListener);
     } else {
       setEmptyViewVisibility(true);
@@ -268,9 +239,6 @@
    */
   private void saveOffsets(int removedItemHeight) {
     final int firstVisiblePosition = listView.getFirstVisiblePosition();
-    if (DEBUG) {
-      LogUtil.d("OldSpeedDialFragment.saveOffsets", "Child count : " + listView.getChildCount());
-    }
     for (int i = 0; i < listView.getChildCount(); i++) {
       final View child = listView.getChildAt(i);
       final int position = firstVisiblePosition + i;
@@ -281,11 +249,6 @@
         continue;
       }
       final long itemId = contactTileAdapter.getItemId(position);
-      if (DEBUG) {
-        LogUtil.d(
-            "OldSpeedDialFragment.saveOffsets",
-            "Saving itemId: " + itemId + " for listview child " + i + " Top: " + child.getTop());
-      }
       itemIdTopMap.put(itemId, child.getTop());
       itemIdLeftMap.put(itemId, child.getLeft());
     }
@@ -350,19 +313,6 @@
                     animators.add(ObjectAnimator.ofFloat(child, "translationY", deltaY, 0.0f));
                   }
                 }
-
-                if (DEBUG) {
-                  LogUtil.d(
-                      "OldSpeedDialFragment.onPreDraw",
-                      "Found itemId: "
-                          + itemId
-                          + " for listview child "
-                          + i
-                          + " Top: "
-                          + top
-                          + " Delta: "
-                          + deltaY);
-                }
               }
             }
 
@@ -399,11 +349,6 @@
 
   @Override
   public void onEmptyViewActionButtonClicked() {
-    final Activity activity = getActivity();
-    if (activity == null) {
-      return;
-    }
-
     String[] deniedPermissions =
         PermissionsUtil.getPermissionsCurrentlyDenied(
             getContext(), PermissionsUtil.allContactsGroupPermissionsUsedInDialer);
@@ -415,7 +360,7 @@
           this, deniedPermissions, READ_CONTACTS_PERMISSION_REQUEST_CODE);
     } else {
       // Switch tabs
-      ((HostInterface) activity).showAllContactsTab();
+      FragmentUtils.getParentUnsafe(this, HostInterface.class).showAllContactsTab();
     }
   }
 
@@ -424,79 +369,88 @@
       int requestCode, String[] permissions, int[] grantResults) {
     if (requestCode == READ_CONTACTS_PERMISSION_REQUEST_CODE) {
       if (grantResults.length == 1 && PackageManager.PERMISSION_GRANTED == grantResults[0]) {
-        PermissionsUtil.notifyPermissionGranted(getActivity(), READ_CONTACTS);
+        PermissionsUtil.notifyPermissionGranted(getContext(), READ_CONTACTS);
       }
     }
   }
 
+  private static final class ContactTileLoaderListener
+      implements LoaderManager.LoaderCallbacks<Cursor> {
+
+    private final OldSpeedDialFragment fragment;
+    private final PhoneFavoritesTileAdapter adapter;
+
+    ContactTileLoaderListener(OldSpeedDialFragment fragment, PhoneFavoritesTileAdapter adapter) {
+      this.fragment = fragment;
+      this.adapter = adapter;
+    }
+
+    @Override
+    public CursorLoader onCreateLoader(int id, Bundle args) {
+      return ContactTileLoaderFactory.createStrequentPhoneOnlyLoader(fragment.getContext());
+    }
+
+    @Override
+    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+      adapter.setContactCursor(data);
+      fragment.setEmptyViewVisibility(adapter.getCount() == 0);
+    }
+
+    @Override
+    public void onLoaderReset(Loader<Cursor> loader) {}
+  }
+
+  private static final class ContactTileAdapterListener implements ContactTileView.Listener {
+
+    private final OldSpeedDialFragment fragment;
+
+    ContactTileAdapterListener(OldSpeedDialFragment fragment) {
+      this.fragment = fragment;
+    }
+
+    @Override
+    public void onContactSelected(
+        Uri contactUri, Rect targetRect, CallSpecificAppData callSpecificAppData) {
+      FragmentUtils.getParentUnsafe(fragment, OnPhoneNumberPickerActionListener.class)
+          .onPickDataUri(contactUri, false /* isVideoCall */, callSpecificAppData);
+    }
+
+    @Override
+    public void onCallNumberDirectly(String phoneNumber, CallSpecificAppData callSpecificAppData) {
+      FragmentUtils.getParentUnsafe(fragment, OnPhoneNumberPickerActionListener.class)
+          .onPickPhoneNumber(phoneNumber, false /* isVideoCall */, callSpecificAppData);
+    }
+  }
+
+  private static class ScrollListener implements ListView.OnScrollListener {
+
+    private final OldSpeedDialFragment fragment;
+
+    ScrollListener(OldSpeedDialFragment fragment) {
+      this.fragment = fragment;
+    }
+
+    @Override
+    public void onScroll(
+        AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+      FragmentUtils.getParentUnsafe(fragment, OnListFragmentScrolledListener.class)
+          .onListFragmentScroll(firstVisibleItem, visibleItemCount, totalItemCount);
+    }
+
+    @Override
+    public void onScrollStateChanged(AbsListView view, int scrollState) {
+      FragmentUtils.getParentUnsafe(fragment, OnListFragmentScrolledListener.class)
+          .onListFragmentScrollStateChange(scrollState);
+    }
+  }
+
+  /** Interface for parents of OldSpeedDialFragment to implement. */
   public interface HostInterface {
 
     void setDragDropController(DragDropController controller);
 
     void showAllContactsTab();
-  }
 
-  class ContactTileLoaderListener implements LoaderManager.LoaderCallbacks<Cursor> {
-
-    @Override
-    public CursorLoader onCreateLoader(int id, Bundle args) {
-      if (DEBUG) {
-        LogUtil.d("ContactTileLoaderListener.onCreateLoader", null);
-      }
-      return ContactTileLoaderFactory.createStrequentPhoneOnlyLoader(getActivity());
-    }
-
-    @Override
-    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
-      if (DEBUG) {
-        LogUtil.d("ContactTileLoaderListener.onLoadFinished", null);
-      }
-      contactTileAdapter.setContactCursor(data);
-      setEmptyViewVisibility(contactTileAdapter.getCount() == 0);
-    }
-
-    @Override
-    public void onLoaderReset(Loader<Cursor> loader) {
-      if (DEBUG) {
-        LogUtil.d("ContactTileLoaderListener.onLoaderReset", null);
-      }
-    }
-  }
-
-  private class ContactTileAdapterListener implements ContactTileView.Listener {
-
-    @Override
-    public void onContactSelected(
-        Uri contactUri, Rect targetRect, CallSpecificAppData callSpecificAppData) {
-      if (phoneNumberPickerActionListener != null) {
-        phoneNumberPickerActionListener.onPickDataUri(
-            contactUri, false /* isVideoCall */, callSpecificAppData);
-      }
-    }
-
-    @Override
-    public void onCallNumberDirectly(String phoneNumber, CallSpecificAppData callSpecificAppData) {
-      if (phoneNumberPickerActionListener != null) {
-        phoneNumberPickerActionListener.onPickPhoneNumber(
-            phoneNumber, false /* isVideoCall */, callSpecificAppData);
-      }
-    }
-  }
-
-  private class ScrollListener implements ListView.OnScrollListener {
-
-    @Override
-    public void onScroll(
-        AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
-      if (activityScrollListener != null) {
-        activityScrollListener.onListFragmentScroll(
-            firstVisibleItem, visibleItemCount, totalItemCount);
-      }
-    }
-
-    @Override
-    public void onScrollStateChanged(AbsListView view, int scrollState) {
-      activityScrollListener.onListFragmentScrollStateChange(scrollState);
-    }
+    ImageView getDragShadowOverlay();
   }
 }
diff --git a/java/com/android/dialer/calldetails/CallDetailsFooterViewHolder.java b/java/com/android/dialer/calldetails/CallDetailsFooterViewHolder.java
index eeb19a8..30b28d8 100644
--- a/java/com/android/dialer/calldetails/CallDetailsFooterViewHolder.java
+++ b/java/com/android/dialer/calldetails/CallDetailsFooterViewHolder.java
@@ -36,7 +36,6 @@
 
   private final ReportCallIdListener reportCallIdListener;
   private final DeleteCallDetailsListener deleteCallDetailsListener;
-  private final View container;
   private final View copy;
   private final View edit;
   private final View reportCallerId;
@@ -51,7 +50,6 @@
     super(view);
     this.reportCallIdListener = reportCallIdListener;
     this.deleteCallDetailsListener = deleteCallDetailsListener;
-    container = view.findViewById(R.id.footer_container);
     copy = view.findViewById(R.id.call_detail_action_copy);
     edit = view.findViewById(R.id.call_detail_action_edit_before_call);
     reportCallerId = view.findViewById(R.id.call_detail_action_report_caller_id);
@@ -65,7 +63,8 @@
   public void setPhoneNumber(String number) {
     this.number = number;
     if (TextUtils.isEmpty(number)) {
-      container.setVisibility(View.GONE);
+      copy.setVisibility(View.GONE);
+      edit.setVisibility(View.GONE);
     } else if (reportCallIdListener.canReportCallerId(number)) {
       reportCallerId.setVisibility(View.VISIBLE);
     }
diff --git a/java/com/android/dialer/common/FragmentUtils.java b/java/com/android/dialer/common/FragmentUtils.java
index ad7ec73..947a9b2 100644
--- a/java/com/android/dialer/common/FragmentUtils.java
+++ b/java/com/android/dialer/common/FragmentUtils.java
@@ -16,13 +16,11 @@
 
 package com.android.dialer.common;
 
-import android.app.Activity;
 import android.support.annotation.CheckResult;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.VisibleForTesting;
 import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentActivity;
 
 /** Utility methods for working with Fragments */
 public class FragmentUtils {
@@ -35,8 +33,8 @@
   }
 
   /**
-   * @return The parent of frag that implements the callbackInterface or null if no such parent can
-   *     be found
+   * Returns an instance of the {@code callbackInterface} that is defined in the parent of the
+   * {@code fragment}, or null if no such call back can be found.
    */
   @CheckResult(suggest = "#checkParent(Fragment, Class)}")
   @Nullable
@@ -52,18 +50,22 @@
       @SuppressWarnings("unchecked") // Casts are checked using runtime methods
       T parent = (T) parentFragment;
       return parent;
-    } else {
-      FragmentActivity activity = fragment.getActivity();
-      if (callbackInterface.isInstance(activity)) {
-        @SuppressWarnings("unchecked") // Casts are checked using runtime methods
-        T parent = (T) activity;
-        return parent;
-      }
+    } else if (callbackInterface.isInstance(fragment.getActivity())) {
+      @SuppressWarnings("unchecked") // Casts are checked using runtime methods
+      T parent = (T) fragment.getActivity();
+      return parent;
+    } else if (fragment.getActivity() instanceof FragmentUtilListener) {
+      @SuppressWarnings("unchecked") // Casts are checked using runtime methods
+      T parent = ((FragmentUtilListener) fragment.getActivity()).getImpl(callbackInterface);
+      return parent;
     }
     return null;
   }
 
-  /** Version of {@link #getParent(Fragment, Class)} which supports {@link android.app.Fragment}. */
+  /**
+   * Returns an instance of the {@code callbackInterface} that is defined in the parent of the
+   * {@code fragment}, or null if no such call back can be found.
+   */
   @CheckResult(suggest = "#checkParent(Fragment, Class)}")
   @Nullable
   public static <T> T getParent(
@@ -79,13 +81,14 @@
       @SuppressWarnings("unchecked") // Casts are checked using runtime methods
       T parent = (T) parentFragment;
       return parent;
-    } else {
-      Activity activity = fragment.getActivity();
-      if (callbackInterface.isInstance(activity)) {
-        @SuppressWarnings("unchecked") // Casts are checked using runtime methods
-        T parent = (T) activity;
-        return parent;
-      }
+    } else if (callbackInterface.isInstance(fragment.getActivity())) {
+      @SuppressWarnings("unchecked") // Casts are checked using runtime methods
+      T parent = (T) fragment.getActivity();
+      return parent;
+    } else if (fragment.getActivity() instanceof FragmentUtilListener) {
+      @SuppressWarnings("unchecked") // Casts are checked using runtime methods
+      T parent = ((FragmentUtilListener) fragment.getActivity()).getImpl(callbackInterface);
+      return parent;
     }
     return null;
   }
@@ -133,4 +136,12 @@
               + parent);
     }
   }
+
+  /** Useful interface for activities that don't want to implement arbitrary listeners. */
+  public interface FragmentUtilListener {
+
+    /** Returns an implementation of T if parent has one, otherwise null. */
+    @Nullable
+    <T> T getImpl(Class<T> callbackInterface);
+  }
 }
diff --git a/java/com/android/dialer/database/DialerDatabaseHelper.java b/java/com/android/dialer/database/DialerDatabaseHelper.java
index 18c6134..cb07615 100644
--- a/java/com/android/dialer/database/DialerDatabaseHelper.java
+++ b/java/com/android/dialer/database/DialerDatabaseHelper.java
@@ -42,6 +42,7 @@
 import com.android.dialer.common.concurrent.DialerExecutor.Worker;
 import com.android.dialer.common.concurrent.DialerExecutorComponent;
 import com.android.dialer.common.database.Selection;
+import com.android.dialer.configprovider.ConfigProviderBindings;
 import com.android.dialer.database.FilteredNumberContract.FilteredNumberColumns;
 import com.android.dialer.smartdial.util.SmartDialNameMatcher;
 import com.android.dialer.smartdial.util.SmartDialPrefix;
@@ -76,6 +77,10 @@
   private static final String DATABASE_LAST_CREATED_SHARED_PREF = "com.android.dialer";
 
   private static final String LAST_UPDATED_MILLIS = "last_updated_millis";
+
+  @VisibleForTesting
+  static final String DEFAULT_LAST_UPDATED_CONFIG_KEY = "smart_dial_default_last_update_millis";
+
   private static final String DATABASE_VERSION_PROPERTY = "database_version";
   private static final int MAX_ENTRIES = 20;
 
@@ -635,12 +640,17 @@
     /** Gets the last update time on the database. */
     final SharedPreferences databaseLastUpdateSharedPref =
         context.getSharedPreferences(DATABASE_LAST_CREATED_SHARED_PREF, Context.MODE_PRIVATE);
-    final String lastUpdateMillis =
-        String.valueOf(
-            forceUpdate ? 0 : databaseLastUpdateSharedPref.getLong(LAST_UPDATED_MILLIS, 0));
 
-    LogUtil.v(
-        "DialerDatabaseHelper.updateSmartDialDatabase", "last updated at " + lastUpdateMillis);
+    long defaultLastUpdateMillis =
+        ConfigProviderBindings.get(context).getLong(DEFAULT_LAST_UPDATED_CONFIG_KEY, 0);
+
+    long sharedPrefLastUpdateMillis =
+        databaseLastUpdateSharedPref.getLong(LAST_UPDATED_MILLIS, defaultLastUpdateMillis);
+
+    final String lastUpdateMillis = String.valueOf(forceUpdate ? 0 : sharedPrefLastUpdateMillis);
+
+    LogUtil.i(
+        "DialerDatabaseHelper.updateSmartDialDatabase", "last updated at %s", lastUpdateMillis);
 
     /** Sets the time after querying the database as the current update time. */
     final Long currentMillis = System.currentTimeMillis();
diff --git a/java/com/android/dialer/dialpadview/DialpadFragment.java b/java/com/android/dialer/dialpadview/DialpadFragment.java
index 6b8401e..6801590 100644
--- a/java/com/android/dialer/dialpadview/DialpadFragment.java
+++ b/java/com/android/dialer/dialpadview/DialpadFragment.java
@@ -415,7 +415,8 @@
               if (isDigitsEmpty()) {
                 if (getActivity() != null) {
                   LogUtil.i("DialpadFragment.onCreateView", "dialpad spacer touched");
-                  return ((HostInterface) getActivity()).onDialpadSpacerTouchWithEmptyQuery();
+                  return FragmentUtils.getParentUnsafe(this, HostInterface.class)
+                      .onDialpadSpacerTouchWithEmptyQuery();
                 }
                 return true;
               }
diff --git a/java/com/android/dialer/main/impl/BottomNavBar.java b/java/com/android/dialer/main/impl/BottomNavBar.java
index 66a57be..a4ddc06 100644
--- a/java/com/android/dialer/main/impl/BottomNavBar.java
+++ b/java/com/android/dialer/main/impl/BottomNavBar.java
@@ -119,6 +119,20 @@
     }
   }
 
+  void setNotificationCount(@TabIndex int tab, int count) {
+    if (tab == TabIndex.SPEED_DIAL) {
+      speedDial.setNotificationCount(count);
+    } else if (tab == TabIndex.HISTORY) {
+      callLog.setNotificationCount(count);
+    } else if (tab == TabIndex.CONTACTS) {
+      contacts.setNotificationCount(count);
+    } else if (tab == TabIndex.VOICEMAIL) {
+      voicemail.setNotificationCount(count);
+    } else {
+      throw new IllegalStateException("Invalid tab: " + tab);
+    }
+  }
+
   void setOnTabSelectedListener(OnBottomNavTabSelectedListener listener) {
     this.listener = listener;
   }
diff --git a/java/com/android/dialer/main/impl/BottomNavItem.java b/java/com/android/dialer/main/impl/BottomNavItem.java
index 14706ab..af7399b 100644
--- a/java/com/android/dialer/main/impl/BottomNavItem.java
+++ b/java/com/android/dialer/main/impl/BottomNavItem.java
@@ -22,15 +22,18 @@
 import android.support.annotation.Nullable;
 import android.support.annotation.StringRes;
 import android.util.AttributeSet;
+import android.view.View;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
+import com.android.dialer.common.Assert;
 
 /** Navigation item in a bottom nav. */
 final class BottomNavItem extends LinearLayout {
 
   private ImageView image;
   private TextView text;
+  private TextView notificationBadge;
 
   public BottomNavItem(Context context, @Nullable AttributeSet attrs) {
     super(context, attrs);
@@ -41,6 +44,7 @@
     super.onFinishInflate();
     image = findViewById(R.id.bottom_nav_item_image);
     text = findViewById(R.id.bottom_nav_item_text);
+    notificationBadge = findViewById(R.id.notification_badge);
   }
 
   @Override
@@ -56,4 +60,14 @@
     text.setText(stringRes);
     image.setImageResource(drawableRes);
   }
+
+  void setNotificationCount(int count) {
+    Assert.checkArgument(count >= 0, "Invalid count: " + count);
+    if (count == 0) {
+      notificationBadge.setVisibility(View.GONE);
+    } else {
+      notificationBadge.setVisibility(View.VISIBLE);
+      notificationBadge.setText(String.format(Integer.toString(count)));
+    }
+  }
 }
diff --git a/java/com/android/dialer/main/impl/MainActivity.java b/java/com/android/dialer/main/impl/MainActivity.java
index a7a9e6c..57cc684 100644
--- a/java/com/android/dialer/main/impl/MainActivity.java
+++ b/java/com/android/dialer/main/impl/MainActivity.java
@@ -16,21 +16,38 @@
 
 package com.android.dialer.main.impl;
 
+import android.app.Fragment;
+import android.app.FragmentManager;
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
 import android.provider.CallLog.Calls;
 import android.provider.ContactsContract.QuickContact;
+import android.support.annotation.Nullable;
 import android.support.design.widget.FloatingActionButton;
 import android.support.v4.app.FragmentTransaction;
-import android.support.v7.app.AppCompatActivity;
 import android.view.View;
 import android.widget.ImageView;
+import com.android.contacts.common.list.OnPhoneNumberPickerActionListener;
+import com.android.dialer.app.calllog.CallLogAdapter;
+import com.android.dialer.app.calllog.CallLogFragment;
+import com.android.dialer.app.calllog.CallLogFragment.CallLogFragmentListener;
+import com.android.dialer.app.list.DragDropController;
+import com.android.dialer.app.list.OldSpeedDialFragment;
+import com.android.dialer.app.list.OnDragDropListener;
+import com.android.dialer.app.list.OnListFragmentScrolledListener;
+import com.android.dialer.app.list.PhoneFavoriteSquareTileView;
+import com.android.dialer.callintent.CallIntentBuilder;
+import com.android.dialer.callintent.CallSpecificAppData;
 import com.android.dialer.calllog.ui.NewCallLogFragment;
+import com.android.dialer.common.Assert;
+import com.android.dialer.common.FragmentUtils.FragmentUtilListener;
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.common.concurrent.DialerExecutorComponent;
+import com.android.dialer.common.concurrent.UiListener;
 import com.android.dialer.compat.CompatUtils;
+import com.android.dialer.configprovider.ConfigProviderComponent;
 import com.android.dialer.constants.ActivityRequestCodes;
 import com.android.dialer.contactsfragment.ContactsFragment;
 import com.android.dialer.contactsfragment.ContactsFragment.Header;
@@ -40,31 +57,56 @@
 import com.android.dialer.dialpadview.DialpadFragment.DialpadListener;
 import com.android.dialer.dialpadview.DialpadFragment.LastOutgoingCallCallback;
 import com.android.dialer.dialpadview.DialpadFragment.OnDialpadQueryChangedListener;
+import com.android.dialer.interactions.PhoneNumberInteraction;
+import com.android.dialer.interactions.PhoneNumberInteraction.DisambigDialogDismissedListener;
+import com.android.dialer.interactions.PhoneNumberInteraction.InteractionErrorCode;
+import com.android.dialer.interactions.PhoneNumberInteraction.InteractionErrorListener;
 import com.android.dialer.main.impl.BottomNavBar.OnBottomNavTabSelectedListener;
 import com.android.dialer.main.impl.toolbar.MainToolbar;
 import com.android.dialer.postcall.PostCall;
+import com.android.dialer.precall.PreCall;
 import com.android.dialer.searchfragment.list.NewSearchFragment.SearchFragmentListener;
 import com.android.dialer.smartdial.util.SmartDialPrefix;
 import com.android.dialer.speeddial.SpeedDialFragment;
 import com.android.dialer.telecom.TelecomUtil;
+import com.android.dialer.util.DialerUtils;
+import com.android.dialer.util.TransactionSafeActivity;
 import com.android.dialer.voicemail.listui.NewVoicemailFragment;
+import com.google.common.util.concurrent.ListenableFuture;
 
 /** This is the main activity for dialer. It hosts favorites, call log, search, dialpad, etc... */
-public final class MainActivity extends AppCompatActivity
-    implements OnContactSelectedListener,
-        OnDialpadQueryChangedListener,
-        DialpadListener,
-        DialpadFragment.HostInterface,
-        SearchFragmentListener {
+// TODO(calderwoodra): Do not extend TransactionSafeActivity after new SpeedDial is launched
+public final class MainActivity extends TransactionSafeActivity
+    implements FragmentUtilListener,
+        // TODO(calderwoodra): remove these 2 interfaces when we migrate to new speed dial fragment
+        InteractionErrorListener,
+        DisambigDialogDismissedListener {
 
   private static final String KEY_SAVED_LANGUAGE_CODE = "saved_language_code";
 
+  private final MainOnContactSelectedListener onContactSelectedListener =
+      new MainOnContactSelectedListener(this);
+  private final MainDialpadFragmentHost dialpadFragmentHostInterface =
+      new MainDialpadFragmentHost();
+
   private MainSearchController searchController;
+  private MainOnDialpadQueryChangedListener onDialpadQueryChangedListener;
+  private MainDialpadListener dialpadListener;
+  private MainSearchFragmentListener searchFragmentListener;
+  private MainCallLogAdapterOnActionModeStateChangedListener
+      callLogAdapterOnActionModeStateChangedListener;
+  private MainCallLogHost callLogHostInterface;
+  private MainCallLogFragmentListener callLogFragmentListener;
+  private MainOnListFragmentScrolledListener onListFragmentScrolledListener;
+  private MainOnPhoneNumberPickerActionListener onPhoneNumberPickerActionListener;
+  private MainOldSpeedDialFragmentHostInterface oldSpeedDialFragmentHostInterface;
+  private MainOnDragDropListener onDragDropListener;
 
   /** Language the device was in last time {@link #onSaveInstanceState(Bundle)} was called. */
   private String savedLanguageCode;
 
   private View snackbarContainer;
+  private UiListener<String> getLastOutgoingCallListener;
 
   /**
    * @param context Context of the application package implementing MainActivity class.
@@ -81,10 +123,17 @@
     super.onCreate(savedInstanceState);
     LogUtil.enterBlock("MainActivity.onCreate");
     setContentView(R.layout.main_activity);
+    initUiListeners();
     initLayout(savedInstanceState);
     SmartDialPrefix.initializeNanpSettings(this);
   }
 
+  private void initUiListeners() {
+    getLastOutgoingCallListener =
+        DialerExecutorComponent.get(this)
+            .createUiListener(getFragmentManager(), "Query last phone number");
+  }
+
   private void initLayout(Bundle savedInstanceState) {
     snackbarContainer = findViewById(R.id.coordinator_layout);
 
@@ -95,11 +144,28 @@
     setSupportActionBar(findViewById(R.id.toolbar));
 
     BottomNavBar bottomNav = findViewById(R.id.bottom_nav_bar);
-    bottomNav.setOnTabSelectedListener(new MainBottomNavBarBottomNavTabListener());
+    MainBottomNavBarBottomNavTabListener bottomNavTabListener =
+        new MainBottomNavBarBottomNavTabListener(
+            this, getFragmentManager(), getSupportFragmentManager());
+    bottomNav.setOnTabSelectedListener(bottomNavTabListener);
 
     searchController = new MainSearchController(this, bottomNav, fab, toolbar);
     toolbar.setSearchBarListener(searchController);
 
+    onDialpadQueryChangedListener = new MainOnDialpadQueryChangedListener(searchController);
+    dialpadListener = new MainDialpadListener(this, searchController, getLastOutgoingCallListener);
+    searchFragmentListener = new MainSearchFragmentListener(searchController);
+    callLogAdapterOnActionModeStateChangedListener =
+        new MainCallLogAdapterOnActionModeStateChangedListener();
+    callLogHostInterface = new MainCallLogHost(searchController, fab);
+    callLogFragmentListener = new MainCallLogFragmentListener();
+    onListFragmentScrolledListener = new MainOnListFragmentScrolledListener(snackbarContainer);
+    onPhoneNumberPickerActionListener = new MainOnPhoneNumberPickerActionListener(this);
+    oldSpeedDialFragmentHostInterface =
+        new MainOldSpeedDialFragmentHostInterface(
+            bottomNavTabListener, findViewById(R.id.contact_tile_drag_shadow_overlay));
+    onDragDropListener = new MainOnDragDropListener();
+
     // Restore our view state if needed, else initialize as if the app opened for the first time
     if (savedInstanceState != null) {
       savedLanguageCode = savedInstanceState.getString(KEY_SAVED_LANGUAGE_CODE);
@@ -152,39 +218,6 @@
   }
 
   @Override
-  public void onContactSelected(ImageView photo, Uri contactUri, long contactId) {
-    // TODO(calderwoodra): Add impression logging
-    QuickContact.showQuickContact(
-        this, photo, contactUri, QuickContact.MODE_LARGE, null /* excludeMimes */);
-  }
-
-  @Override // OnDialpadQueryChangedListener
-  public void onDialpadQueryChanged(String query) {
-    searchController.onDialpadQueryChanged(query);
-  }
-
-  @Override // DialpadListener
-  public void getLastOutgoingCall(LastOutgoingCallCallback callback) {
-    DialerExecutorComponent.get(this)
-        .dialerExecutorFactory()
-        .createUiTaskBuilder(
-            getFragmentManager(), "Query last phone number", Calls::getLastOutgoingCall)
-        .onSuccess(output -> callback.lastOutgoingCall(output))
-        .build()
-        .executeParallel(this);
-  }
-
-  @Override // DialpadListener
-  public void onDialpadShown() {
-    searchController.onDialpadShown();
-  }
-
-  @Override // DialpadListener
-  public void onCallPlacedFromDialpad() {
-    // TODO(calderwoodra): logging
-  }
-
-  @Override
   public void onBackPressed() {
     if (searchController.onBackPressed()) {
       return;
@@ -192,27 +225,329 @@
     super.onBackPressed();
   }
 
-  @Override // DialpadFragment.HostInterface
-  public boolean onDialpadSpacerTouchWithEmptyQuery() {
-    // No-op, just let the clicks fall through to the search list
-    return false;
+  @Nullable
+  @Override
+  @SuppressWarnings("unchecked") // Casts are checked using runtime methods
+  public <T> T getImpl(Class<T> callbackInterface) {
+    if (callbackInterface.isInstance(onContactSelectedListener)) {
+      return (T) onContactSelectedListener;
+    } else if (callbackInterface.isInstance(onDialpadQueryChangedListener)) {
+      return (T) onDialpadQueryChangedListener;
+    } else if (callbackInterface.isInstance(dialpadListener)) {
+      return (T) dialpadListener;
+    } else if (callbackInterface.isInstance(dialpadFragmentHostInterface)) {
+      return (T) dialpadFragmentHostInterface;
+    } else if (callbackInterface.isInstance(searchFragmentListener)) {
+      return (T) searchFragmentListener;
+    } else if (callbackInterface.isInstance(callLogAdapterOnActionModeStateChangedListener)) {
+      return (T) callLogAdapterOnActionModeStateChangedListener;
+    } else if (callbackInterface.isInstance(callLogHostInterface)) {
+      return (T) callLogHostInterface;
+    } else if (callbackInterface.isInstance(callLogFragmentListener)) {
+      return (T) callLogFragmentListener;
+    } else if (callbackInterface.isInstance(onListFragmentScrolledListener)) {
+      return (T) onListFragmentScrolledListener;
+    } else if (callbackInterface.isInstance(onPhoneNumberPickerActionListener)) {
+      return (T) onPhoneNumberPickerActionListener;
+    } else if (callbackInterface.isInstance(oldSpeedDialFragmentHostInterface)) {
+      return (T) oldSpeedDialFragmentHostInterface;
+    } else if (callbackInterface.isInstance(onDragDropListener)) {
+      return (T) onDragDropListener;
+    } else {
+      return null;
+    }
   }
 
-  @Override // SearchFragmentListener
-  public void onSearchListTouch() {
-    searchController.onSearchListTouch();
+  @Override
+  public void interactionError(@InteractionErrorCode int interactionErrorCode) {
+    switch (interactionErrorCode) {
+      case InteractionErrorCode.USER_LEAVING_ACTIVITY:
+        // This is expected to happen if the user exits the activity before the interaction occurs.
+        return;
+      case InteractionErrorCode.CONTACT_NOT_FOUND:
+      case InteractionErrorCode.CONTACT_HAS_NO_NUMBER:
+      case InteractionErrorCode.OTHER_ERROR:
+      default:
+        // All other error codes are unexpected. For example, it should be impossible to start an
+        // interaction with an invalid contact from this activity.
+        throw Assert.createIllegalStateFailException(
+            "PhoneNumberInteraction error: " + interactionErrorCode);
+    }
   }
 
-  @Override // SearchFragmentListener
-  public void onCallPlacedFromSearch() {
-    // TODO(calderwoodra): logging
+  @Override
+  public void onDisambigDialogDismissed() {
+    // Don't do anything; the app will remain open with favorites tiles displayed.
+  }
+
+  /** @see OnContactSelectedListener */
+  private static final class MainOnContactSelectedListener implements OnContactSelectedListener {
+
+    private final Context context;
+
+    MainOnContactSelectedListener(Context context) {
+      this.context = context;
+    }
+
+    @Override
+    public void onContactSelected(ImageView photo, Uri contactUri, long contactId) {
+      // TODO(calderwoodra): Add impression logging
+      QuickContact.showQuickContact(
+          context, photo, contactUri, QuickContact.MODE_LARGE, null /* excludeMimes */);
+    }
+  }
+
+  /** @see OnDialpadQueryChangedListener */
+  private static final class MainOnDialpadQueryChangedListener
+      implements OnDialpadQueryChangedListener {
+
+    private final MainSearchController searchController;
+
+    MainOnDialpadQueryChangedListener(MainSearchController searchController) {
+      this.searchController = searchController;
+    }
+
+    @Override
+    public void onDialpadQueryChanged(String query) {
+      searchController.onDialpadQueryChanged(query);
+    }
+  }
+
+  /** @see DialpadListener */
+  private static final class MainDialpadListener implements DialpadListener {
+
+    private final MainSearchController searchController;
+    private final Context context;
+    private final UiListener<String> listener;
+
+    MainDialpadListener(
+        Context context, MainSearchController searchController, UiListener<String> uiListener) {
+      this.context = context;
+      this.searchController = searchController;
+      this.listener = uiListener;
+    }
+
+    @Override
+    public void getLastOutgoingCall(LastOutgoingCallCallback callback) {
+      ListenableFuture<String> listenableFuture =
+          DialerExecutorComponent.get(context)
+              .backgroundExecutor()
+              .submit(() -> Calls.getLastOutgoingCall(context));
+      listener.listen(context, listenableFuture, callback::lastOutgoingCall, throwable -> {});
+    }
+
+    @Override
+    public void onDialpadShown() {
+      searchController.onDialpadShown();
+    }
+
+    @Override
+    public void onCallPlacedFromDialpad() {
+      // TODO(calderwoodra): logging
+    }
+  }
+
+  /** @see SearchFragmentListener */
+  private static final class MainSearchFragmentListener implements SearchFragmentListener {
+
+    private final MainSearchController searchController;
+
+    MainSearchFragmentListener(MainSearchController searchController) {
+      this.searchController = searchController;
+    }
+
+    @Override
+    public void onSearchListTouch() {
+      searchController.onSearchListTouch();
+    }
+
+    @Override
+    public void onCallPlacedFromSearch() {
+      // TODO(calderwoodra): logging
+    }
+  }
+
+  /** @see DialpadFragment.HostInterface */
+  private static final class MainDialpadFragmentHost implements DialpadFragment.HostInterface {
+
+    @Override
+    public boolean onDialpadSpacerTouchWithEmptyQuery() {
+      // No-op, just let the clicks fall through to the search list
+      return false;
+    }
+  }
+
+  /** @see CallLogAdapter.OnActionModeStateChangedListener */
+  // TODO(a bug): handle multiselect mode
+  private static final class MainCallLogAdapterOnActionModeStateChangedListener
+      implements CallLogAdapter.OnActionModeStateChangedListener {
+
+    @Override
+    public void onActionModeStateChanged(boolean isEnabled) {}
+
+    @Override
+    public boolean isActionModeStateEnabled() {
+      return false;
+    }
+  }
+
+  /** @see CallLogFragment.HostInterface */
+  private static final class MainCallLogHost implements CallLogFragment.HostInterface {
+
+    private final MainSearchController searchController;
+    private final FloatingActionButton fab;
+
+    MainCallLogHost(MainSearchController searchController, FloatingActionButton fab) {
+      this.searchController = searchController;
+      this.fab = fab;
+    }
+
+    @Override
+    public void showDialpad() {
+      searchController.showDialpad(true);
+    }
+
+    @Override
+    public void enableFloatingButton(boolean enabled) {
+      if (enabled) {
+        fab.show();
+      } else {
+        fab.hide();
+      }
+    }
+  }
+
+  /** @see CallLogFragmentListener */
+  private static final class MainCallLogFragmentListener implements CallLogFragmentListener {
+
+    @Override
+    public void updateTabUnreadCounts() {
+      // TODO(a bug): implement unread counts
+    }
+
+    @Override
+    public void showMultiSelectRemoveView(boolean show) {
+      // TODO(a bug): handle multiselect mode
+    }
+  }
+
+  /** @see OnListFragmentScrolledListener */
+  private static final class MainOnListFragmentScrolledListener
+      implements OnListFragmentScrolledListener {
+
+    private final View parentLayout;
+
+    MainOnListFragmentScrolledListener(View parentLayout) {
+      this.parentLayout = parentLayout;
+    }
+
+    @Override
+    public void onListFragmentScrollStateChange(int scrollState) {
+      DialerUtils.hideInputMethod(parentLayout);
+    }
+
+    @Override
+    public void onListFragmentScroll(
+        int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+      // TODO: No-op for now. This should eventually show/hide the actionBar based on
+      // interactions with the ListsFragments.
+    }
+  }
+
+  /** @see OnPhoneNumberPickerActionListener */
+  private static final class MainOnPhoneNumberPickerActionListener
+      implements OnPhoneNumberPickerActionListener {
+
+    private final TransactionSafeActivity activity;
+
+    MainOnPhoneNumberPickerActionListener(TransactionSafeActivity activity) {
+      this.activity = activity;
+    }
+
+    @Override
+    public void onPickDataUri(
+        Uri dataUri, boolean isVideoCall, CallSpecificAppData callSpecificAppData) {
+      PhoneNumberInteraction.startInteractionForPhoneCall(
+          activity, dataUri, isVideoCall, callSpecificAppData);
+    }
+
+    @Override
+    public void onPickPhoneNumber(
+        String phoneNumber, boolean isVideoCall, CallSpecificAppData callSpecificAppData) {
+      if (phoneNumber == null) {
+        // Invalid phone number, but let the call go through so that InCallUI can show
+        // an error message.
+        phoneNumber = "";
+      }
+      PreCall.start(
+          activity,
+          new CallIntentBuilder(phoneNumber, callSpecificAppData)
+              .setIsVideoCall(isVideoCall)
+              .setAllowAssistedDial(callSpecificAppData.getAllowAssistedDialing()));
+    }
+
+    @Override
+    public void onHomeInActionBarSelected() {
+      // TODO(calderwoodra): investigate if we need to exit search here
+      // PhoneNumberPickerFragment#onOptionsItemSelected
+    }
+  }
+
+  /** @see OldSpeedDialFragment.HostInterface */
+  private static final class MainOldSpeedDialFragmentHostInterface
+      implements OldSpeedDialFragment.HostInterface {
+
+    private final MainBottomNavBarBottomNavTabListener listener;
+    private final ImageView dragShadowOverlay;
+
+    // TODO(calderwoodra): Use this for drag and drop
+    @SuppressWarnings("unused")
+    private DragDropController dragDropController;
+
+    MainOldSpeedDialFragmentHostInterface(
+        MainBottomNavBarBottomNavTabListener listener, ImageView dragShadowOverlay) {
+      this.listener = listener;
+      this.dragShadowOverlay = dragShadowOverlay;
+    }
+
+    @Override
+    public void setDragDropController(DragDropController dragDropController) {
+      this.dragDropController = dragDropController;
+    }
+
+    @Override
+    public void showAllContactsTab() {
+      listener.onContactsSelected();
+    }
+
+    @Override
+    public ImageView getDragShadowOverlay() {
+      return dragShadowOverlay;
+    }
+  }
+
+  /** @see com.android.dialer.app.list.OnDragDropListener */
+  // TODO(calderwoodra): implement drag and drop
+  private static final class MainOnDragDropListener implements OnDragDropListener {
+
+    @Override
+    public void onDragStarted(int x, int y, PhoneFavoriteSquareTileView view) {}
+
+    @Override
+    public void onDragHovered(int x, int y, PhoneFavoriteSquareTileView view) {}
+
+    @Override
+    public void onDragFinished(int x, int y) {}
+
+    @Override
+    public void onDroppedOnRemove() {}
   }
 
   /**
    * Implementation of {@link OnBottomNavTabSelectedListener} that handles logic for showing each of
    * the main tabs.
    */
-  private final class MainBottomNavBarBottomNavTabListener
+  private static final class MainBottomNavBarBottomNavTabListener
       implements OnBottomNavTabSelectedListener {
 
     private static final String SPEED_DIAL_TAG = "speed_dial";
@@ -220,33 +555,67 @@
     private static final String CONTACTS_TAG = "contacts";
     private static final String VOICEMAIL_TAG = "voicemail";
 
+    private final Context context;
+    private final FragmentManager fragmentManager;
+    private final android.support.v4.app.FragmentManager supportFragmentManager;
+
+    private MainBottomNavBarBottomNavTabListener(
+        Context context,
+        FragmentManager fragmentManager,
+        android.support.v4.app.FragmentManager supportFragmentManager) {
+      this.context = context;
+      this.fragmentManager = fragmentManager;
+      this.supportFragmentManager = supportFragmentManager;
+    }
+
     @Override
     public void onSpeedDialSelected() {
       hideAllFragments();
-      SpeedDialFragment fragment =
-          (SpeedDialFragment) getFragmentManager().findFragmentByTag(SPEED_DIAL_TAG);
+      Fragment fragment = fragmentManager.findFragmentByTag(SPEED_DIAL_TAG);
       if (fragment == null) {
-        getFragmentManager()
+        if (ConfigProviderComponent.get(context)
+            .getConfigProvider()
+            .getBoolean("enable_new_favorites_tab", false)) {
+          fragment = SpeedDialFragment.newInstance();
+        } else {
+          fragment = new OldSpeedDialFragment();
+        }
+        fragmentManager
             .beginTransaction()
-            .add(R.id.fragment_container, SpeedDialFragment.newInstance(), SPEED_DIAL_TAG)
+            .add(R.id.fragment_container, fragment, SPEED_DIAL_TAG)
             .commit();
       } else {
-        getFragmentManager().beginTransaction().show(fragment).commit();
+        fragmentManager.beginTransaction().show(fragment).commit();
       }
     }
 
     @Override
     public void onCallLogSelected() {
       hideAllFragments();
-      NewCallLogFragment fragment =
-          (NewCallLogFragment) getSupportFragmentManager().findFragmentByTag(CALL_LOG_TAG);
-      if (fragment == null) {
-        getSupportFragmentManager()
-            .beginTransaction()
-            .add(R.id.fragment_container, new NewCallLogFragment(), CALL_LOG_TAG)
-            .commit();
+      if (ConfigProviderComponent.get(context)
+          .getConfigProvider()
+          .getBoolean("enable_new_call_log", false)) {
+        NewCallLogFragment fragment =
+            (NewCallLogFragment) supportFragmentManager.findFragmentByTag(CALL_LOG_TAG);
+        if (fragment == null) {
+          supportFragmentManager
+              .beginTransaction()
+              .add(R.id.fragment_container, new NewCallLogFragment(), CALL_LOG_TAG)
+              .commit();
+        } else {
+          supportFragmentManager.beginTransaction().show(fragment).commit();
+        }
       } else {
-        getSupportFragmentManager().beginTransaction().show(fragment).commit();
+        CallLogFragment fragment =
+            (CallLogFragment) fragmentManager.findFragmentByTag(CALL_LOG_TAG);
+        if (fragment == null) {
+          fragmentManager
+              .beginTransaction()
+              .add(R.id.fragment_container, new CallLogFragment(), CALL_LOG_TAG)
+              .commit();
+        } else {
+          fragmentManager.beginTransaction().show(fragment).commit();
+        }
       }
     }
 
@@ -254,9 +623,9 @@
     public void onContactsSelected() {
       hideAllFragments();
       ContactsFragment fragment =
-          (ContactsFragment) getFragmentManager().findFragmentByTag(CONTACTS_TAG);
+          (ContactsFragment) fragmentManager.findFragmentByTag(CONTACTS_TAG);
       if (fragment == null) {
-        getFragmentManager()
+        fragmentManager
             .beginTransaction()
             .add(
                 R.id.fragment_container,
@@ -264,7 +633,7 @@
                 CONTACTS_TAG)
             .commit();
       } else {
-        getFragmentManager().beginTransaction().show(fragment).commit();
+        fragmentManager.beginTransaction().show(fragment).commit();
       }
     }
 
@@ -272,33 +641,38 @@
     public void onVoicemailSelected() {
       hideAllFragments();
       NewVoicemailFragment fragment =
-          (NewVoicemailFragment) getSupportFragmentManager().findFragmentByTag(VOICEMAIL_TAG);
+          (NewVoicemailFragment) supportFragmentManager.findFragmentByTag(VOICEMAIL_TAG);
       if (fragment == null) {
-        getSupportFragmentManager()
+        supportFragmentManager
             .beginTransaction()
             .add(R.id.fragment_container, new NewVoicemailFragment(), VOICEMAIL_TAG)
             .commit();
       } else {
-        getSupportFragmentManager().beginTransaction().show(fragment).commit();
+        supportFragmentManager.beginTransaction().show(fragment).commit();
       }
     }
 
     private void hideAllFragments() {
-      FragmentTransaction supportTransaction = getSupportFragmentManager().beginTransaction();
-      if (getSupportFragmentManager().findFragmentByTag(CALL_LOG_TAG) != null) {
-        supportTransaction.hide(getSupportFragmentManager().findFragmentByTag(CALL_LOG_TAG));
+      FragmentTransaction supportTransaction = supportFragmentManager.beginTransaction();
+      if (supportFragmentManager.findFragmentByTag(CALL_LOG_TAG) != null) {
+        // NewCallLogFragment
+        supportTransaction.hide(supportFragmentManager.findFragmentByTag(CALL_LOG_TAG));
       }
-      if (getSupportFragmentManager().findFragmentByTag(VOICEMAIL_TAG) != null) {
-        supportTransaction.hide(getSupportFragmentManager().findFragmentByTag(VOICEMAIL_TAG));
+      if (supportFragmentManager.findFragmentByTag(VOICEMAIL_TAG) != null) {
+        supportTransaction.hide(supportFragmentManager.findFragmentByTag(VOICEMAIL_TAG));
       }
       supportTransaction.commit();
 
-      android.app.FragmentTransaction transaction = getFragmentManager().beginTransaction();
-      if (getFragmentManager().findFragmentByTag(SPEED_DIAL_TAG) != null) {
-        transaction.hide(getFragmentManager().findFragmentByTag(SPEED_DIAL_TAG));
+      android.app.FragmentTransaction transaction = fragmentManager.beginTransaction();
+      if (fragmentManager.findFragmentByTag(SPEED_DIAL_TAG) != null) {
+        transaction.hide(fragmentManager.findFragmentByTag(SPEED_DIAL_TAG));
       }
-      if (getFragmentManager().findFragmentByTag(CONTACTS_TAG) != null) {
-        transaction.hide(getFragmentManager().findFragmentByTag(CONTACTS_TAG));
+      if (fragmentManager.findFragmentByTag(CALL_LOG_TAG) != null) {
+        // Old CallLogFragment
+        transaction.hide(fragmentManager.findFragmentByTag(CALL_LOG_TAG));
+      }
+      if (fragmentManager.findFragmentByTag(CONTACTS_TAG) != null) {
+        transaction.hide(fragmentManager.findFragmentByTag(CONTACTS_TAG));
       }
       transaction.commit();
     }
diff --git a/java/com/android/dialer/main/impl/res/drawable/notification_badge.xml b/java/com/android/dialer/main/impl/res/drawable/notification_badge.xml
new file mode 100644
index 0000000..2d0dafe
--- /dev/null
+++ b/java/com/android/dialer/main/impl/res/drawable/notification_badge.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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
+  -->
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+  <solid android:color="@color/dialer_secondary_color"/>
+  <size android:height="14dp" android:width="14dp"/>
+</shape>
\ No newline at end of file
diff --git a/java/com/android/dialer/main/impl/res/layout/bottom_nav_item.xml b/java/com/android/dialer/main/impl/res/layout/bottom_nav_item.xml
index f9f2b61..2d9998a 100644
--- a/java/com/android/dialer/main/impl/res/layout/bottom_nav_item.xml
+++ b/java/com/android/dialer/main/impl/res/layout/bottom_nav_item.xml
@@ -25,13 +25,31 @@
     android:paddingStart="12dp"
     android:paddingEnd="12dp"
     android:gravity="center"
+    android:theme="@style/Theme.AppCompat"
     android:background="?android:selectableItemBackgroundBorderless">
 
-  <ImageView
-      android:id="@+id/bottom_nav_item_image"
-      android:layout_width="24dp"
-      android:layout_height="24dp"
-      android:layout_marginBottom="6dp"/>
+  <FrameLayout
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_marginBottom="2dp">
+
+    <ImageView
+        android:id="@+id/bottom_nav_item_image"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:layout_margin="4dp"/>
+
+    <TextView
+        android:id="@+id/notification_badge"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="top|end"
+        android:gravity="center"
+        android:textSize="12sp"
+        android:textColor="@color/dialer_primary_text_color_white"
+        android:background="@drawable/notification_badge"
+        android:visibility="gone"/>
+  </FrameLayout>
 
   <TextView
       android:id="@+id/bottom_nav_item_text"
diff --git a/java/com/android/dialer/main/impl/res/layout/main_activity.xml b/java/com/android/dialer/main/impl/res/layout/main_activity.xml
index aaba8da..2094a73 100644
--- a/java/com/android/dialer/main/impl/res/layout/main_activity.xml
+++ b/java/com/android/dialer/main/impl/res/layout/main_activity.xml
@@ -66,4 +66,18 @@
   <include
       android:id="@+id/toolbar"
       layout="@layout/toolbar_layout"/>
+
+  <!-- TODO(calderwoodra): investigate what this is for and why we want it. -->
+  <!-- Host container for the contact tile drag shadow -->
+  <FrameLayout
+      android:id="@+id/activity_overlay"
+      android:layout_width="match_parent"
+      android:layout_height="match_parent">
+    <ImageView
+        android:id="@+id/contact_tile_drag_shadow_overlay"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:importantForAccessibility="no"
+        android:visibility="gone"/>
+  </FrameLayout>
 </RelativeLayout>
\ No newline at end of file
diff --git a/java/com/android/incallui/ContactInfoCache.java b/java/com/android/incallui/ContactInfoCache.java
index fc41df4..d2ae709 100644
--- a/java/com/android/incallui/ContactInfoCache.java
+++ b/java/com/android/incallui/ContactInfoCache.java
@@ -541,7 +541,9 @@
       hasUpdate = true;
     }
     // Set contact to exist to avoid phone number service lookup.
-    callerInfo.contactExists = hasUpdate;
+    if (hasUpdate) {
+      callerInfo.contactExists = true;
+    }
   }
 
   /**