Merge "Split MainActivity into two peers, old and new to keep logic isolated."
diff --git a/java/com/android/dialer/common/FragmentUtils.java b/java/com/android/dialer/common/FragmentUtils.java
index 947a9b2..c07d9a7 100644
--- a/java/com/android/dialer/common/FragmentUtils.java
+++ b/java/com/android/dialer/common/FragmentUtils.java
@@ -21,6 +21,7 @@
 import android.support.annotation.Nullable;
 import android.support.annotation.VisibleForTesting;
 import android.support.v4.app.Fragment;
+import com.android.dialer.main.MainActivityPeer;
 
 /** Utility methods for working with Fragments */
 public class FragmentUtils {
@@ -89,6 +90,11 @@
       @SuppressWarnings("unchecked") // Casts are checked using runtime methods
       T parent = ((FragmentUtilListener) fragment.getActivity()).getImpl(callbackInterface);
       return parent;
+    } else if (fragment.getActivity() instanceof MainActivityPeer.PeerSupplier) {
+      MainActivityPeer peer = ((MainActivityPeer.PeerSupplier) fragment.getActivity()).getPeer();
+      if (peer instanceof FragmentUtilListener) {
+        return ((FragmentUtilListener) peer).getImpl(callbackInterface);
+      }
     }
     return null;
   }
diff --git a/java/com/android/dialer/main/MainActivityPeer.java b/java/com/android/dialer/main/MainActivityPeer.java
new file mode 100644
index 0000000..6457b60
--- /dev/null
+++ b/java/com/android/dialer/main/MainActivityPeer.java
@@ -0,0 +1,44 @@
+/*
+ * 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
+ */
+
+package com.android.dialer.main;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+/** Interface for peers of MainActivity. */
+public interface MainActivityPeer {
+
+  void onActivityCreate(Bundle saveInstanceState);
+
+  void onActivityResume();
+
+  void onActivityStop();
+
+  void onNewIntent(Intent intent);
+
+  void onActivityResult(int requestCode, int resultCode, Intent data);
+
+  void onSaveInstanceState(Bundle bundle);
+
+  boolean onBackPressed();
+
+  /** Supplies the MainActivityPeer */
+  interface PeerSupplier {
+
+    MainActivityPeer getPeer();
+  }
+}
diff --git a/java/com/android/dialer/main/impl/MainActivity.java b/java/com/android/dialer/main/impl/MainActivity.java
index ca9d478..1a79fba 100644
--- a/java/com/android/dialer/main/impl/MainActivity.java
+++ b/java/com/android/dialer/main/impl/MainActivity.java
@@ -16,113 +16,27 @@
 
 package com.android.dialer.main.impl;
 
-import android.app.Activity;
-import android.app.Fragment;
-import android.app.FragmentManager;
-import android.app.KeyguardManager;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
-import android.database.Cursor;
-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.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.calllog.CallLogNotificationsService;
-import com.android.dialer.app.calllog.VisualVoicemailCallLogFragment;
-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.ConfigProviderBindings;
 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;
-import com.android.dialer.contactsfragment.ContactsFragment.OnContactSelectedListener;
-import com.android.dialer.database.CallLogQueryHandler;
-import com.android.dialer.database.Database;
-import com.android.dialer.dialpadview.DialpadFragment;
-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.BottomNavBar.TabIndex;
-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.storage.StorageComponent;
-import com.android.dialer.telecom.TelecomUtil;
-import com.android.dialer.util.DialerUtils;
+import com.android.dialer.main.MainActivityPeer;
 import com.android.dialer.util.TransactionSafeActivity;
-import com.android.dialer.voicemail.listui.NewVoicemailFragment;
-import com.google.common.util.concurrent.ListenableFuture;
-import java.util.concurrent.TimeUnit;
 
 /** This is the main activity for dialer. It hosts favorites, call log, search, dialpad, etc... */
 // TODO(calderwoodra): Do not extend TransactionSafeActivity after new SpeedDial is launched
 public final class MainActivity extends TransactionSafeActivity
-    implements FragmentUtilListener,
+    implements MainActivityPeer.PeerSupplier,
         // 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 static final String KEY_CURRENT_TAB = "current_tab";
-  private static final String KEY_LAST_TAB = "last_tab";
-
-  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 LastTabController lastTabController;
-
-  private BottomNavBar bottomNav;
-  private View snackbarContainer;
-  private UiListener<String> getLastOutgoingCallListener;
+  private MainActivityPeer activePeer;
 
   /**
    * @param context Context of the application package implementing MainActivity class.
@@ -138,164 +52,54 @@
   protected void onCreate(Bundle savedInstanceState) {
     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);
-
-    FloatingActionButton fab = findViewById(R.id.fab);
-    fab.setOnClickListener(v -> searchController.showDialpad(true));
-
-    MainToolbar toolbar = findViewById(R.id.toolbar);
-    setSupportActionBar(findViewById(R.id.toolbar));
-
-    bottomNav = findViewById(R.id.bottom_nav_bar);
-    MainBottomNavBarBottomNavTabListener bottomNavTabListener =
-        new MainBottomNavBarBottomNavTabListener(
-            this, getFragmentManager(), getSupportFragmentManager());
-    bottomNav.addOnTabSelectedListener(bottomNavTabListener);
-
-    callLogFragmentListener =
-        new MainCallLogFragmentListener(this, getContentResolver(), bottomNav);
-    bottomNav.addOnTabSelectedListener(callLogFragmentListener);
-
-    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);
-
-    onListFragmentScrolledListener = new MainOnListFragmentScrolledListener(snackbarContainer);
-    onPhoneNumberPickerActionListener = new MainOnPhoneNumberPickerActionListener(this);
-    oldSpeedDialFragmentHostInterface =
-        new MainOldSpeedDialFragmentHostInterface(
-            bottomNavTabListener, findViewById(R.id.contact_tile_drag_shadow_overlay));
-    onDragDropListener = new MainOnDragDropListener();
-
-    lastTabController = new LastTabController(this, bottomNav);
-
-    // 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);
-      searchController.onRestoreInstanceState(savedInstanceState);
-      bottomNav.selectTab(savedInstanceState.getInt(KEY_CURRENT_TAB));
+    if (ConfigProviderComponent.get(this)
+        .getConfigProvider()
+        .getBoolean("nui_peer_enabled", false)) {
+      activePeer = new NewMainActivityPeer(this);
     } else {
-      lastTabController.selectLastTab();
+      activePeer = new OldMainActivityPeer(this);
     }
+    activePeer.onActivityCreate(savedInstanceState);
   }
 
   @Override
   protected void onNewIntent(Intent intent) {
     super.onNewIntent(intent);
-    lastTabController.selectLastTab();
+    activePeer.onNewIntent(intent);
   }
 
   @Override
   protected void onResume() {
     super.onResume();
-    callLogFragmentListener.onActivityResume();
-    // Start the thread that updates the smart dial database if the activity is recreated with a
-    // language change.
-    boolean forceUpdate = !CompatUtils.getLocale(this).getISO3Language().equals(savedLanguageCode);
-    Database.get(this).getDatabaseHelper(this).startSmartDialUpdateThread(forceUpdate);
-    showPostCallPrompt();
+    activePeer.onActivityResume();
   }
 
   @Override
   protected void onStop() {
     super.onStop();
-    lastTabController.onActivityStop();
-    callLogFragmentListener.onActivityStop(
-        isChangingConfigurations(), getSystemService(KeyguardManager.class).isKeyguardLocked());
-  }
-
-  private void showPostCallPrompt() {
-    if (TelecomUtil.isInManagedCall(this)) {
-      // No prompt to show if the user is in a call
-      return;
-    }
-
-    if (searchController.isInSearch()) {
-      // Don't show the prompt if we're in the search ui
-      return;
-    }
-
-    PostCall.promptUserForMessageIfNecessary(this, snackbarContainer);
+    activePeer.onActivityStop();
   }
 
   @Override
   protected void onSaveInstanceState(Bundle bundle) {
     super.onSaveInstanceState(bundle);
-    bundle.putString(KEY_SAVED_LANGUAGE_CODE, CompatUtils.getLocale(this).getISO3Language());
-    bundle.putInt(KEY_CURRENT_TAB, bottomNav.getSelectedTab());
-    searchController.onSaveInstanceState(bundle);
+    activePeer.onSaveInstanceState(bundle);
   }
 
   @Override
   protected void onActivityResult(int requestCode, int resultCode, Intent data) {
     super.onActivityResult(requestCode, resultCode, data);
-    if (requestCode == ActivityRequestCodes.DIALTACTS_VOICE_SEARCH) {
-      searchController.onVoiceResults(resultCode, data);
-    } else {
-      LogUtil.e("MainActivity.onActivityResult", "Unknown request code: " + requestCode);
-    }
+    activePeer.onActivityResult(requestCode, resultCode, data);
   }
 
   @Override
   public void onBackPressed() {
-    if (searchController.onBackPressed()) {
+    if (activePeer.onBackPressed()) {
       return;
     }
     super.onBackPressed();
   }
 
-  @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
   public void interactionError(@InteractionErrorCode int interactionErrorCode) {
     switch (interactionErrorCode) {
@@ -318,583 +122,8 @@
     // 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();
-      }
-    }
-  }
-
-  /**
-   * Handles the logic for callbacks from:
-   *
-   * <ul>
-   *   <li>{@link CallLogFragment}
-   *   <li>{@link CallLogQueryHandler}
-   *   <li>{@link BottomNavBar}
-   * </ul>
-   *
-   * This mainly entails:
-   *
-   * <ul>
-   *   <li>Handling querying for missed calls/unread voicemails.
-   *   <li>Displaying a badge to the user in the bottom nav when there are missed calls/unread
-   *       voicemails present.
-   *   <li>Marking missed calls as read when appropriate. See {@link
-   *       #markMissedCallsAsReadAndRemoveNotification()}
-   *   <li>TODO(calderwoodra): multiselect
-   *   <li>TODO(calderwoodra): voicemail status
-   * </ul>
-   *
-   * @see CallLogFragmentListener
-   * @see CallLogQueryHandler.Listener
-   * @see OnBottomNavTabSelectedListener
-   */
-  private static final class MainCallLogFragmentListener
-      implements CallLogFragmentListener,
-          CallLogQueryHandler.Listener,
-          OnBottomNavTabSelectedListener {
-
-    private final CallLogQueryHandler callLogQueryHandler;
-    private final BottomNavBar bottomNavBar;
-    private final Context context;
-
-    private @TabIndex int currentTab = TabIndex.SPEED_DIAL;
-    private long timeSelected = -1;
-    private boolean activityIsAlive;
-
-    MainCallLogFragmentListener(
-        Context context, ContentResolver contentResolver, BottomNavBar bottomNavBar) {
-      callLogQueryHandler = new CallLogQueryHandler(context, contentResolver, this);
-      this.bottomNavBar = bottomNavBar;
-      this.context = context;
-    }
-
-    @Override
-    public void updateTabUnreadCounts() {
-      callLogQueryHandler.fetchMissedCallsUnreadCount();
-      callLogQueryHandler.fetchVoicemailUnreadCount();
-    }
-
-    @Override
-    public void showMultiSelectRemoveView(boolean show) {
-      // TODO(a bug): handle multiselect mode
-    }
-
-    @Override
-    public void onVoicemailStatusFetched(Cursor statusCursor) {
-      // TODO(calderwoodra): handle this when voicemail is implemented
-    }
-
-    @Override
-    public void onVoicemailUnreadCountFetched(Cursor cursor) {
-      if (activityIsAlive) {
-        bottomNavBar.setNotificationCount(TabIndex.VOICEMAIL, cursor.getCount());
-      }
-      cursor.close();
-    }
-
-    @Override
-    public void onMissedCallsUnreadCountFetched(Cursor cursor) {
-      if (activityIsAlive) {
-        bottomNavBar.setNotificationCount(TabIndex.CALL_LOG, cursor.getCount());
-      }
-      cursor.close();
-    }
-
-    @Override
-    public boolean onCallsFetched(Cursor combinedCursor) {
-      // Return false; did not take ownership of cursor
-      return false;
-    }
-
-    @Override
-    public void onSpeedDialSelected() {
-      setCurrentTab(TabIndex.SPEED_DIAL);
-    }
-
-    @Override
-    public void onCallLogSelected() {
-      setCurrentTab(TabIndex.CALL_LOG);
-    }
-
-    @Override
-    public void onContactsSelected() {
-      setCurrentTab(TabIndex.CONTACTS);
-    }
-
-    @Override
-    public void onVoicemailSelected() {
-      setCurrentTab(TabIndex.VOICEMAIL);
-    }
-
-    private void markMissedCallsAsReadAndRemoveNotification() {
-      callLogQueryHandler.markMissedCallsAsRead();
-      CallLogNotificationsService.cancelAllMissedCalls(context);
-    }
-
-    private void setCurrentTab(@TabIndex int tabIndex) {
-      if (currentTab == TabIndex.CALL_LOG && tabIndex != TabIndex.CALL_LOG) {
-        markMissedCallsAsReadAndRemoveNotification();
-      }
-      currentTab = tabIndex;
-      timeSelected = System.currentTimeMillis();
-    }
-
-    public void onActivityResume() {
-      activityIsAlive = true;
-      callLogQueryHandler.fetchVoicemailStatus();
-      callLogQueryHandler.fetchMissedCallsUnreadCount();
-      // Reset the tab on resume to restart the timer
-      setCurrentTab(bottomNavBar.getSelectedTab());
-    }
-
-    /** Should be called when {@link Activity#onStop()} is called. */
-    public void onActivityStop(boolean changingConfigurations, boolean keyguardLocked) {
-      activityIsAlive = false;
-      if (viewedCallLogTabPastTimeThreshold() && !changingConfigurations && !keyguardLocked) {
-        markMissedCallsAsReadAndRemoveNotification();
-      }
-    }
-
-    /**
-     * Returns true if the user has been (and still is) on the history tab for long than the
-     * threshold.
-     */
-    private boolean viewedCallLogTabPastTimeThreshold() {
-      return currentTab == TabIndex.CALL_LOG
-          && timeSelected != -1
-          && System.currentTimeMillis() - timeSelected > TimeUnit.SECONDS.toMillis(3);
-    }
-  }
-
-  /** @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 static final class MainBottomNavBarBottomNavTabListener
-      implements OnBottomNavTabSelectedListener {
-
-    private static final String SPEED_DIAL_TAG = "speed_dial";
-    private static final String CALL_LOG_TAG = "call_log";
-    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();
-      Fragment fragment = fragmentManager.findFragmentByTag(SPEED_DIAL_TAG);
-      if (fragment == null) {
-        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, fragment, SPEED_DIAL_TAG)
-            .commit();
-      } else {
-        fragmentManager.beginTransaction().show(fragment).commit();
-      }
-    }
-
-    @Override
-    public void onCallLogSelected() {
-      hideAllFragments();
-      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 {
-        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();
-        }
-      }
-    }
-
-    @Override
-    public void onContactsSelected() {
-      hideAllFragments();
-      ContactsFragment fragment =
-          (ContactsFragment) fragmentManager.findFragmentByTag(CONTACTS_TAG);
-      if (fragment == null) {
-        fragmentManager
-            .beginTransaction()
-            .add(
-                R.id.fragment_container,
-                ContactsFragment.newInstance(Header.ADD_CONTACT),
-                CONTACTS_TAG)
-            .commit();
-      } else {
-        fragmentManager.beginTransaction().show(fragment).commit();
-      }
-    }
-
-    @Override
-    public void onVoicemailSelected() {
-      hideAllFragments();
-      if (ConfigProviderComponent.get(context)
-          .getConfigProvider()
-          .getBoolean("enable_new_voicemail_fragment", false)) {
-        NewVoicemailFragment fragment =
-            (NewVoicemailFragment) supportFragmentManager.findFragmentByTag(VOICEMAIL_TAG);
-        if (fragment == null) {
-          supportFragmentManager
-              .beginTransaction()
-              .add(R.id.fragment_container, new NewVoicemailFragment(), VOICEMAIL_TAG)
-              .commit();
-        } else {
-          supportFragmentManager.beginTransaction().show(fragment).commit();
-        }
-      } else {
-        VisualVoicemailCallLogFragment fragment =
-            (VisualVoicemailCallLogFragment) fragmentManager.findFragmentByTag(VOICEMAIL_TAG);
-        if (fragment == null) {
-          fragmentManager
-              .beginTransaction()
-              .add(R.id.fragment_container, new VisualVoicemailCallLogFragment(), VOICEMAIL_TAG)
-              .commit();
-        } else {
-          fragmentManager.beginTransaction().show(fragment).commit();
-        }
-      }
-    }
-
-    private void hideAllFragments() {
-      FragmentTransaction supportTransaction = supportFragmentManager.beginTransaction();
-      if (supportFragmentManager.findFragmentByTag(CALL_LOG_TAG) != null) {
-        // NewCallLogFragment
-        supportTransaction.hide(supportFragmentManager.findFragmentByTag(CALL_LOG_TAG));
-      }
-      if (supportFragmentManager.findFragmentByTag(VOICEMAIL_TAG) != null) {
-        // NewVoicemailFragment
-        supportTransaction.hide(supportFragmentManager.findFragmentByTag(VOICEMAIL_TAG));
-      }
-      supportTransaction.commit();
-
-      android.app.FragmentTransaction transaction = fragmentManager.beginTransaction();
-      if (fragmentManager.findFragmentByTag(SPEED_DIAL_TAG) != null) {
-        transaction.hide(fragmentManager.findFragmentByTag(SPEED_DIAL_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));
-      }
-      if (fragmentManager.findFragmentByTag(VOICEMAIL_TAG) != null) {
-        // Old VisualVoicemailFragment
-        transaction.hide(fragmentManager.findFragmentByTag(VOICEMAIL_TAG));
-      }
-      transaction.commit();
-    }
-  }
-
-  private static final class LastTabController {
-
-    private final Context context;
-    private final BottomNavBar bottomNavBar;
-    private final boolean isEnabled;
-
-    public LastTabController(Context context, BottomNavBar bottomNavBar) {
-      this.context = context;
-      this.bottomNavBar = bottomNavBar;
-      isEnabled = ConfigProviderBindings.get(context).getBoolean("last_tab_enabled", false);
-    }
-
-    /** Sets the last tab if the feature is enabled, otherwise defaults to speed dial. */
-    public void selectLastTab() {
-      @TabIndex int tabIndex = TabIndex.SPEED_DIAL;
-      if (isEnabled) {
-        tabIndex =
-            StorageComponent.get(context)
-                .unencryptedSharedPrefs()
-                .getInt(KEY_LAST_TAB, TabIndex.SPEED_DIAL);
-      }
-      bottomNavBar.selectTab(tabIndex);
-    }
-
-    public void onActivityStop() {
-      StorageComponent.get(context)
-          .unencryptedSharedPrefs()
-          .edit()
-          .putInt(KEY_LAST_TAB, bottomNavBar.getSelectedTab())
-          .apply();
-    }
+  @Override
+  public MainActivityPeer getPeer() {
+    return activePeer;
   }
 }
diff --git a/java/com/android/dialer/main/impl/NewMainActivityPeer.java b/java/com/android/dialer/main/impl/NewMainActivityPeer.java
new file mode 100644
index 0000000..cc4c3e7
--- /dev/null
+++ b/java/com/android/dialer/main/impl/NewMainActivityPeer.java
@@ -0,0 +1,137 @@
+/*
+ * 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
+ */
+
+package com.android.dialer.main.impl;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
+import com.android.dialer.calllog.ui.NewCallLogFragment;
+import com.android.dialer.main.MainActivityPeer;
+import com.android.dialer.main.impl.BottomNavBar.OnBottomNavTabSelectedListener;
+import com.android.dialer.main.impl.BottomNavBar.TabIndex;
+import com.android.dialer.voicemail.listui.NewVoicemailFragment;
+
+/** MainActivityPeer that implements the new fragments. */
+public class NewMainActivityPeer implements MainActivityPeer {
+
+  private final MainActivity mainActivity;
+
+  public NewMainActivityPeer(MainActivity mainActivity) {
+    this.mainActivity = mainActivity;
+  }
+
+  @Override
+  public void onActivityCreate(Bundle saveInstanceState) {
+    mainActivity.setContentView(R.layout.main_activity);
+    MainBottomNavBarBottomNavTabListener bottomNavBarBottomNavTabListener =
+        new MainBottomNavBarBottomNavTabListener(mainActivity.getSupportFragmentManager());
+    BottomNavBar bottomNav = mainActivity.findViewById(R.id.bottom_nav_bar);
+    bottomNav.addOnTabSelectedListener(bottomNavBarBottomNavTabListener);
+    bottomNav.selectTab(TabIndex.SPEED_DIAL);
+  }
+
+  @Override
+  public void onActivityResume() {}
+
+  @Override
+  public void onActivityStop() {}
+
+  @Override
+  public void onNewIntent(Intent intent) {}
+
+  @Override
+  public void onActivityResult(int requestCode, int resultCode, Intent data) {}
+
+  @Override
+  public void onSaveInstanceState(Bundle bundle) {}
+
+  @Override
+  public boolean onBackPressed() {
+    return false;
+  }
+
+  /**
+   * Implementation of {@link OnBottomNavTabSelectedListener} that handles logic for showing each of
+   * the main tabs.
+   */
+  private static final class MainBottomNavBarBottomNavTabListener
+      implements OnBottomNavTabSelectedListener {
+
+    private static final String CALL_LOG_TAG = "call_log";
+    private static final String VOICEMAIL_TAG = "voicemail";
+
+    private final FragmentManager supportFragmentManager;
+
+    private MainBottomNavBarBottomNavTabListener(FragmentManager supportFragmentManager) {
+      this.supportFragmentManager = supportFragmentManager;
+    }
+
+    @Override
+    public void onSpeedDialSelected() {
+      hideAllFragments();
+      // TODO(calderwoodra): Implement SpeedDialFragment when FragmentUtils#getParent works
+    }
+
+    @Override
+    public void onCallLogSelected() {
+      hideAllFragments();
+      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();
+      }
+    }
+
+    @Override
+    public void onContactsSelected() {
+      hideAllFragments();
+      // TODO(calderwoodra): Implement ContactsFragment when FragmentUtils#getParent works
+    }
+
+    @Override
+    public void onVoicemailSelected() {
+      hideAllFragments();
+      NewVoicemailFragment fragment =
+          (NewVoicemailFragment) supportFragmentManager.findFragmentByTag(VOICEMAIL_TAG);
+      if (fragment == null) {
+        supportFragmentManager
+            .beginTransaction()
+            .add(R.id.fragment_container, new NewVoicemailFragment(), VOICEMAIL_TAG)
+            .commit();
+      } else {
+        supportFragmentManager.beginTransaction().show(fragment).commit();
+      }
+    }
+
+    private void hideAllFragments() {
+      FragmentTransaction supportTransaction = supportFragmentManager.beginTransaction();
+      if (supportFragmentManager.findFragmentByTag(CALL_LOG_TAG) != null) {
+        supportTransaction.hide(supportFragmentManager.findFragmentByTag(CALL_LOG_TAG));
+      }
+      if (supportFragmentManager.findFragmentByTag(VOICEMAIL_TAG) != null) {
+        supportTransaction.hide(supportFragmentManager.findFragmentByTag(VOICEMAIL_TAG));
+      }
+      supportTransaction.commit();
+    }
+  }
+}
diff --git a/java/com/android/dialer/main/impl/OldMainActivityPeer.java b/java/com/android/dialer/main/impl/OldMainActivityPeer.java
new file mode 100644
index 0000000..489de1a
--- /dev/null
+++ b/java/com/android/dialer/main/impl/OldMainActivityPeer.java
@@ -0,0 +1,815 @@
+/*
+ * 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
+ */
+
+package com.android.dialer.main.impl;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.KeyguardManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+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.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.calllog.CallLogNotificationsService;
+import com.android.dialer.app.calllog.VisualVoicemailCallLogFragment;
+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.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.ConfigProviderBindings;
+import com.android.dialer.constants.ActivityRequestCodes;
+import com.android.dialer.contactsfragment.ContactsFragment;
+import com.android.dialer.contactsfragment.ContactsFragment.Header;
+import com.android.dialer.contactsfragment.ContactsFragment.OnContactSelectedListener;
+import com.android.dialer.database.CallLogQueryHandler;
+import com.android.dialer.database.Database;
+import com.android.dialer.dialpadview.DialpadFragment;
+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.main.MainActivityPeer;
+import com.android.dialer.main.impl.BottomNavBar.OnBottomNavTabSelectedListener;
+import com.android.dialer.main.impl.BottomNavBar.TabIndex;
+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.storage.StorageComponent;
+import com.android.dialer.telecom.TelecomUtil;
+import com.android.dialer.util.DialerUtils;
+import com.android.dialer.util.TransactionSafeActivity;
+import com.google.common.util.concurrent.ListenableFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * OldMainActivityPeer which implements all of the old fragments we know and love <3
+ *
+ * <p>TODO(calderwoodra): Deprecate this class when we launch NewmainActivityPeer.
+ */
+public class OldMainActivityPeer implements MainActivityPeer, FragmentUtilListener {
+
+  private static final String KEY_SAVED_LANGUAGE_CODE = "saved_language_code";
+  private static final String KEY_CURRENT_TAB = "current_tab";
+  private static final String KEY_LAST_TAB = "last_tab";
+
+  private final MainActivity mainActivity;
+
+  // Contacts
+  private MainOnContactSelectedListener onContactSelectedListener;
+
+  // Dialpad and Search
+  private MainDialpadFragmentHost dialpadFragmentHostInterface;
+  private MainSearchController searchController;
+  private MainOnDialpadQueryChangedListener onDialpadQueryChangedListener;
+  private MainDialpadListener dialpadListener;
+  private MainSearchFragmentListener searchFragmentListener;
+
+  // Action Mode
+  private MainCallLogAdapterOnActionModeStateChangedListener
+      callLogAdapterOnActionModeStateChangedListener;
+
+  // Call Log
+  private MainCallLogHost callLogHostInterface;
+  private MainCallLogFragmentListener callLogFragmentListener;
+  private MainOnListFragmentScrolledListener onListFragmentScrolledListener;
+
+  // Speed Dial
+  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 LastTabController lastTabController;
+
+  private BottomNavBar bottomNav;
+  private View snackbarContainer;
+  private UiListener<String> getLastOutgoingCallListener;
+
+  public OldMainActivityPeer(MainActivity mainActivity) {
+    this.mainActivity = mainActivity;
+  }
+
+  @Override
+  public void onActivityCreate(Bundle savedInstanceState) {
+    mainActivity.setContentView(R.layout.main_activity);
+    initUiListeners();
+    initLayout(savedInstanceState);
+    SmartDialPrefix.initializeNanpSettings(mainActivity);
+  }
+
+  private void initUiListeners() {
+    getLastOutgoingCallListener =
+        DialerExecutorComponent.get(mainActivity)
+            .createUiListener(mainActivity.getFragmentManager(), "Query last phone number");
+  }
+
+  private void initLayout(Bundle savedInstanceState) {
+    onContactSelectedListener = new MainOnContactSelectedListener(mainActivity);
+    dialpadFragmentHostInterface = new MainDialpadFragmentHost();
+
+    snackbarContainer = mainActivity.findViewById(R.id.coordinator_layout);
+
+    FloatingActionButton fab = mainActivity.findViewById(R.id.fab);
+    fab.setOnClickListener(v -> searchController.showDialpad(true));
+
+    MainToolbar toolbar = mainActivity.findViewById(R.id.toolbar);
+    mainActivity.setSupportActionBar(mainActivity.findViewById(R.id.toolbar));
+
+    bottomNav = mainActivity.findViewById(R.id.bottom_nav_bar);
+    MainBottomNavBarBottomNavTabListener bottomNavTabListener =
+        new MainBottomNavBarBottomNavTabListener(mainActivity.getFragmentManager());
+    bottomNav.addOnTabSelectedListener(bottomNavTabListener);
+
+    callLogFragmentListener =
+        new MainCallLogFragmentListener(mainActivity, mainActivity.getContentResolver(), bottomNav);
+    bottomNav.addOnTabSelectedListener(callLogFragmentListener);
+
+    searchController = new MainSearchController(mainActivity, bottomNav, fab, toolbar);
+    toolbar.setSearchBarListener(searchController);
+
+    onDialpadQueryChangedListener = new MainOnDialpadQueryChangedListener(searchController);
+    dialpadListener =
+        new MainDialpadListener(mainActivity, searchController, getLastOutgoingCallListener);
+    searchFragmentListener = new MainSearchFragmentListener(searchController);
+    callLogAdapterOnActionModeStateChangedListener =
+        new MainCallLogAdapterOnActionModeStateChangedListener();
+    callLogHostInterface = new MainCallLogHost(searchController, fab);
+
+    onListFragmentScrolledListener = new MainOnListFragmentScrolledListener(snackbarContainer);
+    onPhoneNumberPickerActionListener = new MainOnPhoneNumberPickerActionListener(mainActivity);
+    oldSpeedDialFragmentHostInterface =
+        new MainOldSpeedDialFragmentHostInterface(
+            bottomNav, mainActivity.findViewById(R.id.contact_tile_drag_shadow_overlay));
+    onDragDropListener = new MainOnDragDropListener();
+
+    lastTabController = new LastTabController(mainActivity, bottomNav);
+
+    // 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);
+      searchController.onRestoreInstanceState(savedInstanceState);
+      bottomNav.selectTab(savedInstanceState.getInt(KEY_CURRENT_TAB));
+    } else {
+      lastTabController.selectLastTab();
+    }
+  }
+
+  @Override
+  public void onNewIntent(Intent intent) {
+    lastTabController.selectLastTab();
+  }
+
+  @Override
+  public void onActivityResume() {
+    callLogFragmentListener.onActivityResume();
+    // Start the thread that updates the smart dial database if the activity is recreated with a
+    // language change.
+    boolean forceUpdate =
+        !CompatUtils.getLocale(mainActivity).getISO3Language().equals(savedLanguageCode);
+    Database.get(mainActivity)
+        .getDatabaseHelper(mainActivity)
+        .startSmartDialUpdateThread(forceUpdate);
+    showPostCallPrompt();
+  }
+
+  @Override
+  public void onActivityStop() {
+    lastTabController.onActivityStop();
+    callLogFragmentListener.onActivityStop(
+        mainActivity.isChangingConfigurations(),
+        mainActivity.getSystemService(KeyguardManager.class).isKeyguardLocked());
+  }
+
+  private void showPostCallPrompt() {
+    if (TelecomUtil.isInManagedCall(mainActivity)) {
+      // No prompt to show if the user is in a call
+      return;
+    }
+
+    if (searchController.isInSearch()) {
+      // Don't show the prompt if we're in the search ui
+      return;
+    }
+
+    PostCall.promptUserForMessageIfNecessary(mainActivity, snackbarContainer);
+  }
+
+  @Override
+  public void onSaveInstanceState(Bundle bundle) {
+    bundle.putString(
+        KEY_SAVED_LANGUAGE_CODE, CompatUtils.getLocale(mainActivity).getISO3Language());
+    bundle.putInt(KEY_CURRENT_TAB, bottomNav.getSelectedTab());
+    searchController.onSaveInstanceState(bundle);
+  }
+
+  @Override
+  public void onActivityResult(int requestCode, int resultCode, Intent data) {
+    if (requestCode == ActivityRequestCodes.DIALTACTS_VOICE_SEARCH) {
+      searchController.onVoiceResults(resultCode, data);
+    } else {
+      LogUtil.e("MainActivity.onActivityResult", "Unknown request code: " + requestCode);
+    }
+  }
+
+  @Override
+  public boolean onBackPressed() {
+    if (searchController.onBackPressed()) {
+      return true;
+    }
+    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;
+    }
+  }
+
+  /** @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 FloatingActionButton fab;
+    private final MainSearchController searchController;
+
+    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();
+      }
+    }
+  }
+
+  /**
+   * Handles the logic for callbacks from:
+   *
+   * <ul>
+   *   <li>{@link CallLogFragment}
+   *   <li>{@link CallLogQueryHandler}
+   *   <li>{@link BottomNavBar}
+   * </ul>
+   *
+   * This mainly entails:
+   *
+   * <ul>
+   *   <li>Handling querying for missed calls/unread voicemails.
+   *   <li>Displaying a badge to the user in the bottom nav when there are missed calls/unread
+   *       voicemails present.
+   *   <li>Marking missed calls as read when appropriate. See {@link
+   *       #markMissedCallsAsReadAndRemoveNotification()}
+   *   <li>TODO(calderwoodra): multiselect
+   *   <li>TODO(calderwoodra): voicemail status
+   * </ul>
+   *
+   * @see CallLogFragmentListener
+   * @see CallLogQueryHandler.Listener
+   * @see OnBottomNavTabSelectedListener
+   */
+  private static final class MainCallLogFragmentListener
+      implements CallLogFragmentListener,
+          CallLogQueryHandler.Listener,
+          OnBottomNavTabSelectedListener {
+
+    private final CallLogQueryHandler callLogQueryHandler;
+    private final BottomNavBar bottomNavBar;
+    private final Context context;
+
+    private @TabIndex int currentTab = TabIndex.SPEED_DIAL;
+    private long timeSelected = -1;
+    private boolean activityIsAlive;
+
+    MainCallLogFragmentListener(
+        Context context, ContentResolver contentResolver, BottomNavBar bottomNavBar) {
+      callLogQueryHandler = new CallLogQueryHandler(context, contentResolver, this);
+      this.bottomNavBar = bottomNavBar;
+      this.context = context;
+    }
+
+    @Override
+    public void updateTabUnreadCounts() {
+      callLogQueryHandler.fetchMissedCallsUnreadCount();
+      callLogQueryHandler.fetchVoicemailUnreadCount();
+    }
+
+    @Override
+    public void showMultiSelectRemoveView(boolean show) {
+      // TODO(a bug): handle multiselect mode
+    }
+
+    @Override
+    public void onVoicemailStatusFetched(Cursor statusCursor) {
+      // TODO(calderwoodra): handle this when voicemail is implemented
+    }
+
+    @Override
+    public void onVoicemailUnreadCountFetched(Cursor cursor) {
+      if (activityIsAlive) {
+        bottomNavBar.setNotificationCount(TabIndex.VOICEMAIL, cursor.getCount());
+      }
+      cursor.close();
+    }
+
+    @Override
+    public void onMissedCallsUnreadCountFetched(Cursor cursor) {
+      if (activityIsAlive) {
+        bottomNavBar.setNotificationCount(TabIndex.CALL_LOG, cursor.getCount());
+      }
+      cursor.close();
+    }
+
+    @Override
+    public boolean onCallsFetched(Cursor combinedCursor) {
+      // Return false; did not take ownership of cursor
+      return false;
+    }
+
+    @Override
+    public void onSpeedDialSelected() {
+      setCurrentTab(TabIndex.SPEED_DIAL);
+    }
+
+    @Override
+    public void onCallLogSelected() {
+      setCurrentTab(TabIndex.CALL_LOG);
+    }
+
+    @Override
+    public void onContactsSelected() {
+      setCurrentTab(TabIndex.CONTACTS);
+    }
+
+    @Override
+    public void onVoicemailSelected() {
+      setCurrentTab(TabIndex.VOICEMAIL);
+    }
+
+    private void markMissedCallsAsReadAndRemoveNotification() {
+      callLogQueryHandler.markMissedCallsAsRead();
+      CallLogNotificationsService.cancelAllMissedCalls(context);
+    }
+
+    private void setCurrentTab(@TabIndex int tabIndex) {
+      if (currentTab == TabIndex.CALL_LOG && tabIndex != TabIndex.CALL_LOG) {
+        markMissedCallsAsReadAndRemoveNotification();
+      }
+      currentTab = tabIndex;
+      timeSelected = System.currentTimeMillis();
+    }
+
+    public void onActivityResume() {
+      activityIsAlive = true;
+      callLogQueryHandler.fetchVoicemailStatus();
+      callLogQueryHandler.fetchMissedCallsUnreadCount();
+      // Reset the tab on resume to restart the timer
+      setCurrentTab(bottomNavBar.getSelectedTab());
+    }
+
+    /** Should be called when {@link Activity#onStop()} is called. */
+    public void onActivityStop(boolean changingConfigurations, boolean keyguardLocked) {
+      activityIsAlive = false;
+      if (viewedCallLogTabPastTimeThreshold() && !changingConfigurations && !keyguardLocked) {
+        markMissedCallsAsReadAndRemoveNotification();
+      }
+    }
+
+    /**
+     * Returns true if the user has been (and still is) on the history tab for long than the
+     * threshold.
+     */
+    private boolean viewedCallLogTabPastTimeThreshold() {
+      return currentTab == TabIndex.CALL_LOG
+          && timeSelected != -1
+          && System.currentTimeMillis() - timeSelected > TimeUnit.SECONDS.toMillis(3);
+    }
+  }
+
+  /** @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 BottomNavBar bottomNavBar;
+    private final ImageView dragShadowOverlay;
+
+    // TODO(calderwoodra): Use this for drag and drop
+    @SuppressWarnings("unused")
+    private DragDropController dragDropController;
+
+    MainOldSpeedDialFragmentHostInterface(BottomNavBar bottomNavBar, ImageView dragShadowOverlay) {
+      this.bottomNavBar = bottomNavBar;
+      this.dragShadowOverlay = dragShadowOverlay;
+    }
+
+    @Override
+    public void setDragDropController(DragDropController dragDropController) {
+      this.dragDropController = dragDropController;
+    }
+
+    @Override
+    public void showAllContactsTab() {
+      bottomNavBar.selectTab(TabIndex.CONTACTS);
+    }
+
+    @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 static final class MainBottomNavBarBottomNavTabListener
+      implements OnBottomNavTabSelectedListener {
+
+    private static final String SPEED_DIAL_TAG = "speed_dial";
+    private static final String CALL_LOG_TAG = "call_log";
+    private static final String CONTACTS_TAG = "contacts";
+    private static final String VOICEMAIL_TAG = "voicemail";
+
+    private final FragmentManager fragmentManager;
+
+    private MainBottomNavBarBottomNavTabListener(FragmentManager fragmentManager) {
+      this.fragmentManager = fragmentManager;
+    }
+
+    @Override
+    public void onSpeedDialSelected() {
+      hideAllFragments();
+      Fragment fragment = fragmentManager.findFragmentByTag(SPEED_DIAL_TAG);
+      if (fragment == null) {
+        fragmentManager
+            .beginTransaction()
+            .add(R.id.fragment_container, new OldSpeedDialFragment(), SPEED_DIAL_TAG)
+            .commit();
+      } else {
+        fragmentManager.beginTransaction().show(fragment).commit();
+      }
+    }
+
+    @Override
+    public void onCallLogSelected() {
+      hideAllFragments();
+      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();
+      }
+    }
+
+    @Override
+    public void onContactsSelected() {
+      hideAllFragments();
+      ContactsFragment fragment =
+          (ContactsFragment) fragmentManager.findFragmentByTag(CONTACTS_TAG);
+      if (fragment == null) {
+        fragmentManager
+            .beginTransaction()
+            .add(
+                R.id.fragment_container,
+                ContactsFragment.newInstance(Header.ADD_CONTACT),
+                CONTACTS_TAG)
+            .commit();
+      } else {
+        fragmentManager.beginTransaction().show(fragment).commit();
+      }
+    }
+
+    @Override
+    public void onVoicemailSelected() {
+      hideAllFragments();
+      VisualVoicemailCallLogFragment fragment =
+          (VisualVoicemailCallLogFragment) fragmentManager.findFragmentByTag(VOICEMAIL_TAG);
+      if (fragment == null) {
+        fragmentManager
+            .beginTransaction()
+            .add(R.id.fragment_container, new VisualVoicemailCallLogFragment(), VOICEMAIL_TAG)
+            .commit();
+      } else {
+        fragmentManager.beginTransaction().show(fragment).commit();
+      }
+    }
+
+    private void hideAllFragments() {
+      android.app.FragmentTransaction transaction = fragmentManager.beginTransaction();
+      if (fragmentManager.findFragmentByTag(SPEED_DIAL_TAG) != null) {
+        transaction.hide(fragmentManager.findFragmentByTag(SPEED_DIAL_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));
+      }
+      if (fragmentManager.findFragmentByTag(VOICEMAIL_TAG) != null) {
+        // Old VisualVoicemailFragment
+        transaction.hide(fragmentManager.findFragmentByTag(VOICEMAIL_TAG));
+      }
+      transaction.commit();
+    }
+  }
+
+  private static final class LastTabController {
+
+    private final Context context;
+    private final BottomNavBar bottomNavBar;
+    private final boolean isEnabled;
+
+    LastTabController(Context context, BottomNavBar bottomNavBar) {
+      this.context = context;
+      this.bottomNavBar = bottomNavBar;
+      isEnabled = ConfigProviderBindings.get(context).getBoolean("last_tab_enabled", false);
+    }
+
+    /** Sets the last tab if the feature is enabled, otherwise defaults to speed dial. */
+    void selectLastTab() {
+      @TabIndex int tabIndex = TabIndex.SPEED_DIAL;
+      if (isEnabled) {
+        tabIndex =
+            StorageComponent.get(context)
+                .unencryptedSharedPrefs()
+                .getInt(KEY_LAST_TAB, TabIndex.SPEED_DIAL);
+      }
+      bottomNavBar.selectTab(tabIndex);
+    }
+
+    void onActivityStop() {
+      StorageComponent.get(context)
+          .unencryptedSharedPrefs()
+          .edit()
+          .putInt(KEY_LAST_TAB, bottomNavBar.getSelectedTab())
+          .apply();
+    }
+  }
+}