Merge "Move view-specific logic to ViewHolders."
diff --git a/src/com/android/dialer/calllog/CallLogAdapter.java b/src/com/android/dialer/calllog/CallLogAdapter.java
index e061551..1733068 100644
--- a/src/com/android/dialer/calllog/CallLogAdapter.java
+++ b/src/com/android/dialer/calllog/CallLogAdapter.java
@@ -36,14 +36,12 @@
 import android.view.View;
 import android.view.View.AccessibilityDelegate;
 import android.view.ViewGroup;
-import android.view.ViewStub;
 import android.view.ViewTreeObserver;
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.ImageView;
 import android.widget.TextView;
 
 import com.android.common.widget.GroupingListAdapter;
-import com.android.contacts.common.CallUtil;
 import com.android.contacts.common.ContactPhotoManager;
 import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
 import com.android.contacts.common.util.UriUtils;
@@ -271,7 +269,14 @@
     private final View.OnClickListener mActionListener = new View.OnClickListener() {
         @Override
         public void onClick(View view) {
-            startActivityForAction(view);
+            final IntentProvider intentProvider = (IntentProvider) view.getTag();
+            if (intentProvider != null) {
+                final Intent intent = intentProvider.getIntent(mContext);
+                // See IntentProvider.getCallDetailIntentProvider() for why this may be null.
+                if (intent != null) {
+                    DialerUtils.startActivityWithErrorToast(mContext, intent);
+                }
+            }
         }
     };
 
@@ -299,17 +304,6 @@
         }
     };
 
-    private void startActivityForAction(View view) {
-        final IntentProvider intentProvider = (IntentProvider) view.getTag();
-        if (intentProvider != null) {
-            final Intent intent = intentProvider.getIntent(mContext);
-            // See IntentProvider.getCallDetailIntentProvider() for why this may be null.
-            if (intent != null) {
-                DialerUtils.startActivityWithErrorToast(mContext, intent);
-            }
-        }
-    }
-
     @Override
     public boolean onPreDraw() {
         // We only wanted to listen for the first draw (and this is it).
@@ -366,8 +360,7 @@
         PhoneCallDetailsHelper phoneCallDetailsHelper =
                 new PhoneCallDetailsHelper(mContext, resources, mPhoneNumberUtilsWrapper);
         mCallLogViewsHelper =
-                new CallLogListItemHelper(
-                        phoneCallDetailsHelper, mPhoneNumberHelper, resources);
+                new CallLogListItemHelper(phoneCallDetailsHelper, mPhoneNumberHelper, resources);
         mCallLogGroupBuilder = new CallLogGroupBuilder(this);
     }
 
@@ -585,7 +578,7 @@
         View view = inflater.inflate(R.layout.call_log_list_item, parent, false);
 
         // Get the views to bind to and cache them.
-        CallLogListItemViews views = CallLogListItemViews.fromView(view);
+        CallLogListItemViews views = CallLogListItemViews.fromView(context, view);
         view.setTag(views);
 
         // Set text height to false on the TextViews so they don't have extra padding.
@@ -669,7 +662,7 @@
                 mPhoneNumberUtilsWrapper.isVoicemailNumber(accountHandle, number);
 
         // Expand/collapse an actions section for the call log entry when the primary view is tapped.
-        views.primaryActionView.setOnClickListener(this.mExpandCollapseListener);
+        views.primaryActionView.setOnClickListener(mExpandCollapseListener);
 
         // Note: Binding of the action buttons is done as required in configureActionViews when the
         // user expands the actions ViewStub.
@@ -860,12 +853,13 @@
      * @param isExpanded The new expansion state of the view.
      */
     private void expandOrCollapseActions(View callLogItem, boolean isExpanded) {
-        final CallLogListItemViews views = (CallLogListItemViews)callLogItem.getTag();
+        final CallLogListItemViews views = (CallLogListItemViews) callLogItem.getTag();
 
         expandVoicemailTranscriptionView(views, isExpanded);
         if (isExpanded) {
             // Inflate the view stub if necessary, and wire up the event handlers.
-            inflateActionViewStub(callLogItem);
+            views.inflateActionViewStub(callLogItem, mOnReportButtonClickListener, mActionListener,
+                    mPhoneNumberUtilsWrapper, mCallLogViewsHelper);
 
             views.actionsView.setVisibility(View.VISIBLE);
             views.actionsView.setAlpha(1.0f);
@@ -899,135 +893,6 @@
         view.setSingleLine(!isExpanded);
     }
 
-    /**
-     * 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(final 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 = (ViewGroup) stub.inflate();
-        }
-
-        if (views.callBackButtonView == null) {
-            views.callBackButtonView = (TextView)views.actionsView.findViewById(
-                    R.id.call_back_action);
-        }
-
-        if (views.videoCallButtonView == null) {
-            views.videoCallButtonView = (TextView)views.actionsView.findViewById(
-                    R.id.video_call_action);
-        }
-
-        if (views.voicemailButtonView == null) {
-            views.voicemailButtonView = (TextView)views.actionsView.findViewById(
-                    R.id.voicemail_action);
-        }
-
-        if (views.detailsButtonView == null) {
-            views.detailsButtonView = (TextView)views.actionsView.findViewById(R.id.details_action);
-        }
-
-        if (views.reportButtonView == null) {
-            views.reportButtonView = (TextView)views.actionsView.findViewById(R.id.report_action);
-            views.reportButtonView.setOnClickListener(new View.OnClickListener() {
-                @Override
-                public void onClick(View v) {
-                    if (mOnReportButtonClickListener != null) {
-                        mOnReportButtonClickListener.onReportButtonClick(views.number);
-                    }
-                }
-            });
-        }
-
-        bindActionButtons(views);
-    }
-
-    /***
-     * Binds text titles, click handlers and intents to the voicemail, details and callback action
-     * buttons.
-     *
-     * @param views  The call log item views.
-     */
-    private void bindActionButtons(CallLogListItemViews views) {
-        boolean canPlaceCallToNumber =
-                PhoneNumberUtilsWrapper.canPlaceCallsTo(views.number, views.numberPresentation);
-        // Set return call intent, otherwise null.
-        if (canPlaceCallToNumber) {
-            boolean isVoicemailNumber =
-                    mPhoneNumberUtilsWrapper.isVoicemailNumber(views.accountHandle, views.number);
-            if (isVoicemailNumber) {
-                // Make a general call to voicemail to ensure that if there are multiple accounts
-                // it does not call the voicemail number of a specific phone account.
-                views.callBackButtonView.setTag(
-                        IntentProvider.getReturnVoicemailCallIntentProvider());
-            } else {
-                // Sets the primary action to call the number.
-                views.callBackButtonView.setTag(
-                        IntentProvider.getReturnCallIntentProvider(views.number));
-            }
-            views.callBackButtonView.setVisibility(View.VISIBLE);
-            views.callBackButtonView.setOnClickListener(mActionListener);
-
-            final int titleId;
-            if (views.callType == Calls.VOICEMAIL_TYPE || views.callType == Calls.OUTGOING_TYPE) {
-                titleId = R.string.call_log_action_redial;
-            } else {
-                titleId = R.string.call_log_action_call_back;
-            }
-            views.callBackButtonView.setText(mContext.getString(titleId));
-        } else {
-            // Number is not callable, so hide button.
-            views.callBackButtonView.setTag(null);
-            views.callBackButtonView.setVisibility(View.GONE);
-        }
-
-        // If one of the calls had video capabilities, show the video call button.
-        if (CallUtil.isVideoEnabled(mContext) && canPlaceCallToNumber &&
-                views.phoneCallDetailsViews.callTypeIcons.isVideoShown()) {
-            views.videoCallButtonView.setTag(
-                    IntentProvider.getReturnVideoCallIntentProvider(views.number));
-            views.videoCallButtonView.setVisibility(View.VISIBLE);
-            views.videoCallButtonView.setOnClickListener(mActionListener);
-        } else {
-            views.videoCallButtonView.setTag(null);
-            views.videoCallButtonView.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);
-
-            views.detailsButtonView.setVisibility(View.GONE);
-        } else {
-            views.voicemailButtonView.setTag(null);
-            views.voicemailButtonView.setVisibility(View.GONE);
-
-            views.detailsButtonView.setOnClickListener(mActionListener);
-            views.detailsButtonView.setTag(
-                    IntentProvider.getCallDetailIntentProvider(
-                            views.rowId, views.callIds, null)
-            );
-
-            if (views.canBeReportedAsInvalid && !views.reported) {
-                views.reportButtonView.setVisibility(View.VISIBLE);
-            } else {
-                views.reportButtonView.setVisibility(View.GONE);
-            }
-        }
-
-        mCallLogViewsHelper.setActionContentDescriptions(views);
-    }
-
     /** Checks whether the contact info from the call log matches the one from the contacts db. */
     private boolean callLogInfoMatches(ContactInfo callLogInfo, ContactInfo info) {
         // The call log only contains a subset of the fields in the contacts db.
@@ -1204,7 +1069,9 @@
     @VisibleForTesting
     void bindViewForTest(View view, Context context, Cursor cursor) {
         bindStandAloneView(view, context, cursor);
-        inflateActionViewStub(view);
+        CallLogListItemViews views = CallLogListItemViews.fromView(context, view);
+        views.inflateActionViewStub(view, mOnReportButtonClickListener, mActionListener,
+                mPhoneNumberUtilsWrapper, mCallLogViewsHelper);
     }
 
     /**
diff --git a/src/com/android/dialer/calllog/CallLogListItemViews.java b/src/com/android/dialer/calllog/CallLogListItemViews.java
index 0ccdf00..b9a76a8 100644
--- a/src/com/android/dialer/calllog/CallLogListItemViews.java
+++ b/src/com/android/dialer/calllog/CallLogListItemViews.java
@@ -17,17 +17,27 @@
 package com.android.dialer.calllog;
 
 import android.content.Context;
+import android.provider.CallLog.Calls;
 import android.telecom.PhoneAccountHandle;
 import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewStub;
 import android.widget.QuickContactBadge;
 import android.widget.TextView;
 
+import com.android.contacts.common.CallUtil;
 import com.android.contacts.common.testing.NeededForTesting;
 import com.android.dialer.PhoneCallDetailsViews;
 import com.android.dialer.R;
 
 /**
- * Simple value object containing the various views within a call log entry.
+ * This is an object containing the various views within a call log entry. It contains values
+ * pointing to views contained by a call log list item view, so that we can improve performance
+ * by reducing the frequency with which we need to find views by IDs.
+ *
+ * This object also contains methods for inflating action views and binding action behaviors. This
+ * is a way of isolating view logic from the CallLogAdapter. We should consider moving that logic
+ * if the call log list item is eventually represented as a UI component.
  */
 public final class CallLogListItemViews {
     /** The quick contact badge for the contact. */
@@ -113,9 +123,17 @@
      */
     public boolean canBeReportedAsInvalid;
 
-    private CallLogListItemViews(QuickContactBadge quickContactView, View primaryActionView,
-            PhoneCallDetailsViews phoneCallDetailsViews, View callLogEntryView,
+    private Context mContext;
+
+    private CallLogListItemViews(
+            Context context,
+            QuickContactBadge quickContactView,
+            View primaryActionView,
+            PhoneCallDetailsViews phoneCallDetailsViews,
+            View callLogEntryView,
             TextView dayGroupHeader) {
+        mContext = context;
+
         this.quickContactView = quickContactView;
         this.primaryActionView = primaryActionView;
         this.phoneCallDetailsViews = phoneCallDetailsViews;
@@ -123,8 +141,134 @@
         this.dayGroupHeader = dayGroupHeader;
     }
 
-    public static CallLogListItemViews fromView(View view) {
+    /**
+     * 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.
+     */
+    public void inflateActionViewStub(
+            final View callLogItem,
+            final CallLogAdapter.OnReportButtonClickListener onReportButtonClickListener,
+            View.OnClickListener actionListener,
+            PhoneNumberUtilsWrapper phoneNumberUtilsWrapper,
+            CallLogListItemHelper callLogViewsHelper) {
+        ViewStub stub = (ViewStub) callLogItem.findViewById(R.id.call_log_entry_actions_stub);
+        if (stub != null) {
+            actionsView = (ViewGroup) stub.inflate();
+        }
+
+        if (callBackButtonView == null) {
+            callBackButtonView = (TextView) actionsView.findViewById(R.id.call_back_action);
+        }
+
+        if (videoCallButtonView == null) {
+            videoCallButtonView = (TextView) actionsView.findViewById(R.id.video_call_action);
+        }
+
+        if (voicemailButtonView == null) {
+            voicemailButtonView = (TextView) actionsView.findViewById(R.id.voicemail_action);
+        }
+
+        if (detailsButtonView == null) {
+            detailsButtonView = (TextView) actionsView.findViewById(R.id.details_action);
+        }
+
+        if (reportButtonView == null) {
+            reportButtonView = (TextView) actionsView.findViewById(R.id.report_action);
+            reportButtonView.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    if (onReportButtonClickListener != null) {
+                        onReportButtonClickListener.onReportButtonClick(number);
+                    }
+                }
+            });
+        }
+
+        bindActionButtons(actionListener, phoneNumberUtilsWrapper, callLogViewsHelper);
+    }
+
+    /**
+     * Binds text titles, click handlers and intents to the voicemail, details and callback action
+     * buttons.
+     */
+    private void bindActionButtons(
+            View.OnClickListener actionListener,
+            PhoneNumberUtilsWrapper phoneNumberUtilsWrapper,
+            CallLogListItemHelper callLogViewsHelper) {
+        boolean canPlaceCallToNumber =
+                PhoneNumberUtilsWrapper.canPlaceCallsTo(number, numberPresentation);
+
+        // Set return call intent, otherwise null.
+        if (canPlaceCallToNumber) {
+            boolean isVoicemailNumber =
+                    phoneNumberUtilsWrapper.isVoicemailNumber(accountHandle, number);
+            if (isVoicemailNumber) {
+                // Make a general call to voicemail to ensure that if there are multiple accounts
+                // it does not call the voicemail number of a specific phone account.
+                callBackButtonView.setTag(IntentProvider.getReturnVoicemailCallIntentProvider());
+            } else {
+                // Sets the primary action to call the number.
+                callBackButtonView.setTag(IntentProvider.getReturnCallIntentProvider(number));
+            }
+            callBackButtonView.setVisibility(View.VISIBLE);
+            callBackButtonView.setOnClickListener(actionListener);
+
+            final int titleId;
+            if (callType == Calls.VOICEMAIL_TYPE || callType == Calls.OUTGOING_TYPE) {
+                titleId = R.string.call_log_action_redial;
+            } else {
+                titleId = R.string.call_log_action_call_back;
+            }
+            callBackButtonView.setText(mContext.getString(titleId));
+        } else {
+            // Number is not callable, so hide button.
+            callBackButtonView.setTag(null);
+            callBackButtonView.setVisibility(View.GONE);
+        }
+
+        // If one of the calls had video capabilities, show the video call button.
+        if (CallUtil.isVideoEnabled(mContext) && canPlaceCallToNumber &&
+                phoneCallDetailsViews.callTypeIcons.isVideoShown()) {
+            videoCallButtonView.setTag(IntentProvider.getReturnVideoCallIntentProvider(number));
+            videoCallButtonView.setVisibility(View.VISIBLE);
+            videoCallButtonView.setOnClickListener(actionListener);
+        } else {
+            videoCallButtonView.setTag(null);
+            videoCallButtonView.setVisibility(View.GONE);
+        }
+
+        // For voicemail calls, show the "VOICEMAIL" action button; hide otherwise.
+        if (callType == Calls.VOICEMAIL_TYPE) {
+            voicemailButtonView.setOnClickListener(actionListener);
+            voicemailButtonView.setTag(
+                    IntentProvider.getPlayVoicemailIntentProvider(rowId, voicemailUri));
+            voicemailButtonView.setVisibility(View.VISIBLE);
+
+            detailsButtonView.setVisibility(View.GONE);
+        } else {
+            voicemailButtonView.setTag(null);
+            voicemailButtonView.setVisibility(View.GONE);
+
+            detailsButtonView.setOnClickListener(actionListener);
+            detailsButtonView.setTag(
+                    IntentProvider.getCallDetailIntentProvider(rowId, callIds, null));
+
+            if (canBeReportedAsInvalid && !reported) {
+                reportButtonView.setVisibility(View.VISIBLE);
+            } else {
+                reportButtonView.setVisibility(View.GONE);
+            }
+        }
+
+        callLogViewsHelper.setActionContentDescriptions(this);
+    }
+
+    public static CallLogListItemViews fromView(Context context, View view) {
         return new CallLogListItemViews(
+                context,
                 (QuickContactBadge) view.findViewById(R.id.quick_contact_photo),
                 view.findViewById(R.id.primary_action_view),
                 PhoneCallDetailsViews.fromView(view),
@@ -135,6 +279,7 @@
     @NeededForTesting
     public static CallLogListItemViews createForTest(Context context) {
         CallLogListItemViews views = new CallLogListItemViews(
+                context,
                 new QuickContactBadge(context),
                 new View(context),
                 PhoneCallDetailsViews.createForTest(context),