CallLogFragment.java revision 630fa468c79d44daae4e48f465cbaccdb9e2fe67
1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.dialer.calllog;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ValueAnimator;
22import android.app.Activity;
23import android.app.DialogFragment;
24import android.app.KeyguardManager;
25import android.app.ListFragment;
26import android.content.Context;
27import android.content.Intent;
28import android.database.ContentObserver;
29import android.database.Cursor;
30import android.graphics.Rect;
31import android.os.Bundle;
32import android.os.Handler;
33import android.provider.CallLog;
34import android.provider.CallLog.Calls;
35import android.provider.ContactsContract;
36import android.provider.VoicemailContract.Status;
37import android.view.LayoutInflater;
38import android.view.View;
39import android.view.ViewGroup;
40import android.view.ViewTreeObserver;
41import android.view.View.OnClickListener;
42import android.view.ViewGroup.LayoutParams;
43import android.widget.ListView;
44import android.widget.TextView;
45
46import com.android.contacts.common.GeoUtil;
47import com.android.contacts.common.util.ViewUtil;
48import com.android.dialer.R;
49import com.android.dialer.list.ListsFragment.HostInterface;
50import com.android.dialer.util.DialerUtils;
51import com.android.dialer.util.EmptyLoader;
52import com.android.dialer.voicemail.VoicemailStatusHelper;
53import com.android.dialer.voicemail.VoicemailStatusHelper.StatusMessage;
54import com.android.dialer.voicemail.VoicemailStatusHelperImpl;
55import com.android.dialerbind.ObjectFactory;
56
57import java.util.List;
58
59/**
60 * Displays a list of call log entries. To filter for a particular kind of call
61 * (all, missed or voicemails), specify it in the constructor.
62 */
63public class CallLogFragment extends ListFragment
64        implements CallLogQueryHandler.Listener, CallLogAdapter.OnReportButtonClickListener,
65        CallLogAdapter.CallFetcher,
66        CallLogAdapter.CallItemExpandedListener {
67    private static final String TAG = "CallLogFragment";
68
69    private static final String REPORT_DIALOG_TAG = "report_dialog";
70    private String mReportDialogNumber;
71    private boolean mIsReportDialogShowing;
72
73    /**
74     * ID of the empty loader to defer other fragments.
75     */
76    private static final int EMPTY_LOADER_ID = 0;
77
78    private static final String KEY_FILTER_TYPE = "filter_type";
79    private static final String KEY_LOG_LIMIT = "log_limit";
80    private static final String KEY_DATE_LIMIT = "date_limit";
81    private static final String KEY_SHOW_FOOTER = "show_footer";
82    private static final String KEY_IS_REPORT_DIALOG_SHOWING = "is_report_dialog_showing";
83    private static final String KEY_REPORT_DIALOG_NUMBER = "report_dialog_number";
84
85    private CallLogAdapter mAdapter;
86    private CallLogQueryHandler mCallLogQueryHandler;
87    private boolean mScrollToTop;
88
89    /** Whether there is at least one voicemail source installed. */
90    private boolean mVoicemailSourcesAvailable = false;
91
92    private VoicemailStatusHelper mVoicemailStatusHelper;
93    private View mStatusMessageView;
94    private TextView mStatusMessageText;
95    private TextView mStatusMessageAction;
96    private KeyguardManager mKeyguardManager;
97    private View mFooterView;
98
99    private boolean mEmptyLoaderRunning;
100    private boolean mCallLogFetched;
101    private boolean mVoicemailStatusFetched;
102
103    private float mExpandedItemTranslationZ;
104    private int mFadeInDuration;
105    private int mFadeInStartDelay;
106    private int mFadeOutDuration;
107    private int mExpandCollapseDuration;
108
109    private final Handler mHandler = new Handler();
110
111    private class CustomContentObserver extends ContentObserver {
112        public CustomContentObserver() {
113            super(mHandler);
114        }
115        @Override
116        public void onChange(boolean selfChange) {
117            mRefreshDataRequired = true;
118        }
119    }
120
121    // See issue 6363009
122    private final ContentObserver mCallLogObserver = new CustomContentObserver();
123    private final ContentObserver mContactsObserver = new CustomContentObserver();
124    private final ContentObserver mVoicemailStatusObserver = new CustomContentObserver();
125    private boolean mRefreshDataRequired = true;
126
127    // Exactly same variable is in Fragment as a package private.
128    private boolean mMenuVisible = true;
129
130    // Default to all calls.
131    private int mCallTypeFilter = CallLogQueryHandler.CALL_TYPE_ALL;
132
133    // Log limit - if no limit is specified, then the default in {@link CallLogQueryHandler}
134    // will be used.
135    private int mLogLimit = -1;
136
137    // Date limit (in millis since epoch) - when non-zero, only calls which occurred on or after
138    // the date filter are included.  If zero, no date-based filtering occurs.
139    private long mDateLimit = 0;
140
141    // Whether or not to show the Show call history footer view
142    private boolean mHasFooterView = false;
143
144    public CallLogFragment() {
145        this(CallLogQueryHandler.CALL_TYPE_ALL, -1);
146    }
147
148    public CallLogFragment(int filterType) {
149        this(filterType, -1);
150    }
151
152    public CallLogFragment(int filterType, int logLimit) {
153        super();
154        mCallTypeFilter = filterType;
155        mLogLimit = logLimit;
156    }
157
158    /**
159     * Creates a call log fragment, filtering to include only calls of the desired type, occurring
160     * after the specified date.
161     * @param filterType type of calls to include.
162     * @param dateLimit limits results to calls occurring on or after the specified date.
163     */
164    public CallLogFragment(int filterType, long dateLimit) {
165        this(filterType, -1, dateLimit);
166    }
167
168    /**
169     * Creates a call log fragment, filtering to include only calls of the desired type, occurring
170     * after the specified date.  Also provides a means to limit the number of results returned.
171     * @param filterType type of calls to include.
172     * @param logLimit limits the number of results to return.
173     * @param dateLimit limits results to calls occurring on or after the specified date.
174     */
175    public CallLogFragment(int filterType, int logLimit, long dateLimit) {
176        this(filterType, logLimit);
177        mDateLimit = dateLimit;
178    }
179
180    @Override
181    public void onCreate(Bundle state) {
182        super.onCreate(state);
183        if (state != null) {
184            mCallTypeFilter = state.getInt(KEY_FILTER_TYPE, mCallTypeFilter);
185            mLogLimit = state.getInt(KEY_LOG_LIMIT, mLogLimit);
186            mDateLimit = state.getLong(KEY_DATE_LIMIT, mDateLimit);
187            mHasFooterView = state.getBoolean(KEY_SHOW_FOOTER, mHasFooterView);
188            mIsReportDialogShowing = state.getBoolean(KEY_IS_REPORT_DIALOG_SHOWING,
189                    mIsReportDialogShowing);
190            mReportDialogNumber = state.getString(KEY_REPORT_DIALOG_NUMBER, mReportDialogNumber);
191        }
192
193        String currentCountryIso = GeoUtil.getCurrentCountryIso(getActivity());
194        mAdapter = ObjectFactory.newCallLogAdapter(getActivity(), this,
195                new ContactInfoHelper(getActivity(), currentCountryIso), this, this, true);
196        setListAdapter(mAdapter);
197        mCallLogQueryHandler = new CallLogQueryHandler(getActivity().getContentResolver(),
198                this, mLogLimit);
199        mKeyguardManager =
200                (KeyguardManager) getActivity().getSystemService(Context.KEYGUARD_SERVICE);
201        getActivity().getContentResolver().registerContentObserver(CallLog.CONTENT_URI, true,
202                mCallLogObserver);
203        getActivity().getContentResolver().registerContentObserver(
204                ContactsContract.Contacts.CONTENT_URI, true, mContactsObserver);
205        getActivity().getContentResolver().registerContentObserver(
206                Status.CONTENT_URI, true, mVoicemailStatusObserver);
207        setHasOptionsMenu(true);
208        updateCallList(mCallTypeFilter, mDateLimit);
209
210        mExpandedItemTranslationZ =
211                getResources().getDimension(R.dimen.call_log_expanded_translation_z);
212        mFadeInDuration = getResources().getInteger(R.integer.call_log_actions_fade_in_duration);
213        mFadeInStartDelay = getResources().getInteger(R.integer.call_log_actions_fade_start);
214        mFadeOutDuration = getResources().getInteger(R.integer.call_log_actions_fade_out_duration);
215        mExpandCollapseDuration = getResources().getInteger(
216                R.integer.call_log_expand_collapse_duration);
217
218        if (mIsReportDialogShowing) {
219            DialogFragment df = ObjectFactory.getReportDialogFragment(mReportDialogNumber);
220            if (df != null) {
221                df.setTargetFragment(this, 0);
222                df.show(getActivity().getFragmentManager(), REPORT_DIALOG_TAG);
223            }
224        }
225    }
226
227    /** Called by the CallLogQueryHandler when the list of calls has been fetched or updated. */
228    @Override
229    public boolean onCallsFetched(Cursor cursor) {
230        if (getActivity() == null || getActivity().isFinishing()) {
231            // Return false; we did not take ownership of the cursor
232            return false;
233        }
234        mAdapter.setLoading(false);
235        mAdapter.changeCursor(cursor);
236        // This will update the state of the "Clear call log" menu item.
237        getActivity().invalidateOptionsMenu();
238        if (mScrollToTop) {
239            final ListView listView = getListView();
240            // The smooth-scroll animation happens over a fixed time period.
241            // As a result, if it scrolls through a large portion of the list,
242            // each frame will jump so far from the previous one that the user
243            // will not experience the illusion of downward motion.  Instead,
244            // if we're not already near the top of the list, we instantly jump
245            // near the top, and animate from there.
246            if (listView.getFirstVisiblePosition() > 5) {
247                listView.setSelection(5);
248            }
249            // Workaround for framework issue: the smooth-scroll doesn't
250            // occur if setSelection() is called immediately before.
251            mHandler.post(new Runnable() {
252               @Override
253               public void run() {
254                   if (getActivity() == null || getActivity().isFinishing()) {
255                       return;
256                   }
257                   listView.smoothScrollToPosition(0);
258               }
259            });
260
261            mScrollToTop = false;
262        }
263        mCallLogFetched = true;
264        destroyEmptyLoaderIfAllDataFetched();
265        return true;
266    }
267
268    /**
269     * Called by {@link CallLogQueryHandler} after a successful query to voicemail status provider.
270     */
271    @Override
272    public void onVoicemailStatusFetched(Cursor statusCursor) {
273        if (getActivity() == null || getActivity().isFinishing()) {
274            return;
275        }
276        updateVoicemailStatusMessage(statusCursor);
277
278        int activeSources = mVoicemailStatusHelper.getNumberActivityVoicemailSources(statusCursor);
279        setVoicemailSourcesAvailable(activeSources != 0);
280        mVoicemailStatusFetched = true;
281        destroyEmptyLoaderIfAllDataFetched();
282    }
283
284    private void destroyEmptyLoaderIfAllDataFetched() {
285        if (mCallLogFetched && mVoicemailStatusFetched && mEmptyLoaderRunning) {
286            mEmptyLoaderRunning = false;
287            getLoaderManager().destroyLoader(EMPTY_LOADER_ID);
288        }
289    }
290
291    /** Sets whether there are any voicemail sources available in the platform. */
292    private void setVoicemailSourcesAvailable(boolean voicemailSourcesAvailable) {
293        if (mVoicemailSourcesAvailable == voicemailSourcesAvailable) return;
294        mVoicemailSourcesAvailable = voicemailSourcesAvailable;
295
296        Activity activity = getActivity();
297        if (activity != null) {
298            // This is so that the options menu content is updated.
299            activity.invalidateOptionsMenu();
300        }
301    }
302
303    @Override
304    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
305        View view = inflater.inflate(R.layout.call_log_fragment, container, false);
306        mVoicemailStatusHelper = new VoicemailStatusHelperImpl();
307        mStatusMessageView = view.findViewById(R.id.voicemail_status);
308        mStatusMessageText = (TextView) view.findViewById(R.id.voicemail_status_message);
309        mStatusMessageAction = (TextView) view.findViewById(R.id.voicemail_status_action);
310        return view;
311    }
312
313    @Override
314    public void onViewCreated(View view, Bundle savedInstanceState) {
315        super.onViewCreated(view, savedInstanceState);
316        getListView().setEmptyView(view.findViewById(R.id.empty_list_view));
317        getListView().setItemsCanFocus(true);
318        maybeAddFooterView();
319
320        updateEmptyMessage(mCallTypeFilter);
321    }
322
323    /**
324     * Based on the new intent, decide whether the list should be configured
325     * to scroll up to display the first item.
326     */
327    public void configureScreenFromIntent(Intent newIntent) {
328        // Typically, when switching to the call-log we want to show the user
329        // the same section of the list that they were most recently looking
330        // at.  However, under some circumstances, we want to automatically
331        // scroll to the top of the list to present the newest call items.
332        // For example, immediately after a call is finished, we want to
333        // display information about that call.
334        mScrollToTop = Calls.CONTENT_TYPE.equals(newIntent.getType());
335    }
336
337    @Override
338    public void onStart() {
339        // Start the empty loader now to defer other fragments.  We destroy it when both calllog
340        // and the voicemail status are fetched.
341        getLoaderManager().initLoader(EMPTY_LOADER_ID, null,
342                new EmptyLoader.Callback(getActivity()));
343        mEmptyLoaderRunning = true;
344        super.onStart();
345    }
346
347    @Override
348    public void onResume() {
349        super.onResume();
350        refreshData();
351    }
352
353    private void updateVoicemailStatusMessage(Cursor statusCursor) {
354        List<StatusMessage> messages = mVoicemailStatusHelper.getStatusMessages(statusCursor);
355        if (messages.size() == 0) {
356            mStatusMessageView.setVisibility(View.GONE);
357        } else {
358            mStatusMessageView.setVisibility(View.VISIBLE);
359            // TODO: Change the code to show all messages. For now just pick the first message.
360            final StatusMessage message = messages.get(0);
361            if (message.showInCallLog()) {
362                mStatusMessageText.setText(message.callLogMessageId);
363            }
364            if (message.actionMessageId != -1) {
365                mStatusMessageAction.setText(message.actionMessageId);
366            }
367            if (message.actionUri != null) {
368                mStatusMessageAction.setVisibility(View.VISIBLE);
369                mStatusMessageAction.setOnClickListener(new View.OnClickListener() {
370                    @Override
371                    public void onClick(View v) {
372                        getActivity().startActivity(
373                                new Intent(Intent.ACTION_VIEW, message.actionUri));
374                    }
375                });
376            } else {
377                mStatusMessageAction.setVisibility(View.GONE);
378            }
379        }
380    }
381
382    @Override
383    public void onPause() {
384        super.onPause();
385        // Kill the requests thread
386        mAdapter.stopRequestProcessing();
387    }
388
389    @Override
390    public void onStop() {
391        super.onStop();
392        updateOnExit();
393    }
394
395    @Override
396    public void onDestroy() {
397        super.onDestroy();
398        mAdapter.stopRequestProcessing();
399        mAdapter.changeCursor(null);
400        getActivity().getContentResolver().unregisterContentObserver(mCallLogObserver);
401        getActivity().getContentResolver().unregisterContentObserver(mContactsObserver);
402        getActivity().getContentResolver().unregisterContentObserver(mVoicemailStatusObserver);
403    }
404
405    @Override
406    public void onSaveInstanceState(Bundle outState) {
407        super.onSaveInstanceState(outState);
408        outState.putInt(KEY_FILTER_TYPE, mCallTypeFilter);
409        outState.putInt(KEY_LOG_LIMIT, mLogLimit);
410        outState.putLong(KEY_DATE_LIMIT, mDateLimit);
411        outState.putBoolean(KEY_SHOW_FOOTER, mHasFooterView);
412        outState.putBoolean(KEY_IS_REPORT_DIALOG_SHOWING, mIsReportDialogShowing);
413        outState.putString(KEY_REPORT_DIALOG_NUMBER, mReportDialogNumber);
414    }
415
416    @Override
417    public void fetchCalls() {
418        mCallLogQueryHandler.fetchCalls(mCallTypeFilter, mDateLimit);
419    }
420
421    public void startCallsQuery() {
422        mAdapter.setLoading(true);
423        mCallLogQueryHandler.fetchCalls(mCallTypeFilter, mDateLimit);
424    }
425
426    private void startVoicemailStatusQuery() {
427        mCallLogQueryHandler.fetchVoicemailStatus();
428    }
429
430    private void updateCallList(int filterType, long dateLimit) {
431        mCallLogQueryHandler.fetchCalls(filterType, dateLimit);
432    }
433
434    private void updateEmptyMessage(int filterType) {
435        final int messageId;
436        switch (filterType) {
437            case Calls.MISSED_TYPE:
438                messageId = R.string.recentMissed_empty;
439                break;
440            case Calls.VOICEMAIL_TYPE:
441                messageId = R.string.recentVoicemails_empty;
442                break;
443            case CallLogQueryHandler.CALL_TYPE_ALL:
444                messageId = R.string.recentCalls_empty;
445                break;
446            default:
447                throw new IllegalArgumentException("Unexpected filter type in CallLogFragment: "
448                        + filterType);
449        }
450        DialerUtils.configureEmptyListView(
451                getListView().getEmptyView(), R.drawable.empty_call_log, messageId, getResources());
452    }
453
454    CallLogAdapter getAdapter() {
455        return mAdapter;
456    }
457
458    @Override
459    public void setMenuVisibility(boolean menuVisible) {
460        super.setMenuVisibility(menuVisible);
461        if (mMenuVisible != menuVisible) {
462            mMenuVisible = menuVisible;
463            if (!menuVisible) {
464                updateOnExit();
465            } else if (isResumed()) {
466                refreshData();
467            }
468        }
469    }
470
471    /** Requests updates to the data to be shown. */
472    private void refreshData() {
473        // Prevent unnecessary refresh.
474        if (mRefreshDataRequired) {
475            // Mark all entries in the contact info cache as out of date, so they will be looked up
476            // again once being shown.
477            mAdapter.invalidateCache();
478            startCallsQuery();
479            startVoicemailStatusQuery();
480            updateOnEntry();
481            mRefreshDataRequired = false;
482        }
483    }
484
485    /** Updates call data and notification state while leaving the call log tab. */
486    private void updateOnExit() {
487        updateOnTransition(false);
488    }
489
490    /** Updates call data and notification state while entering the call log tab. */
491    private void updateOnEntry() {
492        updateOnTransition(true);
493    }
494
495    // TODO: Move to CallLogActivity
496    private void updateOnTransition(boolean onEntry) {
497        // We don't want to update any call data when keyguard is on because the user has likely not
498        // seen the new calls yet.
499        // This might be called before onCreate() and thus we need to check null explicitly.
500        if (mKeyguardManager != null && !mKeyguardManager.inKeyguardRestrictedInputMode()) {
501            // On either of the transitions we update the missed call and voicemail notifications.
502            // While exiting we additionally consume all missed calls (by marking them as read).
503            mCallLogQueryHandler.markNewCallsAsOld();
504            if (!onEntry) {
505                mCallLogQueryHandler.markMissedCallsAsRead();
506            }
507            CallLogNotificationsHelper.removeMissedCallNotifications(getActivity());
508            CallLogNotificationsHelper.updateVoicemailNotifications(getActivity());
509        }
510    }
511
512    /**
513     * Enables/disables the showing of the view full call history footer
514     *
515     * @param hasFooterView Whether or not to show the footer
516     */
517    public void setHasFooterView(boolean hasFooterView) {
518        mHasFooterView = hasFooterView;
519        maybeAddFooterView();
520    }
521
522    /**
523     * Determine whether or not the footer view should be added to the listview. If getView()
524     * is null, which means onCreateView hasn't been called yet, defer the addition of the footer
525     * until onViewCreated has been called.
526     */
527    private void maybeAddFooterView() {
528        if (!mHasFooterView || getView() == null) {
529            return;
530        }
531
532        if (mFooterView == null) {
533            mFooterView = getActivity().getLayoutInflater().inflate(
534                    R.layout.recents_list_footer, getListView(), false);
535            mFooterView.setOnClickListener(new OnClickListener() {
536                @Override
537                public void onClick(View v) {
538                    ((HostInterface) getActivity()).showCallHistory();
539                }
540            });
541        }
542
543        final ListView listView = getListView();
544        listView.removeFooterView(mFooterView);
545        listView.addFooterView(mFooterView);
546
547        ViewUtil.addBottomPaddingToListViewForFab(listView, getResources());
548    }
549
550    @Override
551    public void onItemExpanded(final View view) {
552        final int startingHeight = view.getHeight();
553        final CallLogListItemViews viewHolder = (CallLogListItemViews) view.getTag();
554        final ViewTreeObserver observer = getListView().getViewTreeObserver();
555        observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
556            @Override
557            public boolean onPreDraw() {
558                // We don't want to continue getting called for every draw.
559                if (observer.isAlive()) {
560                    observer.removeOnPreDrawListener(this);
561                }
562                // Calculate some values to help with the animation.
563                final int endingHeight = view.getHeight();
564                final int distance = Math.abs(endingHeight - startingHeight);
565                final int baseHeight = Math.min(endingHeight, startingHeight);
566                final boolean isExpand = endingHeight > startingHeight;
567
568                // Set the views back to the start state of the animation
569                view.getLayoutParams().height = startingHeight;
570                if (!isExpand) {
571                    viewHolder.actionsView.setVisibility(View.VISIBLE);
572                }
573                CallLogAdapter.expandVoicemailTranscriptionView(viewHolder, !isExpand);
574
575                // Set up the fade effect for the action buttons.
576                if (isExpand) {
577                    // Start the fade in after the expansion has partly completed, otherwise it
578                    // will be mostly over before the expansion completes.
579                    viewHolder.actionsView.setAlpha(0f);
580                    viewHolder.actionsView.animate()
581                            .alpha(1f)
582                            .setStartDelay(mFadeInStartDelay)
583                            .setDuration(mFadeInDuration)
584                            .start();
585                } else {
586                    viewHolder.actionsView.setAlpha(1f);
587                    viewHolder.actionsView.animate()
588                            .alpha(0f)
589                            .setDuration(mFadeOutDuration)
590                            .start();
591                }
592                view.requestLayout();
593
594                // Set up the animator to animate the expansion and shadow depth.
595                ValueAnimator animator = isExpand ? ValueAnimator.ofFloat(0f, 1f)
596                        : ValueAnimator.ofFloat(1f, 0f);
597
598                // Figure out how much scrolling is needed to make the view fully visible.
599                final Rect localVisibleRect = new Rect();
600                view.getLocalVisibleRect(localVisibleRect);
601                final int scrollingNeeded = localVisibleRect.top > 0 ? -localVisibleRect.top
602                        : view.getMeasuredHeight() - localVisibleRect.height();
603                final ListView listView = getListView();
604                animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
605
606                    private int mCurrentScroll = 0;
607
608                    @Override
609                    public void onAnimationUpdate(ValueAnimator animator) {
610                        Float value = (Float) animator.getAnimatedValue();
611
612                        // For each value from 0 to 1, animate the various parts of the layout.
613                        view.getLayoutParams().height = (int) (value * distance + baseHeight);
614                        float z = mExpandedItemTranslationZ * value;
615                        viewHolder.callLogEntryView.setTranslationZ(z);
616                        view.setTranslationZ(z); // WAR
617                        view.requestLayout();
618
619                        if (isExpand) {
620                            if (listView != null) {
621                                int scrollBy = (int) (value * scrollingNeeded) - mCurrentScroll;
622                                listView.smoothScrollBy(scrollBy, /* duration = */ 0);
623                                mCurrentScroll += scrollBy;
624                            }
625                        }
626                    }
627                });
628                // Set everything to their final values when the animation's done.
629                animator.addListener(new AnimatorListenerAdapter() {
630                    @Override
631                    public void onAnimationEnd(Animator animation) {
632                        view.getLayoutParams().height = LayoutParams.WRAP_CONTENT;
633
634                        if (!isExpand) {
635                            viewHolder.actionsView.setVisibility(View.GONE);
636                        } else {
637                            // This seems like it should be unnecessary, but without this, after
638                            // navigating out of the activity and then back, the action view alpha
639                            // is defaulting to the value (0) at the start of the expand animation.
640                            viewHolder.actionsView.setAlpha(1);
641                        }
642                        CallLogAdapter.expandVoicemailTranscriptionView(viewHolder, isExpand);
643                    }
644                });
645
646                animator.setDuration(mExpandCollapseDuration);
647                animator.start();
648
649                // Return false so this draw does not occur to prevent the final frame from
650                // being drawn for the single frame before the animations start.
651                return false;
652            }
653        });
654    }
655
656    /**
657     * Retrieves the call log view for the specified call Id.  If the view is not currently
658     * visible, returns null.
659     *
660     * @param callId The call Id.
661     * @return The call log view.
662     */
663    @Override
664    public View getViewForCallId(long callId) {
665        ListView listView = getListView();
666
667        int firstPosition = listView.getFirstVisiblePosition();
668        int lastPosition = listView.getLastVisiblePosition();
669
670        for (int position = 0; position <= lastPosition - firstPosition; position++) {
671            View view = listView.getChildAt(position);
672
673            if (view != null) {
674                final CallLogListItemViews viewHolder = (CallLogListItemViews) view.getTag();
675                if (viewHolder != null && viewHolder.rowId == callId) {
676                    return view;
677                }
678            }
679        }
680
681        return null;
682    }
683
684    public void onBadDataReported(String number) {
685        mIsReportDialogShowing = false;
686        if (number == null) {
687            return;
688        }
689        mAdapter.onBadDataReported(number);
690        mAdapter.notifyDataSetChanged();
691    }
692
693    public void onReportButtonClick(String number) {
694        DialogFragment df = ObjectFactory.getReportDialogFragment(number);
695        if (df != null) {
696            df.setTargetFragment(this, 0);
697            df.show(getActivity().getFragmentManager(), REPORT_DIALOG_TAG);
698            mReportDialogNumber = number;
699            mIsReportDialogShowing = true;
700        }
701    }
702}
703