Dialer: support sub and call-type filters for msim call log

src/com/android/dialer/calllog/MSimCallLogFragment.java is copied
form the file src/com/android/dialer/calllog/CallLogFragment.java in the
commit b84df80a6f6fd663bb5dd6757d142a1a09fe7678
src/com/android/dialer/calllog/SpinnerContent.java is copied from
src/com/android/dialer/calllog/SpinnerContent.java in the commit
066b90ea588e102042434fcdcb7222ad295ac3c0

Change-Id: I1b0183fe376b9148bb534eb862e50f4dbdb93818
CRs-Fixed: 934138
diff --git a/src/com/android/dialer/calllog/MSimCallLogFragment.java b/src/com/android/dialer/calllog/MSimCallLogFragment.java
new file mode 100644
index 0000000..33525db
--- /dev/null
+++ b/src/com/android/dialer/calllog/MSimCallLogFragment.java
@@ -0,0 +1,703 @@
+/*
+ * Copyright (C) 2011 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.calllog;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.app.Activity;
+import android.app.DialogFragment;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Handler;
+import android.provider.CallLog;
+import android.provider.CallLog.Calls;
+import android.provider.ContactsContract;
+import android.provider.VoicemailContract.Status;
+import android.util.MutableInt;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.android.contacts.common.GeoUtil;
+import com.android.contacts.common.util.ViewUtil;
+import com.android.dialer.R;
+import com.android.dialer.list.ListsFragment.HostInterface;
+import com.android.dialer.util.DialerUtils;
+import com.android.dialer.util.EmptyLoader;
+import com.android.dialer.voicemail.VoicemailStatusHelper;
+import com.android.dialer.voicemail.VoicemailStatusHelper.StatusMessage;
+import com.android.dialer.voicemail.VoicemailStatusHelperImpl;
+import com.android.dialerbind.ObjectFactory;
+import com.android.dialerbind.analytics.AnalyticsListFragment;
+
+import java.util.List;
+
+/**
+ * Displays a list of call log entries. To filter for a particular kind of call
+ * (all, missed or voicemails), specify it in the constructor.
+ */
+public class CallLogFragment extends AnalyticsListFragment
+        implements CallLogQueryHandler.Listener, CallLogAdapter.OnReportButtonClickListener,
+        CallLogAdapter.CallFetcher,
+        CallLogAdapter.CallItemExpandedListener {
+    private static final String TAG = "CallLogFragment";
+
+    private static final String REPORT_DIALOG_TAG = "report_dialog";
+    private String mReportDialogNumber;
+    private boolean mIsReportDialogShowing;
+
+    /**
+     * ID of the empty loader to defer other fragments.
+     */
+    private static final int EMPTY_LOADER_ID = 0;
+
+    private static final String KEY_FILTER_TYPE = "filter_type";
+    private static final String KEY_LOG_LIMIT = "log_limit";
+    private static final String KEY_DATE_LIMIT = "date_limit";
+    private static final String KEY_SHOW_FOOTER = "show_footer";
+    private static final String KEY_IS_REPORT_DIALOG_SHOWING = "is_report_dialog_showing";
+    private static final String KEY_REPORT_DIALOG_NUMBER = "report_dialog_number";
+
+    protected CallLogAdapter mAdapter;
+    protected CallLogQueryHandler mCallLogQueryHandler;
+    private boolean mScrollToTop;
+
+    /** Whether there is at least one voicemail source installed. */
+    protected boolean mVoicemailSourcesAvailable = false;
+
+    protected VoicemailStatusHelper mVoicemailStatusHelper;
+    protected View mStatusMessageView;
+    protected TextView mStatusMessageText;
+    protected TextView mStatusMessageAction;
+    private KeyguardManager mKeyguardManager;
+    private View mFooterView;
+
+    private boolean mEmptyLoaderRunning;
+    private boolean mCallLogFetched;
+    private boolean mVoicemailStatusFetched;
+
+    private float mExpandedItemTranslationZ;
+    private int mFadeInDuration;
+    private int mFadeInStartDelay;
+    private int mFadeOutDuration;
+    private int mExpandCollapseDuration;
+
+    private final Handler mHandler = new Handler();
+
+    private class CustomContentObserver extends ContentObserver {
+        public CustomContentObserver() {
+            super(mHandler);
+        }
+        @Override
+        public void onChange(boolean selfChange) {
+            mRefreshDataRequired = true;
+        }
+    }
+
+    // See issue 6363009
+    private final ContentObserver mCallLogObserver = new CustomContentObserver();
+    private final ContentObserver mContactsObserver = new CustomContentObserver();
+    private final ContentObserver mVoicemailStatusObserver = new CustomContentObserver();
+    private boolean mRefreshDataRequired = true;
+
+    // Exactly same variable is in Fragment as a package private.
+    private boolean mMenuVisible = true;
+
+    // Default to all calls.
+    protected int mCallTypeFilter = CallLogQueryHandler.CALL_TYPE_ALL;
+
+    // Log limit - if no limit is specified, then the default in {@link CallLogQueryHandler}
+    // will be used.
+    private int mLogLimit = -1;
+
+    // Date limit (in millis since epoch) - when non-zero, only calls which occurred on or after
+    // the date filter are included.  If zero, no date-based filtering occurs.
+    private long mDateLimit = 0;
+
+    // Whether or not to show the Show call history footer view
+    private boolean mHasFooterView = false;
+
+    public CallLogFragment() {
+        this(CallLogQueryHandler.CALL_TYPE_ALL, -1);
+    }
+
+    public CallLogFragment(int filterType) {
+        this(filterType, -1);
+    }
+
+    public CallLogFragment(int filterType, int logLimit) {
+        super();
+        mCallTypeFilter = filterType;
+        mLogLimit = logLimit;
+    }
+
+    /**
+     * Creates a call log fragment, filtering to include only calls of the desired type, occurring
+     * after the specified date.
+     * @param filterType type of calls to include.
+     * @param dateLimit limits results to calls occurring on or after the specified date.
+     */
+    public CallLogFragment(int filterType, long dateLimit) {
+        this(filterType, -1, dateLimit);
+    }
+
+    /**
+     * Creates a call log fragment, filtering to include only calls of the desired type, occurring
+     * after the specified date.  Also provides a means to limit the number of results returned.
+     * @param filterType type of calls to include.
+     * @param logLimit limits the number of results to return.
+     * @param dateLimit limits results to calls occurring on or after the specified date.
+     */
+    public CallLogFragment(int filterType, int logLimit, long dateLimit) {
+        this(filterType, logLimit);
+        mDateLimit = dateLimit;
+    }
+
+    @Override
+    public void onCreate(Bundle state) {
+        super.onCreate(state);
+        if (state != null) {
+            mCallTypeFilter = state.getInt(KEY_FILTER_TYPE, mCallTypeFilter);
+            mLogLimit = state.getInt(KEY_LOG_LIMIT, mLogLimit);
+            mDateLimit = state.getLong(KEY_DATE_LIMIT, mDateLimit);
+            mHasFooterView = state.getBoolean(KEY_SHOW_FOOTER, mHasFooterView);
+            mIsReportDialogShowing = state.getBoolean(KEY_IS_REPORT_DIALOG_SHOWING,
+                    mIsReportDialogShowing);
+            mReportDialogNumber = state.getString(KEY_REPORT_DIALOG_NUMBER, mReportDialogNumber);
+        }
+
+        String currentCountryIso = GeoUtil.getCurrentCountryIso(getActivity());
+        mAdapter = ObjectFactory.newCallLogAdapter(getActivity(), this,
+                new ContactInfoHelper(getActivity(), currentCountryIso), this, this, true);
+        setListAdapter(mAdapter);
+        mCallLogQueryHandler = new CallLogQueryHandler(getActivity().getContentResolver(),
+                this, mLogLimit);
+        mKeyguardManager =
+                (KeyguardManager) getActivity().getSystemService(Context.KEYGUARD_SERVICE);
+        getActivity().getContentResolver().registerContentObserver(CallLog.CONTENT_URI, true,
+                mCallLogObserver);
+        getActivity().getContentResolver().registerContentObserver(
+                ContactsContract.Contacts.CONTENT_URI, true, mContactsObserver);
+        getActivity().getContentResolver().registerContentObserver(
+                Status.CONTENT_URI, true, mVoicemailStatusObserver);
+        setHasOptionsMenu(true);
+        updateCallList(mCallTypeFilter, mDateLimit);
+
+        mExpandedItemTranslationZ =
+                getResources().getDimension(R.dimen.call_log_expanded_translation_z);
+        mFadeInDuration = getResources().getInteger(R.integer.call_log_actions_fade_in_duration);
+        mFadeInStartDelay = getResources().getInteger(R.integer.call_log_actions_fade_start);
+        mFadeOutDuration = getResources().getInteger(R.integer.call_log_actions_fade_out_duration);
+        mExpandCollapseDuration = getResources().getInteger(
+                R.integer.call_log_expand_collapse_duration);
+
+        if (mIsReportDialogShowing) {
+            DialogFragment df = ObjectFactory.getReportDialogFragment(mReportDialogNumber);
+            if (df != null) {
+                df.setTargetFragment(this, 0);
+                df.show(getActivity().getFragmentManager(), REPORT_DIALOG_TAG);
+            }
+        }
+    }
+
+    /** Called by the CallLogQueryHandler when the list of calls has been fetched or updated. */
+    @Override
+    public boolean onCallsFetched(Cursor cursor) {
+        if (getActivity() == null || getActivity().isFinishing()) {
+            // Return false; we did not take ownership of the cursor
+            return false;
+        }
+        mAdapter.setLoading(false);
+        mAdapter.changeCursor(cursor);
+        // This will update the state of the "Clear call log" menu item.
+        getActivity().invalidateOptionsMenu();
+        if (mScrollToTop) {
+            final ListView listView = getListView();
+            // The smooth-scroll animation happens over a fixed time period.
+            // As a result, if it scrolls through a large portion of the list,
+            // each frame will jump so far from the previous one that the user
+            // will not experience the illusion of downward motion.  Instead,
+            // if we're not already near the top of the list, we instantly jump
+            // near the top, and animate from there.
+            if (listView.getFirstVisiblePosition() > 5) {
+                listView.setSelection(5);
+            }
+            // Workaround for framework issue: the smooth-scroll doesn't
+            // occur if setSelection() is called immediately before.
+            mHandler.post(new Runnable() {
+               @Override
+               public void run() {
+                   if (getActivity() == null || getActivity().isFinishing()) {
+                       return;
+                   }
+                   listView.smoothScrollToPosition(0);
+               }
+            });
+
+            mScrollToTop = false;
+        }
+        mCallLogFetched = true;
+        destroyEmptyLoaderIfAllDataFetched();
+        return true;
+    }
+
+    /**
+     * Called by {@link CallLogQueryHandler} after a successful query to voicemail status provider.
+     */
+    @Override
+    public void onVoicemailStatusFetched(Cursor statusCursor) {
+        if (getActivity() == null || getActivity().isFinishing()) {
+            return;
+        }
+        updateVoicemailStatusMessage(statusCursor);
+
+        int activeSources = mVoicemailStatusHelper.getNumberActivityVoicemailSources(statusCursor);
+        setVoicemailSourcesAvailable(activeSources != 0);
+        mVoicemailStatusFetched = true;
+        destroyEmptyLoaderIfAllDataFetched();
+    }
+
+    private void destroyEmptyLoaderIfAllDataFetched() {
+        if (mCallLogFetched && mVoicemailStatusFetched && mEmptyLoaderRunning) {
+            mEmptyLoaderRunning = false;
+            getLoaderManager().destroyLoader(EMPTY_LOADER_ID);
+        }
+    }
+
+    /** Sets whether there are any voicemail sources available in the platform. */
+    protected void setVoicemailSourcesAvailable(boolean voicemailSourcesAvailable) {
+        if (mVoicemailSourcesAvailable == voicemailSourcesAvailable) return;
+        mVoicemailSourcesAvailable = voicemailSourcesAvailable;
+
+        Activity activity = getActivity();
+        if (activity != null) {
+            // This is so that the options menu content is updated.
+            activity.invalidateOptionsMenu();
+        }
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
+        View view = inflater.inflate(R.layout.call_log_fragment, container, false);
+        mVoicemailStatusHelper = new VoicemailStatusHelperImpl();
+        mStatusMessageView = view.findViewById(R.id.voicemail_status);
+        mStatusMessageText = (TextView) view.findViewById(R.id.voicemail_status_message);
+        mStatusMessageAction = (TextView) view.findViewById(R.id.voicemail_status_action);
+        return view;
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        getListView().setEmptyView(view.findViewById(R.id.empty_list_view));
+        getListView().setItemsCanFocus(true);
+        maybeAddFooterView();
+
+        updateEmptyMessage(mCallTypeFilter);
+    }
+
+    /**
+     * Based on the new intent, decide whether the list should be configured
+     * to scroll up to display the first item.
+     */
+    public void configureScreenFromIntent(Intent newIntent) {
+        // Typically, when switching to the call-log we want to show the user
+        // the same section of the list that they were most recently looking
+        // at.  However, under some circumstances, we want to automatically
+        // scroll to the top of the list to present the newest call items.
+        // For example, immediately after a call is finished, we want to
+        // display information about that call.
+        mScrollToTop = Calls.CONTENT_TYPE.equals(newIntent.getType());
+    }
+
+    @Override
+    public void onStart() {
+        // Start the empty loader now to defer other fragments.  We destroy it when both calllog
+        // and the voicemail status are fetched.
+        getLoaderManager().initLoader(EMPTY_LOADER_ID, null,
+                new EmptyLoader.Callback(getActivity()));
+        mEmptyLoaderRunning = true;
+        super.onStart();
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        refreshData();
+    }
+
+    private void updateVoicemailStatusMessage(Cursor statusCursor) {
+        List<StatusMessage> messages = mVoicemailStatusHelper.getStatusMessages(statusCursor);
+        if (messages.size() == 0) {
+            mStatusMessageView.setVisibility(View.GONE);
+        } else {
+            mStatusMessageView.setVisibility(View.VISIBLE);
+            // TODO: Change the code to show all messages. For now just pick the first message.
+            final StatusMessage message = messages.get(0);
+            if (message.showInCallLog()) {
+                mStatusMessageText.setText(message.callLogMessageId);
+            }
+            if (message.actionMessageId != -1) {
+                mStatusMessageAction.setText(message.actionMessageId);
+            }
+            if (message.actionUri != null) {
+                mStatusMessageAction.setVisibility(View.VISIBLE);
+                mStatusMessageAction.setOnClickListener(new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        getActivity().startActivity(
+                                new Intent(Intent.ACTION_VIEW, message.actionUri));
+                    }
+                });
+            } else {
+                mStatusMessageAction.setVisibility(View.GONE);
+            }
+        }
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        // Kill the requests thread
+        mAdapter.stopRequestProcessing();
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        updateOnExit();
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        mAdapter.stopRequestProcessing();
+        mAdapter.changeCursor(null);
+        getActivity().getContentResolver().unregisterContentObserver(mCallLogObserver);
+        getActivity().getContentResolver().unregisterContentObserver(mContactsObserver);
+        getActivity().getContentResolver().unregisterContentObserver(mVoicemailStatusObserver);
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt(KEY_FILTER_TYPE, mCallTypeFilter);
+        outState.putInt(KEY_LOG_LIMIT, mLogLimit);
+        outState.putLong(KEY_DATE_LIMIT, mDateLimit);
+        outState.putBoolean(KEY_SHOW_FOOTER, mHasFooterView);
+        outState.putBoolean(KEY_IS_REPORT_DIALOG_SHOWING, mIsReportDialogShowing);
+        outState.putString(KEY_REPORT_DIALOG_NUMBER, mReportDialogNumber);
+    }
+
+    @Override
+    public void fetchCalls() {
+        mCallLogQueryHandler.fetchCalls(mCallTypeFilter, mDateLimit);
+    }
+
+    public void startCallsQuery() {
+        mAdapter.setLoading(true);
+        mCallLogQueryHandler.fetchCalls(mCallTypeFilter, mDateLimit);
+    }
+
+    private void startVoicemailStatusQuery() {
+        mCallLogQueryHandler.fetchVoicemailStatus();
+    }
+
+    private void updateCallList(int filterType, long dateLimit) {
+        mCallLogQueryHandler.fetchCalls(filterType, dateLimit);
+    }
+
+    private void updateEmptyMessage(int filterType) {
+        final int messageId;
+        switch (filterType) {
+            case Calls.MISSED_TYPE:
+                messageId = R.string.recentMissed_empty;
+                break;
+            case Calls.VOICEMAIL_TYPE:
+                messageId = R.string.recentVoicemails_empty;
+                break;
+            case CallLogQueryHandler.CALL_TYPE_ALL:
+                messageId = R.string.recentCalls_empty;
+                break;
+            default:
+                throw new IllegalArgumentException("Unexpected filter type in CallLogFragment: "
+                        + filterType);
+        }
+        DialerUtils.configureEmptyListView(
+                getListView().getEmptyView(), R.drawable.empty_call_log, messageId, getResources());
+    }
+
+    CallLogAdapter getAdapter() {
+        return mAdapter;
+    }
+
+    @Override
+    public void setMenuVisibility(boolean menuVisible) {
+        super.setMenuVisibility(menuVisible);
+        if (mMenuVisible != menuVisible) {
+            mMenuVisible = menuVisible;
+            if (!menuVisible) {
+                updateOnExit();
+            } else if (isResumed()) {
+                refreshData();
+            }
+        }
+    }
+
+    /** Requests updates to the data to be shown. */
+    private void refreshData() {
+        // Prevent unnecessary refresh.
+        if (mRefreshDataRequired) {
+            // Mark all entries in the contact info cache as out of date, so they will be looked up
+            // again once being shown.
+            mAdapter.invalidateCache();
+            startCallsQuery();
+            startVoicemailStatusQuery();
+            updateOnEntry();
+            mRefreshDataRequired = false;
+        }
+    }
+
+    /** Updates call data and notification state while leaving the call log tab. */
+    private void updateOnExit() {
+        updateOnTransition(false);
+    }
+
+    /** Updates call data and notification state while entering the call log tab. */
+    private void updateOnEntry() {
+        updateOnTransition(true);
+    }
+
+    // TODO: Move to CallLogActivity
+    private void updateOnTransition(boolean onEntry) {
+        // We don't want to update any call data when keyguard is on because the user has likely not
+        // seen the new calls yet.
+        // This might be called before onCreate() and thus we need to check null explicitly.
+        if (mKeyguardManager != null && !mKeyguardManager.inKeyguardRestrictedInputMode()) {
+            // On either of the transitions we update the missed call and voicemail notifications.
+            // While exiting we additionally consume all missed calls (by marking them as read).
+            mCallLogQueryHandler.markNewCallsAsOld();
+            if (!onEntry) {
+                mCallLogQueryHandler.markMissedCallsAsRead();
+            }
+            CallLogNotificationsHelper.removeMissedCallNotifications(getActivity());
+            CallLogNotificationsHelper.updateVoicemailNotifications(getActivity());
+        }
+    }
+
+    /**
+     * Enables/disables the showing of the view full call history footer
+     *
+     * @param hasFooterView Whether or not to show the footer
+     */
+    public void setHasFooterView(boolean hasFooterView) {
+        mHasFooterView = hasFooterView;
+        maybeAddFooterView();
+    }
+
+    /**
+     * Determine whether or not the footer view should be added to the listview. If getView()
+     * is null, which means onCreateView hasn't been called yet, defer the addition of the footer
+     * until onViewCreated has been called.
+     */
+    private void maybeAddFooterView() {
+        if (!mHasFooterView || getView() == null) {
+            return;
+        }
+
+        if (mFooterView == null) {
+            mFooterView = getActivity().getLayoutInflater().inflate(
+                    R.layout.recents_list_footer, getListView(), false);
+            mFooterView.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    ((HostInterface) getActivity()).showCallHistory();
+                }
+            });
+        }
+
+        final ListView listView = getListView();
+        listView.removeFooterView(mFooterView);
+        listView.addFooterView(mFooterView);
+
+        ViewUtil.addBottomPaddingToListViewForFab(listView, getResources());
+    }
+
+    @Override
+    public void onItemExpanded(final CallLogListItemView view) {
+        final int startingHeight = view.getHeight();
+        final CallLogListItemViews viewHolder = (CallLogListItemViews) view.getTag();
+        final ViewTreeObserver observer = getListView().getViewTreeObserver();
+        observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+            @Override
+            public boolean onPreDraw() {
+                // We don't want to continue getting called for every draw.
+                if (observer.isAlive()) {
+                    observer.removeOnPreDrawListener(this);
+                }
+                // Calculate some values to help with the animation.
+                final int endingHeight = view.getHeight();
+                final int distance = Math.abs(endingHeight - startingHeight);
+                final int baseHeight = Math.min(endingHeight, startingHeight);
+                final boolean isExpand = endingHeight > startingHeight;
+
+                // Set the views back to the start state of the animation
+                view.getLayoutParams().height = startingHeight;
+                if (!isExpand) {
+                    viewHolder.actionsView.setVisibility(View.VISIBLE);
+                }
+                CallLogAdapter.expandVoicemailTranscriptionView(viewHolder, !isExpand);
+
+                // Set up the fade effect for the action buttons.
+                if (isExpand) {
+                    // Start the fade in after the expansion has partly completed, otherwise it
+                    // will be mostly over before the expansion completes.
+                    viewHolder.actionsView.setAlpha(0f);
+                    viewHolder.actionsView.animate()
+                            .alpha(1f)
+                            .setStartDelay(mFadeInStartDelay)
+                            .setDuration(mFadeInDuration)
+                            .start();
+                } else {
+                    viewHolder.actionsView.setAlpha(1f);
+                    viewHolder.actionsView.animate()
+                            .alpha(0f)
+                            .setDuration(mFadeOutDuration)
+                            .start();
+                }
+                view.requestLayout();
+
+                // Set up the animator to animate the expansion and shadow depth.
+                ValueAnimator animator = isExpand ? ValueAnimator.ofFloat(0f, 1f)
+                        : ValueAnimator.ofFloat(1f, 0f);
+
+                // Figure out how much scrolling is needed to make the view fully visible.
+                final Rect localVisibleRect = new Rect();
+                view.getLocalVisibleRect(localVisibleRect);
+                final int scrollingNeeded = localVisibleRect.top > 0 ? -localVisibleRect.top
+                        : view.getMeasuredHeight() - localVisibleRect.height();
+                final ListView listView = getListView();
+                animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+
+                    private int mCurrentScroll = 0;
+
+                    @Override
+                    public void onAnimationUpdate(ValueAnimator animator) {
+                        Float value = (Float) animator.getAnimatedValue();
+
+                        // For each value from 0 to 1, animate the various parts of the layout.
+                        view.getLayoutParams().height = (int) (value * distance + baseHeight);
+                        float z = mExpandedItemTranslationZ * value;
+                        viewHolder.callLogEntryView.setTranslationZ(z);
+                        view.setTranslationZ(z); // WAR
+                        view.requestLayout();
+
+                        if (isExpand) {
+                            if (listView != null) {
+                                int scrollBy = (int) (value * scrollingNeeded) - mCurrentScroll;
+                                listView.smoothScrollBy(scrollBy, /* duration = */ 0);
+                                mCurrentScroll += scrollBy;
+                            }
+                        }
+                    }
+                });
+                // Set everything to their final values when the animation's done.
+                animator.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        view.getLayoutParams().height = LayoutParams.WRAP_CONTENT;
+
+                        if (!isExpand) {
+                            viewHolder.actionsView.setVisibility(View.GONE);
+                        } else {
+                            // This seems like it should be unnecessary, but without this, after
+                            // navigating out of the activity and then back, the action view alpha
+                            // is defaulting to the value (0) at the start of the expand animation.
+                            viewHolder.actionsView.setAlpha(1);
+                        }
+                        CallLogAdapter.expandVoicemailTranscriptionView(viewHolder, isExpand);
+                    }
+                });
+
+                animator.setDuration(mExpandCollapseDuration);
+                animator.start();
+
+                // Return false so this draw does not occur to prevent the final frame from
+                // being drawn for the single frame before the animations start.
+                return false;
+            }
+        });
+    }
+
+    /**
+     * Retrieves the call log view for the specified call Id.  If the view is not currently
+     * visible, returns null.
+     *
+     * @param callId The call Id.
+     * @return The call log view.
+     */
+    @Override
+    public CallLogListItemView getViewForCallId(long callId) {
+        ListView listView = getListView();
+
+        int firstPosition = listView.getFirstVisiblePosition();
+        int lastPosition = listView.getLastVisiblePosition();
+
+        for (int position = 0; position <= lastPosition - firstPosition; position++) {
+            View view = listView.getChildAt(position);
+
+            if (view != null) {
+                final CallLogListItemViews viewHolder = (CallLogListItemViews) view.getTag();
+                if (viewHolder != null && viewHolder.rowId == callId) {
+                    return (CallLogListItemView)view;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    public void onBadDataReported(String number) {
+        mIsReportDialogShowing = false;
+        if (number == null) {
+            return;
+        }
+        mAdapter.onBadDataReported(number);
+        mAdapter.notifyDataSetChanged();
+    }
+
+    public void onReportButtonClick(String number) {
+        DialogFragment df = ObjectFactory.getReportDialogFragment(number);
+        if (df != null) {
+            df.setTargetFragment(this, 0);
+            df.show(getActivity().getFragmentManager(), REPORT_DIALOG_TAG);
+            mReportDialogNumber = number;
+            mIsReportDialogShowing = true;
+        }
+    }
+}
diff --git a/src/com/android/dialer/calllog/SpinnerContent.java b/src/com/android/dialer/calllog/SpinnerContent.java
new file mode 100644
index 0000000..614b594
--- /dev/null
+++ b/src/com/android/dialer/calllog/SpinnerContent.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod 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.calllog;
+
+import android.content.Context;
+import android.provider.CallLog;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.widget.Spinner;
+
+import com.android.contacts.common.MoreContactUtils;
+import com.android.dialer.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * To save the spinner content.
+ */
+public class SpinnerContent {
+    private static String TAG = SpinnerContent.class.getSimpleName();
+
+    public final int value;
+    public final String label;
+
+    // The index for call type spinner.
+    private static final int INDEX_CALL_TYPE_ALL = 0;
+    private static final int INDEX_CALL_TYPE_INCOMING = 1;
+    private static final int INDEX_CALL_TYPE_OUTGOING = 2;
+    private static final int INDEX_CALL_TYPE_MISSED = 3;
+    private static final int INDEX_CALL_TYPE_VOICEMAIL = 4;
+
+    public static void setSpinnerContentValue(Spinner spinner, int value) {
+        for (int i = 0, count = spinner.getCount(); i < count; i++) {
+            SpinnerContent sc = (SpinnerContent) spinner.getItemAtPosition(i);
+            if (sc.value == value) {
+                spinner.setSelection(i, true);
+                Log.i(TAG, "Set selection for spinner(" + sc + ") with the value: " + value);
+                return;
+            }
+        }
+    }
+
+    public SpinnerContent(int value, String label) {
+        this.value = value;
+        this.label = label;
+    }
+
+    @Override
+    public String toString() {
+        return label;
+    }
+
+    /**
+     * @return the spinner contents for the different sims (all, sim0, sim1 etc)
+     */
+    public static List<SpinnerContent> setupSubFilterContent(Context context) {
+        TelephonyManager telephonyManager =
+                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+        int count = telephonyManager.getPhoneCount();
+        // Update the filter sub content.
+        ArrayList<SpinnerContent> values = new ArrayList<SpinnerContent>(count + 1);
+        values.add(new SpinnerContent(CallLogQueryHandler.CALL_SUB_ALL,
+                context.getString(R.string.call_log_show_all_slots)));
+        for (int i = 0; i < count; i++) {
+            String subDisplayName = PhoneAccountUtils.getAccountLabel(context,
+                    MoreContactUtils.getAccount(i));
+            if (!TextUtils.isEmpty(subDisplayName)) {
+                values.add(new SpinnerContent(i, subDisplayName));
+            }
+        }
+        return values;
+    }
+
+    /**
+     * @param voicemailAvailable true if voicemail should be included in the return values
+     * @return the spinner contents for the different call types (incoming, outgoing etc)
+     */
+    public static List<SpinnerContent> setupStatusFilterContent(Context context,
+            boolean voicemailAvailable) {
+        // Didn't show the voice mail item if not available.
+        int statusCount = voicemailAvailable ? 5 : 4;
+        ArrayList<SpinnerContent> values = new ArrayList<SpinnerContent>(statusCount);
+        for (int i = 0; i < statusCount; i++) {
+            int value = CallLogQueryHandler.CALL_TYPE_ALL;
+            String label = null;
+            switch (i) {
+                case INDEX_CALL_TYPE_ALL:
+                    value = CallLogQueryHandler.CALL_TYPE_ALL;
+                    label = context.getString(R.string.call_log_all_calls_header);
+                    break;
+                case INDEX_CALL_TYPE_INCOMING:
+                    value = CallLog.Calls.INCOMING_TYPE;
+                    label = context.getString(R.string.call_log_incoming_header);
+                    break;
+                case INDEX_CALL_TYPE_OUTGOING:
+                    value = CallLog.Calls.OUTGOING_TYPE;
+                    label = context.getString(R.string.call_log_outgoing_header);
+                    break;
+                case INDEX_CALL_TYPE_MISSED:
+                    value = CallLog.Calls.MISSED_TYPE;
+                    label = context.getString(R.string.call_log_missed_header);
+                    break;
+                case INDEX_CALL_TYPE_VOICEMAIL:
+                    value = CallLog.Calls.VOICEMAIL_TYPE;
+                    label = context.getString(R.string.call_log_voicemail_header);
+                    break;
+            }
+            values.add(new SpinnerContent(value, label));
+        }
+        return values;
+    }
+}