Adding support for expandable call log entries.

Bug: 13962594
Change-Id: I19a4a65dce922619df0a709293ca291c345c8be7
diff --git a/src/com/android/dialer/PhoneCallDetailsHelper.java b/src/com/android/dialer/PhoneCallDetailsHelper.java
index 4424fcb..edd0831 100644
--- a/src/com/android/dialer/PhoneCallDetailsHelper.java
+++ b/src/com/android/dialer/PhoneCallDetailsHelper.java
@@ -18,9 +18,7 @@
 
 import android.content.res.Resources;
 import android.graphics.Typeface;
-import android.provider.ContactsContract;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.telephony.PhoneNumberUtils;
 import android.text.SpannableString;
 import android.text.Spanned;
 import android.text.TextUtils;
@@ -36,6 +34,10 @@
 import com.android.dialer.calllog.ContactInfo;
 import com.android.dialer.calllog.PhoneNumberDisplayHelper;
 import com.android.dialer.calllog.PhoneNumberUtilsWrapper;
+import com.google.android.collect.Lists;
+
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Helper class to fill in the views in {@link PhoneCallDetailsViews}.
@@ -53,6 +55,11 @@
     private final PhoneNumberUtilsWrapper mPhoneNumberUtilsWrapper;
 
     /**
+     * List of items to be concatenated together for accessibility descriptions
+     */
+    private ArrayList<CharSequence> mDescriptionItems = Lists.newArrayList();
+
+    /**
      * Creates a new instance of the helper.
      * <p>
      * Generally you should have a single instance of this helper in any context.
@@ -90,42 +97,54 @@
         Integer highlightColor =
                 isHighlighted ? mCallTypeHelper.getHighlightedColor(details.callTypes[0]) : null;
 
-        // The date of this call, relative to the current time.
-        CharSequence dateText = getCallDate(details);
+        CharSequence callLocationAndDate = getCallLocationAndDate(details);
 
-        // Set the call count and date.
-        setCallCountAndDate(views, callCount, dateText, highlightColor);
-
-        // Get type of call (ie mobile, home, etc) if known, or the caller's
-        CharSequence numberFormattedLabel = getCallTypeOrLocation(details);
+        // Set the call count, location and date.
+        setCallCountAndDate(views, callCount, callLocationAndDate, highlightColor);
 
         final CharSequence nameText;
-        final CharSequence numberText;
-        final CharSequence labelText;
         final CharSequence displayNumber =
             mPhoneNumberHelper.getDisplayNumber(details.number,
                     details.numberPresentation, details.formattedNumber);
         if (TextUtils.isEmpty(details.name)) {
             nameText = displayNumber;
-            if (TextUtils.isEmpty(details.geocode)
-                    || mPhoneNumberUtilsWrapper.isVoicemailNumber(details.number)) {
-                numberText = mResources.getString(R.string.call_log_empty_geocode);
-            } else {
-                numberText = details.geocode;
-            }
-            labelText = numberText;
             // We have a real phone number as "nameView" so make it always LTR
             views.nameView.setTextDirection(View.TEXT_DIRECTION_LTR);
         } else {
             nameText = details.name;
-            numberText = displayNumber;
-            labelText = TextUtils.isEmpty(numberFormattedLabel) ? numberText :
-                    numberFormattedLabel;
         }
 
         views.nameView.setText(nameText);
-        views.labelView.setText(labelText);
-        views.labelView.setVisibility(TextUtils.isEmpty(labelText) ? View.GONE : View.VISIBLE);
+
+        // TODO: At the current time the voicemail transcription is not supported.  This view
+        // is kept for future expansion when we may wish to show a transcription of voicemail.
+        views.voicemailTranscriptionView.setText("");
+        views.voicemailTranscriptionView.setVisibility(View.GONE);
+    }
+
+    /**
+     * Builds a string containing the call location and date.
+     *
+     * @param details The call details.
+     * @return The call location and date string.
+     */
+    private CharSequence getCallLocationAndDate(PhoneCallDetails details) {
+        mDescriptionItems.clear();
+
+        // Get type of call (ie mobile, home, etc) if known, or the caller's location.
+        CharSequence callTypeOrLocation = getCallTypeOrLocation(details);
+
+        // Only add the call type or location if its not empty.  It will be empty for unknown
+        // callers.
+        if (!TextUtils.isEmpty(callTypeOrLocation)) {
+            mDescriptionItems.add(callTypeOrLocation);
+        }
+        // The date of this call, relative to the current time.
+        mDescriptionItems.add(getCallDate(details));
+
+        // Create a comma separated list from the call type or location, and call date.
+        // TextUtils.join ensures a locale appropriate list separator is used.
+        return TextUtils.join((List<CharSequence>)mDescriptionItems);
     }
 
     /**
@@ -139,7 +158,9 @@
         CharSequence numberFormattedLabel = null;
         // Only show a label if the number is shown and it is not a SIP address.
         if (!TextUtils.isEmpty(details.number)
-                && !PhoneNumberHelper.isUriNumber(details.number.toString())) {
+                && !PhoneNumberHelper.isUriNumber(details.number.toString())
+                && !mPhoneNumberUtilsWrapper.isVoicemailNumber(details.number)) {
+
             if (details.numberLabel == ContactInfo.GEOCODE_AS_LABEL) {
                 numberFormattedLabel = details.geocode;
             } else {
@@ -147,6 +168,11 @@
                         details.numberLabel);
             }
         }
+
+        if (!TextUtils.isEmpty(details.name) && TextUtils.isEmpty(numberFormattedLabel)) {
+            numberFormattedLabel = mPhoneNumberHelper.getDisplayNumber(details.number,
+                    details.numberPresentation, details.formattedNumber);
+        }
         return numberFormattedLabel;
     }
 
@@ -216,7 +242,7 @@
             formattedText = text;
         }
 
-        views.callTypeAndDate.setText(formattedText);
+        views.callLocationAndDate.setText(formattedText);
     }
 
     /** Creates a SpannableString for the given text which is bold and in the given color. */
diff --git a/src/com/android/dialer/PhoneCallDetailsViews.java b/src/com/android/dialer/PhoneCallDetailsViews.java
index 4e48210..30023ea 100644
--- a/src/com/android/dialer/PhoneCallDetailsViews.java
+++ b/src/com/android/dialer/PhoneCallDetailsViews.java
@@ -29,16 +29,17 @@
     public final TextView nameView;
     public final View callTypeView;
     public final CallTypeIconsView callTypeIcons;
-    public final TextView callTypeAndDate;
-    public final TextView labelView;
+    public final TextView callLocationAndDate;
+    public final TextView voicemailTranscriptionView;
 
     private PhoneCallDetailsViews(TextView nameView, View callTypeView,
-            CallTypeIconsView callTypeIcons, TextView callTypeAndDate, TextView labelView) {
+            CallTypeIconsView callTypeIcons, TextView callLocationAndDate,
+            TextView voicemailTranscriptionView) {
         this.nameView = nameView;
         this.callTypeView = callTypeView;
         this.callTypeIcons = callTypeIcons;
-        this.callTypeAndDate = callTypeAndDate;
-        this.labelView = labelView;
+        this.callLocationAndDate = callLocationAndDate;
+        this.voicemailTranscriptionView = voicemailTranscriptionView;
     }
 
     /**
@@ -52,8 +53,8 @@
         return new PhoneCallDetailsViews((TextView) view.findViewById(R.id.name),
                 view.findViewById(R.id.call_type),
                 (CallTypeIconsView) view.findViewById(R.id.call_type_icons),
-                (TextView) view.findViewById(R.id.call_count_and_date),
-                (TextView) view.findViewById(R.id.label));
+                (TextView) view.findViewById(R.id.call_location_and_date),
+                (TextView) view.findViewById(R.id.voicemail_transcription));
     }
 
     public static PhoneCallDetailsViews createForTest(Context context) {
diff --git a/src/com/android/dialer/calllog/CallLogAdapter.java b/src/com/android/dialer/calllog/CallLogAdapter.java
index 266be34..a8cd72a 100644
--- a/src/com/android/dialer/calllog/CallLogAdapter.java
+++ b/src/com/android/dialer/calllog/CallLogAdapter.java
@@ -22,6 +22,7 @@
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.AsyncTask;
 import android.os.Handler;
 import android.os.Message;
 import android.provider.CallLog.Calls;
@@ -35,20 +36,26 @@
 import android.view.ViewTreeObserver;
 import android.widget.ImageView;
 import android.widget.TextView;
+import android.widget.Toast;
 
 import com.android.common.widget.GroupingListAdapter;
 import com.android.contacts.common.ContactPhotoManager;
 import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
 import com.android.contacts.common.util.UriUtils;
+import com.android.dialer.CallDetailActivity;
 import com.android.dialer.PhoneCallDetails;
 import com.android.dialer.PhoneCallDetailsHelper;
 import com.android.dialer.R;
+import com.android.dialer.util.AsyncTaskExecutor;
+import com.android.dialer.util.AsyncTaskExecutors;
 import com.android.dialer.util.ExpirableCache;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Objects;
 
+import java.util.HashMap;
 import java.util.LinkedList;
+import java.util.Map;
 
 /**
  * Adapter class to fill in data for the Call Log.
@@ -56,6 +63,12 @@
 public class CallLogAdapter extends GroupingListAdapter
         implements ViewTreeObserver.OnPreDrawListener, CallLogGroupBuilder.GroupCreator {
 
+
+    /** The enumeration of {@link android.os.AsyncTask} objects used in this class. */
+    public enum Tasks {
+        REMOVE_CALL_LOG_ENTRIES,
+    }
+
     /** Interface used to initiate a refresh of the content. */
     public interface CallFetcher {
         public void fetchCalls();
@@ -103,6 +116,9 @@
     private final CallFetcher mCallFetcher;
     private ViewTreeObserver mViewTreeObserver = null;
 
+    /** Aynchronous task executor, lazy instantiated as needed. */
+    private AsyncTaskExecutor mAsyncTaskExecutor;
+
     /**
      * A cache of the contact details for the phone numbers in the call log.
      * <p>
@@ -113,6 +129,9 @@
      */
     private ExpirableCache<NumberWithCountryIso, ContactInfo> mContactInfoCache;
 
+    /** Hashmap, keyed by call Id, used to track which call log entries have been expanded or not */
+    private HashMap<Long,Boolean> mIsExpanded = new HashMap<Long,Boolean>();
+
     /**
      * A request for contact details for the given number.
      */
@@ -186,12 +205,6 @@
     /** Can be set to true by tests to disable processing of requests. */
     private volatile boolean mRequestProcessingDisabled = false;
 
-    /**
-     * Whether to show the secondary action button used to play voicemail or show call details.
-     * True if created from a CallLogFragment.
-     * False if created from the PhoneFavoriteFragment. */
-    private boolean mShowSecondaryActionButton = true;
-
     private boolean mIsCallLog = true;
     private int mNumMissedCalls = 0;
     private int mNumMissedCallsShown = 0;
@@ -211,6 +224,35 @@
         }
     };
 
+    /**
+     * Click listener for the delete from call log button.  Removes the current call log
+     * entry and its associated calls from the call log.
+     */
+    private final View.OnClickListener mDeleteListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            // Retrieve the views from the call log view.
+            final CallLogListItemViews views =
+                    (CallLogListItemViews)
+                            ((View)v.getParent().getParent().getParent().getParent()).getTag();
+
+            deleteCalls(views.callIds);
+            notifyDataSetChanged();
+        }
+    };
+
+    /**
+     * The onClickListener used to expand or collapse the action buttons section for a call log
+     * entry.
+     */
+    private final View.OnClickListener mExpandCollapseListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            expandOrCollapseActions((View) v.getParent().getParent());
+            notifyDataSetChanged();
+        }
+    };
+
     private void startActivityForAction(View view) {
         final IntentProvider intentProvider = (IntentProvider) view.getTag();
         if (intentProvider != null) {
@@ -251,14 +293,13 @@
     };
 
     public CallLogAdapter(Context context, CallFetcher callFetcher,
-            ContactInfoHelper contactInfoHelper, boolean showSecondaryActionButton,
+            ContactInfoHelper contactInfoHelper,
             boolean isCallLog) {
         super(context);
 
         mContext = context;
         mCallFetcher = callFetcher;
         mContactInfoHelper = contactInfoHelper;
-        mShowSecondaryActionButton = showSecondaryActionButton;
         mIsCallLog = isCallLog;
 
         mContactInfoCache = ExpirableCache.create(CONTACT_INFO_CACHE_SIZE);
@@ -512,8 +553,6 @@
     private void findAndCacheViews(View view) {
         // Get the views to bind to.
         CallLogListItemViews views = CallLogListItemViews.fromView(view);
-        views.primaryActionView.setOnClickListener(mActionListener);
-        views.secondaryActionButtonView.setOnClickListener(mActionListener);
         view.setTag(views);
     }
 
@@ -537,36 +576,55 @@
         final long duration = c.getLong(CallLogQuery.DURATION);
         final int callType = c.getInt(CallLogQuery.CALL_TYPE);
         final String countryIso = c.getString(CallLogQuery.COUNTRY_ISO);
+        final long rowId = c.getLong(CallLogQuery.ID);
+        views.rowId = rowId;
+
+        // Store some values used when the actions ViewStub is inflated on expansion of the actions
+        // section.
+        views.number = number;
+        views.numberPresentation = numberPresentation;
+        views.callType = callType;
+        views.voicemailUri = c.getString(CallLogQuery.VOICEMAIL_URI);
+        // Stash away the Ids of the calls so that we can support deleting a row in the call log.
+        views.callIds = getCallIds(c, rowId, count);
 
         final ContactInfo cachedContactInfo = getContactInfoFromCallLog(c);
 
         final boolean isVoicemailNumber =
                 PhoneNumberUtilsWrapper.INSTANCE.isVoicemailNumber(number);
 
-        // Primary action is always to call, if possible.
-        if (PhoneNumberUtilsWrapper.canPlaceCallsTo(number, numberPresentation)) {
-            // Sets the primary action to call the number.
-            views.primaryActionView.setTag(IntentProvider.getReturnCallIntentProvider(number));
-        } else {
-            views.primaryActionView.setTag(null);
-        }
+        // Where binding and not in the call log, use default behaviour of invoking a call when
+        // tapping the primary view.
+        if (!mIsCallLog) {
+            views.primaryActionView.setOnClickListener(this.mActionListener);
 
-        if ( mShowSecondaryActionButton ) {
-            // Store away the voicemail information so we can play it directly.
-            if (callType == Calls.VOICEMAIL_TYPE) {
-                String voicemailUri = c.getString(CallLogQuery.VOICEMAIL_URI);
-                final long rowId = c.getLong(CallLogQuery.ID);
-                views.secondaryActionButtonView.setTag(
-                        IntentProvider.getPlayVoicemailIntentProvider(rowId, voicemailUri));
+            // Set return call intent, otherwise null.
+            if (PhoneNumberUtilsWrapper.canPlaceCallsTo(number, numberPresentation)) {
+                // Sets the primary action to call the number.
+                views.primaryActionView.setTag(IntentProvider.getReturnCallIntentProvider(number));
             } else {
-                // Store the call details information.
-                views.secondaryActionButtonView.setTag(
-                        IntentProvider.getCallDetailIntentProvider(
-                                getCursor(), c.getPosition(), c.getLong(CallLogQuery.ID), count));
+                // Number is not callable, so hide button.
+                views.primaryActionView.setTag(null);
             }
         } else {
-            // No action enabled.
-            views.secondaryActionButtonView.setTag(null);
+            // In the call log, expand/collapse an actions section for the call log entry when
+            // the primary view is tapped.
+
+            // TODO: This needs to be changed to do the proper QP open/close animation.
+            views.primaryActionView.setOnClickListener(this.mExpandCollapseListener);
+
+            // Note: Binding of the action buttons is done as required in configureActionViews
+            // when the user expands the actions ViewStub.
+        }
+
+        // Restore expansion state of the row on rebind.  Inflate the actions ViewStub if required,
+        // and set its visibility state accordingly.
+        if (isExpanded(rowId)) {
+            // Inflate the view stub if necessary, and wire up the event handlers.
+            inflateActionViewStub(view);
+            views.actionsView.setVisibility(View.VISIBLE);
+        } else if (views.actionsView != null) {
+            views.actionsView.setVisibility(View.GONE);
         }
 
         // Lookup contacts with this number
@@ -631,8 +689,7 @@
         final boolean isNew = c.getInt(CallLogQuery.IS_READ) == 0;
         // New items also use the highlighted version of the text.
         final boolean isHighlighted = isNew;
-        mCallLogViewsHelper.setPhoneCallDetails(views, details, isHighlighted,
-                mShowSecondaryActionButton);
+        mCallLogViewsHelper.setPhoneCallDetails(views, details, isHighlighted);
 
         int contactType = ContactPhotoManager.TYPE_DEFAULT;
 
@@ -668,6 +725,130 @@
         bindBadge(view, info, details, callType);
     }
 
+    /**
+     * Determines if a call log row with the given Id is expanded to show the action buttons or
+     * not. If the row Id is not yet tracked, add a new entry assuming the row is collapsed.
+     * @param rowId
+     * @return
+     */
+    private boolean isExpanded(long rowId) {
+        if (!mIsExpanded.containsKey(rowId)) {
+            mIsExpanded.put(rowId, false);
+        }
+
+        return mIsExpanded.get(rowId);
+    }
+
+    /**
+     * Toggles the expansion state tracked for the call log row identified by rowId and returns
+     * the new expansion state.
+     *
+     * @param rowId The row Id associated with the call log row to expand/collapse.
+     * @return True where the row is now expanded, false otherwise.
+     */
+    private boolean toggleExpansion(long rowId) {
+        boolean isExpanded = isExpanded(rowId);
+
+        mIsExpanded.put(rowId, !isExpanded);
+        return !isExpanded;
+    }
+
+    /**
+     * Expands or collapses the view containing the CALLBACK, VOICEMAIL and DELETE action buttons.
+     *
+     * @param callLogItem The call log entry parent view.
+     */
+    private void expandOrCollapseActions(View callLogItem) {
+        final CallLogListItemViews views = (CallLogListItemViews)callLogItem.getTag();
+
+        // Hide or show the actions view.
+        boolean expanded = toggleExpansion(views.rowId);
+
+        // Inflate the view stub if necessary, and wire up the event handlers.
+        inflateActionViewStub(callLogItem);
+
+        if (expanded) {
+            views.actionsView.setVisibility(View.VISIBLE);
+
+            // Attempt to give accessibility focus to one of the action buttons.
+            // This ensures that a user realizes the expansion occurred.
+            // NOTE(tgunn): requestAccessibilityFocus returns true if the requested
+            // focus was successful.  The first successful focus will satisfy the OR
+            // block and block further attempts to set focus.
+            boolean focused = views.callBackButtonView.requestAccessibilityFocus() ||
+                    views.voicemailButtonView.requestAccessibilityFocus() ||
+                    views.deleteButtonView.requestAccessibilityFocus();
+        } else {
+            views.actionsView.setVisibility(View.GONE);
+        }
+    }
+
+    /**
+     * Configures the action buttons in the expandable actions ViewStub.  The ViewStub is not
+     * inflated during initial binding, so click handlers, tags and accessibility text must be set
+     * here, if necessary.
+     *
+     * @param callLogItem The call log list item view.
+     */
+    private void inflateActionViewStub(View callLogItem) {
+        final CallLogListItemViews views = (CallLogListItemViews)callLogItem.getTag();
+
+        ViewStub stub = (ViewStub)callLogItem.findViewById(R.id.call_log_entry_actions_stub);
+        if (stub != null) {
+            views.actionsView = stub.inflate();
+        }
+
+        if (views.callBackButtonView == null) {
+            views.callBackButtonView = (TextView)views.actionsView.findViewById(R.id.call_back_action);
+        }
+
+        if (views.voicemailButtonView == null) {
+            views.voicemailButtonView = (TextView)views.actionsView.findViewById(R.id.voicemail_action);
+        }
+
+        if ( views.deleteButtonView == null) {
+            views.deleteButtonView = (TextView)views.actionsView.findViewById(R.id.delete_action);
+        }
+
+        bindActionButtons(views);
+    }
+
+    /***
+     * Binds click handlers and intents to the voicemail, delete and callback action buttons.
+     *
+     * @param views  The call log item views.
+     */
+    private void bindActionButtons(CallLogListItemViews views) {
+        // Set return call intent, otherwise null.
+        if (PhoneNumberUtilsWrapper.canPlaceCallsTo(views.number, views.numberPresentation)) {
+            // Sets the primary action to call the number.
+            views.callBackButtonView.setTag(
+                    IntentProvider.getReturnCallIntentProvider(views.number));
+            views.callBackButtonView.setVisibility(View.VISIBLE);
+            views.callBackButtonView.setOnClickListener(mActionListener);
+        } else {
+            // Number is not callable, so hide button.
+            views.callBackButtonView.setTag(null);
+            views.callBackButtonView.setVisibility(View.GONE);
+        }
+
+        // For voicemail calls, show the "VOICEMAIL" action button; hide otherwise.
+        if (views.callType == Calls.VOICEMAIL_TYPE) {
+            views.voicemailButtonView.setOnClickListener(mActionListener);
+            views.voicemailButtonView.setTag(
+                    IntentProvider.getPlayVoicemailIntentProvider(
+                            views.rowId, views.voicemailUri));
+            views.voicemailButtonView.setVisibility(View.VISIBLE);
+        } else {
+            views.voicemailButtonView.setTag(null);
+            views.voicemailButtonView.setVisibility(View.GONE);
+        }
+
+        views.deleteButtonView.setOnClickListener(this.mDeleteListener);
+
+        mCallLogViewsHelper.setActionContentDescriptions(views);
+    }
+
     protected void bindBadge(View view, ContactInfo info, PhoneCallDetails details, int callType) {
 
         // Do not show badge in call log.
@@ -960,4 +1141,76 @@
         }
         return number;
     }
+
+    /**
+     * Retrieves the call Ids represented by the current call log row.
+     *
+     * @param cursor Call log cursor to retrieve call Ids from.
+     * @param id Id of the first call of the grouping.
+     * @param groupSize Number of calls associated with the current call log row.
+     * @return Array of call Ids.
+     */
+    private long[] getCallIds(final Cursor cursor, final long id, final int groupSize) {
+        // We want to restore the position in the cursor at the end.
+        int startingPosition = cursor.getPosition();
+        long[] ids = new long[groupSize];
+        // Copy the ids of the rows in the group.
+        for (int index = 0; index < groupSize; ++index) {
+            ids[index] = cursor.getLong(CallLogQuery.ID);
+            cursor.moveToNext();
+        }
+        cursor.moveToPosition(startingPosition);
+        return ids;
+    }
+
+    /**
+     * Retrieves an instance of the asynchronous task executor, creating one if required.
+     * @return The {@link com.android.dialer.util.AsyncTaskExecutor}
+     */
+    private AsyncTaskExecutor getTaskExecutor() {
+        if (mAsyncTaskExecutor == null) {
+            mAsyncTaskExecutor = AsyncTaskExecutors.createAsyncTaskExecutor();
+        }
+        return mAsyncTaskExecutor;
+    }
+
+    /**
+     * Deletes the calls specified in the callIds array, asynchronously.
+     *
+     * @param callIds Ids of calls to be deleted.
+     */
+    private void deleteCalls(long[] callIds) {
+        if (callIds == null) {
+            return;
+        }
+
+        // Build comma separated list of ids to delete.
+        final StringBuilder callIdString = new StringBuilder();
+        for (long callId : callIds) {
+            if (callIdString.length() != 0) {
+                callIdString.append(",");
+            }
+            callIdString.append(callId);
+        }
+
+        // Perform removal of call log entries asynchronously.
+        getTaskExecutor().submit(Tasks.REMOVE_CALL_LOG_ENTRIES,
+                new AsyncTask<Void, Void, Void>() {
+                    @Override
+                    public Void doInBackground(Void... params) {
+                        // Issue delete.
+                        mContext.getContentResolver().delete(Calls.CONTENT_URI_WITH_VOICEMAIL,
+                                Calls._ID + " IN (" + callIdString + ")", null);
+                        return null;
+                    }
+
+                    @Override
+                    public void onPostExecute(Void result) {
+                        // Somewhere went wrong: we're going to bail out and show error to users.
+                        Toast.makeText(mContext, R.string.toast_entry_removed,
+                                Toast.LENGTH_SHORT).show();
+                    }
+                }
+        );
+    }
 }
diff --git a/src/com/android/dialer/calllog/CallLogFragment.java b/src/com/android/dialer/calllog/CallLogFragment.java
index 2ab6136..0948151 100644
--- a/src/com/android/dialer/calllog/CallLogFragment.java
+++ b/src/com/android/dialer/calllog/CallLogFragment.java
@@ -154,7 +154,7 @@
 
         String currentCountryIso = GeoUtil.getCurrentCountryIso(getActivity());
         mAdapter = ObjectFactory.newCallLogAdapter(getActivity(), this, new ContactInfoHelper(
-                getActivity(), currentCountryIso), true, true);
+                getActivity(), currentCountryIso), true);
         setListAdapter(mAdapter);
         mCallLogQueryHandler = new CallLogQueryHandler(getActivity().getContentResolver(),
                 this, mLogLimit);
diff --git a/src/com/android/dialer/calllog/CallLogListItemHelper.java b/src/com/android/dialer/calllog/CallLogListItemHelper.java
index a85cd01..81d1a27 100644
--- a/src/com/android/dialer/calllog/CallLogListItemHelper.java
+++ b/src/com/android/dialer/calllog/CallLogListItemHelper.java
@@ -25,6 +25,7 @@
 import com.android.dialer.PhoneCallDetails;
 import com.android.dialer.PhoneCallDetailsHelper;
 import com.android.dialer.R;
+import com.android.internal.util.CharSequences;
 
 /**
  * Helper class to fill in the views of a call log entry.
@@ -56,13 +57,11 @@
      * @param views the views to populate
      * @param details the details of a phone call needed to fill in the data
      * @param isHighlighted whether to use the highlight text for the call
-     * @param showSecondaryActionButton whether to show the secondary action button or not
      */
     public void setPhoneCallDetails(CallLogListItemViews views, PhoneCallDetails details,
-            boolean isHighlighted, boolean showSecondaryActionButton) {
+            boolean isHighlighted) {
         mPhoneCallDetailsHelper.setPhoneCallDetails(views.phoneCallDetailsViews, details,
                 isHighlighted);
-        boolean canPlay = details.callTypes[0] == Calls.VOICEMAIL_TYPE;
 
         // Set the accessibility text for the contact badge
         views.quickContactView.setContentDescription(getContactBadgeDescription(details));
@@ -70,35 +69,25 @@
         // Set the primary action accessibility description
         views.primaryActionView.setContentDescription(getCallDescription(details));
 
-        // If secondary action is visible, either show voicemail playback icon, or
-        // show the "clock" icon corresponding to the call details screen.
-        if (showSecondaryActionButton) {
-            if (canPlay) {
-                // Playback action takes preference.
-                configurePlaySecondaryAction(views, isHighlighted);
-            } else {
-                // Call details is the secondary action.
-                configureCallDetailsSecondaryAction(views, details);
-            }
-        } else {
-            // No secondary action is to be shown (ie this is likely a PhoneFavoriteFragment)
-            views.secondaryActionView.setVisibility(View.GONE);
-        }
+        // Cache name or number of caller.  Used when setting the content descriptions of buttons
+        // when the actions ViewStub is inflated.
+        views.nameOrNumber = this.getNameOrNumber(details);
     }
 
     /**
-     * Sets the secondary action to invoke call details.
+     * Sets the accessibility descriptions for the action buttons in the action button ViewStub.
      *
-     * @param views   the views to populate
-     * @param details the details of a phone call needed to fill in the call details data
+     * @param views The views associated with the current call log entry.
      */
-    private void configureCallDetailsSecondaryAction(CallLogListItemViews views,
-            PhoneCallDetails details) {
-        views.secondaryActionView.setVisibility(View.VISIBLE);
-        // Use the small dark grey clock icon.
-        views.secondaryActionButtonView.setImageResource(R.drawable.ic_menu_history_dk);
-        views.secondaryActionButtonView.setContentDescription(
-                mResources.getString(R.string.description_call_details));
+    public void setActionContentDescriptions(CallLogListItemViews views) {
+        views.callBackButtonView.setContentDescription(
+                mResources.getString(R.string.description_call_back_action, views.nameOrNumber));
+
+        views.voicemailButtonView.setContentDescription(
+                mResources.getString(R.string.description_voicemail_action, views.nameOrNumber));
+
+        views.deleteButtonView.setContentDescription(
+                mResources.getString(R.string.description_delete_action, views.nameOrNumber));
     }
 
     /**
@@ -123,16 +112,11 @@
      *
      * The {Caller Information} references the most recent call associated with the caller.
      * For incoming calls:
-     * If missed call:  Return missed call from {Name/Number} {Call Type} {Call Time}.
-     * If answered call: Return answered call from {Name/Number} {Call Type} {Call Time}.
-     *
-     * For unknown callers, drop the "Return" part, since the call can't be returned:
-     * If answered unknown: Answered call from {Name/Number} {Call Time}.
-     * If missed unknown: Missed call from {Name/Number} {Call Time}.
+     * If missed call:  Missed call from {Name/Number} {Call Type} {Call Time}.
+     * If answered call: Answered call from {Name/Number} {Call Type} {Call Time}.
      *
      * For outgoing calls:
-     * If outgoing:  Call {Name/Number] {Call Type}.  {Last} called {Call Time}.
-     * Where {Last} is dropped if the number of calls for the caller is 1.
+     * If outgoing:  Call to {Name/Number] {Call Type} {Call Time}.
      *
      * Where:
      * {Name/Number} is the name or number of the caller (as shown in call log).
@@ -140,8 +124,8 @@
      * {Call Time} is the time since the last call for the contact occurred.
      *
      * Examples:
-     * 3 calls.  New Voicemail.  Return missed call from Joe Smith mobile 2 hours ago.
-     * 2 calls.  Call John Doe mobile.  Last called 1 hour ago.
+     * 3 calls.  New Voicemail.  Missed call from Joe Smith mobile 2 hours ago.
+     * 2 calls.  Answered call from John Doe mobile.  Last called 1 hour ago.
      * @param details Details of call.
      * @return Return call action description.
      */
@@ -191,43 +175,17 @@
      */
     public int getCallDescriptionStringID(PhoneCallDetails details) {
         int lastCallType = getLastCallType(details.callTypes);
-        boolean isNumberCallable = PhoneNumberUtilsWrapper.canPlaceCallsTo(details.number,
-                details.numberPresentation);
+        int stringID;
 
-        // Default string to use is "call XYZ..." just in case we manage to fall through.
-        int stringID = R.string.description_call_last_multiple;
-
-        if (!isNumberCallable) {
-            // Number isn't callable; this is an incoming call from an unknown caller.
-            // An uncallable outgoing call wouldn't be in the call log.
-
-            // Voicemail and missed calls are both considered missed.
-            if (lastCallType == Calls.VOICEMAIL_TYPE ||
-                    lastCallType == Calls.MISSED_TYPE) {
-                stringID = R.string.description_unknown_missed_call;
-            } else if (lastCallType == Calls.INCOMING_TYPE) {
-                stringID = R.string.description_unknown_answered_call;
-            }
+        if (lastCallType == Calls.VOICEMAIL_TYPE || lastCallType == Calls.MISSED_TYPE) {
+            //Message: Missed call from <NameOrNumber>, <TypeOrLocation>, <TimeOfCall>.
+            stringID = R.string.description_incoming_missed_call;
+        } else if (lastCallType == Calls.INCOMING_TYPE) {
+            //Message: Answered call from <NameOrNumber>, <TypeOrLocation>, <TimeOfCall>.
+            stringID = R.string.description_incoming_answered_call;
         } else {
-            // Known caller, so callable.
-
-            // Missed call (ie voicemail or missed)
-            if (lastCallType == Calls.VOICEMAIL_TYPE ||
-                    lastCallType == Calls.MISSED_TYPE) {
-                stringID = R.string.description_return_missed_call;
-            } else if (lastCallType == Calls.INCOMING_TYPE) {
-                // Incoming answered.
-                stringID = R.string.description_return_answered_call;
-            } else {
-                // Outgoing call.
-
-                // If we have a history of multiple calls
-                if (details.callTypes.length > 1) {
-                    stringID = R.string.description_call_last_multiple;
-                } else {
-                    stringID = R.string.description_call_last;
-                }
-            }
+            //Message: Call to <NameOrNumber>, <TypeOrLocation>, <TimeOfCall>.
+            stringID = R.string.description_outgoing_call;
         }
         return stringID;
     }
@@ -260,13 +218,4 @@
         }
         return recipient;
     }
-
-    /** Sets the secondary action to correspond to the play button. */
-    private void configurePlaySecondaryAction(CallLogListItemViews views, boolean isHighlighted) {
-        views.secondaryActionView.setVisibility(View.VISIBLE);
-        views.secondaryActionButtonView.setImageResource(
-                isHighlighted ? R.drawable.ic_play_active_holo_dark : R.drawable.ic_play_holo_light);
-        views.secondaryActionButtonView.setContentDescription(
-                mResources.getString(R.string.description_call_log_play_button));
-    }
 }
diff --git a/src/com/android/dialer/calllog/CallLogListItemViews.java b/src/com/android/dialer/calllog/CallLogListItemViews.java
index a378956..879647f 100644
--- a/src/com/android/dialer/calllog/CallLogListItemViews.java
+++ b/src/com/android/dialer/calllog/CallLogListItemViews.java
@@ -34,25 +34,67 @@
     public final QuickContactBadge quickContactView;
     /** The primary action view of the entry. */
     public final View primaryActionView;
-    /** The secondary action view, which includes both the vertical divider line and
-     *  the action button itself.  Used so that the button and divider line can be
-     *  made visible/hidden as a whole. */
-    public final View secondaryActionView;
-    /** The secondary action button on the entry. */
-    public final ImageView secondaryActionButtonView;
     /** The details of the phone call. */
     public final PhoneCallDetailsViews phoneCallDetailsViews;
     /** The text of the header of a section. */
     public final TextView listHeaderTextView;
+    /** The view containing call log item actions.  Null until the ViewStub is inflated. */
+    public View actionsView;
+    /** The "call back" action button - assigned only when the action section is expanded. */
+    public TextView callBackButtonView;
+    /** The "delete" action button - assigned only when the action section is expanded. */
+    public TextView deleteButtonView;
+    /** The "voicemail" action button - assigned only when the action section is expanded. */
+    public TextView voicemailButtonView;
+
+    /**
+     * The row Id for the first call associated with the call log entry.  Used as a key for the
+     * map used to track which call log entries have the action button section expanded.
+     */
+    public long rowId;
+
+    /**
+     * The call Ids for the calls represented by the current call log entry.  Used when the user
+     * deletes a call log entry.
+     */
+    public long[] callIds;
+
+    /**
+     * The callable phone number for the current call log entry.  Cached here as the call back
+     * intent is set only when the actions ViewStub is inflated.
+     */
+    public String number;
+
+    /**
+     * The phone number presentation for the current call log entry.  Cached here as the call back
+     * intent is set only when the actions ViewStub is inflated.
+     */
+    public int numberPresentation;
+
+    /**
+     * The type of call for the current call log entry.  Cached here as the call back
+     * intent is set only when the actions ViewStub is inflated.
+     */
+    public int callType;
+
+    /**
+     * If the call has an associated voicemail message, the URI of the voicemail message for
+     * playback.  Cached here as the voicemail intent is only set when the actions ViewStub is
+     * inflated.
+     */
+    public String voicemailUri;
+
+    /**
+     * The name or number associated with the call.  Cached here for use when setting content
+     * descriptions on buttons in the actions ViewStub when it is inflated.
+     */
+    public CharSequence nameOrNumber;
 
     private CallLogListItemViews(QuickContactBadge quickContactView, View primaryActionView,
-            View secondaryActionView, ImageView secondaryActionButtonView,
             PhoneCallDetailsViews phoneCallDetailsViews,
             TextView listHeaderTextView) {
         this.quickContactView = quickContactView;
         this.primaryActionView = primaryActionView;
-        this.secondaryActionView = secondaryActionView;
-        this.secondaryActionButtonView = secondaryActionButtonView;
         this.phoneCallDetailsViews = phoneCallDetailsViews;
         this.listHeaderTextView = listHeaderTextView;
     }
@@ -61,8 +103,6 @@
         return new CallLogListItemViews(
                 (QuickContactBadge) view.findViewById(R.id.quick_contact_photo),
                 view.findViewById(R.id.primary_action_view),
-                view.findViewById(R.id.secondary_action_view),
-                (ImageView) view.findViewById(R.id.secondary_action_icon),
                 PhoneCallDetailsViews.fromView(view),
                 (TextView) view.findViewById(R.id.call_log_header));
     }
@@ -72,8 +112,6 @@
         return new CallLogListItemViews(
                 new QuickContactBadge(context),
                 new View(context),
-                new View(context),
-                new ImageView(context),
                 PhoneCallDetailsViews.createForTest(context),
                 new TextView(context));
     }
diff --git a/src/com/android/dialer/list/ListsFragment.java b/src/com/android/dialer/list/ListsFragment.java
index 49d6b3a..88e5ce0 100644
--- a/src/com/android/dialer/list/ListsFragment.java
+++ b/src/com/android/dialer/list/ListsFragment.java
@@ -177,7 +177,7 @@
                 this, 1);
         final String currentCountryIso = GeoUtil.getCurrentCountryIso(getActivity());
         mCallLogAdapter = ObjectFactory.newCallLogAdapter(getActivity(), this,
-                new ContactInfoHelper(getActivity(), currentCountryIso), false, false);
+                new ContactInfoHelper(getActivity(), currentCountryIso), false);
 
         mMergedAdapter = new ShortcutCardsAdapter(getActivity(), this, mCallLogAdapter);
     }
diff --git a/src/com/android/dialerbind/ObjectFactory.java b/src/com/android/dialerbind/ObjectFactory.java
index be91e33..e7ca8d9 100644
--- a/src/com/android/dialerbind/ObjectFactory.java
+++ b/src/com/android/dialerbind/ObjectFactory.java
@@ -39,15 +39,11 @@
      * @param context The context to use.
      * @param callFetcher Instance of call fetcher to use.
      * @param contactInfoHelper Instance of contact info helper class to use.
-     * @param hideSecondaryAction If true, secondary action will be hidden (ie call details
-     *                            or play voicemail).
      * @param isCallLog Is this call log adapter being used on the call log?
      * @return Instance of CallLogAdapter.
      */
     public static CallLogAdapter newCallLogAdapter(Context context, CallFetcher callFetcher,
-            ContactInfoHelper contactInfoHelper, boolean hideSecondaryAction,
-            boolean isCallLog) {
-        return new CallLogAdapter(context, callFetcher, contactInfoHelper, hideSecondaryAction,
-                isCallLog);
+            ContactInfoHelper contactInfoHelper, boolean isCallLog) {
+        return new CallLogAdapter(context, callFetcher, contactInfoHelper, isCallLog);
     }
 }