Phone landscape view of contact card

- No tab carousel, show half of the other (about or updates) page,
tap or swipe to see the other page
- Add alpha layer and touch interceptor layer
- Add new resources folder for w470dp to cover landscape on
phone sized devices

Change-Id: Ia3b1cd76ebe35420b1facd415998b14ba161b0ba
diff --git a/res/layout-w470dp/contact_detail_activity.xml b/res/layout-w470dp/contact_detail_activity.xml
new file mode 100644
index 0000000..bf649a2
--- /dev/null
+++ b/res/layout-w470dp/contact_detail_activity.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<FrameLayout 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">
+
+    <com.android.contacts.detail.ContactDetailFragmentCarousel
+        android:id="@+id/fragment_carousel"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentLeft="true"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"/>
+
+</FrameLayout>
diff --git a/res/layout-w470dp/contact_detail_fragment.xml b/res/layout-w470dp/contact_detail_fragment.xml
new file mode 100644
index 0000000..64fec60
--- /dev/null
+++ b/res/layout-w470dp/contact_detail_fragment.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/contact_detail"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <ImageView android:id="@+id/photo"
+            android:scaleType="centerCrop"
+            android:layout_width="100dip"
+            android:layout_height="100dip"
+            android:layout_marginLeft="@dimen/detail_contact_photo_margin"
+            android:layout_marginRight="@dimen/detail_contact_photo_margin"
+            android:layout_marginTop="@dimen/detail_contact_photo_margin"
+            android:layout_marginBottom="@dimen/detail_contact_photo_margin"/>
+
+        <ListView android:id="@android:id/list"
+            android:layout_width="0dip"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:divider="@null"/>
+
+   </LinearLayout>
+
+    <ScrollView android:id="@android:id/empty"
+        android:layout_width="match_parent"
+        android:layout_height="0px"
+        android:layout_weight="1"
+        android:visibility="gone">
+        <TextView android:id="@+id/emptyText"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/no_contact_details"
+            android:textSize="20sp"
+            android:textColor="?android:attr/textColorSecondary"
+            android:paddingLeft="10dip"
+            android:paddingRight="10dip"
+            android:paddingTop="10dip"
+            android:lineSpacingMultiplier="0.92"/>
+    </ScrollView>
+
+    <!-- "Copy to my contacts"- button -->
+    <Button
+        android:id="@+id/copyLocal"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/menu_copyContact"
+        android:visibility="gone"
+        android:layout_gravity="right"
+        android:layout_marginRight="40dip"
+        android:layout_marginTop="20dip"
+        android:layout_marginBottom="20dip" />
+
+    <View
+        android:id="@+id/alpha_overlay"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentTop="true"
+        android:background="@android:color/black"
+        android:alpha=".50"
+        android:visibility="gone"/>
+
+    <View
+        android:id="@+id/touch_intercept_overlay"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentTop="true"
+        android:background="@android:color/transparent"
+        android:visibility="gone"/>
+</RelativeLayout>
+
diff --git a/res/layout/contact_detail_fragment_carousel.xml b/res/layout/contact_detail_fragment_carousel.xml
new file mode 100644
index 0000000..165b6a1
--- /dev/null
+++ b/res/layout/contact_detail_fragment_carousel.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:scrollbars="none"
+    android:orientation="horizontal">
+
+    <fragment class="com.android.contacts.detail.ContactDetailAboutFragment"
+        android:id="@+id/about_fragment"
+        android:layout_width="@dimen/detail_fragment_carousel_fragment_width"
+        android:layout_height="match_parent" />
+
+    <fragment class="com.android.contacts.detail.ContactDetailUpdatesFragment"
+        android:id="@+id/updates_fragment"
+        android:layout_width="@dimen/detail_fragment_carousel_fragment_width"
+        android:layout_height="match_parent" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/contact_detail_updates_fragment.xml b/res/layout/contact_detail_updates_fragment.xml
index f60be2a..daa5608 100644
--- a/res/layout/contact_detail_updates_fragment.xml
+++ b/res/layout/contact_detail_updates_fragment.xml
@@ -14,7 +14,7 @@
      limitations under the License.
 -->
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/contact_detail"
     android:orientation="vertical"
     android:layout_width="match_parent"
@@ -30,5 +30,20 @@
         android:paddingRight="10dip"
         android:paddingTop="@dimen/detail_tab_carousel_height"
         android:layout_marginTop="10dip"/>
-</LinearLayout>
+
+    <View
+        android:id="@+id/alpha_overlay"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@android:color/black"
+        android:alpha=".50"
+        android:visibility="gone"/>
+
+    <View
+        android:id="@+id/touch_intercept_overlay"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@android:color/transparent"
+        android:visibility="gone"/>
+</FrameLayout>
 
diff --git a/res/values-w470dp/dimens.xml b/res/values-w470dp/dimens.xml
new file mode 100644
index 0000000..ba7f3f8
--- /dev/null
+++ b/res/values-w470dp/dimens.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<resources>
+    <dimen name="detail_tab_carousel_height">0dip</dimen>
+</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index e55ec34..3e5b554 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -97,12 +97,18 @@
     <!-- Height of the tab text label in the tab carousel on the contact detail page -->
     <dimen name="detail_tab_carousel_tab_label_height">40dip</dimen>
 
+    <!-- Width of one fragment in the fragment carousel on the contact detail page -->
+    <dimen name="detail_fragment_carousel_fragment_width">420dip</dimen>
+
     <!-- Vertical margin of the text within the update tab in the tab carousel -->
     <dimen name="detail_update_tab_vertical_margin">20dip</dimen>
 
     <!-- Left and right padding of the text within the update tab in the tab carousel -->
     <dimen name="detail_update_tab_side_padding">10dip</dimen>
 
+    <!-- Margin around the contact's photo on the contact card -->
+    <dimen name="detail_contact_photo_margin">15dip</dimen>
+
     <!-- Left and right padding for a contact detail item -->
     <dimen name="detail_item_icon_margin">10dip</dimen>
 
diff --git a/src/com/android/contacts/activities/ContactDetailActivity.java b/src/com/android/contacts/activities/ContactDetailActivity.java
index 1488bec..c62084b 100644
--- a/src/com/android/contacts/activities/ContactDetailActivity.java
+++ b/src/com/android/contacts/activities/ContactDetailActivity.java
@@ -23,11 +23,10 @@
 import com.android.contacts.R;
 import com.android.contacts.detail.ContactDetailAboutFragment;
 import com.android.contacts.detail.ContactDetailFragment;
-import com.android.contacts.detail.ContactDetailHeaderView;
+import com.android.contacts.detail.ContactDetailFragmentCarousel;
 import com.android.contacts.detail.ContactDetailTabCarousel;
 import com.android.contacts.detail.ContactDetailUpdatesFragment;
 import com.android.contacts.interactions.ContactDeletionInteraction;
-import com.android.contacts.list.ContactBrowseListFragment;
 import com.android.contacts.util.PhoneCapabilityTester;
 
 import android.accounts.Account;
@@ -43,9 +42,7 @@
 import android.support.v4.view.ViewPager.OnPageChangeListener;
 import android.util.Log;
 import android.view.KeyEvent;
-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;
@@ -63,12 +60,11 @@
     private ContactDetailTabCarousel mTabCarousel;
     private ViewPager mViewPager;
 
-    private Uri mUri;
+    private ContactDetailFragmentCarousel mFragmentCarousel;
 
     @Override
     public void onCreate(Bundle savedState) {
         super.onCreate(savedState);
-
         if (PhoneCapabilityTester.isUsingTwoPanes(this)) {
             // This activity must not be shown. We have to select the contact in the
             // PeopleActivity instead ==> Create a forward intent and finish
@@ -88,25 +84,35 @@
 
         setContentView(R.layout.contact_detail_activity);
 
+        // Narrow width screens have a {@link ViewPager} and {@link ContactDetailTabCarousel}
         mViewPager = (ViewPager) findViewById(R.id.pager);
-        mViewPager.setAdapter(new ViewPagerAdapter(getFragmentManager()));
-        mViewPager.setOnPageChangeListener(mOnPageChangeListener);
+        if (mViewPager != null) {
+            mViewPager.setAdapter(new ViewPagerAdapter(getFragmentManager()));
+            mViewPager.setOnPageChangeListener(mOnPageChangeListener);
+        }
 
         mTabCarousel = (ContactDetailTabCarousel) findViewById(R.id.tab_carousel);
-        mTabCarousel.setListener(mTabCarouselListener);
+        if (mTabCarousel != null) {
+            mTabCarousel.setListener(mTabCarouselListener);
+        }
 
-        mUri = getIntent().getData();
+        // Otherwise, wide width screens have a {@link ContactDetailFragmentCarousel}
+        mFragmentCarousel = (ContactDetailFragmentCarousel) findViewById(R.id.fragment_carousel);
+        if (mFragmentCarousel != null) {
+            if (mAboutFragment != null) mFragmentCarousel.setAboutFragment(mAboutFragment);
+            if (mUpdatesFragment != null) mFragmentCarousel.setUpdatesFragment(mUpdatesFragment);
+        }
+
         Log.i(TAG, getIntent().getData().toString());
     }
 
-
     @Override
     public void onAttachFragment(Fragment fragment) {
         if (fragment instanceof ContactDetailAboutFragment) {
             mAboutFragment = (ContactDetailAboutFragment) fragment;
             mAboutFragment.setListener(mFragmentListener);
             mAboutFragment.setVerticalScrollListener(mVerticalScrollListener);
-            mAboutFragment.loadUri(mUri);
+            mAboutFragment.loadUri(getIntent().getData());
         } else if (fragment instanceof ContactDetailUpdatesFragment) {
             mUpdatesFragment = (ContactDetailUpdatesFragment) fragment;
         }
@@ -125,7 +131,7 @@
     @Override
     public boolean onKeyDown(int keyCode, KeyEvent event) {
         FragmentKeyListener mCurrentFragment;
-        switch (mViewPager.getCurrentItem()) {
+        switch (getCurrentPage()) {
             case 0:
                 mCurrentFragment = (FragmentKeyListener) mAboutFragment;
                 break;
@@ -140,6 +146,16 @@
         return super.onKeyDown(keyCode, event);
     }
 
+    private int getCurrentPage() {
+        if (mViewPager != null) {
+            return mViewPager.getCurrentItem();
+        } else if (mFragmentCarousel != null) {
+            return mFragmentCarousel.getCurrentPage();
+        }
+        throw new IllegalStateException("Can't figure out the currently selected page. The activity"
+                + "must either have the ViewPager or fragment carousel");
+    }
+
     private final ContactDetailFragment.Listener mFragmentListener =
             new ContactDetailFragment.Listener() {
         @Override
@@ -149,7 +165,9 @@
 
         @Override
         public void onDetailsLoaded(ContactLoader.Result result) {
-            mTabCarousel.loadData(result);
+            if (mTabCarousel != null) {
+                mTabCarousel.loadData(result);
+            }
         }
 
         @Override
@@ -278,6 +296,9 @@
         @Override
         public void onScroll(
                 AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+            if (mTabCarousel == null) {
+                return;
+            }
             // 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).
diff --git a/src/com/android/contacts/detail/ContactDetailAboutFragment.java b/src/com/android/contacts/detail/ContactDetailAboutFragment.java
index a1377e8..fc6b9cb 100644
--- a/src/com/android/contacts/detail/ContactDetailAboutFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailAboutFragment.java
@@ -19,20 +19,12 @@
 import com.android.contacts.ContactLoader;
 import com.android.contacts.R;
 
-import android.accounts.Account;
 import android.app.ActionBar;
 import android.app.Activity;
-import android.content.ContentValues;
-import android.content.Intent;
-import android.net.Uri;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.TextView;
 
-import java.util.ArrayList;
-
 public class ContactDetailAboutFragment extends ContactDetailFragment {
 
     private static final String TAG = "ContactDetailAboutFragment";
diff --git a/src/com/android/contacts/detail/ContactDetailFragment.java b/src/com/android/contacts/detail/ContactDetailFragment.java
index 74c05a5..c2aaea4 100644
--- a/src/com/android/contacts/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailFragment.java
@@ -109,7 +109,7 @@
 import java.util.Collections;
 import java.util.List;
 
-public class ContactDetailFragment extends Fragment implements FragmentKeyListener,
+public class ContactDetailFragment extends Fragment implements FragmentKeyListener, FragmentOverlay,
         OnItemClickListener, OnItemLongClickListener, SelectAccountDialogFragment.Listener {
 
     private static final String TAG = "ContactDetailFragment";
@@ -128,6 +128,7 @@
 
     private ContactLoader.Result mContactData;
     private ContactDetailHeaderView mHeaderView;
+    private ImageView mPhotoView;
     private ListView mListView;
     private ViewAdapter mAdapter;
     private Uri mPrimaryPhoneUri = null;
@@ -166,6 +167,17 @@
     private View mEmptyView;
 
     /**
+     * This optional view adds an alpha layer over the entire fragment.
+     */
+    private View mAlphaLayer;
+
+    /**
+     * This optional view adds a layer over the entire fragment so that when visible, it intercepts
+     * all touch events on the fragment.
+     */
+    private View mTouchInterceptLayer;
+
+    /**
      * A list of distinct contact IDs included in the current contact.
      */
     private ArrayList<Long> mRawContactIds = new ArrayList<Long>();
@@ -233,6 +245,8 @@
 
         mInflater = inflater;
 
+        mPhotoView = (ImageView) mView.findViewById(R.id.photo);
+
         mListView = (ListView) mView.findViewById(android.R.id.list);
         mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY);
         mListView.setOnItemClickListener(this);
@@ -242,6 +256,9 @@
         // Don't set it to mListView yet.  We do so later when we bind the adapter.
         mEmptyView = mView.findViewById(android.R.id.empty);
 
+        mAlphaLayer = mView.findViewById(R.id.alpha_overlay);
+        mTouchInterceptLayer = mView.findViewById(R.id.touch_intercept_overlay);
+
         mCopyGalToLocalButton = (Button) mView.findViewById(R.id.copyLocal);
         mCopyGalToLocalButton.setOnClickListener(new OnClickListener() {
             @Override
@@ -262,6 +279,35 @@
         mListener = value;
     }
 
+    @Override
+    public void setAlphaLayerValue(float alpha) {
+        if (mAlphaLayer != null) {
+            mAlphaLayer.setAlpha(alpha);
+        }
+    }
+
+    @Override
+    public void enableAlphaLayer() {
+        if (mAlphaLayer != null) {
+            mAlphaLayer.setVisibility(View.VISIBLE);
+        }
+    }
+
+    @Override
+    public void enableTouchInterceptor(OnClickListener clickListener) {
+        if (mTouchInterceptLayer != null) {
+            mTouchInterceptLayer.setVisibility(View.VISIBLE);
+            mTouchInterceptLayer.setOnClickListener(clickListener);
+        }
+    }
+
+    @Override
+    public void disableTouchInterceptor() {
+        if (mTouchInterceptLayer != null) {
+            mTouchInterceptLayer.setVisibility(View.GONE);
+        }
+    }
+
     protected Context getContext() {
         return mContext;
     }
@@ -335,6 +381,11 @@
         // Clear old header
         mHeaderView = null;
 
+        // Setup the photo if applicable
+        if (mPhotoView != null) {
+            ContactDetailDisplayUtils.setPhoto(mContext, mContactData, mPhotoView);
+        }
+
         // Build up the contact entries
         buildEntries();
 
diff --git a/src/com/android/contacts/detail/ContactDetailFragmentCarousel.java b/src/com/android/contacts/detail/ContactDetailFragmentCarousel.java
new file mode 100644
index 0000000..da64c34
--- /dev/null
+++ b/src/com/android/contacts/detail/ContactDetailFragmentCarousel.java
@@ -0,0 +1,219 @@
+/*
+ * 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 com.android.contacts.R;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnTouchListener;
+import android.widget.HorizontalScrollView;
+
+/**
+ * This is a horizontally scrolling carousel with 2 fragments: one to see info about the contact and
+ * one to see updates from the contact. Depending on the scroll position and user selection of which
+ * fragment to currently view, the alpha values and touch interceptors over each fragment are
+ * configured accordingly.
+ */
+public class ContactDetailFragmentCarousel extends HorizontalScrollView implements OnTouchListener {
+
+    private static final String TAG = ContactDetailFragmentCarousel.class.getSimpleName();
+
+    private int mAllowedHorizontalScrollLength = Integer.MIN_VALUE;
+    private int mLowerThreshold = Integer.MIN_VALUE;
+    private int mUpperThreshold = Integer.MIN_VALUE;
+
+    private static final int ABOUT_PAGE = 0;
+    private static final int UPDATES_PAGE = 1;
+
+    private int mCurrentPage = ABOUT_PAGE;
+    private int mLastScrollPosition;
+
+    private FragmentOverlay mAboutFragment;
+    private FragmentOverlay mUpdatesFragment;
+
+    private static final float MAX_ALPHA = 0.5f;
+
+    public ContactDetailFragmentCarousel(Context context) {
+        this(context, null);
+    }
+
+    public ContactDetailFragmentCarousel(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ContactDetailFragmentCarousel(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        final LayoutInflater inflater =
+                (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        inflater.inflate(R.layout.contact_detail_fragment_carousel, this);
+
+        setOnTouchListener(this);
+    }
+
+    public void setAboutFragment(FragmentOverlay fragment) {
+        // TODO: We can't always assume the "about" page will be the current page.
+        mAboutFragment = fragment;
+        mAboutFragment.enableAlphaLayer();
+        mAboutFragment.setAlphaLayerValue(0);
+        mAboutFragment.disableTouchInterceptor();
+    }
+
+    public void setUpdatesFragment(FragmentOverlay fragment) {
+        mUpdatesFragment = fragment;
+        mUpdatesFragment.enableAlphaLayer();
+        mUpdatesFragment.setAlphaLayerValue(MAX_ALPHA);
+        mUpdatesFragment.enableTouchInterceptor(mUpdatesFragTouchInterceptListener);
+    }
+
+    public int getCurrentPage() {
+        return mCurrentPage;
+    }
+
+    private final OnClickListener mAboutFragTouchInterceptListener = new OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            mCurrentPage = ABOUT_PAGE;
+            snapToEdge();
+        }
+    };
+
+    private final OnClickListener mUpdatesFragTouchInterceptListener = new OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            mCurrentPage = UPDATES_PAGE;
+            snapToEdge();
+        }
+    };
+
+    private void updateTouchInterceptors() {
+        switch (mCurrentPage) {
+            case ABOUT_PAGE:
+                // The "about this contact" page has been selected, so disable the touch interceptor
+                // on this page and enable it for the "updates" page.
+                mAboutFragment.disableTouchInterceptor();
+                mUpdatesFragment.enableTouchInterceptor(mUpdatesFragTouchInterceptListener);
+                break;
+            case UPDATES_PAGE:
+                mUpdatesFragment.disableTouchInterceptor();
+                mAboutFragment.enableTouchInterceptor(mAboutFragTouchInterceptListener);
+                break;
+        }
+    }
+
+    private void updateAlphaLayers() {
+        mAboutFragment.setAlphaLayerValue(mLastScrollPosition * MAX_ALPHA /
+                getAllowedHorizontalScrollLength());
+        mUpdatesFragment.setAlphaLayerValue(MAX_ALPHA - mLastScrollPosition * MAX_ALPHA /
+                getAllowedHorizontalScrollLength());
+    }
+
+    @Override
+    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+        super.onScrollChanged(l, t, oldl, oldt);
+        mLastScrollPosition= l;
+        updateAlphaLayers();
+    }
+
+    private void snapToEdge() {
+        switch (mCurrentPage) {
+            case ABOUT_PAGE:
+                smoothScrollTo(0, 0);
+                break;
+            case UPDATES_PAGE:
+                smoothScrollTo(getAllowedHorizontalScrollLength(), 0);
+                break;
+        }
+        updateTouchInterceptors();
+    }
+
+    /**
+     * Returns the desired page we should scroll to based on the current X scroll position and the
+     * current page.
+     */
+    private int getDesiredPage() {
+        switch (mCurrentPage) {
+            case ABOUT_PAGE:
+                // If the user is on the "about" page, and the scroll position exceeds the lower
+                // threshold, then we should switch to the "updates" page.
+                return (mLastScrollPosition > getLowerThreshold()) ? UPDATES_PAGE : ABOUT_PAGE;
+            case UPDATES_PAGE:
+                // If the user is on the "updates" page, and the scroll position goes below the
+                // upper threshold, then we should switch to the "about" page.
+                return (mLastScrollPosition < getUpperThreshold()) ? ABOUT_PAGE : UPDATES_PAGE;
+        }
+        throw new IllegalStateException("Invalid current page " + mCurrentPage);
+    }
+
+    /**
+     * Returns the number of pixels that this view can be scrolled horizontally.
+     */
+    private int getAllowedHorizontalScrollLength() {
+        if (mAllowedHorizontalScrollLength == Integer.MIN_VALUE) {
+            computeThresholds();
+        }
+        return mAllowedHorizontalScrollLength;
+    }
+
+    /**
+     * Returns the minimum X scroll position that must be surpassed (if the user is on the "about"
+     * page of the contact card), in order for this view to automatically snap to the "updates"
+     * page.
+     */
+    private int getLowerThreshold() {
+        if (mLowerThreshold == Integer.MIN_VALUE) {
+            computeThresholds();
+        }
+        return mLowerThreshold;
+    }
+
+    /**
+     * Returns the maximum X scroll position (if the user is on the "updates" page of the contact
+     * card), below which this view will automatically snap to the "about" page.
+     */
+    private int getUpperThreshold() {
+        if (mLowerThreshold == Integer.MIN_VALUE) {
+            computeThresholds();
+        }
+        return mUpperThreshold;
+    }
+
+    // TODO: Move this to a Fragment override method (i.e. onActivityCreated or some method where
+    // we can be sure the width of the views are non-zero) instead of doing it on the fly when the
+    // values are requested for the first time.
+    private void computeThresholds() {
+        int screenWidth = getWidth();
+        int fragmentWidth = findViewById(R.id.about_fragment).getWidth();
+        mAllowedHorizontalScrollLength = (2 * fragmentWidth) - screenWidth;
+        mLowerThreshold = (screenWidth - fragmentWidth) / 2;
+        mUpperThreshold = mAllowedHorizontalScrollLength - mLowerThreshold;
+    }
+
+    @Override
+    public boolean onTouch(View v, MotionEvent event) {
+        if (event.getAction() == MotionEvent.ACTION_UP) {
+            mCurrentPage = getDesiredPage();
+            snapToEdge();
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/src/com/android/contacts/detail/ContactDetailTabCarousel.java b/src/com/android/contacts/detail/ContactDetailTabCarousel.java
index dc3e126..a12106f 100644
--- a/src/com/android/contacts/detail/ContactDetailTabCarousel.java
+++ b/src/com/android/contacts/detail/ContactDetailTabCarousel.java
@@ -19,12 +19,10 @@
 import com.android.contacts.ContactLoader;
 import com.android.contacts.ContactSaveService;
 import com.android.contacts.R;
-import com.android.contacts.activities.ContactDetailActivity;
 
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
-import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
diff --git a/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java b/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java
index 02678de..0c4dc4f 100644
--- a/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java
@@ -23,19 +23,67 @@
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.View.OnClickListener;
 import android.view.ViewGroup;
 
-public class ContactDetailUpdatesFragment extends Fragment implements FragmentKeyListener {
+public class ContactDetailUpdatesFragment extends Fragment
+        implements FragmentKeyListener, FragmentOverlay {
 
     private static final String TAG = "ContactDetailUpdatesFragment";
 
+    /**
+     * This optional view adds an alpha layer over the entire fragment.
+     */
+    private View mAlphaLayer;
+
+    /**
+     * This optional view adds a layer over the entire fragment so that when visible, it intercepts
+     * all touch events on the fragment.
+     */
+    private View mTouchInterceptLayer;
+
     public ContactDetailUpdatesFragment() {
         // Explicit constructor for inflation
     }
 
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
-        return inflater.inflate(R.layout.contact_detail_updates_fragment, container, false);
+        View rootView = inflater.inflate(R.layout.contact_detail_updates_fragment, container,
+                false);
+
+        mAlphaLayer = rootView.findViewById(R.id.alpha_overlay);
+        mTouchInterceptLayer = rootView.findViewById(R.id.touch_intercept_overlay);
+
+        return rootView;
+    }
+
+    @Override
+    public void setAlphaLayerValue(float alpha) {
+        if (mAlphaLayer != null) {
+            mAlphaLayer.setAlpha(alpha);
+        }
+    }
+
+    @Override
+    public void enableAlphaLayer() {
+        if (mAlphaLayer != null) {
+            mAlphaLayer.setVisibility(View.VISIBLE);
+        }
+    }
+
+    @Override
+    public void enableTouchInterceptor(OnClickListener clickListener) {
+        if (mTouchInterceptLayer != null) {
+            mTouchInterceptLayer.setVisibility(View.VISIBLE);
+            mTouchInterceptLayer.setOnClickListener(clickListener);
+        }
+    }
+
+    @Override
+    public void disableTouchInterceptor() {
+        if (mTouchInterceptLayer != null) {
+            mTouchInterceptLayer.setVisibility(View.GONE);
+        }
     }
 
     @Override
diff --git a/src/com/android/contacts/detail/FragmentOverlay.java b/src/com/android/contacts/detail/FragmentOverlay.java
new file mode 100644
index 0000000..6ef0846
--- /dev/null
+++ b/src/com/android/contacts/detail/FragmentOverlay.java
@@ -0,0 +1,49 @@
+/*
+ * 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.OnClickListener;
+
+/**
+ * This is implemented by {@link Fragment}s that contain an alpha layer and touch interceptor layer.
+ * The alpha layer covers the entire fragment and has an alpha value which makes the fragment
+ * contents appear "dimmed" out. The touch interceptor layer covers the entire fragment so that
+ * when visible, it intercepts all touch events on the {@link Fragment}.
+ */
+public interface FragmentOverlay {
+
+    /**
+     * Sets the alpha value on the alpha layer (if there is one).
+     */
+    public void setAlphaLayerValue(float alpha);
+
+    /**
+     * Makes the alpha layer on this fragment visible (if there is one).
+     */
+    public void enableAlphaLayer();
+
+    /**
+     * Makes the touch intercept layer on this fragment visible (if there is one). Also adds a click
+     * listener which is called when there is a touch event on the layer.
+     */
+    public void enableTouchInterceptor(OnClickListener clickListener);
+
+    /**
+     * Makes the touch intercept layer on this fragment gone (if there is one).
+     */
+    public void disableTouchInterceptor();
+}