Coordinate scrolling between two lists on contact card

FRAGMENT CHANGES:

- Add a method on each of these fragments to scroll the list by
a given offset (only when the first "fake" item in the list
is at the first visible position on the list). If any other
item (2nd, 3rd, etc..) is the first visible position on the list,
then it gets too complicated to figure out how much to offset the
list, so it's better to just animate the tab carousel to be at the
right spot.

- Add a method that returns the top position of the first item
in the list if the first item in list is visible (to verify
that the offset was done properly). If the offset is not
what we expect, then we know the list couldn't be scrolled.

TAB CAROUSEL:

- Keep track of the Y position of the carousel the last time
the user was on the details page, and the last time the user was
on the updates page. This is because syncing the scroll position
of the 2 lists or animating the tab carousel vertically is only
necessary if the tab carousel position has changed between the
two pages.

LAYOUT CONTROLLER:

- We need to be able to animate the tab carousel to the correct
Y position anyways because if the list in the fragment is not
scrollable (but the other list is scrollable), we need to rely on
animation to hide the big white space below the carousel.

- When a list (i.e. in the ContactDetailFragment) is idle, the user
has stopped interacting with the list, so use the time to sync
its position with the other list (since this may involve a
requestLayout() if a scroll is triggered).

- Because the list may not become completely idle (i.e. the user
flings the list and then starts to swipe), we still need a check
when the user starts swiping the view pager to sync the two lists.
If it's already at the right spot, then do nothing. If we can't
scroll the other list dynamically and we need to animate the tab
carousel, and we should only do this when the view pager has
settled down and is idle.

MISCELLANEOUSE EDGE CASES:

- Fix the tab carousel jumping to the starting position (if a sync
happens and the loader data is refreshed) because the layout is
likely being redrawn and the tab carousel is potentially redrawn
back inside the bounds of its parent. Hence store and reset the
tab carousel position to its previous Y value (which could be
outside the bounds of its parent).

- If the list is scrollable but only by a little bit, allow
the tab carousel to compensate by animating down the rest of the
offset amount

- Tapping on the tab should also work now

- Make photo in updates tab fill the whole tab, change
updates fragment background except for the tablet landscape mode

Bug: 5044680
Change-Id: Icc9445606ea52779dea97b194763c74a0b2a27ee
diff --git a/res/layout-sw580dp/contact_detail_updates_fragment.xml b/res/layout-sw580dp/contact_detail_updates_fragment.xml
index 6002151..3bcb01c 100644
--- a/res/layout-sw580dp/contact_detail_updates_fragment.xml
+++ b/res/layout-sw580dp/contact_detail_updates_fragment.xml
@@ -24,7 +24,6 @@
     <ListView android:id="@android:id/list"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:background="@color/background_social_updates"
         android:fadingEdge="none"
         android:divider="@null"/>
 
diff --git a/res/layout-w470dp/contact_detail_updates_fragment.xml b/res/layout-w470dp/contact_detail_updates_fragment.xml
index 44207fc..50536de 100644
--- a/res/layout-w470dp/contact_detail_updates_fragment.xml
+++ b/res/layout-w470dp/contact_detail_updates_fragment.xml
@@ -23,7 +23,6 @@
     <ListView android:id="@android:id/list"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:background="@color/background_social_updates"
         android:fadingEdge="none"
         android:divider="@null"/>
 
diff --git a/res/layout/carousel_updates_tab.xml b/res/layout/carousel_updates_tab.xml
index 3f633fc..967b60a 100644
--- a/res/layout/carousel_updates_tab.xml
+++ b/res/layout/carousel_updates_tab.xml
@@ -27,7 +27,8 @@
     android:background="@color/detail_tab_background">
 
     <ImageView android:id="@+id/status_photo"
-        android:layout_width="wrap_content"
+        android:scaleType="centerCrop"
+        android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_alignParentTop="true"
         android:layout_alignParentLeft="true"
diff --git a/res/layout/contact_detail_updates_fragment.xml b/res/layout/contact_detail_updates_fragment.xml
index a949cd8..4c056a8 100644
--- a/res/layout/contact_detail_updates_fragment.xml
+++ b/res/layout/contact_detail_updates_fragment.xml
@@ -18,7 +18,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:ex="http://schemas.android.com/apk/res/com.android.contacts"
     android:id="@android:id/list"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:background="@color/background_social_updates"
-        android:divider="@null"/>
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/background_primary"
+    android:divider="@null"/>
diff --git a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
index 86cb7fa..6bca80d 100644
--- a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
+++ b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
@@ -56,6 +56,7 @@
 import android.widget.CheckBox;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
+import android.widget.ListView;
 import android.widget.TextView;
 
 import java.util.List;
@@ -500,4 +501,44 @@
             view.setBackgroundColor((int) (alpha * 255) << 24);
         }
     }
+
+    /**
+     * Returns the top coordinate of the first item in the {@link ListView}. If the first item
+     * in the {@link ListView} is not visible or there are no children in the list, then return
+     * Integer.MIN_VALUE. Note that the returned value will be <= 0 because the first item in the
+     * list cannot have a positive offset.
+     */
+    public static int getFirstListItemOffset(ListView listView) {
+        if (listView == null || listView.getChildCount() == 0 ||
+                listView.getFirstVisiblePosition() != 0) {
+            return Integer.MIN_VALUE;
+        }
+        return listView.getChildAt(0).getTop();
+    }
+
+    /**
+     * Tries to scroll the first item in the list to the given offset (this can be a no-op if the
+     * list is already in the correct position).
+     * @param listView that should be scrolled
+     * @param offset which should be <= 0
+     */
+    public static void requestToMoveToOffset(ListView listView, int offset) {
+        // We try to offset the list if the first item in the list is showing (which is presumed
+        // to have a larger height than the desired offset). If the first item in the list is not
+        // visible, then we simply do not scroll the list at all (since it can get complicated to
+        // compute how many items in the list will equal the given offset). Potentially
+        // some animation elsewhere will make the transition smoother for the user to compensate
+        // for this simplification.
+        if (listView == null || listView.getChildCount() == 0 ||
+                listView.getFirstVisiblePosition() != 0 || offset > 0) {
+            return;
+        }
+
+        // As an optimization, check if the first item is already at the given offset.
+        if (listView.getChildAt(0).getTop() == offset) {
+            return;
+        }
+
+        listView.setSelectionFromTop(0, offset);
+    }
 }
diff --git a/src/com/android/contacts/detail/ContactDetailFragment.java b/src/com/android/contacts/detail/ContactDetailFragment.java
index 4d50a39..daf8229 100644
--- a/src/com/android/contacts/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailFragment.java
@@ -377,6 +377,25 @@
         bindData();
     }
 
+    /**
+     * Returns the top coordinate of the first item in the {@link ListView}. If the first item
+     * in the {@link ListView} is not visible or there are no children in the list, then return
+     * Integer.MIN_VALUE. Note that the returned value will be <= 0 because the first item in the
+     * list cannot have a positive offset.
+     */
+    public int getFirstListItemOffset() {
+        return ContactDetailDisplayUtils.getFirstListItemOffset(mListView);
+    }
+
+    /**
+     * Tries to scroll the first item to the given offset (this can be a no-op if the list is
+     * already in the correct position).
+     * @param offset which should be <= 0
+     */
+    public void requestToMoveToOffset(int offset) {
+        ContactDetailDisplayUtils.requestToMoveToOffset(mListView, offset);
+    }
+
     protected void bindData() {
         if (mView == null) {
             return;
diff --git a/src/com/android/contacts/detail/ContactDetailLayoutController.java b/src/com/android/contacts/detail/ContactDetailLayoutController.java
index a10431b..579d7bf 100644
--- a/src/com/android/contacts/detail/ContactDetailLayoutController.java
+++ b/src/com/android/contacts/detail/ContactDetailLayoutController.java
@@ -20,6 +20,7 @@
 import com.android.contacts.R;
 import com.android.contacts.activities.ContactDetailActivity.FragmentKeyListener;
 
+import android.animation.ObjectAnimator;
 import android.app.FragmentManager;
 import android.app.FragmentTransaction;
 import android.content.Context;
@@ -28,7 +29,9 @@
 import android.support.v4.view.ViewPager.OnPageChangeListener;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
+import android.widget.AbsListView;
+import android.widget.AbsListView.OnScrollListener;
 
 /**
  * Determines the layout of the contact card.
@@ -38,6 +41,9 @@
     private static final String KEY_CONTACT_HAS_UPDATES = "contactHasUpdates";
     private static final String KEY_CURRENT_PAGE_INDEX = "currentPageIndex";
 
+    private static final int TAB_INDEX_DETAIL = 0;
+    private static final int TAB_INDEX_UPDATES = 1;
+
     /**
      * There are 3 possible layouts for the contact detail screen:
      * 1. TWO_COLUMN - Tall and wide screen so the 2 pages can be shown side-by-side
@@ -48,6 +54,7 @@
         TWO_COLUMN, VIEW_PAGER_AND_TAB_CAROUSEL, FRAGMENT_CAROUSEL,
     }
 
+    private final Context mContext;
     private final LayoutInflater mLayoutInflater;
     private final FragmentManager mFragmentManager;
 
@@ -58,10 +65,11 @@
     private View mUpdatesFragmentView;
 
     private final ViewPager mViewPager;
-    private final ContactDetailTabCarousel mTabCarousel;
     private ContactDetailViewPagerAdapter mViewPagerAdapter;
+    private int mViewPagerState;
 
-    private ContactDetailFragmentCarousel mFragmentCarousel;
+    private final ContactDetailTabCarousel mTabCarousel;
+    private final ContactDetailFragmentCarousel mFragmentCarousel;
 
     private ContactDetailFragment.Listener mContactDetailFragmentListener;
 
@@ -80,6 +88,7 @@
                     + "without a non-null FragmentManager");
         }
 
+        mContext = context;
         mLayoutInflater = (LayoutInflater) context.getSystemService(
                 Context.LAYOUT_INFLATER_SERVICE);
         mFragmentManager = fragmentManager;
@@ -139,17 +148,17 @@
                 // which will in turn be the parents to the mDetailFragment and mUpdatesFragment
                 // since the fragments must have the same parent view IDs in both landscape and
                 // portrait layouts.
-                ViewGroup detailContainer = (ViewGroup) mLayoutInflater.inflate(
+                mDetailFragmentView = mLayoutInflater.inflate(
                         R.layout.contact_detail_about_fragment_container, mViewPager, false);
-                ViewGroup updatesContainer = (ViewGroup) mLayoutInflater.inflate(
+                mUpdatesFragmentView = mLayoutInflater.inflate(
                         R.layout.contact_detail_updates_fragment_container, mViewPager, false);
 
                 mViewPagerAdapter = new ContactDetailViewPagerAdapter();
-                mViewPagerAdapter.setAboutFragmentView(detailContainer);
-                mViewPagerAdapter.setUpdatesFragmentView(updatesContainer);
+                mViewPagerAdapter.setAboutFragmentView(mDetailFragmentView);
+                mViewPagerAdapter.setUpdatesFragmentView(mUpdatesFragmentView);
 
-                mViewPager.addView(detailContainer);
-                mViewPager.addView(updatesContainer);
+                mViewPager.addView(mDetailFragmentView);
+                mViewPager.addView(mUpdatesFragmentView);
                 mViewPager.setAdapter(mViewPagerAdapter);
                 mViewPager.setOnPageChangeListener(mOnPageChangeListener);
 
@@ -164,7 +173,10 @@
                 }
 
                 mTabCarousel.setListener(mTabCarouselListener);
-                TabCarouselScrollManager.bind(mTabCarousel, mDetailFragment, mUpdatesFragment);
+                mDetailFragment.setVerticalScrollListener(
+                        new VerticalScrollListener(TAB_INDEX_DETAIL));
+                mUpdatesFragment.setVerticalScrollListener(
+                        new VerticalScrollListener(TAB_INDEX_UPDATES));
                 mViewPager.setCurrentItem(currentPageIndex);
                 break;
             }
@@ -239,8 +251,9 @@
                 break;
             }
             case VIEW_PAGER_AND_TAB_CAROUSEL: {
-                // Update and show the tab carousel
+                // Update and show the tab carousel (also restore its last saved position)
                 mTabCarousel.loadData(mContactData);
+                mTabCarousel.restoreYCoordinate();
                 mTabCarousel.setVisibility(View.VISIBLE);
                 // Update ViewPager to allow swipe between all the fragments (to see updates)
                 mViewPagerAdapter.enableSwipe(true);
@@ -320,7 +333,9 @@
         outState.putInt(KEY_CURRENT_PAGE_INDEX, getCurrentPageIndex());
     }
 
-    private OnPageChangeListener mOnPageChangeListener = new OnPageChangeListener() {
+    private final OnPageChangeListener mOnPageChangeListener = new OnPageChangeListener() {
+
+        private ObjectAnimator mTabCarouselAnimator;
 
         @Override
         public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
@@ -338,17 +353,102 @@
 
         @Override
         public void onPageSelected(int position) {
-            // Since a new page has been selected by the {@link ViewPager},
-            // update the tab selection in the carousel.
+            // Since the {@link ViewPager} has committed to a new page now (but may not have
+            // finished scrolling yet), update the tab selection in the carousel.
             mTabCarousel.setCurrentTab(position);
         }
 
         @Override
-        public void onPageScrollStateChanged(int state) {}
+        public void onPageScrollStateChanged(int state) {
+            if (mViewPagerState == ViewPager.SCROLL_STATE_IDLE) {
 
+                // If we are leaving the IDLE state, we are starting a swipe.
+                // First cancel any pending animations on the tab carousel.
+                cancelTabCarouselAnimator();
+
+                // Sync the two lists because the list on the other page will start to show as
+                // we swipe over more.
+                syncScrollStateBetweenLists(mViewPager.getCurrentItem());
+
+            } else if (state == ViewPager.SCROLL_STATE_IDLE) {
+
+                // Otherwise if the {@link ViewPager} is idle now, a page has been selected and
+                // scrolled into place. Perform an animation of the tab carousel is needed.
+                int currentPageIndex = mViewPager.getCurrentItem();
+                int tabCarouselOffset = (int) mTabCarousel.getY();
+                boolean shouldAnimateTabCarousel;
+
+                // Find the offset position of the first item in the list of the current page.
+                int listOffset = getOffsetOfFirstItemInList(currentPageIndex);
+
+                // If the list was able to successfully offset by the tab carousel amount, then
+                // log this as the new Y coordinate for that page, and no animation is needed.
+                if (listOffset == tabCarouselOffset) {
+                    mTabCarousel.storeYCoordinate(currentPageIndex, tabCarouselOffset);
+                    shouldAnimateTabCarousel = false;
+                } else if (listOffset == Integer.MIN_VALUE) {
+                    // If the offset of the first item in the list is unknown (i.e. the item
+                    // is no longer visible on screen) then just animate the tab carousel to the
+                    // previously logged position.
+                    shouldAnimateTabCarousel = true;
+                } else if (Math.abs(listOffset) < Math.abs(tabCarouselOffset)) {
+                    // If the list could not offset the full amount of the tab carousel offset (i.e.
+                    // the list can only be scrolled a tiny amount), then animate the carousel down
+                    // to compensate.
+                    mTabCarousel.storeYCoordinate(currentPageIndex, listOffset);
+                    shouldAnimateTabCarousel = true;
+                } else {
+                    // By default, animate back to the Y coordinate of the tab carousel the last
+                    // time the other page was selected.
+                    shouldAnimateTabCarousel = true;
+                }
+
+                if (shouldAnimateTabCarousel) {
+                    float desiredOffset = mTabCarousel.getStoredYCoordinateForTab(currentPageIndex);
+                    if (desiredOffset != tabCarouselOffset) {
+                        createTabCarouselAnimator(desiredOffset);
+                        mTabCarouselAnimator.start();
+                    }
+                }
+            }
+            mViewPagerState = state;
+        }
+
+        private void createTabCarouselAnimator(float desiredValue) {
+            mTabCarouselAnimator = ObjectAnimator.ofFloat(
+                    mTabCarousel, "y", desiredValue).setDuration(75);
+            mTabCarouselAnimator.setInterpolator(AnimationUtils.loadInterpolator(
+                    mContext, android.R.anim.accelerate_decelerate_interpolator));
+        }
+
+        private void cancelTabCarouselAnimator() {
+            if (mTabCarouselAnimator != null) {
+                mTabCarouselAnimator.cancel();
+                mTabCarouselAnimator = null;
+            }
+        }
     };
 
-    private ContactDetailTabCarousel.Listener mTabCarouselListener =
+    private void syncScrollStateBetweenLists(int currentPageIndex) {
+        // Since the user interacted with the currently visible page, we need to sync the
+        // list on the other page (i.e. if the updates page is the current page, modify the
+        // list in the details page).
+        if (currentPageIndex == TAB_INDEX_UPDATES) {
+            mDetailFragment.requestToMoveToOffset((int) mTabCarousel.getY());
+        } else {
+            mUpdatesFragment.requestToMoveToOffset((int) mTabCarousel.getY());
+        }
+    }
+
+    private int getOffsetOfFirstItemInList(int currentPageIndex) {
+        if (currentPageIndex == TAB_INDEX_DETAIL) {
+            return mDetailFragment.getFirstListItemOffset();
+        } else {
+            return mUpdatesFragment.getFirstListItemOffset();
+        }
+    }
+
+    private final ContactDetailTabCarousel.Listener mTabCarouselListener =
             new ContactDetailTabCarousel.Listener() {
 
         @Override
@@ -385,4 +485,52 @@
             mViewPager.setCurrentItem(position);
         }
     };
+
+    private final class VerticalScrollListener implements OnScrollListener {
+
+        private final int mPageIndex;
+
+        public VerticalScrollListener(int pageIndex) {
+            mPageIndex = pageIndex;
+        }
+
+        @Override
+        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
+                int totalItemCount) {
+            int currentPageIndex = mViewPager.getCurrentItem();
+            // Don't move the carousel if: 1) the contact does not have social updates because then
+            // tab carousel must not be visible, 2) if the view pager is still being scrolled, or
+            // 3) if the current page being viewed is not this one.
+            if (!mContactHasUpdates || mViewPagerState != ViewPager.SCROLL_STATE_IDLE ||
+                    mPageIndex != currentPageIndex) {
+                return;
+            }
+            // If the FIRST item is not visible on the screen, then the carousel must be pinned
+            // at the top of the screen.
+            if (firstVisibleItem != 0) {
+                mTabCarousel.moveToYCoordinate(mPageIndex,
+                        -mTabCarousel.getAllowedVerticalScrollLength());
+                return;
+            }
+            View topView = view.getChildAt(firstVisibleItem);
+            if (topView == null) {
+                return;
+            }
+            int amtToScroll = Math.max((int) view.getChildAt(firstVisibleItem).getY(),
+                    -mTabCarousel.getAllowedVerticalScrollLength());
+            mTabCarousel.moveToYCoordinate(mPageIndex, amtToScroll);
+        }
+
+        @Override
+        public void onScrollStateChanged(AbsListView view, int scrollState) {
+            // Once the list has become IDLE, check if we need to sync the scroll position of
+            // the other list now. This will make swiping faster by doing the re-layout now
+            // (instead of at the start of a swipe). However, there will still be another check
+            // when we start swiping if the scroll positions are correct (to catch the edge case
+            // where the user flings and immediately starts a swipe so we never get the idle state).
+            if (scrollState == SCROLL_STATE_IDLE) {
+                syncScrollStateBetweenLists(mPageIndex);
+            }
+        }
+    }
 }
diff --git a/src/com/android/contacts/detail/ContactDetailTabCarousel.java b/src/com/android/contacts/detail/ContactDetailTabCarousel.java
index bb30f7e..8a51d81 100644
--- a/src/com/android/contacts/detail/ContactDetailTabCarousel.java
+++ b/src/com/android/contacts/detail/ContactDetailTabCarousel.java
@@ -24,7 +24,6 @@
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.View.OnClickListener;
 import android.view.View.OnTouchListener;
 import android.widget.HorizontalScrollView;
 import android.widget.ImageView;
@@ -59,6 +58,9 @@
     private CarouselTab mAboutTab;
     private CarouselTab mUpdatesTab;
 
+    /** Last Y coordinate of the carousel when the tab at the given index was selected */
+    private final float[] mYCoordinateArray = new float[TAB_COUNT];
+
     private int mTabDisplayLabelHeight;
 
     private int mLastScrollPosition;
@@ -172,6 +174,39 @@
     }
 
     /**
+     * Restore the Y position of this view to the last manually requested value. This can be done
+     * after the parent has been re-laid out again, where this view's position could have been
+     * lost if the view laid outside its parent's bounds.
+     */
+    public void restoreYCoordinate() {
+        setY(getStoredYCoordinateForTab(mCurrentTab));
+    }
+
+    /**
+     * Request that the view move to the given Y coordinate. Also store the Y coordinate as the
+     * last requested Y coordinate for the given tabIndex.
+     */
+    public void moveToYCoordinate(int tabIndex, float y) {
+        setY(y);
+        storeYCoordinate(tabIndex, y);
+    }
+
+    /**
+     * Store this information as the last requested Y coordinate for the given tabIndex.
+     */
+    public void storeYCoordinate(int tabIndex, float y) {
+        mYCoordinateArray[tabIndex] = y;
+    }
+
+    /**
+     * Returns the stored Y coordinate of this view the last time the user was on the selected
+     * tab given by tabIndex.
+     */
+    public float getStoredYCoordinateForTab(int tabIndex) {
+        return mYCoordinateArray[tabIndex];
+    }
+
+    /**
      * Returns the number of pixels that this view can be scrolled horizontally.
      */
     public int getAllowedHorizontalScrollLength() {
diff --git a/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java b/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java
index 99766f2..afe159b 100644
--- a/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java
@@ -23,7 +23,6 @@
 import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountTypeManager;
 import com.android.contacts.util.StreamItemEntry;
-import com.android.contacts.util.StreamItemPhotoEntry;
 
 import android.app.ListFragment;
 import android.content.ContentUris;
@@ -31,12 +30,12 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.provider.ContactsContract.StreamItems;
-import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.ViewGroup;
 import android.widget.AbsListView.OnScrollListener;
+import android.widget.ListView;
 
 public class ContactDetailUpdatesFragment extends ListFragment
         implements FragmentKeyListener, ViewOverlay {
@@ -194,4 +193,22 @@
         mVerticalScrollListener = listener;
     }
 
+    /**
+     * Returns the top coordinate of the first item in the {@link ListView}. If the first item
+     * in the {@link ListView} is not visible or there are no children in the list, then return
+     * Integer.MIN_VALUE. Note that the returned value will be <= 0 because the first item in the
+     * list cannot have a positive offset.
+     */
+    public int getFirstListItemOffset() {
+        return ContactDetailDisplayUtils.getFirstListItemOffset(getListView());
+    }
+
+    /**
+     * Tries to scroll the first item to the given offset (this can be a no-op if the list is
+     * already in the correct position).
+     * @param offset which should be <= 0
+     */
+    public void requestToMoveToOffset(int offset) {
+        ContactDetailDisplayUtils.requestToMoveToOffset(getListView(), offset);
+    }
 }
diff --git a/src/com/android/contacts/detail/ContactDetailViewPagerAdapter.java b/src/com/android/contacts/detail/ContactDetailViewPagerAdapter.java
index 40e391d..e0ed5e9 100644
--- a/src/com/android/contacts/detail/ContactDetailViewPagerAdapter.java
+++ b/src/com/android/contacts/detail/ContactDetailViewPagerAdapter.java
@@ -36,17 +36,17 @@
 
     private int mFragmentViewCount = MAX_FRAGMENT_VIEW_COUNT;
 
-    private ViewGroup mAboutFragmentView;
-    private ViewGroup mUpdatesFragmentView;
+    private View mAboutFragmentView;
+    private View mUpdatesFragmentView;
 
     public ContactDetailViewPagerAdapter() {
     }
 
-    public void setAboutFragmentView(ViewGroup view) {
+    public void setAboutFragmentView(View view) {
         mAboutFragmentView = view;
     }
 
-    public void setUpdatesFragmentView(ViewGroup view) {
+    public void setUpdatesFragmentView(View view) {
         mUpdatesFragmentView = view;
     }
 
diff --git a/src/com/android/contacts/detail/TabCarouselScrollManager.java b/src/com/android/contacts/detail/TabCarouselScrollManager.java
deleted file mode 100644
index 2415b50..0000000
--- a/src/com/android/contacts/detail/TabCarouselScrollManager.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.contacts.detail;
-
-import android.view.View;
-import android.widget.AbsListView;
-import android.widget.AbsListView.OnScrollListener;
-
-/**
- * Takes care of managing scrolling for the side-by-side views using the tab carousel.
- */
-public class TabCarouselScrollManager {
-    private final ContactDetailTabCarousel mTabCarousel;
-    private final ContactDetailFragment mAboutFragment;
-    private final ContactDetailUpdatesFragment mUpdatesFragment;
-    private final OnScrollListener mVerticalScrollListener = new OnScrollListener() {
-        @Override
-        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
-                int totalItemCount) {
-            if (mTabCarousel == null) {
-                return;
-            }
-            // If the FIRST item is not visible on the screen, then the carousel must be pinned
-            // at the top of the screen.
-            if (firstVisibleItem != 0) {
-                mTabCarousel.setY(-mTabCarousel.getAllowedVerticalScrollLength());
-                return;
-            }
-            View topView = view.getChildAt(firstVisibleItem);
-            if (topView == null) {
-                return;
-            }
-            int amtToScroll = Math.max((int) view.getChildAt(firstVisibleItem).getY(),
-                    -mTabCarousel.getAllowedVerticalScrollLength());
-            mTabCarousel.setY(amtToScroll);
-        }
-
-        @Override
-        public void onScrollStateChanged(AbsListView view, int scrollState) {}
-    };
-
-    private TabCarouselScrollManager(ContactDetailTabCarousel tabCarousel,
-            ContactDetailFragment aboutFragment, ContactDetailUpdatesFragment updatesFragment) {
-        mTabCarousel = tabCarousel;
-        mAboutFragment = aboutFragment;
-        mUpdatesFragment = updatesFragment;
-    }
-
-    public void bind() {
-        mAboutFragment.setVerticalScrollListener(mVerticalScrollListener);
-        mUpdatesFragment.setVerticalScrollListener(mVerticalScrollListener);
-    }
-
-    public static void bind(ContactDetailTabCarousel tabCarousel,
-            ContactDetailFragment aboutFragment, ContactDetailUpdatesFragment updatesFragment) {
-        TabCarouselScrollManager scrollManager =
-                new TabCarouselScrollManager(tabCarousel, aboutFragment, updatesFragment);
-        scrollManager.bind();
-    }
-}