Pinned tab headers on contact card

- When vertically scrolling the contact details, move the
tab carousel vertically off the screen until just the
tabs are showing
- Make ViewPager span the entire height/width of the screen
- Make the tab carousel go on top of the ViewPager

Change-Id: I8e93fb64e8bb6d1749b371a030a36883fc57f0dd
diff --git a/res/layout/contact_detail_activity.xml b/res/layout/contact_detail_activity.xml
index 744f343..d840d6f 100644
--- a/res/layout/contact_detail_activity.xml
+++ b/res/layout/contact_detail_activity.xml
@@ -14,19 +14,24 @@
      limitations under the License.
 -->
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/contact_detail_view"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical">
 
+    <android.support.v4.view.ViewPager
+        android:id="@+id/pager"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentLeft="true"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
     <com.android.contacts.detail.ContactDetailTabCarousel
         android:id="@+id/tab_carousel"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentLeft="true"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"/>
 
-    <android.support.v4.view.ViewPager
-        android:id="@+id/pager"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent" />
-</LinearLayout>
+</RelativeLayout>
diff --git a/res/layout/contact_detail_updates_fragment.xml b/res/layout/contact_detail_updates_fragment.xml
index 7baba42..f60be2a 100644
--- a/res/layout/contact_detail_updates_fragment.xml
+++ b/res/layout/contact_detail_updates_fragment.xml
@@ -28,6 +28,7 @@
         android:textColor="?android:attr/textColorSecondary"
         android:paddingLeft="10dip"
         android:paddingRight="10dip"
-        android:paddingTop="10dip"/>
+        android:paddingTop="@dimen/detail_tab_carousel_height"
+        android:layout_marginTop="10dip"/>
 </LinearLayout>
 
diff --git a/res/layout/simple_contact_detail_header_view_list_item.xml b/res/layout/simple_contact_detail_header_view_list_item.xml
index 117aef1..1fd9ec5 100644
--- a/res/layout/simple_contact_detail_header_view_list_item.xml
+++ b/res/layout/simple_contact_detail_header_view_list_item.xml
@@ -24,7 +24,8 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:orientation="vertical">
+    android:orientation="vertical"
+    android:paddingTop="@dimen/detail_tab_carousel_height">
 
     <TextView
         android:id="@+id/phonetic_name"
diff --git a/src/com/android/contacts/activities/ContactDetailActivity.java b/src/com/android/contacts/activities/ContactDetailActivity.java
index 0dbc8df..1488bec 100644
--- a/src/com/android/contacts/activities/ContactDetailActivity.java
+++ b/src/com/android/contacts/activities/ContactDetailActivity.java
@@ -46,6 +46,8 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnTouchListener;
+import android.widget.AbsListView;
+import android.widget.AbsListView.OnScrollListener;
 import android.widget.Toast;
 
 import java.util.ArrayList;
@@ -103,6 +105,7 @@
         if (fragment instanceof ContactDetailAboutFragment) {
             mAboutFragment = (ContactDetailAboutFragment) fragment;
             mAboutFragment.setListener(mFragmentListener);
+            mAboutFragment.setVerticalScrollListener(mVerticalScrollListener);
             mAboutFragment.loadUri(mUri);
         } else if (fragment instanceof ContactDetailUpdatesFragment) {
             mUpdatesFragment = (ContactDetailUpdatesFragment) fragment;
@@ -215,7 +218,8 @@
             if (mViewPager.isFakeDragging()) {
                 return;
             }
-            int x = (int) ((position + positionOffset) * mTabCarousel.getAllowedScrollLength());
+            int x = (int) ((position + positionOffset) *
+                    mTabCarousel.getAllowedHorizontalScrollLength());
             mTabCarousel.scrollTo(x, 0);
         }
 
@@ -269,6 +273,31 @@
         }
     };
 
+    private OnScrollListener mVerticalScrollListener = new OnScrollListener() {
+
+        @Override
+        public void onScroll(
+                AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+            // Only re-position the tab carousel vertically if the FIRST item is still visible on
+            // the screen, otherwise the carousel should be in the correct place (pinned at the
+            // top).
+            if (firstVisibleItem != 0) {
+                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) {}
+
+    };
+
     /**
      * This interface should be implemented by {@link Fragment}s within this
      * activity so that the activity can determine whether the currently
diff --git a/src/com/android/contacts/detail/ContactDetailFragment.java b/src/com/android/contacts/detail/ContactDetailFragment.java
index 599a12e..74c05a5 100644
--- a/src/com/android/contacts/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailFragment.java
@@ -94,6 +94,7 @@
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.ViewGroup;
+import android.widget.AbsListView.OnScrollListener;
 import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemClickListener;
 import android.widget.AdapterView.OnItemLongClickListener;
@@ -120,6 +121,7 @@
 
     private Context mContext;
     private View mView;
+    private OnScrollListener mVerticalScrollListener;
     private Uri mLookupUri;
     private Listener mListener;
     private NfcHandler mNfcHandler;
@@ -235,6 +237,7 @@
         mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY);
         mListView.setOnItemClickListener(this);
         mListView.setOnItemLongClickListener(this);
+        mListView.setOnScrollListener(mVerticalScrollListener);
 
         // Don't set it to mListView yet.  We do so later when we bind the adapter.
         mEmptyView = mView.findViewById(android.R.id.empty);
@@ -271,6 +274,10 @@
         return mContactData;
     }
 
+    public void setVerticalScrollListener(OnScrollListener listener) {
+        mVerticalScrollListener = listener;
+    }
+
     public Uri getUri() {
         return mLookupUri;
     }
diff --git a/src/com/android/contacts/detail/ContactDetailTabCarousel.java b/src/com/android/contacts/detail/ContactDetailTabCarousel.java
index a8803f5..dc3e126 100644
--- a/src/com/android/contacts/detail/ContactDetailTabCarousel.java
+++ b/src/com/android/contacts/detail/ContactDetailTabCarousel.java
@@ -55,7 +55,8 @@
 
     private View[] mTabs = new View[2];
 
-    private int mAllowedScrollLength;
+    private int mAllowedHorizontalScrollLength = Integer.MIN_VALUE;
+    private int mAllowedVerticalScrollLength = Integer.MIN_VALUE;
 
     /**
      * Interface for callbacks invoked when the user interacts with the carousel.
@@ -92,10 +93,13 @@
     }
 
     /**
-     * Returns the number of pixels that this view can be scrolled.
+     * Returns the number of pixels that this view can be scrolled horizontally.
      */
-    public int getAllowedScrollLength() {
-        if (mAllowedScrollLength == 0) {
+    public int getAllowedHorizontalScrollLength() {
+        // We can't compute this in the constructor because the view widths are 0, so do the
+        // calculation only when this getter method is called (all the views should be created
+        // by this time).
+        if (mAllowedHorizontalScrollLength == Integer.MIN_VALUE) {
             // Find the total length of two tabs side-by-side
             int totalLength = 0;
             for (int i=0; i < mTabs.length; i++) {
@@ -103,9 +107,27 @@
             }
             // Find the allowed scrolling length by subtracting the current visible screen width
             // from the total length of the tabs.
-            mAllowedScrollLength = totalLength - getWidth();
+            mAllowedHorizontalScrollLength = totalLength - getWidth();
         }
-        return mAllowedScrollLength;
+        return mAllowedHorizontalScrollLength;
+    }
+
+    /**
+     * Returns the number of pixels that this view can be scrolled vertically while still allowing
+     * the tab labels to still show.
+     */
+    public int getAllowedVerticalScrollLength() {
+        if (mAllowedVerticalScrollLength == Integer.MIN_VALUE) {
+            // Find the total height of a tab
+            View aboutView = findViewById(R.id.tab_about);
+            int totalHeight = aboutView.getHeight();
+            // Find the height of a tab label
+            TextView aboutTab = (TextView) aboutView.findViewById(R.id.label);
+            int labelHeight = aboutTab.getHeight();
+            // Find the allowed scrolling length by subtracting the two values
+            mAllowedVerticalScrollLength = totalHeight - labelHeight;
+        }
+        return mAllowedVerticalScrollLength;
     }
 
     /**