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