Merge "Throw exception if there's a contact loader error"
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 9148651..91a72d3 100644
--- a/res/layout/carousel_updates_tab.xml
+++ b/res/layout/carousel_updates_tab.xml
@@ -27,7 +27,8 @@
     android:background="@drawable/bg_people_updates_holo">
 
     <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();
-    }
-}