ContactDetailLayoutController.java revision 226dcdf399e0a637711f9a76b575b7a23d2c7782
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.contacts.detail;
18
19import com.android.contacts.ContactLoader;
20import com.android.contacts.NfcHandler;
21import com.android.contacts.R;
22import com.android.contacts.activities.ContactDetailActivity.FragmentKeyListener;
23
24import android.animation.Animator;
25import android.animation.Animator.AnimatorListener;
26import android.animation.ObjectAnimator;
27import android.app.Activity;
28import android.app.FragmentManager;
29import android.app.FragmentTransaction;
30import android.content.Context;
31import android.os.Bundle;
32import android.support.v4.view.ViewPager;
33import android.support.v4.view.ViewPager.OnPageChangeListener;
34import android.view.LayoutInflater;
35import android.view.View;
36import android.view.animation.AnimationUtils;
37import android.widget.AbsListView;
38import android.widget.AbsListView.OnScrollListener;
39
40/**
41 * Determines the layout of the contact card.
42 */
43public class ContactDetailLayoutController {
44
45    private static final String KEY_CONTACT_HAS_UPDATES = "contactHasUpdates";
46    private static final String KEY_CURRENT_PAGE_INDEX = "currentPageIndex";
47
48    private static final int TAB_INDEX_DETAIL = 0;
49    private static final int TAB_INDEX_UPDATES = 1;
50
51    /**
52     * There are 3 possible layouts for the contact detail screen:
53     * 1. TWO_COLUMN - Tall and wide screen so the 2 pages can be shown side-by-side
54     * 2. VIEW_PAGER_AND_TAB_CAROUSEL - Tall and narrow screen to allow swipe between the 2 pages
55     * 3. FRAGMENT_CAROUSEL- Short and wide screen to allow half of the other page to show at a time
56     */
57    private enum LayoutMode {
58        TWO_COLUMN, VIEW_PAGER_AND_TAB_CAROUSEL, FRAGMENT_CAROUSEL,
59    }
60
61    private final Activity mActivity;
62    private final LayoutInflater mLayoutInflater;
63    private final FragmentManager mFragmentManager;
64
65    private ContactDetailFragment mDetailFragment;
66    private ContactDetailUpdatesFragment mUpdatesFragment;
67
68    private View mDetailFragmentView;
69    private View mUpdatesFragmentView;
70
71    private final ViewPager mViewPager;
72    private ContactDetailViewPagerAdapter mViewPagerAdapter;
73    private int mViewPagerState;
74
75    private final ContactDetailTabCarousel mTabCarousel;
76    private final ContactDetailFragmentCarousel mFragmentCarousel;
77
78    private ContactDetailFragment.Listener mContactDetailFragmentListener;
79
80    private ContactLoader.Result mContactData;
81
82    private boolean mTabCarouselIsAnimating;
83    private boolean mContactHasUpdates;
84
85    private LayoutMode mLayoutMode;
86
87    public ContactDetailLayoutController(Activity activity, Bundle savedState,
88            FragmentManager fragmentManager, View viewContainer, ContactDetailFragment.Listener
89            contactDetailFragmentListener) {
90
91        if (fragmentManager == null) {
92            throw new IllegalStateException("Cannot initialize a ContactDetailLayoutController "
93                    + "without a non-null FragmentManager");
94        }
95
96        mActivity = activity;
97        mLayoutInflater = (LayoutInflater) activity.getSystemService(
98                Context.LAYOUT_INFLATER_SERVICE);
99        mFragmentManager = fragmentManager;
100        mContactDetailFragmentListener = contactDetailFragmentListener;
101
102        // Retrieve views in case this is view pager and carousel mode
103        mViewPager = (ViewPager) viewContainer.findViewById(R.id.pager);
104        mTabCarousel = (ContactDetailTabCarousel) viewContainer.findViewById(R.id.tab_carousel);
105
106        // Retrieve view in case this is in fragment carousel mode
107        mFragmentCarousel = (ContactDetailFragmentCarousel) viewContainer.findViewById(
108                R.id.fragment_carousel);
109
110        // Retrieve container views in case they are already in the XML layout
111        mDetailFragmentView = viewContainer.findViewById(R.id.about_fragment_container);
112        mUpdatesFragmentView = viewContainer.findViewById(R.id.updates_fragment_container);
113
114        // Determine the layout mode based on the presence of certain views in the layout XML.
115        if (mViewPager != null) {
116            mLayoutMode = LayoutMode.VIEW_PAGER_AND_TAB_CAROUSEL;
117        } else {
118            mLayoutMode = (mFragmentCarousel != null) ? LayoutMode.FRAGMENT_CAROUSEL :
119                    LayoutMode.TWO_COLUMN;
120        }
121
122        initialize(savedState);
123    }
124
125    private void initialize(Bundle savedState) {
126        boolean fragmentsAddedToFragmentManager = true;
127        mDetailFragment = (ContactDetailFragment) mFragmentManager.findFragmentByTag(
128                ContactDetailViewPagerAdapter.ABOUT_FRAGMENT_TAG);
129        mUpdatesFragment = (ContactDetailUpdatesFragment) mFragmentManager.findFragmentByTag(
130                ContactDetailViewPagerAdapter.UPDTES_FRAGMENT_TAG);
131
132        // If the detail fragment was found in the {@link FragmentManager} then we don't need to add
133        // it again. Otherwise, create the fragments dynamically and remember to add them to the
134        // {@link FragmentManager}.
135        if (mDetailFragment == null) {
136            mDetailFragment = new ContactDetailFragment();
137            mUpdatesFragment = new ContactDetailUpdatesFragment();
138            fragmentsAddedToFragmentManager = false;
139        }
140
141        mDetailFragment.setListener(mContactDetailFragmentListener);
142        NfcHandler.register(mActivity, mDetailFragment);
143
144        // Read from savedState if possible
145        int currentPageIndex = 0;
146        if (savedState != null) {
147            mContactHasUpdates = savedState.getBoolean(KEY_CONTACT_HAS_UPDATES);
148            currentPageIndex = savedState.getInt(KEY_CURRENT_PAGE_INDEX, 0);
149        }
150
151        switch (mLayoutMode) {
152            case VIEW_PAGER_AND_TAB_CAROUSEL: {
153                // Inflate 2 view containers to pass in as children to the {@link ViewPager},
154                // which will in turn be the parents to the mDetailFragment and mUpdatesFragment
155                // since the fragments must have the same parent view IDs in both landscape and
156                // portrait layouts.
157                mDetailFragmentView = mLayoutInflater.inflate(
158                        R.layout.contact_detail_about_fragment_container, mViewPager, false);
159                mUpdatesFragmentView = mLayoutInflater.inflate(
160                        R.layout.contact_detail_updates_fragment_container, mViewPager, false);
161
162                mViewPagerAdapter = new ContactDetailViewPagerAdapter();
163                mViewPagerAdapter.setAboutFragmentView(mDetailFragmentView);
164                mViewPagerAdapter.setUpdatesFragmentView(mUpdatesFragmentView);
165
166                mViewPager.addView(mDetailFragmentView);
167                mViewPager.addView(mUpdatesFragmentView);
168                mViewPager.setAdapter(mViewPagerAdapter);
169                mViewPager.setOnPageChangeListener(mOnPageChangeListener);
170
171                if (!fragmentsAddedToFragmentManager) {
172                    FragmentTransaction transaction = mFragmentManager.beginTransaction();
173                    transaction.add(R.id.about_fragment_container, mDetailFragment,
174                            ContactDetailViewPagerAdapter.ABOUT_FRAGMENT_TAG);
175                    transaction.add(R.id.updates_fragment_container, mUpdatesFragment,
176                            ContactDetailViewPagerAdapter.UPDTES_FRAGMENT_TAG);
177                    transaction.commitAllowingStateLoss();
178                    mFragmentManager.executePendingTransactions();
179                }
180
181                mTabCarousel.setListener(mTabCarouselListener);
182                mTabCarousel.restoreCurrentTab(currentPageIndex);
183                mDetailFragment.setVerticalScrollListener(
184                        new VerticalScrollListener(TAB_INDEX_DETAIL));
185                mUpdatesFragment.setVerticalScrollListener(
186                        new VerticalScrollListener(TAB_INDEX_UPDATES));
187                mViewPager.setCurrentItem(currentPageIndex);
188                break;
189            }
190            case TWO_COLUMN: {
191                if (!fragmentsAddedToFragmentManager) {
192                    FragmentTransaction transaction = mFragmentManager.beginTransaction();
193                    transaction.add(R.id.about_fragment_container, mDetailFragment,
194                            ContactDetailViewPagerAdapter.ABOUT_FRAGMENT_TAG);
195                    transaction.add(R.id.updates_fragment_container, mUpdatesFragment,
196                            ContactDetailViewPagerAdapter.UPDTES_FRAGMENT_TAG);
197                    transaction.commitAllowingStateLoss();
198                    mFragmentManager.executePendingTransactions();
199                }
200                break;
201            }
202            case FRAGMENT_CAROUSEL: {
203                // Add the fragments to the fragment containers in the carousel using a
204                // {@link FragmentTransaction} if they haven't already been added to the
205                // {@link FragmentManager}.
206                if (!fragmentsAddedToFragmentManager) {
207                    FragmentTransaction transaction = mFragmentManager.beginTransaction();
208                    transaction.add(R.id.about_fragment_container, mDetailFragment,
209                            ContactDetailViewPagerAdapter.ABOUT_FRAGMENT_TAG);
210                    transaction.add(R.id.updates_fragment_container, mUpdatesFragment,
211                            ContactDetailViewPagerAdapter.UPDTES_FRAGMENT_TAG);
212                    transaction.commitAllowingStateLoss();
213                    mFragmentManager.executePendingTransactions();
214                }
215
216                mFragmentCarousel.setFragmentViews(mDetailFragmentView, mUpdatesFragmentView);
217                mFragmentCarousel.setFragments(mDetailFragment, mUpdatesFragment);
218                mFragmentCarousel.restoreCurrentPage(currentPageIndex);
219                break;
220            }
221        }
222
223        // Setup the layout if we already have a saved state
224        if (savedState != null) {
225            if (mContactHasUpdates) {
226                showContactWithUpdates();
227            } else {
228                showContactWithoutUpdates();
229            }
230        }
231    }
232
233    public void setContactData(ContactLoader.Result data) {
234        mContactData = data;
235        mContactHasUpdates = !data.getStreamItems().isEmpty();
236        if (mContactHasUpdates) {
237            showContactWithUpdates();
238        } else {
239            showContactWithoutUpdates();
240        }
241    }
242
243    public void showEmptyState() {
244        switch (mLayoutMode) {
245            case FRAGMENT_CAROUSEL: {
246                mFragmentCarousel.setCurrentPage(0);
247                mFragmentCarousel.enableSwipe(false);
248                mDetailFragment.showEmptyState();
249                break;
250            }
251            case TWO_COLUMN: {
252                mDetailFragment.setShowStaticPhoto(false);
253                mUpdatesFragmentView.setVisibility(View.GONE);
254                mDetailFragment.showEmptyState();
255                break;
256            }
257            case VIEW_PAGER_AND_TAB_CAROUSEL: {
258                mDetailFragment.setShowStaticPhoto(false);
259                mDetailFragment.showEmptyState();
260                mTabCarousel.loadData(null);
261                mTabCarousel.setVisibility(View.GONE);
262                mViewPagerAdapter.enableSwipe(false);
263                mViewPager.setCurrentItem(0);
264                break;
265            }
266            default:
267                throw new IllegalStateException("Invalid LayoutMode " + mLayoutMode);
268        }
269    }
270
271    /**
272     * Setup the layout for the contact with updates. Pass in the index of the current page to
273     * select or null if the current selection should be left as is.
274     */
275    private void showContactWithUpdates() {
276        if (mContactData == null) {
277            return;
278        }
279        switch (mLayoutMode) {
280            case TWO_COLUMN: {
281                // Set the contact data (hide the static photo because the photo will already be in
282                // the header that scrolls with contact details).
283                mDetailFragment.setShowStaticPhoto(false);
284                // Show the updates fragment
285                mUpdatesFragmentView.setVisibility(View.VISIBLE);
286                break;
287            }
288            case VIEW_PAGER_AND_TAB_CAROUSEL: {
289                // Update and show the tab carousel (also restore its last saved position)
290                mTabCarousel.loadData(mContactData);
291                mTabCarousel.restoreYCoordinate();
292                mTabCarousel.setVisibility(View.VISIBLE);
293                // Update ViewPager to allow swipe between all the fragments (to see updates)
294                mViewPagerAdapter.enableSwipe(true);
295                break;
296            }
297            case FRAGMENT_CAROUSEL: {
298                // Allow swiping between all fragments
299                mFragmentCarousel.enableSwipe(true);
300                break;
301            }
302            default:
303                throw new IllegalStateException("Invalid LayoutMode " + mLayoutMode);
304        }
305
306        mDetailFragment.setData(mContactData.getLookupUri(), mContactData);
307        mUpdatesFragment.setData(mContactData.getLookupUri(), mContactData);
308    }
309
310    private void showContactWithoutUpdates() {
311        if (mContactData == null) {
312            return;
313        }
314        switch (mLayoutMode) {
315            case TWO_COLUMN:
316                // Show the static photo which is next to the list of scrolling contact details
317                mDetailFragment.setShowStaticPhoto(true);
318                // Hide the updates fragment
319                mUpdatesFragmentView.setVisibility(View.GONE);
320                break;
321            case VIEW_PAGER_AND_TAB_CAROUSEL:
322                // Hide the tab carousel
323                mTabCarousel.setVisibility(View.GONE);
324                // Update ViewPager to disable swipe so that it only shows the detail fragment
325                // and switch to the detail fragment
326                mViewPagerAdapter.enableSwipe(false);
327                mViewPager.setCurrentItem(0);
328                break;
329            case FRAGMENT_CAROUSEL: {
330                // Disable swipe so only the detail fragment shows
331                mFragmentCarousel.setCurrentPage(0);
332                mFragmentCarousel.enableSwipe(false);
333                break;
334            }
335            default:
336                throw new IllegalStateException("Invalid LayoutMode " + mLayoutMode);
337        }
338
339        mDetailFragment.setData(mContactData.getLookupUri(), mContactData);
340    }
341
342    public FragmentKeyListener getCurrentPage() {
343        switch (getCurrentPageIndex()) {
344            case 0:
345                return mDetailFragment;
346            case 1:
347                return mUpdatesFragment;
348            default:
349                throw new IllegalStateException("Invalid current item for ViewPager");
350        }
351    }
352
353    private int getCurrentPageIndex() {
354        // If the contact has social updates, then retrieve the current page based on the
355        // {@link ViewPager} or fragment carousel.
356        if (mContactHasUpdates) {
357            if (mViewPager != null) {
358                return mViewPager.getCurrentItem();
359            } else if (mFragmentCarousel != null) {
360                return mFragmentCarousel.getCurrentPage();
361            }
362        }
363        // Otherwise return the default page (detail fragment).
364        return 0;
365    }
366
367    public void onSaveInstanceState(Bundle outState) {
368        outState.putBoolean(KEY_CONTACT_HAS_UPDATES, mContactHasUpdates);
369        outState.putInt(KEY_CURRENT_PAGE_INDEX, getCurrentPageIndex());
370    }
371
372    private final OnPageChangeListener mOnPageChangeListener = new OnPageChangeListener() {
373
374        private ObjectAnimator mTabCarouselAnimator;
375
376        @Override
377        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
378            // The user is horizontally dragging the {@link ViewPager}, so send
379            // these scroll changes to the tab carousel. Ignore these events though if the carousel
380            // is actually controlling the {@link ViewPager} scrolls because it will already be
381            // in the correct position.
382            if (mViewPager.isFakeDragging()) {
383                return;
384            }
385            int x = (int) ((position + positionOffset) *
386                    mTabCarousel.getAllowedHorizontalScrollLength());
387            mTabCarousel.scrollTo(x, 0);
388        }
389
390        @Override
391        public void onPageSelected(int position) {
392            // Since the {@link ViewPager} has committed to a new page now (but may not have
393            // finished scrolling yet), update the tab selection in the carousel.
394            mTabCarousel.setCurrentTab(position);
395        }
396
397        @Override
398        public void onPageScrollStateChanged(int state) {
399            if (mViewPagerState == ViewPager.SCROLL_STATE_IDLE) {
400
401                // If we are leaving the IDLE state, we are starting a swipe.
402                // First cancel any pending animations on the tab carousel.
403                cancelTabCarouselAnimator();
404
405                // Sync the two lists because the list on the other page will start to show as
406                // we swipe over more.
407                syncScrollStateBetweenLists(mViewPager.getCurrentItem());
408
409            } else if (state == ViewPager.SCROLL_STATE_IDLE) {
410
411                // Otherwise if the {@link ViewPager} is idle now, a page has been selected and
412                // scrolled into place. Perform an animation of the tab carousel is needed.
413                int currentPageIndex = mViewPager.getCurrentItem();
414                int tabCarouselOffset = (int) mTabCarousel.getY();
415                boolean shouldAnimateTabCarousel;
416
417                // Find the offset position of the first item in the list of the current page.
418                int listOffset = getOffsetOfFirstItemInList(currentPageIndex);
419
420                // If the list was able to successfully offset by the tab carousel amount, then
421                // log this as the new Y coordinate for that page, and no animation is needed.
422                if (listOffset == tabCarouselOffset) {
423                    mTabCarousel.storeYCoordinate(currentPageIndex, tabCarouselOffset);
424                    shouldAnimateTabCarousel = false;
425                } else if (listOffset == Integer.MIN_VALUE) {
426                    // If the offset of the first item in the list is unknown (i.e. the item
427                    // is no longer visible on screen) then just animate the tab carousel to the
428                    // previously logged position.
429                    shouldAnimateTabCarousel = true;
430                } else if (Math.abs(listOffset) < Math.abs(tabCarouselOffset)) {
431                    // If the list could not offset the full amount of the tab carousel offset (i.e.
432                    // the list can only be scrolled a tiny amount), then animate the carousel down
433                    // to compensate.
434                    mTabCarousel.storeYCoordinate(currentPageIndex, listOffset);
435                    shouldAnimateTabCarousel = true;
436                } else {
437                    // By default, animate back to the Y coordinate of the tab carousel the last
438                    // time the other page was selected.
439                    shouldAnimateTabCarousel = true;
440                }
441
442                if (shouldAnimateTabCarousel) {
443                    float desiredOffset = mTabCarousel.getStoredYCoordinateForTab(currentPageIndex);
444                    if (desiredOffset != tabCarouselOffset) {
445                        createTabCarouselAnimator(desiredOffset);
446                        mTabCarouselAnimator.start();
447                    }
448                }
449            }
450            mViewPagerState = state;
451        }
452
453        private void createTabCarouselAnimator(float desiredValue) {
454            mTabCarouselAnimator = ObjectAnimator.ofFloat(
455                    mTabCarousel, "y", desiredValue).setDuration(75);
456            mTabCarouselAnimator.setInterpolator(AnimationUtils.loadInterpolator(
457                    mActivity, android.R.anim.accelerate_decelerate_interpolator));
458            mTabCarouselAnimator.addListener(mTabCarouselAnimatorListener);
459        }
460
461        private void cancelTabCarouselAnimator() {
462            if (mTabCarouselAnimator != null) {
463                mTabCarouselAnimator.cancel();
464                mTabCarouselAnimator = null;
465                mTabCarouselIsAnimating = false;
466            }
467        }
468    };
469
470    private void syncScrollStateBetweenLists(int currentPageIndex) {
471        // Since the user interacted with the currently visible page, we need to sync the
472        // list on the other page (i.e. if the updates page is the current page, modify the
473        // list in the details page).
474        if (currentPageIndex == TAB_INDEX_UPDATES) {
475            mDetailFragment.requestToMoveToOffset((int) mTabCarousel.getY());
476        } else {
477            mUpdatesFragment.requestToMoveToOffset((int) mTabCarousel.getY());
478        }
479    }
480
481    private int getOffsetOfFirstItemInList(int currentPageIndex) {
482        if (currentPageIndex == TAB_INDEX_DETAIL) {
483            return mDetailFragment.getFirstListItemOffset();
484        } else {
485            return mUpdatesFragment.getFirstListItemOffset();
486        }
487    }
488
489    /**
490     * This listener keeps track of whether the tab carousel animation is currently going on or not,
491     * in order to prevent other simultaneous changes to the Y position of the tab carousel which
492     * can cause flicker.
493     */
494    private final AnimatorListener mTabCarouselAnimatorListener = new AnimatorListener() {
495
496        @Override
497        public void onAnimationCancel(Animator animation) {
498            mTabCarouselIsAnimating = false;
499        }
500
501        @Override
502        public void onAnimationEnd(Animator animation) {
503            mTabCarouselIsAnimating = false;
504        }
505
506        @Override
507        public void onAnimationRepeat(Animator animation) {
508            mTabCarouselIsAnimating = true;
509        }
510
511        @Override
512        public void onAnimationStart(Animator animation) {
513            mTabCarouselIsAnimating = true;
514        }
515    };
516
517    private final ContactDetailTabCarousel.Listener mTabCarouselListener =
518            new ContactDetailTabCarousel.Listener() {
519
520        @Override
521        public void onTouchDown() {
522            // The user just started scrolling the carousel, so begin "fake dragging" the
523            // {@link ViewPager} if it's not already doing so.
524            if (mViewPager.isFakeDragging()) {
525                return;
526            }
527            mViewPager.beginFakeDrag();
528        }
529
530        @Override
531        public void onTouchUp() {
532            // The user just stopped scrolling the carousel, so stop "fake dragging" the
533            // {@link ViewPager} if was doing so before.
534            if (mViewPager.isFakeDragging()) {
535                mViewPager.endFakeDrag();
536            }
537        }
538
539        @Override
540        public void onScrollChanged(int l, int t, int oldl, int oldt) {
541            // The user is scrolling the carousel, so send the scroll deltas to the
542            // {@link ViewPager} so it can move in sync.
543            if (mViewPager.isFakeDragging()) {
544                mViewPager.fakeDragBy(oldl-l);
545            }
546        }
547
548        @Override
549        public void onTabSelected(int position) {
550            // The user selected a tab, so update the {@link ViewPager}
551            mViewPager.setCurrentItem(position);
552        }
553    };
554
555    private final class VerticalScrollListener implements OnScrollListener {
556
557        private final int mPageIndex;
558
559        public VerticalScrollListener(int pageIndex) {
560            mPageIndex = pageIndex;
561        }
562
563        @Override
564        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
565                int totalItemCount) {
566            int currentPageIndex = mViewPager.getCurrentItem();
567            // Don't move the carousel if: 1) the contact does not have social updates because then
568            // tab carousel must not be visible, 2) if the view pager is still being scrolled,
569            // 3) if the current page being viewed is not this one, or 4) if the tab carousel
570            // is already being animated vertically.
571            if (!mContactHasUpdates || mViewPagerState != ViewPager.SCROLL_STATE_IDLE ||
572                    mPageIndex != currentPageIndex || mTabCarouselIsAnimating) {
573                return;
574            }
575            // If the FIRST item is not visible on the screen, then the carousel must be pinned
576            // at the top of the screen.
577            if (firstVisibleItem != 0) {
578                mTabCarousel.moveToYCoordinate(mPageIndex,
579                        -mTabCarousel.getAllowedVerticalScrollLength());
580                return;
581            }
582            View topView = view.getChildAt(firstVisibleItem);
583            if (topView == null) {
584                return;
585            }
586            int amtToScroll = Math.max((int) view.getChildAt(firstVisibleItem).getY(),
587                    -mTabCarousel.getAllowedVerticalScrollLength());
588            mTabCarousel.moveToYCoordinate(mPageIndex, amtToScroll);
589        }
590
591        @Override
592        public void onScrollStateChanged(AbsListView view, int scrollState) {
593            // Once the list has become IDLE, check if we need to sync the scroll position of
594            // the other list now. This will make swiping faster by doing the re-layout now
595            // (instead of at the start of a swipe). However, there will still be another check
596            // when we start swiping if the scroll positions are correct (to catch the edge case
597            // where the user flings and immediately starts a swipe so we never get the idle state).
598            if (scrollState == SCROLL_STATE_IDLE) {
599                syncScrollStateBetweenLists(mPageIndex);
600            }
601        }
602    }
603}
604