Internal cleanup for the new UI's bottom sheet.

Test: Existing tests
PiperOrigin-RevId: 187254014
Change-Id: I8a57b632d45e87ad075eb8bbb25180858e890f08
diff --git a/java/com/android/dialer/calllog/ui/menu/DeleteCallLogItemModule.java b/java/com/android/dialer/calllog/ui/menu/DeleteCallLogItemModule.java
index ac2e3b3..ad5671e 100644
--- a/java/com/android/dialer/calllog/ui/menu/DeleteCallLogItemModule.java
+++ b/java/com/android/dialer/calllog/ui/menu/DeleteCallLogItemModule.java
@@ -28,13 +28,13 @@
 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.contactactions.ContactActionModule;
+import com.android.dialer.historyitemactions.HistoryItemActionModule;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
 
-/** {@link ContactActionModule} for deleting a call log item in the new call log. */
-public final class DeleteCallLogItemModule implements ContactActionModule {
+/** {@link HistoryItemActionModule} for deleting a call log item in the new call log. */
+public final class DeleteCallLogItemModule implements HistoryItemActionModule {
   private static final String TAG = DeleteCallLogItemModule.class.getName();
 
   private final Context context;
diff --git a/java/com/android/dialer/calllog/ui/menu/Modules.java b/java/com/android/dialer/calllog/ui/menu/Modules.java
index d86c04d..1479727 100644
--- a/java/com/android/dialer/calllog/ui/menu/Modules.java
+++ b/java/com/android/dialer/calllog/ui/menu/Modules.java
@@ -25,36 +25,42 @@
 import com.android.dialer.calllog.model.CoalescedRow;
 import com.android.dialer.calllogutils.CallLogContactTypes;
 import com.android.dialer.calllogutils.PhoneNumberDisplayUtil;
-import com.android.dialer.contactactions.ContactActionModule;
-import com.android.dialer.contactactions.DividerModule;
-import com.android.dialer.contactactions.IntentModule;
-import com.android.dialer.contactactions.SharedModules;
 import com.android.dialer.dialercontact.DialerContact;
+import com.android.dialer.historyitemactions.DividerModule;
+import com.android.dialer.historyitemactions.HistoryItemActionModule;
+import com.android.dialer.historyitemactions.IntentModule;
+import com.android.dialer.historyitemactions.SharedModules;
 import com.android.dialer.logging.ReportingLocation;
 import com.android.dialer.phonenumberutil.PhoneNumberHelper;
 import com.android.dialer.telecom.TelecomUtil;
 import com.google.common.base.Optional;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /**
  * Configures the modules for the bottom sheet; these are the rows below the top row (primary
  * action) in the bottom sheet.
  */
+@SuppressWarnings("Guava")
 final class Modules {
 
-  static List<ContactActionModule> fromRow(Context context, CoalescedRow row) {
+  static List<HistoryItemActionModule> fromRow(Context context, CoalescedRow row) {
     // Conditionally add each module, which are items in the bottom sheet's menu.
-    List<ContactActionModule> modules = new ArrayList<>();
+    List<HistoryItemActionModule> modules = new ArrayList<>();
 
     String normalizedNumber = row.number().getNormalizedNumber();
     boolean canPlaceCalls =
         PhoneNumberHelper.canPlaceCallsTo(normalizedNumber, row.numberPresentation());
 
     if (canPlaceCalls) {
-      addModuleForCalls(context, modules, row, normalizedNumber);
-      SharedModules.maybeAddModuleForSendingTextMessage(
-          context, modules, normalizedNumber, row.numberAttributes().getIsBlocked());
+      modules.addAll(createModulesForCalls(context, row, normalizedNumber));
+      Optional<HistoryItemActionModule> moduleForSendingTextMessage =
+          SharedModules.createModuleForSendingTextMessage(
+              context, normalizedNumber, row.numberAttributes().getIsBlocked());
+      if (moduleForSendingTextMessage.isPresent()) {
+        modules.add(moduleForSendingTextMessage.get());
+      }
     }
 
     if (!modules.isEmpty()) {
@@ -65,45 +71,52 @@
     // TODO(zachh): Module for CallComposer.
 
     if (canPlaceCalls) {
-      SharedModules.maybeAddModuleForAddingToContacts(
-          context,
-          modules,
-          row.number(),
-          row.numberAttributes().getName(),
-          row.numberAttributes().getLookupUri(),
-          row.numberAttributes().getIsBlocked(),
-          row.numberAttributes().getIsSpam());
-      SharedModules.addModulesHandlingBlockedOrSpamNumber(
-          context,
-          modules,
-          row.number().getNormalizedNumber(),
-          row.number().getCountryIso(),
-          row.callType(),
-          row.numberAttributes().getIsBlocked(),
-          row.numberAttributes().getIsSpam(),
-          ReportingLocation.Type.CALL_LOG_HISTORY);
-      SharedModules.maybeAddModuleForCopyingNumber(context, modules, normalizedNumber);
+      Optional<HistoryItemActionModule> moduleForAddingToContacts =
+          SharedModules.createModuleForAddingToContacts(
+              context,
+              row.number(),
+              row.numberAttributes().getName(),
+              row.numberAttributes().getLookupUri(),
+              row.numberAttributes().getIsBlocked(),
+              row.numberAttributes().getIsSpam());
+      if (moduleForAddingToContacts.isPresent()) {
+        modules.add(moduleForAddingToContacts.get());
+      }
+
+      modules.addAll(
+          SharedModules.createModulesHandlingBlockedOrSpamNumber(
+              context,
+              row.number().getNormalizedNumber(),
+              row.number().getCountryIso(),
+              row.callType(),
+              row.numberAttributes().getIsBlocked(),
+              row.numberAttributes().getIsSpam(),
+              ReportingLocation.Type.CALL_LOG_HISTORY));
+
+      Optional<HistoryItemActionModule> moduleForCopyingNumber =
+          SharedModules.createModuleForCopyingNumber(context, normalizedNumber);
+      if (moduleForCopyingNumber.isPresent()) {
+        modules.add(moduleForCopyingNumber.get());
+      }
     }
 
     // TODO(zachh): Revisit if DialerContact is the best thing to pass to CallDetails; could
-    // it use a ContactPrimaryActionInfo instead?
-    addModuleForAccessingCallDetails(context, modules, row);
+    // it use a HistoryItemPrimaryActionInfo instead?
+    modules.add(createModuleForAccessingCallDetails(context, row));
 
     modules.add(new DeleteCallLogItemModule(context, row.coalescedIds()));
 
     return modules;
   }
 
-  private static void addModuleForCalls(
-      Context context,
-      List<ContactActionModule> modules,
-      CoalescedRow row,
-      String normalizedNumber) {
+  private static List<HistoryItemActionModule> createModulesForCalls(
+      Context context, CoalescedRow row, String normalizedNumber) {
     // Don't add call options if a number is blocked.
     if (row.numberAttributes().getIsBlocked()) {
-      return;
+      return Collections.emptyList();
     }
 
+    List<HistoryItemActionModule> modules = new ArrayList<>();
     PhoneAccountHandle phoneAccountHandle =
         TelecomUtil.composePhoneAccountHandle(
             row.phoneAccountComponentName(), row.phoneAccountId());
@@ -123,24 +136,25 @@
 
     // TODO(zachh): Also show video option if the call log entry is for an audio call but video
     // capabilities are present?
+
+    return modules;
   }
 
-  private static void addModuleForAccessingCallDetails(
-      Context context, List<ContactActionModule> modules, CoalescedRow row) {
+  private static HistoryItemActionModule createModuleForAccessingCallDetails(
+      Context context, CoalescedRow row) {
     boolean canReportAsInvalidNumber = row.numberAttributes().getCanReportAsInvalidNumber();
     boolean canSupportAssistedDialing = !TextUtils.isEmpty(row.numberAttributes().getLookupUri());
 
-    modules.add(
-        new IntentModule(
+    return new IntentModule(
+        context,
+        CallDetailsActivity.newInstance(
             context,
-            CallDetailsActivity.newInstance(
-                context,
-                row.coalescedIds(),
-                createDialerContactFromRow(context, row),
-                canReportAsInvalidNumber,
-                canSupportAssistedDialing),
-            R.string.call_details_menu_label,
-            R.drawable.quantum_ic_info_outline_vd_theme_24));
+            row.coalescedIds(),
+            createDialerContactFromRow(context, row),
+            canReportAsInvalidNumber,
+            canSupportAssistedDialing),
+        R.string.call_details_menu_label,
+        R.drawable.quantum_ic_info_outline_vd_theme_24);
   }
 
   private static DialerContact createDialerContactFromRow(Context context, CoalescedRow row) {
diff --git a/java/com/android/dialer/calllog/ui/menu/NewCallLogMenu.java b/java/com/android/dialer/calllog/ui/menu/NewCallLogMenu.java
index 02724e6..9efe907 100644
--- a/java/com/android/dialer/calllog/ui/menu/NewCallLogMenu.java
+++ b/java/com/android/dialer/calllog/ui/menu/NewCallLogMenu.java
@@ -22,8 +22,8 @@
 import com.android.dialer.calllog.CallLogComponent;
 import com.android.dialer.calllog.model.CoalescedRow;
 import com.android.dialer.common.concurrent.DefaultFutureCallback;
-import com.android.dialer.contactactions.ContactActionBottomSheet;
 import com.android.dialer.glidephotomanager.GlidePhotoManager;
+import com.android.dialer.historyitemactions.HistoryItemActionBottomSheet;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.MoreExecutors;
 
@@ -34,7 +34,7 @@
   public static View.OnClickListener createOnClickListener(
       Context context, CoalescedRow row, GlidePhotoManager glidePhotoManager) {
     return view -> {
-      ContactActionBottomSheet.show(
+      HistoryItemActionBottomSheet.show(
           context,
           PrimaryAction.fromRow(context, row),
           Modules.fromRow(context, row),
diff --git a/java/com/android/dialer/calllog/ui/menu/PrimaryAction.java b/java/com/android/dialer/calllog/ui/menu/PrimaryAction.java
index 92a8453..de4a70c 100644
--- a/java/com/android/dialer/calllog/ui/menu/PrimaryAction.java
+++ b/java/com/android/dialer/calllog/ui/menu/PrimaryAction.java
@@ -22,14 +22,14 @@
 import com.android.dialer.calllogutils.CallLogEntryText;
 import com.android.dialer.calllogutils.CallLogIntents;
 import com.android.dialer.calllogutils.NumberAttributesConverter;
-import com.android.dialer.contactactions.ContactPrimaryActionInfo;
+import com.android.dialer.historyitemactions.HistoryItemPrimaryActionInfo;
 
 /** Configures the primary action row (top row) for the bottom sheet. */
 final class PrimaryAction {
 
-  static ContactPrimaryActionInfo fromRow(Context context, CoalescedRow row) {
+  static HistoryItemPrimaryActionInfo fromRow(Context context, CoalescedRow row) {
     CharSequence primaryText = CallLogEntryText.buildPrimaryText(context, row);
-    return ContactPrimaryActionInfo.builder()
+    return HistoryItemPrimaryActionInfo.builder()
         .setNumber(row.number())
         .setPhotoInfo(
             NumberAttributesConverter.toPhotoInfoBuilder(row.numberAttributes())
diff --git a/java/com/android/dialer/contactactions/SharedModules.java b/java/com/android/dialer/contactactions/SharedModules.java
deleted file mode 100644
index 3007a59..0000000
--- a/java/com/android/dialer/contactactions/SharedModules.java
+++ /dev/null
@@ -1,288 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.dialer.contactactions;
-
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.provider.ContactsContract;
-import android.support.annotation.Nullable;
-import android.text.TextUtils;
-import android.widget.Toast;
-import com.android.dialer.DialerPhoneNumber;
-import com.android.dialer.blockreportspam.ShowBlockReportSpamDialogNotifier;
-import com.android.dialer.clipboard.ClipboardUtils;
-import com.android.dialer.logging.ReportingLocation;
-import com.android.dialer.util.IntentUtil;
-import com.android.dialer.util.UriUtils;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * Modules for the bottom sheet that are shared between NewVoicemailFragment and NewCallLogFragment
- */
-public class SharedModules {
-
-  public static void maybeAddModuleForAddingToContacts(
-      Context context,
-      List<ContactActionModule> modules,
-      DialerPhoneNumber dialerPhoneNumber,
-      String name,
-      String lookupUri,
-      boolean isBlocked,
-      boolean isSpam) {
-    // Skip showing the menu item for a spam/blocked number.
-    if (isBlocked || isSpam) {
-      return;
-    }
-
-    // Skip showing the menu item for existing contacts.
-    if (isExistingContact(lookupUri)) {
-      return;
-    }
-
-    // Skip showing the menu item if there is no number.
-    String normalizedNumber = dialerPhoneNumber.getNormalizedNumber();
-    if (TextUtils.isEmpty(normalizedNumber)) {
-      return;
-    }
-
-    Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
-    intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
-    intent.putExtra(ContactsContract.Intents.Insert.PHONE, normalizedNumber);
-
-    if (!TextUtils.isEmpty(name)) {
-      intent.putExtra(ContactsContract.Intents.Insert.NAME, name);
-    }
-    modules.add(
-        new IntentModule(
-            context,
-            intent,
-            R.string.add_to_contacts,
-            R.drawable.quantum_ic_person_add_vd_theme_24));
-  }
-
-  /**
-   * Lookup URIs are currently fetched from the cached column of the system call log. This URI
-   * contains encoded information for non-contacts for the purposes of populating contact cards.
-   *
-   * <p>We infer whether a contact is existing or not by checking if the lookup URI is "encoded" or
-   * not.
-   *
-   * <p>TODO(zachh): We should revisit this once the contact URI is no longer being read from the
-   * cached column in the system database, in case we decide not to overload the column.
-   */
-  public static boolean isExistingContact(@Nullable String lookupUri) {
-    return !TextUtils.isEmpty(lookupUri) && !UriUtils.isEncodedContactUri(Uri.parse(lookupUri));
-  }
-
-  public static void maybeAddModuleForSendingTextMessage(
-      Context context,
-      List<ContactActionModule> modules,
-      String normalizedNumber,
-      boolean isBlocked) {
-    // Don't show the option to send a text message if the number is blocked.
-    if (isBlocked) {
-      return;
-    }
-
-    // TODO(zachh): There are some conditions where this module should not be shown; consider
-    // voicemail, business numbers, etc.
-
-    if (!TextUtils.isEmpty(normalizedNumber)) {
-      modules.add(
-          new IntentModule(
-              context,
-              IntentUtil.getSendSmsIntent(normalizedNumber),
-              R.string.send_a_message,
-              R.drawable.quantum_ic_message_vd_theme_24));
-    }
-  }
-
-  /**
-   * Add modules related to blocking/unblocking a number and/or reporting it as spam/not spam.
-   *
-   * @param normalizedNumber The number to be blocked / unblocked / marked as spam/not spam
-   * @param countryIso The ISO 3166-1 two letters country code for the number
-   * @param callType Call type defined in {@link android.provider.CallLog.Calls}
-   * @param reportingLocation The location where the number is reported. See {@link
-   *     ReportingLocation.Type}.
-   */
-  public static void addModulesHandlingBlockedOrSpamNumber(
-      Context context,
-      List<ContactActionModule> modules,
-      String normalizedNumber,
-      String countryIso,
-      int callType,
-      boolean isBlocked,
-      boolean isSpam,
-      ReportingLocation.Type reportingLocation) {
-    // For a spam number, add two options:
-    // (1) "Not spam" and "Block", or
-    // (2) "Not spam" and "Unblock".
-    if (isSpam) {
-      addModuleForMarkingNumberAsNonSpam(
-          context, modules, normalizedNumber, countryIso, callType, reportingLocation);
-      addModuleForBlockingOrUnblockingNumber(context, modules, normalizedNumber, isBlocked);
-      return;
-    }
-
-    // For a blocked non-spam number, add "Unblock" option.
-    if (isBlocked) {
-      addModuleForBlockingOrUnblockingNumber(context, modules, normalizedNumber, isBlocked);
-      return;
-    }
-
-    // For a number that is neither a spam number nor blocked, add "Block/Report spam" option.
-    addModuleForBlockingNumberAndOptionallyReportingSpam(
-        context, modules, normalizedNumber, countryIso, callType, reportingLocation);
-  }
-
-  /**
-   * Add "Not spam" module.
-   *
-   * @param normalizedNumber The number to be marked as not spam
-   * @param countryIso The ISO 3166-1 two letters country code for the number
-   * @param callType Call type defined in {@link android.provider.CallLog.Calls}
-   * @param reportingLocation The location where the number is reported. See {@link
-   *     ReportingLocation.Type}.
-   */
-  private static void addModuleForMarkingNumberAsNonSpam(
-      Context context,
-      List<ContactActionModule> modules,
-      String normalizedNumber,
-      String countryIso,
-      int callType,
-      ReportingLocation.Type reportingLocation) {
-    modules.add(
-        new ContactActionModule() {
-          @Override
-          public int getStringId() {
-            return R.string.not_spam;
-          }
-
-          @Override
-          public int getDrawableId() {
-            return R.drawable.quantum_ic_report_off_vd_theme_24;
-          }
-
-          @Override
-          public boolean onClick() {
-            ShowBlockReportSpamDialogNotifier.notifyShowDialogToReportNotSpam(
-                context, normalizedNumber, countryIso, callType, reportingLocation);
-            return true; // Close the bottom sheet.
-          }
-        });
-  }
-
-  private static void addModuleForBlockingOrUnblockingNumber(
-      Context context,
-      List<ContactActionModule> modules,
-      String normalizedNumber,
-      boolean isBlocked) {
-    modules.add(
-        new ContactActionModule() {
-          @Override
-          public int getStringId() {
-            return isBlocked ? R.string.unblock_number : R.string.block_number;
-          }
-
-          @Override
-          public int getDrawableId() {
-            return isBlocked
-                ? R.drawable.ic_unblock // TODO(a bug): use a vector icon
-                : R.drawable.quantum_ic_block_vd_theme_24;
-          }
-
-          @Override
-          public boolean onClick() {
-            // TODO(a bug): implement this method.
-            Toast.makeText(
-                    context,
-                    String.format(
-                        Locale.ENGLISH,
-                        "TODO: " + (isBlocked ? "Unblock " : "Block ") + " number %s.",
-                        normalizedNumber),
-                    Toast.LENGTH_SHORT)
-                .show();
-            return true; // Close the bottom sheet.
-          }
-        });
-  }
-
-  /**
-   * Add "Block/Report spam" module
-   *
-   * @param normalizedNumber The number to be blocked / unblocked / marked as spam/not spam
-   * @param countryIso The ISO 3166-1 two letters country code for the number
-   * @param callType Call type defined in {@link android.provider.CallLog.Calls}
-   * @param reportingLocation The location where the number is reported. See {@link
-   *     ReportingLocation.Type}.
-   */
-  private static void addModuleForBlockingNumberAndOptionallyReportingSpam(
-      Context context,
-      List<ContactActionModule> modules,
-      String normalizedNumber,
-      String countryIso,
-      int callType,
-      ReportingLocation.Type reportingLocation) {
-    modules.add(
-        new ContactActionModule() {
-          @Override
-          public int getStringId() {
-            return R.string.block_and_optionally_report_spam;
-          }
-
-          @Override
-          public int getDrawableId() {
-            return R.drawable.quantum_ic_block_vd_theme_24;
-          }
-
-          @Override
-          public boolean onClick() {
-            ShowBlockReportSpamDialogNotifier.notifyShowDialogToBlockNumberAndOptionallyReportSpam(
-                context, normalizedNumber, countryIso, callType, reportingLocation);
-            return true; // Close the bottom sheet.
-          }
-        });
-  }
-
-  public static void maybeAddModuleForCopyingNumber(
-      Context context, List<ContactActionModule> modules, String normalizedNumber) {
-    if (TextUtils.isEmpty(normalizedNumber)) {
-      return;
-    }
-    modules.add(
-        new ContactActionModule() {
-          @Override
-          public int getStringId() {
-            return R.string.copy_number;
-          }
-
-          @Override
-          public int getDrawableId() {
-            return R.drawable.quantum_ic_content_copy_vd_theme_24;
-          }
-
-          @Override
-          public boolean onClick() {
-            ClipboardUtils.copyText(context, null, normalizedNumber, true);
-            return false;
-          }
-        });
-  }
-}
diff --git a/java/com/android/dialer/contactactions/AndroidManifest.xml b/java/com/android/dialer/historyitemactions/AndroidManifest.xml
similarity index 90%
rename from java/com/android/dialer/contactactions/AndroidManifest.xml
rename to java/com/android/dialer/historyitemactions/AndroidManifest.xml
index a64066e..4d30268 100644
--- a/java/com/android/dialer/contactactions/AndroidManifest.xml
+++ b/java/com/android/dialer/historyitemactions/AndroidManifest.xml
@@ -13,4 +13,4 @@
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License
  -->
-<manifest package="com.android.dialer.contactactions"/>
+<manifest package="com.android.dialer.historyitemactions"/>
diff --git a/java/com/android/dialer/contactactions/DividerModule.java b/java/com/android/dialer/historyitemactions/DividerModule.java
similarity index 79%
rename from java/com/android/dialer/contactactions/DividerModule.java
rename to java/com/android/dialer/historyitemactions/DividerModule.java
index aabd160..a5ca62a 100644
--- a/java/com/android/dialer/contactactions/DividerModule.java
+++ b/java/com/android/dialer/historyitemactions/DividerModule.java
@@ -14,15 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.dialer.contactactions;
+package com.android.dialer.historyitemactions;
 
 import com.android.dialer.common.Assert;
 
 /**
- * A module that inserts a grey line divider into {@link ContactActionModule}. Layout it provided in
- * R.layout.divider_layout.xml
+ * A module that inserts a grey line divider into {@link HistoryItemActionModule}. Layout it
+ * provided in R.layout.divider_layout.xml
  */
-public final class DividerModule implements ContactActionModule {
+public final class DividerModule implements HistoryItemActionModule {
 
   @Override
   public int getStringId() {
diff --git a/java/com/android/dialer/contactactions/ContactActionBottomSheet.java b/java/com/android/dialer/historyitemactions/HistoryItemActionBottomSheet.java
similarity index 64%
rename from java/com/android/dialer/contactactions/ContactActionBottomSheet.java
rename to java/com/android/dialer/historyitemactions/HistoryItemActionBottomSheet.java
index 98a5dd1..47aa100 100644
--- a/java/com/android/dialer/contactactions/ContactActionBottomSheet.java
+++ b/java/com/android/dialer/historyitemactions/HistoryItemActionBottomSheet.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.dialer.contactactions;
+package com.android.dialer.historyitemactions;
 
 import android.content.Context;
 import android.os.Bundle;
@@ -32,37 +32,38 @@
 import java.util.List;
 
 /**
- * {@link BottomSheetDialog} used for building a list of contact actions in a bottom sheet menu.
+ * {@link BottomSheetDialog} used to show a list of actions in a bottom sheet menu.
  *
- * <p>{@link #show(Context, ContactPrimaryActionInfo, List)} should be used to create and display
- * the menu. Modules are built using {@link ContactActionModule} and some defaults are provided by
- * {@link IntentModule} and {@link DividerModule}.
+ * <p>{@link #show(Context, HistoryItemPrimaryActionInfo, List, GlidePhotoManager)} should be used
+ * to create and display the menu. Modules are built using {@link HistoryItemActionModule} and some
+ * defaults are provided by {@link IntentModule} and {@link DividerModule}.
  */
-public class ContactActionBottomSheet extends BottomSheetDialog implements OnClickListener {
+public class HistoryItemActionBottomSheet extends BottomSheetDialog implements OnClickListener {
 
-  private final List<ContactActionModule> modules;
-  private final ContactPrimaryActionInfo contactPrimaryActionInfo;
+  private final List<HistoryItemActionModule> modules;
+  private final HistoryItemPrimaryActionInfo historyItemPrimaryActionInfo;
   private final GlidePhotoManager glidePhotoManager;
 
-  private ContactActionBottomSheet(
+  private HistoryItemActionBottomSheet(
       Context context,
-      ContactPrimaryActionInfo contactPrimaryActionInfo,
-      List<ContactActionModule> modules,
+      HistoryItemPrimaryActionInfo historyItemPrimaryActionInfo,
+      List<HistoryItemActionModule> modules,
       GlidePhotoManager glidePhotoManager) {
     super(context);
     this.modules = modules;
-    this.contactPrimaryActionInfo = contactPrimaryActionInfo;
+    this.historyItemPrimaryActionInfo = historyItemPrimaryActionInfo;
     this.glidePhotoManager = glidePhotoManager;
     setContentView(LayoutInflater.from(context).inflate(R.layout.sheet_layout, null));
   }
 
-  public static ContactActionBottomSheet show(
+  public static HistoryItemActionBottomSheet show(
       Context context,
-      ContactPrimaryActionInfo contactPrimaryActionInfo,
-      List<ContactActionModule> modules,
+      HistoryItemPrimaryActionInfo historyItemPrimaryActionInfo,
+      List<HistoryItemActionModule> modules,
       GlidePhotoManager glidePhotoManager) {
-    ContactActionBottomSheet sheet =
-        new ContactActionBottomSheet(context, contactPrimaryActionInfo, modules, glidePhotoManager);
+    HistoryItemActionBottomSheet sheet =
+        new HistoryItemActionBottomSheet(
+            context, historyItemPrimaryActionInfo, modules, glidePhotoManager);
     sheet.show();
     return sheet;
   }
@@ -73,7 +74,7 @@
     LinearLayout container = Assert.isNotNull(findViewById(R.id.action_container));
     container.addView(getContactView(container));
 
-    for (ContactActionModule module : modules) {
+    for (HistoryItemActionModule module : modules) {
       if (module instanceof DividerModule) {
         container.addView(getDividerView(container));
       } else {
@@ -88,22 +89,23 @@
 
     // TODO(zachh): The contact image should be badged with a video icon if it is for a video call.
     glidePhotoManager.loadQuickContactBadge(
-        contactView.findViewById(R.id.quick_contact_photo), contactPrimaryActionInfo.photoInfo());
+        contactView.findViewById(R.id.quick_contact_photo),
+        historyItemPrimaryActionInfo.photoInfo());
 
     TextView primaryTextView = contactView.findViewById(R.id.primary_text);
     TextView secondaryTextView = contactView.findViewById(R.id.secondary_text);
 
-    primaryTextView.setText(contactPrimaryActionInfo.primaryText());
-    if (!TextUtils.isEmpty(contactPrimaryActionInfo.secondaryText())) {
-      secondaryTextView.setText(contactPrimaryActionInfo.secondaryText());
+    primaryTextView.setText(historyItemPrimaryActionInfo.primaryText());
+    if (!TextUtils.isEmpty(historyItemPrimaryActionInfo.secondaryText())) {
+      secondaryTextView.setText(historyItemPrimaryActionInfo.secondaryText());
     } else {
       secondaryTextView.setVisibility(View.GONE);
       secondaryTextView.setText(null);
     }
-    if (contactPrimaryActionInfo.intent() != null) {
+    if (historyItemPrimaryActionInfo.intent() != null) {
       contactView.setOnClickListener(
           (view) -> {
-            getContext().startActivity(contactPrimaryActionInfo.intent());
+            getContext().startActivity(historyItemPrimaryActionInfo.intent());
             dismiss();
           });
     }
@@ -115,7 +117,7 @@
     return inflater.inflate(R.layout.divider_layout, container, false);
   }
 
-  private View getModuleView(ViewGroup container, ContactActionModule module) {
+  private View getModuleView(ViewGroup container, HistoryItemActionModule module) {
     LayoutInflater inflater = LayoutInflater.from(getContext());
     View moduleView = inflater.inflate(R.layout.module_layout, container, false);
     ((TextView) moduleView.findViewById(R.id.module_text)).setText(module.getStringId());
@@ -128,7 +130,7 @@
 
   @Override
   public void onClick(View view) {
-    if (((ContactActionModule) view.getTag()).onClick()) {
+    if (((HistoryItemActionModule) view.getTag()).onClick()) {
       dismiss();
     }
   }
diff --git a/java/com/android/dialer/contactactions/ContactActionModule.java b/java/com/android/dialer/historyitemactions/HistoryItemActionModule.java
similarity index 72%
rename from java/com/android/dialer/contactactions/ContactActionModule.java
rename to java/com/android/dialer/historyitemactions/HistoryItemActionModule.java
index 8089edc..d64cbca 100644
--- a/java/com/android/dialer/contactactions/ContactActionModule.java
+++ b/java/com/android/dialer/historyitemactions/HistoryItemActionModule.java
@@ -14,18 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.dialer.contactactions;
+package com.android.dialer.historyitemactions;
 
 import android.support.annotation.DrawableRes;
 import android.support.annotation.StringRes;
 
 /**
- * Modules used to build {@link ContactActionBottomSheet}.
+ * Modules used to build {@link HistoryItemActionBottomSheet}.
  *
- * <p>Contacts as they relate to this class should be thought of as any entity that an action can be
- * performed on like unknown/restricted contacts, along with saved and non-saved contacts.
+ * <p>A history item is one that is displayed in the call log or the voicemail fragment.
  */
-public interface ContactActionModule {
+public interface HistoryItemActionModule {
 
   @StringRes
   int getStringId();
diff --git a/java/com/android/dialer/contactactions/ContactPrimaryActionInfo.java b/java/com/android/dialer/historyitemactions/HistoryItemPrimaryActionInfo.java
similarity index 82%
rename from java/com/android/dialer/contactactions/ContactPrimaryActionInfo.java
rename to java/com/android/dialer/historyitemactions/HistoryItemPrimaryActionInfo.java
index 5017d83..f4338ce 100644
--- a/java/com/android/dialer/contactactions/ContactPrimaryActionInfo.java
+++ b/java/com/android/dialer/historyitemactions/HistoryItemPrimaryActionInfo.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.dialer.contactactions;
+package com.android.dialer.historyitemactions;
 
 import android.content.Intent;
 import android.support.annotation.NonNull;
@@ -23,13 +23,12 @@
 import com.google.auto.value.AutoValue;
 
 /**
- * Contains information necessary to construct the primary action for a contact bottom sheet.
+ * Contains information necessary to construct the primary action for a history item's bottom sheet.
  *
- * <p>This may include information about the call, for instance when the bottom sheet is shown from
- * the call log.
+ * <p>A history item is one that is displayed in the call log or the voicemail fragment.
  */
 @AutoValue
-public abstract class ContactPrimaryActionInfo {
+public abstract class HistoryItemPrimaryActionInfo {
 
   @Nullable
   public abstract DialerPhoneNumber number();
@@ -52,7 +51,7 @@
 
   // TODO(zachh): Add SIM info here if should be shown in bottom sheet.
 
-  /** Builder for {@link ContactPrimaryActionInfo}. */
+  /** Builder for {@link HistoryItemPrimaryActionInfo}. */
   @AutoValue.Builder
   public abstract static class Builder {
     public abstract Builder setNumber(@Nullable DialerPhoneNumber dialerPhoneNumber);
@@ -65,10 +64,10 @@
 
     public abstract Builder setIntent(@Nullable Intent intent);
 
-    public abstract ContactPrimaryActionInfo build();
+    public abstract HistoryItemPrimaryActionInfo build();
   }
 
   public static Builder builder() {
-    return new AutoValue_ContactPrimaryActionInfo.Builder();
+    return new AutoValue_HistoryItemPrimaryActionInfo.Builder();
   }
 }
diff --git a/java/com/android/dialer/contactactions/IntentModule.java b/java/com/android/dialer/historyitemactions/IntentModule.java
similarity index 92%
rename from java/com/android/dialer/contactactions/IntentModule.java
rename to java/com/android/dialer/historyitemactions/IntentModule.java
index 9a345c6..dfe9bb0 100644
--- a/java/com/android/dialer/contactactions/IntentModule.java
+++ b/java/com/android/dialer/historyitemactions/IntentModule.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.dialer.contactactions;
+package com.android.dialer.historyitemactions;
 
 import android.content.Context;
 import android.content.Intent;
@@ -28,9 +28,10 @@
 import com.android.dialer.util.DialerUtils;
 
 /**
- * {@link ContactActionModule} useful for making easy to build modules based on starting an intent.
+ * {@link HistoryItemActionModule} useful for making easy to build modules based on starting an
+ * intent.
  */
-public class IntentModule implements ContactActionModule {
+public class IntentModule implements HistoryItemActionModule {
 
   private final Context context;
   private final Intent intent;
diff --git a/java/com/android/dialer/historyitemactions/SharedModules.java b/java/com/android/dialer/historyitemactions/SharedModules.java
new file mode 100644
index 0000000..7cfee37
--- /dev/null
+++ b/java/com/android/dialer/historyitemactions/SharedModules.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.dialer.historyitemactions;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import android.widget.Toast;
+import com.android.dialer.DialerPhoneNumber;
+import com.android.dialer.blockreportspam.ShowBlockReportSpamDialogNotifier;
+import com.android.dialer.clipboard.ClipboardUtils;
+import com.android.dialer.logging.ReportingLocation;
+import com.android.dialer.util.IntentUtil;
+import com.android.dialer.util.UriUtils;
+import com.google.common.base.Optional;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Modules for the bottom sheet that are shared between NewVoicemailFragment and NewCallLogFragment
+ */
+@SuppressWarnings("Guava")
+public class SharedModules {
+
+  public static Optional<HistoryItemActionModule> createModuleForAddingToContacts(
+      Context context,
+      DialerPhoneNumber dialerPhoneNumber,
+      String name,
+      String lookupUri,
+      boolean isBlocked,
+      boolean isSpam) {
+    // Skip showing the menu item for a spam/blocked number.
+    if (isBlocked || isSpam) {
+      return Optional.absent();
+    }
+
+    // Skip showing the menu item for existing contacts.
+    if (isExistingContact(lookupUri)) {
+      return Optional.absent();
+    }
+
+    // Skip showing the menu item if there is no number.
+    String normalizedNumber = dialerPhoneNumber.getNormalizedNumber();
+    if (TextUtils.isEmpty(normalizedNumber)) {
+      return Optional.absent();
+    }
+
+    Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
+    intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
+    intent.putExtra(ContactsContract.Intents.Insert.PHONE, normalizedNumber);
+
+    if (!TextUtils.isEmpty(name)) {
+      intent.putExtra(ContactsContract.Intents.Insert.NAME, name);
+    }
+
+    return Optional.of(
+        new IntentModule(
+            context,
+            intent,
+            R.string.add_to_contacts,
+            R.drawable.quantum_ic_person_add_vd_theme_24));
+  }
+
+  /**
+   * Lookup URIs are currently fetched from the cached column of the system call log. This URI
+   * contains encoded information for non-contacts for the purposes of populating contact cards.
+   *
+   * <p>We infer whether a contact is existing or not by checking if the lookup URI is "encoded" or
+   * not.
+   *
+   * <p>TODO(zachh): We should revisit this once the contact URI is no longer being read from the
+   * cached column in the system database, in case we decide not to overload the column.
+   */
+  private static boolean isExistingContact(@Nullable String lookupUri) {
+    return !TextUtils.isEmpty(lookupUri) && !UriUtils.isEncodedContactUri(Uri.parse(lookupUri));
+  }
+
+  public static Optional<HistoryItemActionModule> createModuleForSendingTextMessage(
+      Context context, String normalizedNumber, boolean isBlocked) {
+    // Don't show the option to send a text message if the number is blocked.
+    if (isBlocked) {
+      return Optional.absent();
+    }
+
+    // TODO(zachh): There are some conditions where this module should not be shown; consider
+    // voicemail, business numbers, etc.
+
+    return !TextUtils.isEmpty(normalizedNumber)
+        ? Optional.of(
+            new IntentModule(
+                context,
+                IntentUtil.getSendSmsIntent(normalizedNumber),
+                R.string.send_a_message,
+                R.drawable.quantum_ic_message_vd_theme_24))
+        : Optional.absent();
+  }
+
+  /**
+   * Add modules related to blocking/unblocking a number and/or reporting it as spam/not spam.
+   *
+   * @param normalizedNumber The number to be blocked / unblocked / marked as spam/not spam
+   * @param countryIso The ISO 3166-1 two letters country code for the number
+   * @param callType Call type defined in {@link android.provider.CallLog.Calls}
+   * @param reportingLocation The location where the number is reported. See {@link
+   *     ReportingLocation.Type}.
+   */
+  public static List<HistoryItemActionModule> createModulesHandlingBlockedOrSpamNumber(
+      Context context,
+      String normalizedNumber,
+      String countryIso,
+      int callType,
+      boolean isBlocked,
+      boolean isSpam,
+      ReportingLocation.Type reportingLocation) {
+    List<HistoryItemActionModule> modules = new ArrayList<>();
+
+    // For a spam number, add two options:
+    // (1) "Not spam" and "Block", or
+    // (2) "Not spam" and "Unblock".
+    if (isSpam) {
+      modules.add(
+          createModuleForMarkingNumberAsNonSpam(
+              context, normalizedNumber, countryIso, callType, reportingLocation));
+      modules.add(createModuleForBlockingOrUnblockingNumber(context, normalizedNumber, isBlocked));
+      return modules;
+    }
+
+    // For a blocked non-spam number, add "Unblock" option.
+    if (isBlocked) {
+      modules.add(createModuleForBlockingOrUnblockingNumber(context, normalizedNumber, isBlocked));
+      return modules;
+    }
+
+    // For a number that is neither a spam number nor blocked, add "Block/Report spam" option.
+    modules.add(
+        createModuleForBlockingNumberAndOptionallyReportingSpam(
+            context, normalizedNumber, countryIso, callType, reportingLocation));
+    return modules;
+  }
+
+  /**
+   * Add "Not spam" module.
+   *
+   * @param normalizedNumber The number to be marked as not spam
+   * @param countryIso The ISO 3166-1 two letters country code for the number
+   * @param callType Call type defined in {@link android.provider.CallLog.Calls}
+   * @param reportingLocation The location where the number is reported. See {@link
+   *     ReportingLocation.Type}.
+   */
+  private static HistoryItemActionModule createModuleForMarkingNumberAsNonSpam(
+      Context context,
+      String normalizedNumber,
+      String countryIso,
+      int callType,
+      ReportingLocation.Type reportingLocation) {
+    return new HistoryItemActionModule() {
+      @Override
+      public int getStringId() {
+        return R.string.not_spam;
+      }
+
+      @Override
+      public int getDrawableId() {
+        return R.drawable.quantum_ic_report_off_vd_theme_24;
+      }
+
+      @Override
+      public boolean onClick() {
+        ShowBlockReportSpamDialogNotifier.notifyShowDialogToReportNotSpam(
+            context, normalizedNumber, countryIso, callType, reportingLocation);
+        return true; // Close the bottom sheet.
+      }
+    };
+  }
+
+  private static HistoryItemActionModule createModuleForBlockingOrUnblockingNumber(
+      Context context, String normalizedNumber, boolean isBlocked) {
+    return new HistoryItemActionModule() {
+      @Override
+      public int getStringId() {
+        return isBlocked ? R.string.unblock_number : R.string.block_number;
+      }
+
+      @Override
+      public int getDrawableId() {
+        return isBlocked
+            ? R.drawable.ic_unblock // TODO(a bug): use a vector icon
+            : R.drawable.quantum_ic_block_vd_theme_24;
+      }
+
+      @Override
+      public boolean onClick() {
+        // TODO(a bug): implement this method.
+        Toast.makeText(
+                context,
+                String.format(
+                    Locale.ENGLISH,
+                    "TODO: " + (isBlocked ? "Unblock " : "Block ") + " number %s.",
+                    normalizedNumber),
+                Toast.LENGTH_SHORT)
+            .show();
+        return true; // Close the bottom sheet.
+      }
+    };
+  }
+
+  /**
+   * Add "Block/Report spam" module
+   *
+   * @param normalizedNumber The number to be blocked / unblocked / marked as spam/not spam
+   * @param countryIso The ISO 3166-1 two letters country code for the number
+   * @param callType Call type defined in {@link android.provider.CallLog.Calls}
+   * @param reportingLocation The location where the number is reported. See {@link
+   *     ReportingLocation.Type}.
+   */
+  private static HistoryItemActionModule createModuleForBlockingNumberAndOptionallyReportingSpam(
+      Context context,
+      String normalizedNumber,
+      String countryIso,
+      int callType,
+      ReportingLocation.Type reportingLocation) {
+    return new HistoryItemActionModule() {
+      @Override
+      public int getStringId() {
+        return R.string.block_and_optionally_report_spam;
+      }
+
+      @Override
+      public int getDrawableId() {
+        return R.drawable.quantum_ic_block_vd_theme_24;
+      }
+
+      @Override
+      public boolean onClick() {
+        ShowBlockReportSpamDialogNotifier.notifyShowDialogToBlockNumberAndOptionallyReportSpam(
+            context, normalizedNumber, countryIso, callType, reportingLocation);
+        return true; // Close the bottom sheet.
+      }
+    };
+  }
+
+  public static Optional<HistoryItemActionModule> createModuleForCopyingNumber(
+      Context context, String normalizedNumber) {
+    if (TextUtils.isEmpty(normalizedNumber)) {
+      return Optional.absent();
+    }
+    return Optional.of(
+        new HistoryItemActionModule() {
+          @Override
+          public int getStringId() {
+            return R.string.copy_number;
+          }
+
+          @Override
+          public int getDrawableId() {
+            return R.drawable.quantum_ic_content_copy_vd_theme_24;
+          }
+
+          @Override
+          public boolean onClick() {
+            ClipboardUtils.copyText(context, null, normalizedNumber, true);
+            return false;
+          }
+        });
+  }
+}
diff --git a/java/com/android/dialer/contactactions/res/drawable-xxxhdpi/ic_unblock.png b/java/com/android/dialer/historyitemactions/res/drawable-xxxhdpi/ic_unblock.png
similarity index 100%
rename from java/com/android/dialer/contactactions/res/drawable-xxxhdpi/ic_unblock.png
rename to java/com/android/dialer/historyitemactions/res/drawable-xxxhdpi/ic_unblock.png
Binary files differ
diff --git a/java/com/android/dialer/contactactions/res/layout/contact_layout.xml b/java/com/android/dialer/historyitemactions/res/layout/contact_layout.xml
similarity index 100%
rename from java/com/android/dialer/contactactions/res/layout/contact_layout.xml
rename to java/com/android/dialer/historyitemactions/res/layout/contact_layout.xml
diff --git a/java/com/android/dialer/contactactions/res/layout/divider_layout.xml b/java/com/android/dialer/historyitemactions/res/layout/divider_layout.xml
similarity index 100%
rename from java/com/android/dialer/contactactions/res/layout/divider_layout.xml
rename to java/com/android/dialer/historyitemactions/res/layout/divider_layout.xml
diff --git a/java/com/android/dialer/contactactions/res/layout/module_layout.xml b/java/com/android/dialer/historyitemactions/res/layout/module_layout.xml
similarity index 100%
rename from java/com/android/dialer/contactactions/res/layout/module_layout.xml
rename to java/com/android/dialer/historyitemactions/res/layout/module_layout.xml
diff --git a/java/com/android/dialer/contactactions/res/layout/sheet_layout.xml b/java/com/android/dialer/historyitemactions/res/layout/sheet_layout.xml
similarity index 100%
rename from java/com/android/dialer/contactactions/res/layout/sheet_layout.xml
rename to java/com/android/dialer/historyitemactions/res/layout/sheet_layout.xml
diff --git a/java/com/android/dialer/contactactions/res/values/dimens.xml b/java/com/android/dialer/historyitemactions/res/values/dimens.xml
similarity index 100%
rename from java/com/android/dialer/contactactions/res/values/dimens.xml
rename to java/com/android/dialer/historyitemactions/res/values/dimens.xml
diff --git a/java/com/android/dialer/contactactions/res/values/strings.xml b/java/com/android/dialer/historyitemactions/res/values/strings.xml
similarity index 100%
rename from java/com/android/dialer/contactactions/res/values/strings.xml
rename to java/com/android/dialer/historyitemactions/res/values/strings.xml
diff --git a/java/com/android/dialer/voicemail/listui/menu/Modules.java b/java/com/android/dialer/voicemail/listui/menu/Modules.java
index 76ce172..9473b80 100644
--- a/java/com/android/dialer/voicemail/listui/menu/Modules.java
+++ b/java/com/android/dialer/voicemail/listui/menu/Modules.java
@@ -17,11 +17,12 @@
 package com.android.dialer.voicemail.listui.menu;
 
 import android.content.Context;
-import com.android.dialer.contactactions.ContactActionModule;
-import com.android.dialer.contactactions.DividerModule;
-import com.android.dialer.contactactions.SharedModules;
+import com.android.dialer.historyitemactions.DividerModule;
+import com.android.dialer.historyitemactions.HistoryItemActionModule;
+import com.android.dialer.historyitemactions.SharedModules;
 import com.android.dialer.logging.ReportingLocation;
 import com.android.dialer.voicemail.model.VoicemailEntry;
+import com.google.common.base.Optional;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -29,43 +30,57 @@
  * Configures the modules for the voicemail bottom sheet; these are the rows below the top row
  * (primary action) in the bottom sheet.
  */
+@SuppressWarnings("Guava")
 final class Modules {
 
-  static List<ContactActionModule> fromVoicemailEntry(
+  static List<HistoryItemActionModule> fromVoicemailEntry(
       Context context, VoicemailEntry voicemailEntry) {
     // Conditionally add each module, which are items in the bottom sheet's menu.
-    List<ContactActionModule> modules = new ArrayList<>();
+    List<HistoryItemActionModule> modules = new ArrayList<>();
 
     // TODO(uabdullah): Handle maybeAddModuleForVideoOrAudioCall(context, modules, row);
-    SharedModules.maybeAddModuleForAddingToContacts(
-        context,
-        modules,
-        voicemailEntry.number(),
-        voicemailEntry.numberAttributes().getName(),
-        voicemailEntry.numberAttributes().getLookupUri(),
-        voicemailEntry.numberAttributes().getIsBlocked(),
-        voicemailEntry.numberAttributes().getIsSpam());
+    Optional<HistoryItemActionModule> moduleForAddingContacts =
+        SharedModules.createModuleForAddingToContacts(
+            context,
+            voicemailEntry.number(),
+            voicemailEntry.numberAttributes().getName(),
+            voicemailEntry.numberAttributes().getLookupUri(),
+            voicemailEntry.numberAttributes().getIsBlocked(),
+            voicemailEntry.numberAttributes().getIsSpam());
+    if (moduleForAddingContacts.isPresent()) {
+      modules.add(moduleForAddingContacts.get());
+    }
 
-    String normalizedNumber = voicemailEntry.number().getNormalizedNumber();
-    SharedModules.maybeAddModuleForSendingTextMessage(
-        context, modules, normalizedNumber, voicemailEntry.numberAttributes().getIsBlocked());
+    Optional<HistoryItemActionModule> moduleForSendingTextMessage =
+        SharedModules.createModuleForSendingTextMessage(
+            context,
+            voicemailEntry.number().getNormalizedNumber(),
+            voicemailEntry.numberAttributes().getIsBlocked());
+    if (moduleForSendingTextMessage.isPresent()) {
+      modules.add(moduleForSendingTextMessage.get());
+    }
 
     if (!modules.isEmpty()) {
       modules.add(new DividerModule());
     }
 
-    SharedModules.addModulesHandlingBlockedOrSpamNumber(
-        context,
-        modules,
-        voicemailEntry.number().getNormalizedNumber(),
-        voicemailEntry.number().getCountryIso(),
-        voicemailEntry.callType(),
-        voicemailEntry.numberAttributes().getIsBlocked(),
-        voicemailEntry.numberAttributes().getIsSpam(),
-        ReportingLocation.Type.VOICEMAIL_HISTORY);
+    modules.addAll(
+        SharedModules.createModulesHandlingBlockedOrSpamNumber(
+            context,
+            voicemailEntry.number().getNormalizedNumber(),
+            voicemailEntry.number().getCountryIso(),
+            voicemailEntry.callType(),
+            voicemailEntry.numberAttributes().getIsBlocked(),
+            voicemailEntry.numberAttributes().getIsSpam(),
+            ReportingLocation.Type.VOICEMAIL_HISTORY));
 
     // TODO(zachh): Module for CallComposer.
-    SharedModules.maybeAddModuleForCopyingNumber(context, modules, normalizedNumber);
+    Optional<HistoryItemActionModule> moduleForCopyingNumber =
+        SharedModules.createModuleForCopyingNumber(
+            context, voicemailEntry.number().getNormalizedNumber());
+    if (moduleForCopyingNumber.isPresent()) {
+      modules.add(moduleForCopyingNumber.get());
+    }
 
     return modules;
   }
diff --git a/java/com/android/dialer/voicemail/listui/menu/NewVoicemailMenu.java b/java/com/android/dialer/voicemail/listui/menu/NewVoicemailMenu.java
index fbd7fe8..7c669e1 100644
--- a/java/com/android/dialer/voicemail/listui/menu/NewVoicemailMenu.java
+++ b/java/com/android/dialer/voicemail/listui/menu/NewVoicemailMenu.java
@@ -18,8 +18,8 @@
 
 import android.content.Context;
 import android.view.View;
-import com.android.dialer.contactactions.ContactActionBottomSheet;
 import com.android.dialer.glidephotomanager.GlidePhotoManager;
+import com.android.dialer.historyitemactions.HistoryItemActionBottomSheet;
 import com.android.dialer.voicemail.model.VoicemailEntry;
 
 /** Handles configuration of the bottom sheet menus for voicemail entries. */
@@ -29,7 +29,7 @@
   public static View.OnClickListener createOnClickListener(
       Context context, VoicemailEntry voicemailEntry, GlidePhotoManager glidePhotoManager) {
     return (view) ->
-        ContactActionBottomSheet.show(
+        HistoryItemActionBottomSheet.show(
             context,
             PrimaryAction.fromVoicemailEntry(context, voicemailEntry),
             Modules.fromVoicemailEntry(context, voicemailEntry),
diff --git a/java/com/android/dialer/voicemail/listui/menu/PrimaryAction.java b/java/com/android/dialer/voicemail/listui/menu/PrimaryAction.java
index 91f505c..9a5aa18 100644
--- a/java/com/android/dialer/voicemail/listui/menu/PrimaryAction.java
+++ b/java/com/android/dialer/voicemail/listui/menu/PrimaryAction.java
@@ -19,7 +19,7 @@
 import android.content.Context;
 import android.text.TextUtils;
 import com.android.dialer.calllogutils.NumberAttributesConverter;
-import com.android.dialer.contactactions.ContactPrimaryActionInfo;
+import com.android.dialer.historyitemactions.HistoryItemPrimaryActionInfo;
 import com.android.dialer.voicemail.model.VoicemailEntry;
 
 /** Configures the primary action row (top row) for theottom sheet for the Voicemail Tab */
@@ -31,9 +31,9 @@
   // setIntent - allow video calling
   // setPrimaryText - check in with UX
   // setSecondaryText - check in with UX
-  static ContactPrimaryActionInfo fromVoicemailEntry(
+  static HistoryItemPrimaryActionInfo fromVoicemailEntry(
       Context context, VoicemailEntry voicemailEntry) {
-    return ContactPrimaryActionInfo.builder()
+    return HistoryItemPrimaryActionInfo.builder()
         .setNumber(voicemailEntry.number())
         .setPhotoInfo(
             NumberAttributesConverter.toPhotoInfoBuilder(voicemailEntry.numberAttributes())
diff --git a/packages.mk b/packages.mk
index abe9eb5..187f969 100644
--- a/packages.mk
+++ b/packages.mk
@@ -27,7 +27,6 @@
 	com.android.dialer.commandline \
 	com.android.dialer.common \
 	com.android.dialer.configprovider \
-	com.android.dialer.contactactions \
 	com.android.dialer.contactphoto \
 	com.android.dialer.contactsfragment \
 	com.android.dialer.databasepopulator \
@@ -35,6 +34,7 @@
 	com.android.dialer.enrichedcall.simulator \
 	com.android.dialer.feedback \
 	com.android.dialer.glidephotomanager.impl \
+  com.android.dialer.historyitemactions \
 	com.android.dialer.interactions \
 	com.android.dialer.lettertile \
 	com.android.dialer.location \