1package com.android.dialer.list;
2
3import android.animation.LayoutTransition;
4import android.app.ActionBar;
5import android.app.Fragment;
6import android.app.FragmentManager;
7import android.content.Context;
8import android.content.SharedPreferences;
9import android.database.Cursor;
10import android.os.Bundle;
11import android.support.v13.app.FragmentPagerAdapter;
12import android.support.v4.view.ViewPager;
13import android.support.v4.view.ViewPager.OnPageChangeListener;
14import android.util.Log;
15import android.view.LayoutInflater;
16import android.view.View;
17import android.view.ViewGroup;
18import android.widget.AbsListView;
19import android.widget.ListView;
20
21import com.android.contacts.common.GeoUtil;
22import com.android.contacts.common.list.ViewPagerTabs;
23import com.android.dialer.DialtactsActivity;
24import com.android.dialer.R;
25import com.android.dialer.calllog.CallLogAdapter;
26import com.android.dialer.calllog.CallLogFragment;
27import com.android.dialer.calllog.CallLogQuery;
28import com.android.dialer.calllog.CallLogQueryHandler;
29import com.android.dialer.calllog.ContactInfoHelper;
30import com.android.dialer.list.ShortcutCardsAdapter.SwipeableShortcutCard;
31import com.android.dialer.util.DialerUtils;
32import com.android.dialer.widget.OverlappingPaneLayout;
33import com.android.dialer.widget.OverlappingPaneLayout.PanelSlideCallbacks;
34import com.android.dialerbind.analytics.AnalyticsFragment;
35import com.android.dialerbind.ObjectFactory;
36
37import java.util.ArrayList;
38
39/**
40 * Fragment that is used as the main screen of the Dialer.
41 *
42 * Contains a ViewPager that contains various contact lists like the Speed Dial list and the
43 * All Contacts list. This will also eventually contain the logic that allows sliding the
44 * ViewPager containing the lists up above the shortcut cards and pin it against the top of the
45 * screen.
46 */
47public class ListsFragment extends AnalyticsFragment implements CallLogQueryHandler.Listener,
48        CallLogAdapter.CallFetcher, ViewPager.OnPageChangeListener {
49
50    private static final boolean DEBUG = DialtactsActivity.DEBUG;
51    private static final String TAG = "ListsFragment";
52
53    public static final int TAB_INDEX_SPEED_DIAL = 0;
54    public static final int TAB_INDEX_RECENTS = 1;
55    public static final int TAB_INDEX_ALL_CONTACTS = 2;
56
57    public static final int TAB_INDEX_COUNT = 3;
58
59    private static final int MAX_RECENTS_ENTRIES = 20;
60    // Oldest recents entry to display is 2 weeks old.
61    private static final long OLDEST_RECENTS_DATE = 1000L * 60 * 60 * 24 * 14;
62
63    private static final String KEY_LAST_DISMISSED_CALL_SHORTCUT_DATE =
64            "key_last_dismissed_call_shortcut_date";
65
66    public static final float REMOVE_VIEW_SHOWN_ALPHA = 0.5f;
67    public static final float REMOVE_VIEW_HIDDEN_ALPHA = 1;
68
69    public interface HostInterface {
70        public void showCallHistory();
71        public int getActionBarHeight();
72        public void setActionBarHideOffset(int offset);
73    }
74
75    private ActionBar mActionBar;
76    private ViewPager mViewPager;
77    private ViewPagerTabs mViewPagerTabs;
78    private ViewPagerAdapter mViewPagerAdapter;
79    private ListView mShortcutCardsListView;
80    private RemoveView mRemoveView;
81    private View mRemoveViewContent;
82    private SpeedDialFragment mSpeedDialFragment;
83    private CallLogFragment mRecentsFragment;
84    private AllContactsFragment mAllContactsFragment;
85    private ArrayList<OnPageChangeListener> mOnPageChangeListeners =
86            new ArrayList<OnPageChangeListener>();
87
88    private String[] mTabTitles;
89
90    private ShortcutCardsAdapter mMergedAdapter;
91    private CallLogAdapter mCallLogAdapter;
92    private CallLogQueryHandler mCallLogQueryHandler;
93
94    private boolean mIsPanelOpen = true;
95
96    /**
97     * Call shortcuts older than this date (persisted in shared preferences) will not show up in
98     * at the top of the screen
99     */
100    private long mLastCallShortcutDate = 0;
101
102    /**
103     * The date of the current call shortcut that is showing on screen.
104     */
105    private long mCurrentCallShortcutDate = 0;
106
107    private PanelSlideCallbacks mPanelSlideCallbacks = new PanelSlideCallbacks() {
108        @Override
109        public void onPanelSlide(View panel, float slideOffset) {
110            // For every 1 percent that the panel is slid upwards, clip 1 percent off the top
111            // edge of the shortcut card, to achieve the animated effect of the shortcut card
112            // being pushed out of view when the panel is slid upwards. slideOffset is 1 when
113            // the shortcut card is fully exposed, and 0 when completely hidden.
114            float ratioCardHidden = (1 - slideOffset);
115            if (mShortcutCardsListView.getChildCount() > 0) {
116                final SwipeableShortcutCard v =
117                        (SwipeableShortcutCard) mShortcutCardsListView.getChildAt(0);
118                v.clipCard(ratioCardHidden);
119            }
120
121            if (mActionBar != null) {
122                // Amount of available space that is not being hidden by the bottom pane
123                final int topPaneHeight = (int) (slideOffset * mShortcutCardsListView.getHeight());
124
125                final int availableActionBarHeight =
126                        Math.min(mActionBar.getHeight(), topPaneHeight);
127                ((HostInterface) getActivity()).setActionBarHideOffset(
128                        mActionBar.getHeight() - availableActionBarHeight);
129
130                if (!mActionBar.isShowing()) {
131                    mActionBar.show();
132                }
133            }
134        }
135
136        @Override
137        public void onPanelOpened(View panel) {
138            if (DEBUG) {
139                Log.d(TAG, "onPanelOpened");
140            }
141            mIsPanelOpen = true;
142        }
143
144        @Override
145        public void onPanelClosed(View panel) {
146            if (DEBUG) {
147                Log.d(TAG, "onPanelClosed");
148            }
149            mIsPanelOpen = false;
150        }
151
152        @Override
153        public void onPanelFlingReachesEdge(int velocityY) {
154            if (getCurrentListView() != null) {
155                getCurrentListView().fling(velocityY);
156            }
157        }
158
159        @Override
160        public boolean isScrollableChildUnscrolled() {
161            final AbsListView listView = getCurrentListView();
162            return listView != null && (listView.getChildCount() == 0
163                    || listView.getChildAt(0).getTop() == listView.getPaddingTop());
164        }
165    };
166
167    private AbsListView getCurrentListView() {
168        final int position = mViewPager.getCurrentItem();
169        switch (getRtlPosition(position)) {
170            case TAB_INDEX_SPEED_DIAL:
171                return mSpeedDialFragment == null ? null : mSpeedDialFragment.getListView();
172            case TAB_INDEX_RECENTS:
173                return mRecentsFragment == null ? null : mRecentsFragment.getListView();
174            case TAB_INDEX_ALL_CONTACTS:
175                return mAllContactsFragment == null ? null : mAllContactsFragment.getListView();
176        }
177        throw new IllegalStateException("No fragment at position " + position);
178    }
179
180    public class ViewPagerAdapter extends FragmentPagerAdapter {
181        public ViewPagerAdapter(FragmentManager fm) {
182            super(fm);
183        }
184
185        @Override
186        public long getItemId(int position) {
187            return getRtlPosition(position);
188        }
189
190        @Override
191        public Fragment getItem(int position) {
192            switch (getRtlPosition(position)) {
193                case TAB_INDEX_SPEED_DIAL:
194                    mSpeedDialFragment = new SpeedDialFragment();
195                    return mSpeedDialFragment;
196                case TAB_INDEX_RECENTS:
197                    mRecentsFragment = new CallLogFragment(CallLogQueryHandler.CALL_TYPE_ALL,
198                            MAX_RECENTS_ENTRIES, System.currentTimeMillis() - OLDEST_RECENTS_DATE);
199                    mRecentsFragment.setHasFooterView(true);
200                    return mRecentsFragment;
201                case TAB_INDEX_ALL_CONTACTS:
202                    mAllContactsFragment = new AllContactsFragment();
203                    return mAllContactsFragment;
204            }
205            throw new IllegalStateException("No fragment at position " + position);
206        }
207
208        @Override
209        public Object instantiateItem(ViewGroup container, int position) {
210            // On rotation the FragmentManager handles rotation. Therefore getItem() isn't called.
211            // Copy the fragments that the FragmentManager finds so that we can store them in
212            // instance variables for later.
213            final Fragment fragment =
214                    (Fragment) super.instantiateItem(container, position);
215            if (fragment instanceof SpeedDialFragment) {
216                mSpeedDialFragment = (SpeedDialFragment) fragment;
217            } else if (fragment instanceof CallLogFragment) {
218                mRecentsFragment = (CallLogFragment) fragment;
219            } else if (fragment instanceof AllContactsFragment) {
220                mAllContactsFragment = (AllContactsFragment) fragment;
221            }
222            return fragment;
223        }
224
225        @Override
226        public int getCount() {
227            return TAB_INDEX_COUNT;
228        }
229
230        @Override
231        public CharSequence getPageTitle(int position) {
232            return mTabTitles[position];
233        }
234    }
235
236    @Override
237    public void onCreate(Bundle savedInstanceState) {
238        super.onCreate(savedInstanceState);
239
240        mCallLogQueryHandler = new CallLogQueryHandler(getActivity().getContentResolver(),
241                this, 1);
242        final String currentCountryIso = GeoUtil.getCurrentCountryIso(getActivity());
243        mCallLogAdapter = ObjectFactory.newCallLogAdapter(getActivity(), this,
244                new ContactInfoHelper(getActivity(), currentCountryIso), null, null, false);
245
246        mMergedAdapter = new ShortcutCardsAdapter(getActivity(), this, mCallLogAdapter);
247    }
248
249    @Override
250    public void onStart() {
251        super.onStart();
252    }
253
254    @Override
255    public void onResume() {
256        super.onResume();
257        final SharedPreferences prefs = getActivity().getSharedPreferences(
258                DialtactsActivity.SHARED_PREFS_NAME, Context.MODE_PRIVATE);
259        mLastCallShortcutDate = prefs.getLong(KEY_LAST_DISMISSED_CALL_SHORTCUT_DATE, 0);
260        mActionBar = getActivity().getActionBar();
261        fetchCalls();
262        mCallLogAdapter.setLoading(true);
263    }
264
265    @Override
266    public void onPause() {
267        // Wipe the cache to refresh the call shortcut item. This is not that expensive because
268        // it only contains one item.
269        mCallLogAdapter.invalidateCache();
270        super.onPause();
271    }
272
273    @Override
274    public void onDestroy() {
275        mCallLogAdapter.stopRequestProcessing();
276        super.onDestroy();
277    }
278
279    @Override
280    public View onCreateView(LayoutInflater inflater, ViewGroup container,
281            Bundle savedInstanceState) {
282        final View parentView = inflater.inflate(R.layout.lists_fragment, container, false);
283        mViewPager = (ViewPager) parentView.findViewById(R.id.lists_pager);
284        mViewPagerAdapter = new ViewPagerAdapter(getChildFragmentManager());
285        mViewPager.setAdapter(mViewPagerAdapter);
286        mViewPager.setOffscreenPageLimit(2);
287        mViewPager.setOnPageChangeListener(this);
288        mViewPager.setCurrentItem(getRtlPosition(TAB_INDEX_SPEED_DIAL));
289
290        mTabTitles = new String[TAB_INDEX_COUNT];
291        mTabTitles[TAB_INDEX_SPEED_DIAL] = getResources().getString(R.string.tab_speed_dial);
292        mTabTitles[TAB_INDEX_RECENTS] = getResources().getString(R.string.tab_recents);
293        mTabTitles[TAB_INDEX_ALL_CONTACTS] = getResources().getString(R.string.tab_all_contacts);
294
295        mViewPagerTabs = (ViewPagerTabs) parentView.findViewById(R.id.lists_pager_header);
296        mViewPagerTabs.setViewPager(mViewPager);
297        addOnPageChangeListener(mViewPagerTabs);
298
299        mShortcutCardsListView = (ListView) parentView.findViewById(R.id.shortcut_card_list);
300        mShortcutCardsListView.setAdapter(mMergedAdapter);
301
302        mRemoveView = (RemoveView) parentView.findViewById(R.id.remove_view);
303        mRemoveViewContent = parentView.findViewById(R.id.remove_view_content);
304
305        setupPaneLayout((OverlappingPaneLayout) parentView);
306
307        return parentView;
308    }
309
310    @Override
311    public void onVoicemailStatusFetched(Cursor statusCursor) {
312        // no-op
313    }
314
315    @Override
316    public boolean onCallsFetched(Cursor cursor) {
317        mCallLogAdapter.setLoading(false);
318
319        // Save the date of the most recent call log item
320        if (cursor != null && cursor.moveToFirst()) {
321            mCurrentCallShortcutDate = cursor.getLong(CallLogQuery.DATE);
322        }
323
324        mCallLogAdapter.changeCursor(cursor);
325        mMergedAdapter.notifyDataSetChanged();
326        // Return true; took ownership of cursor
327        return true;
328    }
329
330    @Override
331    public void fetchCalls() {
332        mCallLogQueryHandler.fetchCalls(CallLogQueryHandler.CALL_TYPE_ALL, mLastCallShortcutDate);
333    }
334
335    public void dismissShortcut(View view) {
336        mLastCallShortcutDate = mCurrentCallShortcutDate;
337        final SharedPreferences prefs = view.getContext().getSharedPreferences(
338                DialtactsActivity.SHARED_PREFS_NAME, Context.MODE_PRIVATE);
339        prefs.edit().putLong(KEY_LAST_DISMISSED_CALL_SHORTCUT_DATE, mLastCallShortcutDate)
340                .apply();
341        fetchCalls();
342    }
343
344    public void addOnPageChangeListener(OnPageChangeListener onPageChangeListener) {
345        if (!mOnPageChangeListeners.contains(onPageChangeListener)) {
346            mOnPageChangeListeners.add(onPageChangeListener);
347        }
348    }
349
350    @Override
351    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
352        final int count = mOnPageChangeListeners.size();
353        for (int i = 0; i < count; i++) {
354            mOnPageChangeListeners.get(i).onPageScrolled(position, positionOffset,
355                    positionOffsetPixels);
356        }
357    }
358
359    @Override
360    public void onPageSelected(int position) {
361        if (position == TAB_INDEX_SPEED_DIAL && mSpeedDialFragment != null) {
362            mSpeedDialFragment.sendScreenView();
363        } else if (position == TAB_INDEX_RECENTS && mRecentsFragment != null) {
364            mRecentsFragment.sendScreenView();
365        } else if (position == TAB_INDEX_ALL_CONTACTS && mAllContactsFragment != null) {
366            mAllContactsFragment.sendScreenView();
367        }
368        final int count = mOnPageChangeListeners.size();
369        for (int i = 0; i < count; i++) {
370            mOnPageChangeListeners.get(i).onPageSelected(position);
371        }
372    }
373
374    @Override
375    public void onPageScrollStateChanged(int state) {
376        final int count = mOnPageChangeListeners.size();
377        for (int i = 0; i < count; i++) {
378            mOnPageChangeListeners.get(i).onPageScrollStateChanged(state);
379        }
380    }
381
382    public void showRemoveView(boolean show) {
383        mRemoveViewContent.setVisibility(show ? View.VISIBLE : View.GONE);
384        mRemoveView.setAlpha(show ? 0 : 1);
385        mRemoveView.animate().alpha(show ? 1 : 0).start();
386
387        if (mShortcutCardsListView.getChildCount() > 0) {
388            View v = mShortcutCardsListView.getChildAt(0);
389            v.animate().withLayer()
390                    .alpha(show ? REMOVE_VIEW_SHOWN_ALPHA : REMOVE_VIEW_HIDDEN_ALPHA)
391                    .start();
392        }
393    }
394
395    public boolean shouldShowActionBar() {
396        return mIsPanelOpen && mActionBar != null;
397    }
398
399    public boolean isPaneOpen() {
400        return mIsPanelOpen;
401    }
402
403    private void setupPaneLayout(OverlappingPaneLayout paneLayout) {
404        // TODO: Remove the notion of a capturable view. The entire view be slideable, once
405        // the framework better supports nested scrolling.
406        paneLayout.setCapturableView(mViewPagerTabs);
407        paneLayout.openPane();
408        paneLayout.setPanelSlideCallbacks(mPanelSlideCallbacks);
409        paneLayout.setIntermediatePinnedOffset(
410                ((HostInterface) getActivity()).getActionBarHeight());
411
412        LayoutTransition transition = paneLayout.getLayoutTransition();
413        // Turns on animations for all types of layout changes so that they occur for
414        // height changes.
415        transition.enableTransitionType(LayoutTransition.CHANGING);
416    }
417
418    public SpeedDialFragment getSpeedDialFragment() {
419        return mSpeedDialFragment;
420    }
421
422    public RemoveView getRemoveView() {
423        return mRemoveView;
424    }
425
426    public int getRtlPosition(int position) {
427        if (DialerUtils.isRtl()) {
428            return TAB_INDEX_COUNT - 1 - position;
429        }
430        return position;
431    }
432}
433