Add tabs to contact card

- Since the ContactHeaderView and ContactDetailFragment will still be used
for contacts without updates, leave this code in. Temporarily make all
contact cards have the about and update tabs.
- Use ViewPager for contact details
- Move name and job to action bar
- Add about fragment and updates fragment
- Add carousel for "about" and "updates" tab
- Create utils class for converting ContactLoader results into
strings for display since the ContactHeaderView will no longer
be the only customer for this data

Change-Id: I84ff759ee09daefcc7e7514564c180f27f0400b6
diff --git a/Android.mk b/Android.mk
index 1c9dcc4..c6bea9d 100644
--- a/Android.mk
+++ b/Android.mk
@@ -9,7 +9,9 @@
     com.android.phone.common \
     com.android.vcard \
     android-common \
-    guava
+    guava \
+    android-support-v13 \
+    android-support-v4
 
 LOCAL_PACKAGE_NAME := Contacts
 LOCAL_CERTIFICATE := shared
diff --git a/res/layout/carousel_about_tab.xml b/res/layout/carousel_about_tab.xml
new file mode 100644
index 0000000..f1ed4f1
--- /dev/null
+++ b/res/layout/carousel_about_tab.xml
@@ -0,0 +1,66 @@
+<?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:layout_width="@dimen/detail_tab_carousel_tab_width"
+    android:layout_height="@dimen/detail_tab_carousel_height"
+    android:background="@color/detail_tab_background">
+
+    <ImageView android:id="@+id/photo"
+        android:scaleType="centerCrop"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentLeft="true"/>
+
+    <!-- Transparent view to overlay on the contact's photo
+    (to allow white text to appear over a white photo). -->
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/detail_tab_carousel_tab_label_height"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentBottom="true"
+        android:background="@android:color/black"
+        android:alpha=".25"/>
+
+    <TextView
+        android:id="@+id/label"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/detail_tab_carousel_tab_label_height"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentBottom="true"
+        android:paddingLeft="@dimen/detail_item_side_margin"
+        android:singleLine="true"
+        android:gravity="left|center_vertical"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:textColor="@color/detail_header_view_text_color"
+        style="@android:style/Widget.Holo.ActionBar.TabView" />
+
+    <CheckBox
+        android:id="@+id/star"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="10dip"
+        android:layout_marginRight="10dip"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentRight="true"
+        android:layout_gravity="center_vertical"
+        android:contentDescription="@string/description_star"
+        android:visibility="invisible"
+        style="?android:attr/starStyle"/>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/res/layout/carousel_updates_tab.xml b/res/layout/carousel_updates_tab.xml
new file mode 100644
index 0000000..9deb2f7
--- /dev/null
+++ b/res/layout/carousel_updates_tab.xml
@@ -0,0 +1,66 @@
+<?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:layout_width="@dimen/detail_tab_carousel_tab_width"
+    android:layout_height="@dimen/detail_tab_carousel_height"
+    android:background="@color/detail_tab_background">
+
+    <!-- Transparent view to overlay on the contact's photo
+    (to allow white text to appear over a white photo). -->
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/detail_tab_carousel_tab_label_height"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentBottom="true"
+        android:background="@android:color/black"
+        android:alpha=".25"/>
+
+    <TextView
+        android:id="@+id/label"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/detail_tab_carousel_tab_label_height"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentBottom="true"
+        android:paddingLeft="@dimen/detail_item_side_margin"
+        android:singleLine="true"
+        android:gravity="left|center_vertical"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:textColor="@color/detail_header_view_text_color"
+        style="@android:style/Widget.Holo.ActionBar.TabView" />
+
+    <TextView android:id="@+id/status"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentLeft="true"
+        android:layout_marginTop="@dimen/detail_update_tab_vertical_margin"
+        android:paddingLeft="@dimen/detail_update_tab_side_padding"
+        android:paddingRight="@dimen/detail_update_tab_side_padding"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:maxLines="3"/>
+
+    <TextView android:id="@+id/status_date"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@+id/status"
+        android:layout_marginBottom="@dimen/detail_update_tab_vertical_margin"
+        android:paddingLeft="@dimen/detail_update_tab_side_padding"
+        android:paddingRight="@dimen/detail_update_tab_side_padding"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:textColor="?android:attr/textColorTertiary"/>
+</RelativeLayout>
\ No newline at end of file
diff --git a/res/layout/contact_detail_activity.xml b/res/layout/contact_detail_activity.xml
index 9408f89..744f343 100644
--- a/res/layout/contact_detail_activity.xml
+++ b/res/layout/contact_detail_activity.xml
@@ -14,12 +14,19 @@
      limitations under the License.
 -->
 
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout 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:layout_height="match_parent"
+    android:orientation="vertical">
 
-    <fragment class="com.android.contacts.detail.ContactDetailFragment"
-            android:id="@+id/contact_detail_fragment"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent" />
-</FrameLayout>
+    <com.android.contacts.detail.ContactDetailTabCarousel
+        android:id="@+id/tab_carousel"
+        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>
diff --git a/res/layout/contact_detail_tab_carousel.xml b/res/layout/contact_detail_tab_carousel.xml
new file mode 100644
index 0000000..a7321ee
--- /dev/null
+++ b/res/layout/contact_detail_tab_carousel.xml
@@ -0,0 +1,39 @@
+<?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.
+-->
+
+<HorizontalScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:scrollbars="none">
+
+    <LinearLayout
+        android:id="@+id/tab_container"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+
+        <include
+            android:id="@+id/tab_about"
+            layout="@layout/carousel_about_tab" />
+
+        <include
+            android:id="@+id/tab_update"
+            layout="@layout/carousel_updates_tab" />
+
+    </LinearLayout>
+
+</HorizontalScrollView>
\ No newline at end of file
diff --git a/res/layout/contact_detail_updates_fragment.xml b/res/layout/contact_detail_updates_fragment.xml
new file mode 100644
index 0000000..7baba42
--- /dev/null
+++ b/res/layout/contact_detail_updates_fragment.xml
@@ -0,0 +1,33 @@
+<?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:id="@+id/contact_detail"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <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"/>
+</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
new file mode 100644
index 0000000..117aef1
--- /dev/null
+++ b/res/layout/simple_contact_detail_header_view_list_item.xml
@@ -0,0 +1,43 @@
+<?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.
+-->
+
+<!--
+  This view temporarily holds the extra information that used to be in the
+  original contact detail header view, but now must move into the list because
+  of the new tab carousel. TODO: Integrate this better into the list as provided
+  by the mocks.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <TextView
+        android:id="@+id/phonetic_name"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:padding="10dip"
+        android:textAppearance="?android:attr/textAppearanceSmall" />
+
+    <TextView
+        android:id="@+id/attribution"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:padding="10dip"
+        android:textAppearance="?android:attr/textAppearanceSmall" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 9a60815..60873b1 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -49,6 +49,9 @@
     <!-- Color of the text indicating the type of entry (e.g. Home, Work etc) -->
     <color name="detail_header_view_text_color">#FFFFFF</color>
 
+    <!-- Color of the background of the tabs on the contact detail page -->
+    <color name="detail_tab_background">#DBDBDB</color>
+
     <!--  Color of the text foreground and background of Regular Sized ContactTile -->
     <color name="contact_tile_regular_text">#2B1B17</color>
     <color name="contact_tile_regular_text_background">#FFFFFF</color>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 68655d8..f369752 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -70,6 +70,21 @@
     <!-- Font size for the entries in a spinner in the contact editor. -->
     <dimen name="editor_field_spinner_text_size">10sp</dimen>
 
+    <!-- Height of the tab carousel on the contact detail page -->
+    <dimen name="detail_tab_carousel_height">150dip</dimen>
+
+    <!-- Width of a tab in the tab carousel on the contact detail page -->
+    <dimen name="detail_tab_carousel_tab_width">240dip</dimen>
+
+    <!-- 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>
+
+    <!-- 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>
+
     <!-- Left and right padding for a contact detail item -->
     <dimen name="detail_item_icon_margin">10dip</dimen>
 
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f259ea7..c9b2787 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -78,6 +78,12 @@
          creating a new contact. This string represents the built in way to create the contact. -->
     <string name="insertContactDescription">Create contact</string>
 
+    <!-- The tab label for the contact detail activity that displays information about the contact [CHAR LIMIT=11] -->
+    <string name="contactDetailAbout">About</string>
+
+    <!-- The tab label for the contact detail activity that displays information about the contact [CHAR LIMIT=11] -->
+    <string name="contactDetailUpdates">Updates</string>
+
     <!-- Hint text in the search box when the user hits the Search key while in the contacts app -->
     <string name="searchHint">Search contacts</string>
 
diff --git a/src/com/android/contacts/activities/ContactDetailActivity.java b/src/com/android/contacts/activities/ContactDetailActivity.java
index 5992042..0dbc8df 100644
--- a/src/com/android/contacts/activities/ContactDetailActivity.java
+++ b/src/com/android/contacts/activities/ContactDetailActivity.java
@@ -16,22 +16,36 @@
 
 package com.android.contacts.activities;
 
+import com.android.contacts.ContactLoader;
 import com.android.contacts.ContactSaveService;
 import com.android.contacts.ContactsActivity;
 import com.android.contacts.ContactsSearchManager;
 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.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;
+import android.app.Fragment;
+import android.app.FragmentManager;
 import android.content.ActivityNotFoundException;
 import android.content.ContentValues;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
+import android.support.v13.app.FragmentPagerAdapter;
+import android.support.v4.view.ViewPager;
+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.Toast;
 
 import java.util.ArrayList;
@@ -39,7 +53,15 @@
 public class ContactDetailActivity extends ContactsActivity {
     private static final String TAG = "ContactDetailActivity";
 
-    private ContactDetailFragment mFragment;
+    public static final int FRAGMENT_COUNT = 2;
+
+    private ContactDetailAboutFragment mAboutFragment;
+    private ContactDetailUpdatesFragment mUpdatesFragment;
+
+    private ContactDetailTabCarousel mTabCarousel;
+    private ViewPager mViewPager;
+
+    private Uri mUri;
 
     @Override
     public void onCreate(Bundle savedState) {
@@ -64,14 +86,29 @@
 
         setContentView(R.layout.contact_detail_activity);
 
-        mFragment = (ContactDetailFragment) getFragmentManager().findFragmentById(
-                R.id.contact_detail_fragment);
-        mFragment.setListener(mFragmentListener);
-        mFragment.loadUri(getIntent().getData());
+        mViewPager = (ViewPager) findViewById(R.id.pager);
+        mViewPager.setAdapter(new ViewPagerAdapter(getFragmentManager()));
+        mViewPager.setOnPageChangeListener(mOnPageChangeListener);
 
+        mTabCarousel = (ContactDetailTabCarousel) findViewById(R.id.tab_carousel);
+        mTabCarousel.setListener(mTabCarouselListener);
+
+        mUri = getIntent().getData();
         Log.i(TAG, getIntent().getData().toString());
     }
 
+
+    @Override
+    public void onAttachFragment(Fragment fragment) {
+        if (fragment instanceof ContactDetailAboutFragment) {
+            mAboutFragment = (ContactDetailAboutFragment) fragment;
+            mAboutFragment.setListener(mFragmentListener);
+            mAboutFragment.loadUri(mUri);
+        } else if (fragment instanceof ContactDetailUpdatesFragment) {
+            mUpdatesFragment = (ContactDetailUpdatesFragment) fragment;
+        }
+    }
+
     @Override
     public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData,
             boolean globalSearch) {
@@ -84,7 +121,18 @@
 
     @Override
     public boolean onKeyDown(int keyCode, KeyEvent event) {
-        if (mFragment.handleKeyDown(keyCode)) return true;
+        FragmentKeyListener mCurrentFragment;
+        switch (mViewPager.getCurrentItem()) {
+            case 0:
+                mCurrentFragment = (FragmentKeyListener) mAboutFragment;
+                break;
+            case 1:
+                mCurrentFragment = (FragmentKeyListener) mUpdatesFragment;
+                break;
+            default:
+                throw new IllegalStateException("Invalid current item for ViewPager");
+        }
+        if (mCurrentFragment.handleKeyDown(keyCode)) return true;
 
         return super.onKeyDown(keyCode, event);
     }
@@ -97,6 +145,11 @@
         }
 
         @Override
+        public void onDetailsLoaded(ContactLoader.Result result) {
+            mTabCarousel.loadData(result);
+        }
+
+        @Override
         public void onEditRequested(Uri contactLookupUri) {
             startActivity(new Intent(Intent.ACTION_EDIT, contactLookupUri));
         }
@@ -127,4 +180,105 @@
 
         }
     };
+
+    public class ViewPagerAdapter extends FragmentPagerAdapter{
+
+        public ViewPagerAdapter(FragmentManager fm) {
+            super(fm);
+        }
+
+        @Override
+        public Fragment getItem(int position) {
+            switch (position) {
+                case 0:
+                    return new ContactDetailAboutFragment();
+                case 1:
+                    return new ContactDetailUpdatesFragment();
+            }
+            throw new IllegalStateException("No fragment at position " + position);
+        }
+
+        @Override
+        public int getCount() {
+            return FRAGMENT_COUNT;
+        }
+    }
+
+    private OnPageChangeListener mOnPageChangeListener = new OnPageChangeListener() {
+
+        @Override
+        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+            // The user is horizontally dragging the {@link ViewPager}, so send
+            // these scroll changes to the tab carousel. Ignore these events though if the carousel
+            // is actually controlling the {@link ViewPager} scrolls because it will already be
+            // in the correct position.
+            if (mViewPager.isFakeDragging()) {
+                return;
+            }
+            int x = (int) ((position + positionOffset) * mTabCarousel.getAllowedScrollLength());
+            mTabCarousel.scrollTo(x, 0);
+        }
+
+        @Override
+        public void onPageSelected(int position) {
+            // Since a new page has been selected by the {@link ViewPager},
+            // update the tab selection in the carousel.
+            mTabCarousel.setCurrentTab(position);
+        }
+
+        @Override
+        public void onPageScrollStateChanged(int state) {}
+
+    };
+
+    private ContactDetailTabCarousel.Listener mTabCarouselListener =
+            new ContactDetailTabCarousel.Listener() {
+
+        @Override
+        public void onTouchDown() {
+            // The user just started scrolling the carousel, so begin "fake dragging" the
+            // {@link ViewPager} if it's not already doing so.
+            if (mViewPager.isFakeDragging()) {
+                return;
+            }
+            mViewPager.beginFakeDrag();
+        }
+
+        @Override
+        public void onTouchUp() {
+            // The user just stopped scrolling the carousel, so stop "fake dragging" the
+            // {@link ViewPager} if was doing so before.
+            if (mViewPager.isFakeDragging()) {
+                mViewPager.endFakeDrag();
+            }
+        }
+
+        @Override
+        public void onScrollChanged(int l, int t, int oldl, int oldt) {
+            // The user is scrolling the carousel, so send the scroll deltas to the
+            // {@link ViewPager} so it can move in sync.
+            if (mViewPager.isFakeDragging()) {
+                mViewPager.fakeDragBy(oldl-l);
+            }
+        }
+
+        @Override
+        public void onTabSelected(int position) {
+            // The user selected a tab, so update the {@link ViewPager}
+            mViewPager.setCurrentItem(position);
+        }
+    };
+
+    /**
+     * This interface should be implemented by {@link Fragment}s within this
+     * activity so that the activity can determine whether the currently
+     * displayed view is handling the key event or not.
+     */
+    public interface FragmentKeyListener {
+        /**
+         * Returns true if the key down event will be handled by the implementing class, or false
+         * otherwise.
+         */
+        public boolean handleKeyDown(int keyCode);
+    }
 }
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index 53dd306..c247d43 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -16,6 +16,7 @@
 
 package com.android.contacts.activities;
 
+import com.android.contacts.ContactLoader;
 import com.android.contacts.ContactSaveService;
 import com.android.contacts.ContactsActivity;
 import com.android.contacts.R;
@@ -679,6 +680,11 @@
         }
 
         @Override
+        public void onDetailsLoaded(ContactLoader.Result result) {
+            // Nothing needs to be done here
+        }
+
+        @Override
         public void onEditRequested(Uri contactLookupUri) {
             startActivityForResult(
                     new Intent(Intent.ACTION_EDIT, contactLookupUri), SUBACTIVITY_EDIT_CONTACT);
diff --git a/src/com/android/contacts/detail/ContactDetailAboutFragment.java b/src/com/android/contacts/detail/ContactDetailAboutFragment.java
new file mode 100644
index 0000000..a1377e8
--- /dev/null
+++ b/src/com/android/contacts/detail/ContactDetailAboutFragment.java
@@ -0,0 +1,78 @@
+/*
+ * 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.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";
+
+    public ContactDetailAboutFragment() {
+        // Explicit constructor for inflation
+    }
+
+    @Override
+    protected View createNewHeaderView(ViewGroup parent) {
+        ViewGroup headerView = (ViewGroup) inflate(
+                R.layout.simple_contact_detail_header_view_list_item, parent, false);
+        TextView phoneticNameView = (TextView) headerView.findViewById(R.id.phonetic_name);
+        TextView attributionView = (TextView) headerView.findViewById(R.id.attribution);
+        ContactDetailDisplayUtils.setPhoneticName(getContext(), getContactData(), phoneticNameView);
+        ContactDetailDisplayUtils.setAttribution(getContext(), getContactData(), attributionView);
+        return headerView;
+    }
+
+    @Override
+    protected void bindData() {
+        ContactLoader.Result contactData = getContactData();
+        if (contactData != null) {
+            // Setup the activity title and subtitle with contact name and company
+            Activity activity = getActivity();
+            CharSequence displayName = ContactDetailDisplayUtils.getDisplayName(activity,
+                    contactData);
+            String company =  ContactDetailDisplayUtils.getCompany(activity, contactData);
+
+            ActionBar actionBar = activity.getActionBar();
+            actionBar.setTitle(displayName);
+            actionBar.setSubtitle(company);
+
+            // Pass the contact loader result to the listener to finish setup
+            Listener listener = getListener();
+            if (listener != null) {
+                listener.onDetailsLoaded(contactData);
+            }
+        }
+
+        super.bindData();
+    }
+}
diff --git a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
new file mode 100644
index 0000000..0f12de7
--- /dev/null
+++ b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
@@ -0,0 +1,246 @@
+/*
+ * 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.ContactLoader;
+import com.android.contacts.ContactLoader.Result;
+import com.android.contacts.R;
+import com.android.contacts.format.FormatUtils;
+import com.android.contacts.preference.ContactsPreferences;
+import com.android.contacts.util.ContactBadgeUtil;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Entity;
+import android.content.Entity.NamedContentValues;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Typeface;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.DisplayNameSources;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.AlphaAnimation;
+import android.widget.CheckBox;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * This class contains utility methods to bind high-level contact details
+ * (meaning name, phonetic name, job, and attribution) from a
+ * {@link ContactLoader.Result} data object to appropriate {@link View}s.
+ */
+public class ContactDetailDisplayUtils {
+
+    private static final int PHOTO_FADE_IN_ANIMATION_DURATION_MILLIS = 100;
+
+    private ContactDetailDisplayUtils() {
+        // Disallow explicit creation of this class.
+    }
+
+    /**
+     * Returns the display name of the contact. Depending on the preference for
+     * display name ordering, the contact's first name may be bolded if
+     * possible. Returns empty string if there is no display name.
+     */
+    public static CharSequence getDisplayName(Context context, Result contactData) {
+        CharSequence displayName = contactData.getDisplayName();
+        CharSequence altDisplayName = contactData.getAltDisplayName();
+        ContactsPreferences prefs = new ContactsPreferences(context);
+        CharSequence styledName = "";
+        if (!TextUtils.isEmpty(displayName) && !TextUtils.isEmpty(altDisplayName)) {
+            if (prefs.getDisplayOrder() == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
+                int overlapPoint = FormatUtils.overlapPoint(
+                        displayName.toString(), altDisplayName.toString());
+                if (overlapPoint > 0) {
+                    styledName = FormatUtils.applyStyleToSpan(Typeface.BOLD,
+                            displayName, 0, overlapPoint, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                } else {
+                    styledName = displayName;
+                }
+            } else {
+                // Displaying alternate display name.
+                int overlapPoint = FormatUtils.overlapPoint(
+                        altDisplayName.toString(), displayName.toString());
+                if (overlapPoint > 0) {
+                    styledName = FormatUtils.applyStyleToSpan(Typeface.BOLD,
+                            altDisplayName, overlapPoint, altDisplayName.length(),
+                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                } else {
+                    styledName = altDisplayName;
+                }
+            }
+        }
+        return styledName;
+    }
+
+    /**
+     * Returns the phonetic name of the contact or null if there isn't one.
+     */
+    public static String getPhoneticName(Context context, Result contactData) {
+        String phoneticName = contactData.getPhoneticName();
+        if (!TextUtils.isEmpty(phoneticName)) {
+            return phoneticName;
+        }
+        return null;
+    }
+
+    /**
+     * Returns the attribution string for the contact. This could either specify
+     * that this is a joined contact or specify the contact directory that the
+     * contact came from. Returns null if there is none applicable.
+     */
+    public static String getAttribution(Context context, Result contactData) {
+        // Check if this is a joined contact
+        if (contactData.getEntities().size() > 1) {
+            return context.getString(R.string.indicator_joined_contact);
+        } else if (contactData.isDirectoryEntry()) {
+            // This contact is from a directory
+            String directoryDisplayName = contactData.getDirectoryDisplayName();
+            String directoryType = contactData.getDirectoryType();
+            String displayName = !TextUtils.isEmpty(directoryDisplayName)
+                    ? directoryDisplayName
+                    : directoryType;
+            return context.getString(R.string.contact_directory_description, displayName);
+        }
+        return null;
+    }
+
+    /**
+     * Returns the organization of the contact. If several organizations are given,
+     * the first one is used. Returns null if not applicable.
+     */
+    public static String getCompany(Context context, Result contactData) {
+        final boolean displayNameIsOrganization = contactData.getDisplayNameSource()
+                == DisplayNameSources.ORGANIZATION;
+        for (Entity entity : contactData.getEntities()) {
+            for (NamedContentValues subValue : entity.getSubValues()) {
+                final ContentValues entryValues = subValue.values;
+                final String mimeType = entryValues.getAsString(Data.MIMETYPE);
+
+                if (Organization.CONTENT_ITEM_TYPE.equals(mimeType)) {
+                    final String company = entryValues.getAsString(Organization.COMPANY);
+                    final String title = entryValues.getAsString(Organization.TITLE);
+                    final String combined;
+                    // We need to show company and title in a combined string. However, if the
+                    // DisplayName is already the organization, it mirrors company or (if company
+                    // is empty title). Make sure we don't show what's already shown as DisplayName
+                    if (TextUtils.isEmpty(company)) {
+                        combined = displayNameIsOrganization ? null : title;
+                    } else {
+                        if (TextUtils.isEmpty(title)) {
+                            combined = displayNameIsOrganization ? null : company;
+                        } else {
+                            if (displayNameIsOrganization) {
+                                combined = title;
+                            } else {
+                                combined = context.getString(
+                                        R.string.organization_company_and_title,
+                                        company, title);
+                            }
+                        }
+                    }
+
+                    if (!TextUtils.isEmpty(combined)) {
+                        return combined;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+
+    /**
+     * Sets the contact photo to display in the given {@link ImageView}. If bitmap is null, the
+     * default placeholder image is shown.
+     */
+    public static void setPhoto(Context context, Result contactData, ImageView photoView) {
+        if (contactData.isLoadingPhoto()) {
+            photoView.setImageBitmap(null);
+            return;
+        }
+        byte[] photo = contactData.getPhotoBinaryData();
+        Bitmap bitmap = photo != null ? BitmapFactory.decodeByteArray(photo, 0, photo.length)
+                : ContactBadgeUtil.loadPlaceholderPhoto(context);
+        boolean fadeIn = contactData.isDirectoryEntry();
+        if (photoView.getDrawable() == null && fadeIn) {
+            AlphaAnimation animation = new AlphaAnimation(0, 1);
+            animation.setDuration(PHOTO_FADE_IN_ANIMATION_DURATION_MILLIS);
+            animation.setInterpolator(new AccelerateInterpolator());
+            photoView.startAnimation(animation);
+        }
+        photoView.setImageBitmap(bitmap);
+    }
+
+    /**
+     * Sets the starred state of this contact.
+     */
+    public static void setStarred(Result contactData, CheckBox starredView) {
+        // Check if the starred state should be visible
+        if (!contactData.isDirectoryEntry()) {
+            starredView.setVisibility(View.VISIBLE);
+            starredView.setChecked(contactData.getStarred());
+        } else {
+            starredView.setVisibility(View.GONE);
+        }
+    }
+
+    /**
+     * Set the social snippet text and date. If there isn't one, then set the view to gone.
+     */
+    public static void setSocialSnippetAndDate(Context context, Result contactData,
+            TextView statusView, TextView dateView) {
+        setDataOrHideIfNone(contactData.getSocialSnippet(), statusView);
+        setDataOrHideIfNone(ContactBadgeUtil.getSocialDate(contactData, context), dateView);
+    }
+
+    /**
+     * Sets the phonetic name of this contact to the given {@link TextView}. If
+     * there is none, then set the view to gone.
+     */
+    public static void setPhoneticName(Context context, Result contactData, TextView textView) {
+        setDataOrHideIfNone(getPhoneticName(context, contactData), textView);
+    }
+
+    /**
+     * Sets the attribution contact to the given {@link TextView}. If
+     * there is none, then set the view to gone.
+     */
+    public static void setAttribution(Context context, Result contactData, TextView textView) {
+        setDataOrHideIfNone(getAttribution(context, contactData), textView);
+    }
+
+    /**
+     * Helper function to display the given text in the {@link TextView} or
+     * hides the {@link TextView} if the text is empty or null.
+     */
+    private static void setDataOrHideIfNone(CharSequence textToDisplay, TextView textView) {
+        if (!TextUtils.isEmpty(textToDisplay)) {
+            textView.setText(textToDisplay);
+            textView.setVisibility(View.VISIBLE);
+        } else {
+            textView.setText(null);
+            textView.setVisibility(View.GONE);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/detail/ContactDetailFragment.java b/src/com/android/contacts/detail/ContactDetailFragment.java
index 2a0085a..599a12e 100644
--- a/src/com/android/contacts/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailFragment.java
@@ -26,6 +26,7 @@
 import com.android.contacts.NfcHandler;
 import com.android.contacts.R;
 import com.android.contacts.TypePrecedence;
+import com.android.contacts.activities.ContactDetailActivity.FragmentKeyListener;
 import com.android.contacts.editor.SelectAccountDialogFragment;
 import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountType.EditType;
@@ -107,7 +108,7 @@
 import java.util.Collections;
 import java.util.List;
 
-public class ContactDetailFragment extends Fragment implements
+public class ContactDetailFragment extends Fragment implements FragmentKeyListener,
         OnItemClickListener, OnItemLongClickListener, SelectAccountDialogFragment.Listener {
 
     private static final String TAG = "ContactDetailFragment";
@@ -250,10 +251,26 @@
         return mView;
     }
 
+    protected View inflate(int resource, ViewGroup root, boolean attachToRoot) {
+        return mInflater.inflate(resource, root, attachToRoot);
+    }
+
     public void setListener(Listener value) {
         mListener = value;
     }
 
+    protected Context getContext() {
+        return mContext;
+    }
+
+    protected Listener getListener() {
+        return mListener;
+    }
+
+    protected ContactLoader.Result getContactData() {
+        return mContactData;
+    }
+
     public Uri getUri() {
         return mLookupUri;
     }
@@ -289,7 +306,7 @@
         }
     }
 
-    private void bindData() {
+    protected void bindData() {
         if (mView == null) {
             return;
         }
@@ -986,11 +1003,7 @@
             if (mHeaderView != null) {
                 return mHeaderView;
             }
-            mHeaderView = (ContactDetailHeaderView) mInflater.inflate(
-                    R.layout.contact_detail_header_view_list_item, parent, false);
-            mHeaderView.setListener(mHeaderViewListener);
-            mHeaderView.loadData(mContactData);
-            return mHeaderView;
+            return createNewHeaderView(parent);
         }
 
         private View getSeparatorEntryView(View convertView, ViewGroup parent) {
@@ -1172,6 +1185,16 @@
         }
     }
 
+    /**
+     * Returns a new header view for the top of the list of contact details.
+     */
+    protected View createNewHeaderView(ViewGroup parent) {
+        mHeaderView = (ContactDetailHeaderView) inflate(
+                R.layout.contact_detail_header_view_list_item, parent, false);
+        mHeaderView.loadData(mContactData);
+        return mHeaderView;
+    }
+
     @Override
     public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
         inflater.inflate(R.menu.view_contact, menu);
@@ -1336,6 +1359,7 @@
         return true;
     }
 
+    @Override
     public boolean handleKeyDown(int keyCode) {
         switch (keyCode) {
             case KeyEvent.KEYCODE_CALL: {
@@ -1414,17 +1438,6 @@
         }
     };
 
-    private ContactDetailHeaderView.Listener mHeaderViewListener =
-            new ContactDetailHeaderView.Listener() {
-        @Override
-        public void onDisplayNameClick(View view) {
-        }
-
-        @Override
-        public void onPhotoClick(View view) {
-        }
-    };
-
     public static interface Listener {
         /**
          * Contact was not found, so somehow close this fragment. This is raised after a contact
@@ -1433,6 +1446,11 @@
         public void onContactNotFound();
 
         /**
+         * This contact's details have been loaded.
+         */
+        public void onDetailsLoaded(ContactLoader.Result result);
+
+        /**
          * User decided to go to Edit-Mode
          */
         public void onEditRequested(Uri lookupUri);
diff --git a/src/com/android/contacts/detail/ContactDetailHeaderView.java b/src/com/android/contacts/detail/ContactDetailHeaderView.java
index 795ed62..63f8fbe 100644
--- a/src/com/android/contacts/detail/ContactDetailHeaderView.java
+++ b/src/com/android/contacts/detail/ContactDetailHeaderView.java
@@ -56,6 +56,7 @@
  * Header for displaying a title bar with contact info. You
  * can bind specific values by calling
  * {@link ContactDetailHeaderView#loadData(com.android.contacts.ContactLoader.Result)}
+ * TODO: Refactor to use {@link ContactDetailDisplayUtils}
  */
 public class ContactDetailHeaderView extends FrameLayout
         implements View.OnClickListener, View.OnLongClickListener {
diff --git a/src/com/android/contacts/detail/ContactDetailTabCarousel.java b/src/com/android/contacts/detail/ContactDetailTabCarousel.java
new file mode 100644
index 0000000..a8803f5
--- /dev/null
+++ b/src/com/android/contacts/detail/ContactDetailTabCarousel.java
@@ -0,0 +1,230 @@
+/*
+ * 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.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;
+import android.view.View;
+import android.view.View.OnTouchListener;
+import android.widget.CheckBox;
+import android.widget.HorizontalScrollView;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * This is a horizontally scrolling carousel with 2 tabs: one to see info about the contact and
+ * one to see updates from the contact.
+ * TODO: Create custom views for the tabs so their width can be programatically set as 2/3 of the
+ * screen width.
+ */
+public class ContactDetailTabCarousel extends HorizontalScrollView
+        implements View.OnClickListener, OnTouchListener {
+    private static final String TAG = "ContactDetailTabCarousel";
+
+    private CheckBox mStarredView;
+    private ImageView mPhotoView;
+    private TextView mStatusView;
+    private TextView mStatusDateView;
+
+    private Uri mContactUri;
+    private Listener mListener;
+
+    private View[] mTabs = new View[2];
+
+    private int mAllowedScrollLength;
+
+    /**
+     * Interface for callbacks invoked when the user interacts with the carousel.
+     */
+    public interface Listener {
+        public void onTouchDown();
+        public void onTouchUp();
+        public void onScrollChanged(int l, int t, int oldl, int oldt);
+        public void onTabSelected(int position);
+    }
+
+    public ContactDetailTabCarousel(Context context) {
+        this(context, null);
+    }
+
+    public ContactDetailTabCarousel(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ContactDetailTabCarousel(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_tab_carousel, this);
+
+        setOnTouchListener(this);
+    }
+
+    @Override
+    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+        super.onScrollChanged(l, t, oldl, oldt);
+        mListener.onScrollChanged(l, t, oldl, oldt);
+    }
+
+    /**
+     * Returns the number of pixels that this view can be scrolled.
+     */
+    public int getAllowedScrollLength() {
+        if (mAllowedScrollLength == 0) {
+            // Find the total length of two tabs side-by-side
+            int totalLength = 0;
+            for (int i=0; i < mTabs.length; i++) {
+                totalLength += mTabs[i].getWidth();
+            }
+            // Find the allowed scrolling length by subtracting the current visible screen width
+            // from the total length of the tabs.
+            mAllowedScrollLength = totalLength - getWidth();
+        }
+        return mAllowedScrollLength;
+    }
+
+    /**
+     * Updates the tab selection.
+     */
+    public void setCurrentTab(int position) {
+        if (position < 0 || position > mTabs.length) {
+            throw new IllegalStateException("Invalid position in array of tabs: " + position);
+        }
+        // TODO: Handle device rotation (saving and restoring state of the selected tab)
+        // This will take more work because there is no tab carousel in phone landscape
+        if (mTabs[position] == null) {
+            return;
+        }
+        mTabs[position].setSelected(true);
+        unselectAllOtherTabs(position);
+    }
+
+    private void unselectAllOtherTabs(int position) {
+        for (int i = 0; i < mTabs.length; i++) {
+            if (position != i) {
+                mTabs[i].setSelected(false);
+            }
+        }
+    }
+
+    /**
+     * Loads the data from the Loader-Result. This is the only function that has to be called
+     * from the outside to fully setup the View
+     */
+    public void loadData(ContactLoader.Result contactData) {
+        mContactUri = contactData.getLookupUri();
+
+        View aboutView = findViewById(R.id.tab_about);
+        View updateView = findViewById(R.id.tab_update);
+
+        TextView aboutTab = (TextView) aboutView.findViewById(R.id.label);
+        aboutTab.setText(mContext.getString(R.string.contactDetailAbout));
+        aboutTab.setClickable(true);
+        aboutTab.setSelected(true);
+
+        TextView updatesTab = (TextView) updateView.findViewById(R.id.label);
+        updatesTab.setText(mContext.getString(R.string.contactDetailUpdates));
+        updatesTab.setClickable(true);
+
+        aboutTab.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mListener.onTabSelected(0);
+            }
+        });
+        updatesTab.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mListener.onTabSelected(1);
+            }
+        });
+
+        mTabs[0] = aboutTab;
+        mTabs[1] = updatesTab;
+
+        // Retrieve the photo and star views for the "about" tab
+        mPhotoView = (ImageView) aboutView.findViewById(R.id.photo);
+        mStarredView = (CheckBox) aboutView.findViewById(R.id.star);
+        mStarredView.setOnClickListener(this);
+
+        // Retrieve the social update views for the "updates" tab
+        mStatusView = (TextView) updateView.findViewById(R.id.status);
+        mStatusDateView = (TextView) updateView.findViewById(R.id.status_date);
+
+        ContactDetailDisplayUtils.setPhoto(mContext, contactData, mPhotoView);
+        ContactDetailDisplayUtils.setStarred(contactData, mStarredView);
+        ContactDetailDisplayUtils.setSocialSnippetAndDate(mContext, contactData, mStatusView,
+                mStatusDateView);
+    }
+
+    /**
+     * Set the given {@link Listener} to handle carousel events.
+     */
+    public void setListener(Listener listener) {
+        mListener = listener;
+    }
+
+    // TODO: The starred icon needs to move to the action bar.
+    @Override
+    public void onClick(View view) {
+        switch (view.getId()) {
+            case R.id.star: {
+                // Toggle "starred" state
+                // Make sure there is a contact
+                if (mContactUri != null) {
+                    Intent intent = ContactSaveService.createSetStarredIntent(
+                            getContext(), mContactUri, mStarredView.isChecked());
+                    getContext().startService(intent);
+                }
+                break;
+            }
+        }
+    }
+
+    @Override
+    public boolean onTouch(View v, MotionEvent event) {
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                mListener.onTouchDown();
+                return true;
+            case MotionEvent.ACTION_UP:
+                mListener.onTouchUp();
+                return true;
+        }
+        return super.onTouchEvent(event);
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        boolean interceptTouch = super.onInterceptTouchEvent(ev);
+        if (interceptTouch) {
+            mListener.onTouchDown();
+        }
+        return interceptTouch;
+    }
+}
diff --git a/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java b/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java
new file mode 100644
index 0000000..02678de
--- /dev/null
+++ b/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java
@@ -0,0 +1,45 @@
+/*
+ * 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 com.android.contacts.activities.ContactDetailActivity.FragmentKeyListener;
+
+import android.app.Fragment;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class ContactDetailUpdatesFragment extends Fragment implements FragmentKeyListener {
+
+    private static final String TAG = "ContactDetailUpdatesFragment";
+
+    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);
+    }
+
+    @Override
+    public boolean handleKeyDown(int keyCode) {
+        return false;
+    }
+}