Merge "Add an original-package tag that matches the real package name."
diff --git a/res/layout-finger/contacts_list_content.xml b/res/layout-finger/contacts_list_content.xml
index fe65da0..e2ade92 100644
--- a/res/layout-finger/contacts_list_content.xml
+++ b/res/layout-finger/contacts_list_content.xml
@@ -14,7 +14,9 @@
      limitations under the License.
 -->
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout 
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/pinned_header_list_layout"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:orientation="vertical">
@@ -23,10 +25,12 @@
         layout="@layout/search_bar"
         android:visibility="gone"/>
 
-    <ListView android:id="@android:id/list"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:fastScrollEnabled="true"
+    <view
+        class="com.android.contacts.PinnedHeaderListView" 
+        android:id="@android:id/list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:fastScrollEnabled="true"
     />
 
     <ScrollView android:id="@android:id/empty"
@@ -46,4 +50,5 @@
             android:lineSpacingMultiplier="0.92"
         />
     </ScrollView>
+
 </LinearLayout>
diff --git a/res/layout-finger/contacts_list_content_join.xml b/res/layout-finger/contacts_list_content_join.xml
index b50713b..f4704cd 100644
--- a/res/layout-finger/contacts_list_content_join.xml
+++ b/res/layout-finger/contacts_list_content_join.xml
@@ -20,6 +20,10 @@
         android:layout_height="match_parent"
         android:orientation="vertical">
 
+    <include android:id="@+id/searchView" 
+        layout="@layout/search_bar"
+        android:visibility="gone"/>
+
     <LinearLayout
             android:layout_width="match_parent"
             android:layout_height="wrap_content" 
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 3ba7106..32d07c5 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -23,4 +23,5 @@
 
     <color name="edit_divider">#ff666666</color>
     <color name="background_secondary">#ff202020</color>
+    <color name="pinned_header_background">#ff202020</color>
 </resources>
diff --git a/src/com/android/contacts/ContactsListActivity.java b/src/com/android/contacts/ContactsListActivity.java
index 2fdc458..29f059d 100644
--- a/src/com/android/contacts/ContactsListActivity.java
+++ b/src/com/android/contacts/ContactsListActivity.java
@@ -40,6 +40,7 @@
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.UriMatcher;
+import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.database.CharArrayBuffer;
 import android.database.Cursor;
@@ -47,6 +48,7 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.Typeface;
@@ -496,6 +498,15 @@
     private View mSearchView;
     private SearchEditText mSearchEditText;
 
+    /**
+     * An approximation of the background color of the pinned header. This color
+     * is used when the pinned header is being pushed up.  At that point the header
+     * "fades away".  Rather than computing a faded bitmap based on the 9-patch
+     * normally used for the background, we will use a solid color, which will
+     * provide better performance and reduced complexity.
+     */
+    private int mPinnedHeaderBackgroundColor;
+
     @Override
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
@@ -718,6 +729,8 @@
 
     private void setupListView() {
         final ListView list = getListView();
+        final LayoutInflater inflater = getLayoutInflater();
+
         mHighlightingAnimation =
                 new NameHighlightingAnimation(list, TEXT_HIGHLIGHTING_ANIMATION_DURATION);
 
@@ -729,7 +742,6 @@
 
         if ((mMode & MODE_MASK_CREATE_NEW) != 0) {
             // Add the header for creating a new contact
-            final LayoutInflater inflater = getLayoutInflater();
             View header = inflater.inflate(R.layout.create_new_contact, list, false);
             list.addHeaderView(header);
         }
@@ -739,6 +751,15 @@
 
         mAdapter = new ContactItemListAdapter(this);
         setListAdapter(mAdapter);
+
+        if (list instanceof PinnedHeaderListView) {
+            mPinnedHeaderBackgroundColor =
+                    getResources().getColor(R.color.pinned_header_background);
+            PinnedHeaderListView pinnedHeaderList = (PinnedHeaderListView)list;
+            View pinnedHeader = inflater.inflate(R.layout.list_section, list, false);
+            pinnedHeaderList.setPinnedHeaderView(pinnedHeader);
+        }
+
         list.setOnScrollListener(mAdapter);
         list.setOnKeyListener(this);
 
@@ -750,13 +771,15 @@
      * Configures search UI.
      */
     private void setupSearchView() {
-        mSearchView = findViewById(R.id.searchView);
-        mSearchEditText = (SearchEditText)mSearchView.findViewById(R.id.search_src_text);
-        mSearchEditText.addTextChangedListener(this);
-        mSearchEditText.setOnEditorActionListener(this);
+        if ((mMode & MODE_MASK_NO_FILTER) == 0) {
+            mSearchView = findViewById(R.id.searchView);
+            mSearchEditText = (SearchEditText)mSearchView.findViewById(R.id.search_src_text);
+            mSearchEditText.addTextChangedListener(this);
+            mSearchEditText.setOnEditorActionListener(this);
 
-        ImageButton searchButton = (ImageButton)mSearchView.findViewById(R.id.search_btn);
-        searchButton.setOnClickListener(this);
+            ImageButton searchButton = (ImageButton)mSearchView.findViewById(R.id.search_btn);
+            searchButton.setOnClickListener(this);
+        }
     }
 
     private boolean isPickerMode() {
@@ -905,11 +928,16 @@
     }
 
     private String getTextFilter() {
-        return mSearchEditText.getText().toString();
+        if (mSearchEditText != null) {
+            return mSearchEditText.getText().toString();
+        }
+        return null;
     }
 
     private void setTextFilter(String filterText) {
-        mSearchEditText.setText(filterText);
+        if (mSearchEditText != null) {
+            mSearchEditText.setText(filterText);
+        }
     }
 
     @Override
@@ -2411,8 +2439,14 @@
         public QuickContactBadge photoView;
     }
 
+    final static class PinnedHeaderCache {
+        public TextView titleView;
+        public ColorStateList textColor;
+        public Drawable background;
+    }
+
     private final class ContactItemListAdapter extends ResourceCursorAdapter
-            implements SectionIndexer, OnScrollListener {
+            implements SectionIndexer, OnScrollListener, PinnedHeaderListView.PinnedHeaderAdapter {
         private SectionIndexer mIndexer;
         private String mAlphabet;
         private boolean mLoading = true;
@@ -3254,7 +3288,9 @@
 
         public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
                 int totalItemCount) {
-            // no op
+            if (view instanceof PinnedHeaderListView) {
+                ((PinnedHeaderListView)view).configureHeaderView(firstVisibleItem);
+            }
         }
 
         public void onScrollStateChanged(AbsListView view, int scrollState) {
@@ -3320,5 +3356,69 @@
 
             mHandler.clearImageFecthing();
         }
+
+        /**
+         * Computes the state of the pinned header.  It can be invisible, fully
+         * visible or partially pushed up out of the view.
+         */
+        public int getPinnedHeaderState(int position) {
+            if (mIndexer == null || mCursor == null || mCursor.getCount() == 0) {
+                return PINNED_HEADER_GONE;
+            }
+
+            int realPosition = getRealPosition(position);
+            if (realPosition < 0) {
+                return PINNED_HEADER_GONE;
+            }
+
+            // The header should get pushed up if the top item shown
+            // is the last item in a section for a particular letter.
+            int section = getSectionForPosition(realPosition);
+            int nextSectionPosition = getPositionForSection(section + 1);
+            if (nextSectionPosition != -1 && realPosition == nextSectionPosition - 1) {
+                return PINNED_HEADER_PUSHED_UP;
+            }
+
+            return PINNED_HEADER_VISIBLE;
+        }
+
+        /**
+         * Configures the pinned header by setting the appropriate text label
+         * and also adjusting color if necessary.  The color needs to be
+         * adjusted when the pinned header is being pushed up from the view.
+         */
+        public void configurePinnedHeader(View header, int position, int alpha) {
+            PinnedHeaderCache cache = (PinnedHeaderCache)header.getTag();
+            if (cache == null) {
+                cache = new PinnedHeaderCache();
+                cache.titleView = (TextView)header.findViewById(R.id.header_text);
+                cache.textColor = cache.titleView.getTextColors();
+                cache.background = header.getBackground();
+                header.setTag(cache);
+            }
+
+            int realPosition = getRealPosition(position);
+            int section = getSectionForPosition(realPosition);
+
+            String title = mIndexer.getSections()[section].toString().trim();
+            cache.titleView.setText(title);
+
+            if (alpha == 255) {
+                // Opaque: use the default background, and the original text color
+                header.setBackgroundDrawable(cache.background);
+                cache.titleView.setTextColor(cache.textColor);
+            } else {
+                // Faded: use a solid color approximation of the background, and
+                // a translucent text color
+                header.setBackgroundColor(Color.rgb(
+                        Color.red(mPinnedHeaderBackgroundColor) * alpha / 255,
+                        Color.green(mPinnedHeaderBackgroundColor) * alpha / 255,
+                        Color.blue(mPinnedHeaderBackgroundColor) * alpha / 255));
+
+                int textColor = cache.textColor.getDefaultColor();
+                cache.titleView.setTextColor(Color.argb(alpha,
+                        Color.red(textColor), Color.green(textColor), Color.blue(textColor)));
+            }
+        }
     }
 }
diff --git a/src/com/android/contacts/FocusRequestingListView.java b/src/com/android/contacts/FocusRequestingListView.java
deleted file mode 100644
index 7461d70..0000000
--- a/src/com/android/contacts/FocusRequestingListView.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2009 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;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.ListView;
-
-/* Subclass of ListView that requests focus after it is layed out for the first time. */
-public class FocusRequestingListView extends ListView {
-
-    private boolean mFirstLayoutDone = false;
-
-    public FocusRequestingListView(Context context) {
-        super(context);
-    }
-
-    public FocusRequestingListView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public FocusRequestingListView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-    }
-
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-        if (!mFirstLayoutDone) {
-            setFocusable(true);
-            requestFocus();
-        }
-        mFirstLayoutDone = true;
-    }
-}
diff --git a/src/com/android/contacts/PinnedHeaderListView.java b/src/com/android/contacts/PinnedHeaderListView.java
new file mode 100644
index 0000000..9d1391b
--- /dev/null
+++ b/src/com/android/contacts/PinnedHeaderListView.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2010 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;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+
+/**
+ * A ListView that maintains a header pinned at the top of the list. The
+ * pinned header can be pushed up and dissolved as needed.
+ */
+public class PinnedHeaderListView extends ListView {
+
+    /**
+     * Adapter interface.  The list adapter must implement this interface.
+     */
+    public interface PinnedHeaderAdapter {
+
+        /**
+         * Pinned header state: don't show the header.
+         */
+        public static final int PINNED_HEADER_GONE = 0;
+
+        /**
+         * Pinned header state: show the header at the top of the list.
+         */
+        public static final int PINNED_HEADER_VISIBLE = 1;
+
+        /**
+         * Pinned header state: show the header. If the header extends beyond
+         * the bottom of the first shown element, push it up and clip.
+         */
+        public static final int PINNED_HEADER_PUSHED_UP = 2;
+
+        /**
+         * Computes the desired state of the pinned header for the given
+         * position of the first visible list item. Allowed return values are
+         * {@link #PINNED_HEADER_GONE}, {@link #PINNED_HEADER_VISIBLE} or
+         * {@link #PINNED_HEADER_PUSHED_UP}.
+         */
+        int getPinnedHeaderState(int position);
+
+        /**
+         * Configures the pinned header view to match the first visible list item.
+         *
+         * @param header pinned header view.
+         * @param position position of the first visible list item.
+         * @param alpha fading of the header view, between 0 and 255.
+         */
+        void configurePinnedHeader(View header, int position, int alpha);
+    }
+
+    private static final int MAX_ALPHA = 255;
+
+    private PinnedHeaderAdapter mAdapter;
+    private View mHeaderView;
+    private boolean mHeaderViewVisible;
+
+    private int mHeaderViewWidth;
+
+    private int mHeaderViewHeight;
+
+    public PinnedHeaderListView(Context context) {
+        super(context);
+    }
+
+    public PinnedHeaderListView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public PinnedHeaderListView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public void setPinnedHeaderView(View view) {
+        mHeaderView = view;
+
+        // Disable vertical fading when the pinned header is present
+        // TODO change ListView to allow separate measures for top and bottom fading edge;
+        // in this particular case we would like to disable the top, but not the bottom edge.
+        if (mHeaderView != null) {
+            setFadingEdgeLength(0);
+        }
+        requestLayout();
+    }
+
+    @Override
+    public void setAdapter(ListAdapter adapter) {
+        super.setAdapter(adapter);
+        mAdapter = (PinnedHeaderAdapter)adapter;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        if (mHeaderView != null) {
+            measureChild(mHeaderView, widthMeasureSpec, heightMeasureSpec);
+            mHeaderViewWidth = mHeaderView.getMeasuredWidth();
+            mHeaderViewHeight = mHeaderView.getMeasuredHeight();
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        if (mHeaderView != null) {
+            mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight);
+            configureHeaderView(getFirstVisiblePosition());
+        }
+    }
+
+    public void configureHeaderView(int position) {
+        if (mHeaderView == null) {
+            return;
+        }
+
+        int state = mAdapter.getPinnedHeaderState(position);
+        switch (state) {
+            case PinnedHeaderAdapter.PINNED_HEADER_GONE: {
+                mHeaderViewVisible = false;
+                break;
+            }
+
+            case PinnedHeaderAdapter.PINNED_HEADER_VISIBLE: {
+                mAdapter.configurePinnedHeader(mHeaderView, position, MAX_ALPHA);
+                if (mHeaderView.getTop() != 0) {
+                    mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight);
+                }
+                mHeaderViewVisible = true;
+                break;
+            }
+
+            case PinnedHeaderAdapter.PINNED_HEADER_PUSHED_UP: {
+                View firstView = getChildAt(0);
+                int bottom = firstView.getBottom();
+                int itemHeight = firstView.getHeight();
+                int headerHeight = mHeaderView.getHeight();
+                int y;
+                int alpha;
+                if (bottom < headerHeight) {
+                    y = (bottom - headerHeight);
+                    alpha = MAX_ALPHA * (headerHeight + y) / headerHeight;
+                } else {
+                    y = 0;
+                    alpha = MAX_ALPHA;
+                }
+                mAdapter.configurePinnedHeader(mHeaderView, position, alpha);
+                if (mHeaderView.getTop() != y) {
+                    mHeaderView.layout(0, y, mHeaderViewWidth, mHeaderViewHeight + y);
+                }
+                mHeaderViewVisible = true;
+                break;
+            }
+        }
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        super.dispatchDraw(canvas);
+        if (mHeaderViewVisible) {
+            drawChild(canvas, mHeaderView, getDrawingTime());
+        }
+    }
+}