• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.contacts.detail;
18 
19 import com.android.contacts.ContactLoader;
20 import com.android.contacts.NfcHandler;
21 import com.android.contacts.R;
22 import com.android.contacts.activities.ContactDetailActivity.FragmentKeyListener;
23 
24 import android.animation.Animator;
25 import android.animation.Animator.AnimatorListener;
26 import android.animation.ObjectAnimator;
27 import android.app.Activity;
28 import android.app.FragmentManager;
29 import android.app.FragmentTransaction;
30 import android.content.Context;
31 import android.os.Bundle;
32 import android.support.v4.view.ViewPager;
33 import android.support.v4.view.ViewPager.OnPageChangeListener;
34 import android.view.LayoutInflater;
35 import android.view.View;
36 import android.view.animation.AnimationUtils;
37 import android.widget.AbsListView;
38 import android.widget.AbsListView.OnScrollListener;
39 
40 /**
41  * Determines the layout of the contact card.
42  */
43 public 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 
ContactDetailLayoutController(Activity activity, Bundle savedState, FragmentManager fragmentManager, View viewContainer, ContactDetailFragment.Listener contactDetailFragmentListener)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 
initialize(Bundle savedState)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.setCurrentPage(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 
setContactData(ContactLoader.Result data)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 
showEmptyState()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      */
showContactWithUpdates()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 
showContactWithoutUpdates()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 
getCurrentPage()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 
getCurrentPageIndex()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 
onSaveInstanceState(Bundle outState)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 
syncScrollStateBetweenLists(int currentPageIndex)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 
getOffsetOfFirstItemInList(int currentPageIndex)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 
VerticalScrollListener(int pageIndex)559         public VerticalScrollListener(int pageIndex) {
560             mPageIndex = pageIndex;
561         }
562 
563         @Override
onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)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
onScrollStateChanged(AbsListView view, int scrollState)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