Merge changes Ib035855d,I15bc2c45,I079aca18,I9b90aaa0,I452ca635, ... am: 4cd323f227
am: 385e7e81c7

Change-Id: Ie0a55743d7574a0d1bdd3422d1b9ea8449adf1fe
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;
+    }
   }
 
   /**