Merge "Adjustments to the edit views."
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 9640536..fc0036c 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -492,7 +492,6 @@
         <activity
             android:name=".activities.ContactEditorActivity"
             android:theme="@style/EditorActivityTheme"
-            android:uiOptions="splitActionBarWhenNarrow"
             android:windowSoftInputMode="adjustResize">
 
             <intent-filter android:label="@string/editContactDescription">
diff --git a/res/layout/contact_picker_content.xml b/res/layout/contact_picker_content.xml
index c30add7..14eaf15 100644
--- a/res/layout/contact_picker_content.xml
+++ b/res/layout/contact_picker_content.xml
@@ -22,7 +22,7 @@
     android:orientation="vertical">
 
     <view
-        class="com.android.contacts.list.ContactEntryListView"
+        class="com.android.contacts.widget.PinnedHeaderListView"
         android:id="@android:id/list"
         android:layout_width="match_parent"
         android:layout_height="0dip"
diff --git a/res/layout/contacts_list_content.xml b/res/layout/contacts_list_content.xml
index 4223f54..8e1289f 100644
--- a/res/layout/contacts_list_content.xml
+++ b/res/layout/contacts_list_content.xml
@@ -57,7 +57,7 @@
     </LinearLayout>
 
     <view
-        class="com.android.contacts.list.ContactEntryListView"
+        class="com.android.contacts.widget.PinnedHeaderListView"
         android:id="@android:id/list"
         android:layout_width="match_parent"
         android:layout_height="0dip"
diff --git a/res/layout/join_contact_picker_list_content.xml b/res/layout/join_contact_picker_list_content.xml
index 9e72c31..1535539 100644
--- a/res/layout/join_contact_picker_list_content.xml
+++ b/res/layout/join_contact_picker_list_content.xml
@@ -36,7 +36,7 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent">
         <view
-            class="com.android.contacts.list.ContactEntryListView"
+            class="com.android.contacts.widget.PinnedHeaderListView"
             android:id="@android:id/list"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
diff --git a/res/menu/dialtacts_options.xml b/res/menu/dialtacts_options.xml
index cf67dab..f653242 100644
--- a/res/menu/dialtacts_options.xml
+++ b/res/menu/dialtacts_options.xml
@@ -16,9 +16,8 @@
 <menu xmlns:android="http://schemas.android.com/apk/res/android">
     <item
         android:id="@+id/search_on_action_bar"
-        android:title="@string/menu_search"
-        android:icon="@android:drawable/ic_menu_search"
-        android:showAsAction="always" />
+        android:title="@string/menu_all_contacts"
+        android:showAsAction="ifRoom" />
 
     <!-- This should come after the other menus in CallLog and Dialpad -->
     <item
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 965f0d1..113df61 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -360,6 +360,9 @@
     <!-- Displayed at the top of the contacts showing the account filter selected  [CHAR LIMIT=64] -->
     <string name="listAllContactsInAccount">Contacts in <xliff:g id="name" example="abc@gmail.com">%s</xliff:g></string>
 
+    <!-- Displayed at the top of the contacts showing single contact. [CHAR LIMIT=64] -->
+    <string name="listSingleContact">Single contact</string>
+
     <!-- Displayed at the top of the contacts showing the total number of contacts found when "Only contacts with phones" not selected -->
     <plurals name="listFoundAllContacts">
         <item quantity="one">1 found</item>
@@ -1034,6 +1037,9 @@
     <!-- The menu item to share the currently viewed contact [CHAR LIMIT=30] -->
     <string name="menu_share">Share</string>
 
+    <!-- The menu item to show all contacts in Phone entrance [CHAR LIMIT=30] -->
+    <string name="menu_all_contacts">All contacts</string>
+
     <!-- Dialog title when picking the application to share a contact with. -->
     <string name="share_via">Share contact via</string>
 
diff --git a/src/com/android/contacts/activities/ContactDetailActivity.java b/src/com/android/contacts/activities/ContactDetailActivity.java
index c3dc593..3d407ac 100644
--- a/src/com/android/contacts/activities/ContactDetailActivity.java
+++ b/src/com/android/contacts/activities/ContactDetailActivity.java
@@ -115,7 +115,6 @@
     public void onAttachFragment(Fragment fragment) {
          if (fragment instanceof ContactLoaderFragment) {
             mLoaderFragment = (ContactLoaderFragment) fragment;
-            mLoaderFragment.setRetainInstance(true);
             mLoaderFragment.setListener(mLoaderFragmentListener);
             mLoaderFragment.loadUri(getIntent().getData());
         }
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index 1bab98d..a03f83f 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -373,7 +373,6 @@
 
             mContactDetailLoaderFragment = getFragment(R.id.contact_detail_loader_fragment);
             mContactDetailLoaderFragment.setListener(mContactDetailLoaderFragmentListener);
-            mContactDetailLoaderFragment.setRetainInstance(true);
 
             mGroupDetailFragment = getFragment(R.id.group_detail_fragment);
             mGroupDetailFragment.setListener(mGroupDetailFragmentListener);
@@ -1027,11 +1026,18 @@
 
         @Override
         public void onInvalidSelection() {
-            Toast.makeText(PeopleActivity.this, R.string.toast_displaying_all_contacts,
-                    Toast.LENGTH_LONG).show();
-            ContactListFilter filter = ContactListFilter.createFilterWithType(
-                    ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS);
-            mAllFragment.setFilter(filter);
+            ContactListFilter filter;
+            ContactListFilter currentFilter = mAllFragment.getFilter();
+            if (currentFilter != null
+                    && currentFilter.filterType == ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) {
+                filter = ContactListFilter.createFilterWithType(
+                        ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS);
+                mAllFragment.setFilter(filter);
+            } else {
+                filter = ContactListFilter.createFilterWithType(
+                        ContactListFilter.FILTER_TYPE_SINGLE_CONTACT);
+                mAllFragment.setFilter(filter, false);
+            }
             mContactListFilterController.setContactListFilter(filter, true);
         }
     }
diff --git a/src/com/android/contacts/detail/ContactLoaderFragment.java b/src/com/android/contacts/detail/ContactLoaderFragment.java
index f3c6158..ac22677 100644
--- a/src/com/android/contacts/detail/ContactLoaderFragment.java
+++ b/src/com/android/contacts/detail/ContactLoaderFragment.java
@@ -210,15 +210,9 @@
         }
 
         @Override
-        public void onLoaderReset(Loader<ContactLoader.Result> loader) {
-            mContactData = null;
-            if (mListener != null) {
-                mListener.onDetailsLoaded(mContactData);
-            }
-        }
+        public void onLoaderReset(Loader<ContactLoader.Result> loader) {}
     };
 
-
     @Override
     public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
         inflater.inflate(R.menu.view_contact, menu);
diff --git a/src/com/android/contacts/detail/StreamItemAdapter.java b/src/com/android/contacts/detail/StreamItemAdapter.java
index 074db80..6586b23 100644
--- a/src/com/android/contacts/detail/StreamItemAdapter.java
+++ b/src/com/android/contacts/detail/StreamItemAdapter.java
@@ -59,7 +59,10 @@
 
     @Override
     public int getCount() {
-        return mStreamItems.size() + 2;
+        // The header and title should only be included as items in the list if there are other
+        // stream items.
+        int count = mStreamItems.size();
+        return (count == 0) ? 0 : (count + 2);
     }
 
     @Override
diff --git a/src/com/android/contacts/group/GroupDetailFragment.java b/src/com/android/contacts/group/GroupDetailFragment.java
index 1164069..f54dbd6 100644
--- a/src/com/android/contacts/group/GroupDetailFragment.java
+++ b/src/com/android/contacts/group/GroupDetailFragment.java
@@ -403,10 +403,9 @@
         mOptionsMenuEditable = isGroupEditable() && isVisible();
         mOptionsMenuGroupPresent = isGroupPresent() && isVisible();
 
-        // Editing a group is always possible if a group is selected
-        // TODO: check for external group (member editable) buganizer #5049046
+        // Editing is not possible for read only groups
         final MenuItem editMenu = menu.findItem(R.id.menu_edit_group);
-        editMenu.setVisible(mOptionsMenuGroupPresent);
+        editMenu.setVisible(mOptionsMenuGroupPresent && mOptionsMenuEditable);
 
         final MenuItem deleteMenu = menu.findItem(R.id.menu_delete_group);
         deleteMenu.setVisible(mOptionsMenuEditable);
diff --git a/src/com/android/contacts/list/ContactEntryListAdapter.java b/src/com/android/contacts/list/ContactEntryListAdapter.java
index ba16c17..9b968f7 100644
--- a/src/com/android/contacts/list/ContactEntryListAdapter.java
+++ b/src/com/android/contacts/list/ContactEntryListAdapter.java
@@ -242,22 +242,6 @@
         mSortOrder = sortOrder;
     }
 
-    public void setNameHighlightingEnabled(boolean flag) {
-        mNameHighlightingEnabled = flag;
-    }
-
-    public boolean isNameHighlightingEnabled() {
-        return mNameHighlightingEnabled;
-    }
-
-    public void setTextWithHighlightingFactory(TextWithHighlightingFactory factory) {
-        mTextWithHighlightingFactory = factory;
-    }
-
-    protected TextWithHighlightingFactory getTextWithHighlightingFactory() {
-        return mTextWithHighlightingFactory;
-    }
-
     public void setPhotoLoader(ContactPhotoManager photoLoader) {
         mPhotoLoader = photoLoader;
     }
diff --git a/src/com/android/contacts/list/ContactEntryListFragment.java b/src/com/android/contacts/list/ContactEntryListFragment.java
index 4ddba75..a4163fd 100644
--- a/src/com/android/contacts/list/ContactEntryListFragment.java
+++ b/src/com/android/contacts/list/ContactEntryListFragment.java
@@ -717,11 +717,6 @@
             changed = true;
         }
 
-        if (mListView instanceof ContactEntryListView) {
-            ContactEntryListView listView = (ContactEntryListView)mListView;
-            listView.setHighlightNamesWhenScrolling(isNameHighlightingEnabled());
-        }
-
         return changed;
     }
 
@@ -756,6 +751,8 @@
                     "'android.R.id.list'");
         }
 
+        mListView.setSelector(getContext().getResources().getDrawable(R.drawable.list_selector));
+
         View emptyView = mView.findViewById(com.android.internal.R.id.empty);
         if (emptyView != null) {
             mListView.setEmptyView(emptyView);
@@ -810,30 +807,11 @@
         mAdapter.setPinnedPartitionHeadersEnabled(mSearchMode);
         mAdapter.setContactNameDisplayOrder(mDisplayOrder);
         mAdapter.setSortOrder(mSortOrder);
-        mAdapter.setNameHighlightingEnabled(isNameHighlightingEnabled());
         mAdapter.setSectionHeaderDisplayEnabled(mSectionHeaderDisplayEnabled);
         mAdapter.setSelectionVisible(mSelectionVisible);
         mAdapter.setDirectoryResultLimit(mDirectoryResultLimit);
     }
 
-    protected boolean isNameHighlightingEnabled() {
-        if (mAdapter.isNameHighlightingEnabled()) {
-            return true;
-        }
-
-        // When sort order and display order contradict each other, we want to
-        // highlight the part of the name used for sorting.
-        if (mSortOrder == ContactsContract.Preferences.SORT_ORDER_PRIMARY &&
-                mDisplayOrder == ContactsContract.Preferences.DISPLAY_ORDER_ALTERNATIVE) {
-            return true;
-        } else if (mSortOrder == ContactsContract.Preferences.SORT_ORDER_ALTERNATIVE &&
-                mDisplayOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
-            return true;
-        } else {
-            return false;
-        }
-    }
-
     @Override
     public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
             int totalItemCount) {
diff --git a/src/com/android/contacts/list/ContactEntryListView.java b/src/com/android/contacts/list/ContactEntryListView.java
deleted file mode 100644
index 86e33fe..0000000
--- a/src/com/android/contacts/list/ContactEntryListView.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * 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.list;
-
-import com.android.contacts.R;
-import com.android.contacts.widget.PinnedHeaderListView;
-import com.android.contacts.widget.TextHighlightingAnimation;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.AbsListView;
-import android.widget.ListAdapter;
-
-/**
- * A custom list view for a list of contacts or contact-related entries.  It handles
- * animation of names on scroll.
- */
-public class ContactEntryListView extends PinnedHeaderListView {
-
-    private static final int TEXT_HIGHLIGHTING_ANIMATION_DURATION = 350;
-
-    private final TextHighlightingAnimation mHighlightingAnimation =
-            new ContactNameHighlightingAnimation(this, TEXT_HIGHLIGHTING_ANIMATION_DURATION);
-
-    private boolean mHighlightNamesWhenScrolling;
-
-    public ContactEntryListView(Context context) {
-        this(context, null);
-    }
-
-    public ContactEntryListView(Context context, AttributeSet attrs) {
-        this(context, attrs, com.android.internal.R.attr.listViewStyle);
-    }
-
-    public ContactEntryListView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-        setSelector(getContext().getResources().getDrawable(R.drawable.list_selector));
-    }
-
-    public TextHighlightingAnimation getTextHighlightingAnimation() {
-        return mHighlightingAnimation;
-    }
-
-    public boolean getHighlightNamesWhenScrolling() {
-        return mHighlightNamesWhenScrolling;
-    }
-
-    public void setHighlightNamesWhenScrolling(boolean flag) {
-        mHighlightNamesWhenScrolling = flag;
-    }
-
-    @Override
-    public void setAdapter(ListAdapter adapter) {
-        super.setAdapter(adapter);
-        if (adapter instanceof ContactEntryListAdapter) {
-            ((ContactEntryListAdapter)adapter)
-                    .setTextWithHighlightingFactory(mHighlightingAnimation);
-        }
-    }
-
-    @Override
-    public void onScrollStateChanged(AbsListView view, int scrollState) {
-        super.onScrollStateChanged(view, scrollState);
-        if (mHighlightNamesWhenScrolling) {
-            if (scrollState != OnScrollListener.SCROLL_STATE_IDLE) {
-                mHighlightingAnimation.startHighlighting();
-            } else {
-                mHighlightingAnimation.stopHighlighting();
-            }
-        }
-    }
-}
diff --git a/src/com/android/contacts/list/ContactListAdapter.java b/src/com/android/contacts/list/ContactListAdapter.java
index 2e511bc..c057a48 100644
--- a/src/com/android/contacts/list/ContactListAdapter.java
+++ b/src/com/android/contacts/list/ContactListAdapter.java
@@ -222,7 +222,6 @@
             ViewGroup parent) {
         ContactListItemView view = new ContactListItemView(context, null);
         view.setUnknownNameText(mUnknownNameText);
-        view.setTextWithHighlightingFactory(getTextWithHighlightingFactory());
         view.setQuickContactEnabled(isQuickContactEnabled());
         view.setActivatedStateSupported(isSelectionVisible());
         return view;
@@ -271,7 +270,7 @@
 
     protected void bindName(final ContactListItemView view, Cursor cursor) {
         view.showDisplayName(cursor, mDisplayNameColumnIndex, mAlternativeDisplayNameColumnIndex,
-                isNameHighlightingEnabled(), getContactNameDisplayOrder());
+                false, getContactNameDisplayOrder());
         view.showPhoneticName(cursor, CONTACT_PHONETIC_NAME_COLUMN_INDEX);
     }
 
diff --git a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
index ca07516..123ef0e 100644
--- a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
@@ -125,11 +125,19 @@
         if (mAccountFilterHeaderView == null) {
             return; // Before onCreateView -- just ignore it.
         }
-        if (filter != null && filter.filterType != ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS &&
-                !isSearchMode() && filter.filterType != ContactListFilter.FILTER_TYPE_CUSTOM) {
-            mAccountFilterHeaderContainer.setVisibility(View.VISIBLE);
-            mAccountFilterHeaderView.setText(getContext().getString(
-                    R.string.listAllContactsInAccount, filter.accountName));
+        if (filter != null && !isSearchMode()) {
+            if (filter.filterType == ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) {
+                mAccountFilterHeaderContainer.setVisibility(View.VISIBLE);
+                mAccountFilterHeaderView.setText(getContext().getString(
+                        R.string.listSingleContact));
+            } else if (filter.filterType != ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS
+                && filter.filterType != ContactListFilter.FILTER_TYPE_CUSTOM) {
+                mAccountFilterHeaderContainer.setVisibility(View.VISIBLE);
+                mAccountFilterHeaderView.setText(getContext().getString(
+                        R.string.listAllContactsInAccount, filter.accountName));
+            } else {
+                mAccountFilterHeaderContainer.setVisibility(View.GONE);
+            }
         } else {
             mAccountFilterHeaderContainer.setVisibility(View.GONE);
         }
diff --git a/src/com/android/contacts/list/EmailAddressListAdapter.java b/src/com/android/contacts/list/EmailAddressListAdapter.java
index e1c8ea2..5f96297 100644
--- a/src/com/android/contacts/list/EmailAddressListAdapter.java
+++ b/src/com/android/contacts/list/EmailAddressListAdapter.java
@@ -120,7 +120,6 @@
             ViewGroup parent) {
         final ContactListItemView view = new ContactListItemView(context, null);
         view.setUnknownNameText(mUnknownNameText);
-        view.setTextWithHighlightingFactory(getTextWithHighlightingFactory());
         view.setQuickContactEnabled(isQuickContactEnabled());
         return view;
     }
@@ -167,8 +166,7 @@
 
     protected void bindName(final ContactListItemView view, Cursor cursor) {
         view.showDisplayName(cursor, mDisplayNameColumnIndex, mAlternativeDisplayNameColumnIndex,
-                isNameHighlightingEnabled(), getContactNameDisplayOrder());
-//        view.showPhoneticName(cursor, PHONE_PHONETIC_NAME_COLUMN_INDEX);
+                false, getContactNameDisplayOrder());
     }
 
     protected void bindPhoto(final ContactListItemView view, Cursor cursor) {
diff --git a/src/com/android/contacts/list/PhoneNumberListAdapter.java b/src/com/android/contacts/list/PhoneNumberListAdapter.java
index 87fb60f..cac89b1 100644
--- a/src/com/android/contacts/list/PhoneNumberListAdapter.java
+++ b/src/com/android/contacts/list/PhoneNumberListAdapter.java
@@ -231,7 +231,6 @@
             ViewGroup parent) {
         final ContactListItemView view = new ContactListItemView(context, null);
         view.setUnknownNameText(mUnknownNameText);
-        view.setTextWithHighlightingFactory(getTextWithHighlightingFactory());
         view.setQuickContactEnabled(isQuickContactEnabled());
         return view;
     }
@@ -240,9 +239,6 @@
     protected void bindView(View itemView, int partition, Cursor cursor, int position) {
         ContactListItemView view = (ContactListItemView)itemView;
 
-        view.setHighlightedPrefix(isNameHighlightingEnabled() && isSearchMode() ?
-                getUpperCaseQueryString() : null);
-
         // Look at elements before and after this position, checking if contact IDs are same.
         // If they have one same contact ID, it means they can be grouped.
         //
@@ -317,7 +313,7 @@
 
     protected void bindName(final ContactListItemView view, Cursor cursor) {
         view.showDisplayName(cursor, mDisplayNameColumnIndex, mAlternativeDisplayNameColumnIndex,
-                isNameHighlightingEnabled(), getContactNameDisplayOrder());
+                false, getContactNameDisplayOrder());
         view.showPhoneticName(cursor, PHONE_PHONETIC_NAME_COLUMN_INDEX);
     }
 
diff --git a/src/com/android/contacts/list/PhoneNumberPickerFragment.java b/src/com/android/contacts/list/PhoneNumberPickerFragment.java
index 5986b9c..2798905 100644
--- a/src/com/android/contacts/list/PhoneNumberPickerFragment.java
+++ b/src/com/android/contacts/list/PhoneNumberPickerFragment.java
@@ -56,16 +56,6 @@
 
     private static final String KEY_FILTER = "filter";
 
-    /**
-     * Used to remember the result of {@link #setNameHighlightingEnabled(boolean)} when it is called
-     * before this Fragment is attached to its parent Activity. The value will be used after
-     * an actual Adapter is ready.
-     *
-     * Null if the Adapter is already available and thus we don't need to remember the user's
-     * decision.
-     */
-    private Boolean mDelayedNameHighlightingEnabled;
-
     public PhoneNumberPickerFragment() {
         setQuickContactEnabled(false);
         setPhotoLoaderEnabled(true);
@@ -191,16 +181,10 @@
         if (!isLegacyCompatibilityMode()) {
             PhoneNumberListAdapter adapter = new PhoneNumberListAdapter(getActivity());
             adapter.setDisplayPhotos(true);
-            if (mDelayedNameHighlightingEnabled != null) {
-                adapter.setNameHighlightingEnabled(mDelayedNameHighlightingEnabled);
-            }
             return adapter;
         } else {
             LegacyPhoneNumberListAdapter adapter = new LegacyPhoneNumberListAdapter(getActivity());
             adapter.setDisplayPhotos(true);
-            if (mDelayedNameHighlightingEnabled != null) {
-                adapter.setNameHighlightingEnabled(mDelayedNameHighlightingEnabled);
-            }
             return adapter;
         }
     }
@@ -251,25 +235,6 @@
         mListener.onPickPhoneNumberAction(data.getData());
     }
 
-    public void setNameHighlightingEnabled(boolean highlight) {
-        final Adapter adapter = getAdapter();
-        // This may happen when the Fragment is not attached to its parent Activity and thus
-        // parent's onCreateView() isn't called yet (where adapter will be prepared).
-        // See also ContactEntryListFragment#onCreateView()
-        if (adapter == null) {
-            mDelayedNameHighlightingEnabled = highlight;
-        } else {
-            if (!isLegacyCompatibilityMode()) {
-                ((PhoneNumberListAdapter) adapter).setNameHighlightingEnabled(highlight);
-            } else {
-                ((LegacyPhoneNumberListAdapter) adapter).setNameHighlightingEnabled(highlight);
-            }
-
-            // We don't want to remember the choice if the adapter is already available.
-            mDelayedNameHighlightingEnabled = null;
-        }
-    }
-
     public ContactListFilter getFilter() {
         return mFilter;
     }
diff --git a/src/com/android/contacts/list/PostalAddressListAdapter.java b/src/com/android/contacts/list/PostalAddressListAdapter.java
index 5642045..c9da281 100644
--- a/src/com/android/contacts/list/PostalAddressListAdapter.java
+++ b/src/com/android/contacts/list/PostalAddressListAdapter.java
@@ -109,7 +109,6 @@
             ViewGroup parent) {
         final ContactListItemView view = new ContactListItemView(context, null);
         view.setUnknownNameText(mUnknownNameText);
-        view.setTextWithHighlightingFactory(getTextWithHighlightingFactory());
         view.setQuickContactEnabled(isQuickContactEnabled());
         return view;
     }
@@ -156,8 +155,7 @@
 
     protected void bindName(final ContactListItemView view, Cursor cursor) {
         view.showDisplayName(cursor, mDisplayNameColumnIndex, mAlternativeDisplayNameColumnIndex,
-                isNameHighlightingEnabled(), getContactNameDisplayOrder());
-//        view.showPhoneticName(cursor, PHONE_PHONETIC_NAME_COLUMN_INDEX);
+                false, getContactNameDisplayOrder());
     }
 
     protected void bindPhoto(final ContactListItemView view, Cursor cursor) {
diff --git a/src/com/android/contacts/voicemail/VoicemailPlaybackFragment.java b/src/com/android/contacts/voicemail/VoicemailPlaybackFragment.java
index 5eb0ddf..7d29406 100644
--- a/src/com/android/contacts/voicemail/VoicemailPlaybackFragment.java
+++ b/src/com/android/contacts/voicemail/VoicemailPlaybackFragment.java
@@ -111,6 +111,12 @@
         super.onDestroy();
     }
 
+    @Override
+    public void onPause() {
+        mPresenter.onPause();
+        super.onPause();
+    }
+
     private PlaybackViewImpl createPlaybackViewImpl() {
         return new PlaybackViewImpl(new ActivityReference(), getActivity().getApplicationContext(),
                 mPlaybackLayout);
diff --git a/src/com/android/contacts/voicemail/VoicemailPlaybackPresenter.java b/src/com/android/contacts/voicemail/VoicemailPlaybackPresenter.java
index d3e4bef..d54cddc 100644
--- a/src/com/android/contacts/voicemail/VoicemailPlaybackPresenter.java
+++ b/src/com/android/contacts/voicemail/VoicemailPlaybackPresenter.java
@@ -555,4 +555,10 @@
             }
         }
     }
+
+    public void onPause() {
+        if (mPlayer.isPlaying()) {
+            stopPlaybackAtPosition(mPlayer.getCurrentPosition(), mDuration.get());
+        }
+    }
 }
diff --git a/tests/assets/README.txt b/tests/assets/README.txt
new file mode 100644
index 0000000..53bb0bd
--- /dev/null
+++ b/tests/assets/README.txt
@@ -0,0 +1,4 @@
+File quick_test_recording.mp3 is copyright 2011 by
+Hugo Hudson and is licensed under a
+Creative Commons Attribution 3.0 Unported License:
+  http://creativecommons.org/licenses/by/3.0/
diff --git a/tests/assets/quick_test_recording.mp3 b/tests/assets/quick_test_recording.mp3
new file mode 100644
index 0000000..ad7cb9c
--- /dev/null
+++ b/tests/assets/quick_test_recording.mp3
Binary files differ
diff --git a/tests/src/com/android/contacts/CallDetailActivityTest.java b/tests/src/com/android/contacts/CallDetailActivityTest.java
index 6a8fcb3..ac02588 100644
--- a/tests/src/com/android/contacts/CallDetailActivityTest.java
+++ b/tests/src/com/android/contacts/CallDetailActivityTest.java
@@ -26,11 +26,13 @@
 import com.android.contacts.util.LocaleTestUtils;
 import com.android.internal.view.menu.ContextMenuBuilder;
 import com.google.common.base.Preconditions;
+import com.google.common.io.Closeables;
 
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Intent;
+import android.content.res.AssetManager;
 import android.net.Uri;
 import android.provider.CallLog;
 import android.provider.VoicemailContract;
@@ -40,6 +42,9 @@
 import android.view.Menu;
 import android.widget.TextView;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.List;
 import java.util.Locale;
 
@@ -48,6 +53,11 @@
  */
 @LargeTest
 public class CallDetailActivityTest extends ActivityInstrumentationTestCase2<CallDetailActivity> {
+    private static final String TEST_ASSET_NAME = "quick_test_recording.mp3";
+    private static final String MIME_TYPE = "audio/mp3";
+    private static final String CONTACT_NUMBER = "+1412555555";
+    private static final String VOICEMAIL_FILE_LOCATION = "/sdcard/sadlfj893w4j23o9sfu.mp3";
+
     private Uri mCallLogUri;
     private Uri mVoicemailUri;
     private IntegrationTestUtils mTestUtils;
@@ -202,11 +212,25 @@
         assertEquals("00:00", mTestUtils.getText(timeDisplay));
     }
 
+    @Suppress
+    public void testClickingCallStopsPlayback() throws Throwable {
+        setActivityIntentForRealFileVoicemailEntry();
+        startActivityUnderTest();
+        mFakeAsyncTaskExecutor.runTask(CHECK_FOR_CONTENT);
+        mFakeAsyncTaskExecutor.runTask(PREPARE_MEDIA_PLAYER);
+        mTestUtils.clickButton(mActivityUnderTest, R.id.playback_speakerphone);
+        mTestUtils.clickButton(mActivityUnderTest, R.id.playback_start_stop);
+        mTestUtils.clickButton(mActivityUnderTest, R.id.call_and_sms_main_action);
+        Thread.sleep(2000);
+        // TODO: Suppressed the test for now, because I'm looking for an easy way to say "the audio
+        // is not playing at this point", and I can't find it without doing dirty things.
+    }
+
     private void setActivityIntentForTestCallEntry() {
         Preconditions.checkState(mCallLogUri == null, "mUri should be null");
         ContentResolver contentResolver = getContentResolver();
         ContentValues values = new ContentValues();
-        values.put(CallLog.Calls.NUMBER, "01234567890");
+        values.put(CallLog.Calls.NUMBER, CONTACT_NUMBER);
         values.put(CallLog.Calls.TYPE, CallLog.Calls.INCOMING_TYPE);
         mCallLogUri = contentResolver.insert(CallLog.Calls.CONTENT_URI, values);
         setActivityIntent(new Intent(Intent.ACTION_VIEW, mCallLogUri));
@@ -216,8 +240,9 @@
         Preconditions.checkState(mVoicemailUri == null, "mUri should be null");
         ContentResolver contentResolver = getContentResolver();
         ContentValues values = new ContentValues();
-        values.put(VoicemailContract.Voicemails.NUMBER, "01234567890");
+        values.put(VoicemailContract.Voicemails.NUMBER, CONTACT_NUMBER);
         values.put(VoicemailContract.Voicemails.HAS_CONTENT, 1);
+        values.put(VoicemailContract.Voicemails._DATA, VOICEMAIL_FILE_LOCATION);
         mVoicemailUri = contentResolver.insert(VoicemailContract.Voicemails.CONTENT_URI, values);
         Uri callLogUri = ContentUris.withAppendedId(CallLog.Calls.CONTENT_URI_WITH_VOICEMAIL,
                 ContentUris.parseId(mVoicemailUri));
@@ -226,6 +251,44 @@
         setActivityIntent(intent);
     }
 
+    private void setActivityIntentForRealFileVoicemailEntry() throws IOException {
+        Preconditions.checkState(mVoicemailUri == null, "mUri should be null");
+        ContentValues values = new ContentValues();
+        values.put(VoicemailContract.Voicemails.DATE, String.valueOf(System.currentTimeMillis()));
+        values.put(VoicemailContract.Voicemails.NUMBER, CONTACT_NUMBER);
+        values.put(VoicemailContract.Voicemails.MIME_TYPE, MIME_TYPE);
+        values.put(VoicemailContract.Voicemails.HAS_CONTENT, 1);
+        String packageName = getInstrumentation().getTargetContext().getPackageName();
+        mVoicemailUri = getContentResolver().insert(
+                VoicemailContract.Voicemails.buildSourceUri(packageName), values);
+        AssetManager assets = getAssets();
+        OutputStream outputStream = null;
+        InputStream inputStream = null;
+        try {
+            inputStream = assets.open(TEST_ASSET_NAME);
+            outputStream = getContentResolver().openOutputStream(mVoicemailUri);
+            copyBetweenStreams(inputStream, outputStream);
+        } finally {
+            Closeables.closeQuietly(outputStream);
+            Closeables.closeQuietly(inputStream);
+        }
+        Uri callLogUri = ContentUris.withAppendedId(CallLog.Calls.CONTENT_URI_WITH_VOICEMAIL,
+                ContentUris.parseId(mVoicemailUri));
+        Intent intent = new Intent(Intent.ACTION_VIEW, callLogUri);
+        intent.putExtra(CallDetailActivity.EXTRA_VOICEMAIL_URI, mVoicemailUri);
+        setActivityIntent(intent);
+    }
+
+    public void copyBetweenStreams(InputStream in, OutputStream out) throws IOException {
+        byte[] buffer = new byte[1024];
+        int bytesRead;
+        int total = 0;
+        while ((bytesRead = in.read(buffer)) != -1) {
+            total += bytesRead;
+            out.write(buffer, 0, bytesRead);
+        }
+    }
+
     private void cleanUpUri() {
         if (mVoicemailUri != null) {
             getContentResolver().delete(VoicemailContract.Voicemails.CONTENT_URI,
@@ -267,4 +330,8 @@
         // of a single unit test.
         mFakeAsyncTaskExecutor.runAllTasks(UPDATE_PHONE_CALL_DETAILS);
     }
+
+    private AssetManager getAssets() {
+        return getInstrumentation().getContext().getAssets();
+    }
 }
diff --git a/tests/src/com/android/contacts/activities/PeopleActivityTest.java b/tests/src/com/android/contacts/activities/PeopleActivityTest.java
index fe6d60a..f419842 100644
--- a/tests/src/com/android/contacts/activities/PeopleActivityTest.java
+++ b/tests/src/com/android/contacts/activities/PeopleActivityTest.java
@@ -52,6 +52,8 @@
 import android.widget.TextView;
 
 /**
+ * This test is so outdated that it's disabled temporarily.  TODO Update the test and re-enable it.
+ *
  * Tests for {@link PeopleActivity}.
  *
  * Running all tests:
@@ -60,6 +62,7 @@
  * or
  *   adb shell am instrument \
  *     -w com.android.contacts.tests/android.test.InstrumentationTestRunner
+ *
  */
 @Smoke
 public class PeopleActivityTest
@@ -109,6 +112,11 @@
     }
 
     public void testSingleAccountNoGroups() {
+
+        if (true) { // Need this to avoid "unreachable statement"
+            return; // Disabled for now.
+        }
+
         // This two-pane UI test only makes sense if we run with two panes.
         // Let's ignore this in the single pane case
         if (!PhoneCapabilityTester.isUsingTwoPanes(mContext)) return;
diff --git a/tests/src/com/android/contacts/list/ContactListItemViewTest.java b/tests/src/com/android/contacts/list/ContactListItemViewTest.java
index ccd2fb5..8372f96 100644
--- a/tests/src/com/android/contacts/list/ContactListItemViewTest.java
+++ b/tests/src/com/android/contacts/list/ContactListItemViewTest.java
@@ -19,6 +19,7 @@
 import com.android.contacts.activities.PeopleActivity;
 import com.android.contacts.format.SpannedTestUtils;
 import com.android.contacts.format.TestTextWithHighlightingFactory;
+import com.android.contacts.util.IntegrationTestUtils;
 
 import android.database.Cursor;
 import android.database.MatrixCursor;
@@ -40,10 +41,26 @@
     /** The HTML code used to mark the end of the highlighted part. */
     private static final String END = "</font>";
 
+    private IntegrationTestUtils mUtils;
+
     public ContactListItemViewTest() {
         super(PeopleActivity.class);
     }
 
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        // This test requires that the screen be turned on.
+        mUtils = new IntegrationTestUtils(getInstrumentation());
+        mUtils.acquireScreenWakeLock(getInstrumentation().getTargetContext());
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mUtils.releaseScreenWakeLock();
+        super.tearDown();
+    }
+
     public void testShowDisplayName_Simple() {
         Cursor cursor = createCursor("John Doe", "Doe John");
         ContactListItemView view = createView();
diff --git a/tests/src/com/android/contacts/util/FakeAsyncTaskExecutor.java b/tests/src/com/android/contacts/util/FakeAsyncTaskExecutor.java
index e27c6fb..960f0bf 100644
--- a/tests/src/com/android/contacts/util/FakeAsyncTaskExecutor.java
+++ b/tests/src/com/android/contacts/util/FakeAsyncTaskExecutor.java
@@ -16,9 +16,11 @@
 
 package com.android.contacts.util;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
 import com.google.common.base.Preconditions;
 import com.google.common.collect.Lists;
-import com.google.common.util.concurrent.Executors;
 
 import android.app.Instrumentation;
 import android.os.AsyncTask;
@@ -28,10 +30,8 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
 
 import javax.annotation.concurrent.GuardedBy;
 import javax.annotation.concurrent.ThreadSafe;
@@ -46,43 +46,45 @@
  * Tasks submitted to this executor will not be run immediately. Rather they will be stored in a
  * list of submitted tasks, where they can be examined. They can also be run on-demand using the run
  * methods, so that different ordering of AsyncTask execution can be simulated.
+ * <p>
+ * The onPreExecute method of the submitted AsyncTask will be called synchronously during the
+ * call to {@link #submit(Object, AsyncTask, Object...)}.
  */
 @ThreadSafe
 public class FakeAsyncTaskExecutor implements AsyncTaskExecutor {
     private static final long DEFAULT_TIMEOUT_MS = 10000;
-    private static final Executor DEFAULT_EXECUTOR = Executors.sameThreadExecutor();
 
     /** The maximum length of time in ms to wait for tasks to execute during tests. */
     private final long mTimeoutMs = DEFAULT_TIMEOUT_MS;
-    /** The executor for the background part of our test tasks. */
-    private final Executor mExecutor = DEFAULT_EXECUTOR;
 
     private final Object mLock = new Object();
-    @GuardedBy("mLock") private final List<SubmittedTask<?>> mSubmittedTasks = Lists.newArrayList();
+    @GuardedBy("mLock") private final List<SubmittedTask> mSubmittedTasks = Lists.newArrayList();
 
+    private final DelayedExecutor mBlockingExecutor = new DelayedExecutor();
     private final Instrumentation mInstrumentation;
 
     /** Create a fake AsyncTaskExecutor for use in unit tests. */
     public FakeAsyncTaskExecutor(Instrumentation instrumentation) {
-        mInstrumentation = Preconditions.checkNotNull(instrumentation);
+        mInstrumentation = checkNotNull(instrumentation);
     }
 
     /** Encapsulates an async task with the params and identifier it was submitted with. */
-    public interface SubmittedTask<T> {
-        AsyncTask<T, ?, ?> getTask();
-        T[] getParams();
+    public interface SubmittedTask {
+        Runnable getRunnable();
         Object getIdentifier();
+        AsyncTask<?, ?, ?> getAsyncTask();
     }
 
-    private static final class SubmittedTaskImpl<T> implements SubmittedTask<T> {
+    private static final class SubmittedTaskImpl implements SubmittedTask {
         private final Object mIdentifier;
-        private final AsyncTask<T, ?, ?> mTask;
-        private final T[] mParams;
+        private final Runnable mRunnable;
+        private final AsyncTask<?, ?, ?> mAsyncTask;
 
-        public SubmittedTaskImpl(Object identifier, AsyncTask<T, ?, ?> task, T[] params) {
+        public SubmittedTaskImpl(Object identifier, Runnable runnable,
+                AsyncTask<?, ?, ?> asyncTask) {
             mIdentifier = identifier;
-            mTask = task;
-            mParams = params;
+            mRunnable = runnable;
+            mAsyncTask = asyncTask;
         }
 
         @Override
@@ -91,13 +93,13 @@
         }
 
         @Override
-        public AsyncTask<T, ?, ?> getTask() {
-            return mTask;
+        public Runnable getRunnable() {
+            return mRunnable;
         }
 
         @Override
-        public T[] getParams() {
-            return mParams;
+        public AsyncTask<?, ?, ?> getAsyncTask() {
+            return mAsyncTask;
         }
 
         @Override
@@ -106,13 +108,40 @@
         }
     }
 
+    private class DelayedExecutor implements Executor {
+        private final Object mNextLock = new Object();
+        @GuardedBy("mNextLock") private Object mNextIdentifier;
+        @GuardedBy("mNextLock") private AsyncTask<?, ?, ?> mNextTask;
+
+        @Override
+        public void execute(Runnable command) {
+            synchronized (mNextLock) {
+                mSubmittedTasks.add(new SubmittedTaskImpl(mNextIdentifier,
+                        command, checkNotNull(mNextTask)));
+                mNextIdentifier = null;
+                mNextTask = null;
+            }
+        }
+
+        public <T> AsyncTask<T, ?, ?> submit(Object identifier,
+                AsyncTask<T, ?, ?> task, T... params) {
+            synchronized (mNextLock) {
+                checkState(mNextIdentifier == null);
+                checkState(mNextTask == null);
+                mNextIdentifier = identifier;
+                mNextTask = checkNotNull(task, "Already had a valid task.\n"
+                        + "Are you calling AsyncTaskExecutor.submit(...) from within the "
+                        + "onPreExecute() method of another task being submitted?\n"
+                        + "Sorry!  Not that's not supported.");
+            }
+            return task.executeOnExecutor(this, params);
+        }
+    }
+
     @Override
     public <T> AsyncTask<T, ?, ?> submit(Object identifier, AsyncTask<T, ?, ?> task, T... params) {
         AsyncTaskExecutors.checkCalledFromUiThread();
-        synchronized (mLock) {
-            mSubmittedTasks.add(new SubmittedTaskImpl<T>(identifier, task, params));
-            return task;
-        }
+        return mBlockingExecutor.submit(identifier, task, params);
     }
 
     /**
@@ -125,8 +154,8 @@
      * <p>
      * This method blocks until the AsyncTask has completely finished executing.
      */
-    public void runTask(Enum<?> identifier) throws InterruptedException {
-        List<SubmittedTask<?>> tasks = getSubmittedTasksByIdentifier(identifier, true);
+    public void runTask(Object identifier) throws InterruptedException {
+        List<SubmittedTask> tasks = getSubmittedTasksByIdentifier(identifier, true);
         Assert.assertEquals("Expected one task " + identifier + ", got " + tasks, 1, tasks.size());
         runTask(tasks.get(0));
     }
@@ -141,30 +170,21 @@
      * <p>
      * This method blocks until the AsyncTask objects have completely finished executing.
      */
-    public void runAllTasks(Enum<?> identifier) throws InterruptedException {
-        List<SubmittedTask<?>> tasks = getSubmittedTasksByIdentifier(identifier, true);
+    public void runAllTasks(Object identifier) throws InterruptedException {
+        List<SubmittedTask> tasks = getSubmittedTasksByIdentifier(identifier, true);
         Assert.assertTrue("There were no tasks with identifier " + identifier, tasks.size() > 0);
-        for (SubmittedTask<?> task : tasks) {
+        for (SubmittedTask task : tasks) {
             runTask(task);
         }
     }
 
     /**
-     * Executes a single {@link AsyncTask} using the supplied executors.
+     * Executes a single {@link SubmittedTask}.
      * <p>
      * Blocks until the task has completed running.
      */
-    private <T> void runTask(SubmittedTask<T> submittedTask) throws InterruptedException {
-        final AsyncTask<T, ?, ?> task = submittedTask.getTask();
-        task.executeOnExecutor(mExecutor, submittedTask.getParams());
-        // Block until the task has finished running in the background.
-        try {
-            task.get(mTimeoutMs, TimeUnit.MILLISECONDS);
-        } catch (ExecutionException e) {
-            throw new RuntimeException(e.getCause());
-        } catch (TimeoutException e) {
-            throw new RuntimeException("waited too long");
-        }
+    private <T> void runTask(final SubmittedTask submittedTask) throws InterruptedException {
+        submittedTask.getRunnable().run();
         // Block until the onPostExecute or onCancelled has finished.
         // Unfortunately we can't be sure when the AsyncTask will have posted its result handling
         // code to the main ui thread, the best we can do is wait for the Status to be FINISHED.
@@ -172,7 +192,7 @@
         class AsyncTaskHasFinishedRunnable implements Runnable {
             @Override
             public void run() {
-                if (task.getStatus() == AsyncTask.Status.FINISHED) {
+                if (submittedTask.getAsyncTask().getStatus() == AsyncTask.Status.FINISHED) {
                     latch.countDown();
                 } else {
                     mInstrumentation.waitForIdle(this);
@@ -183,14 +203,14 @@
         Assert.assertTrue(latch.await(mTimeoutMs, TimeUnit.MILLISECONDS));
     }
 
-    private List<SubmittedTask<?>> getSubmittedTasksByIdentifier(
-            Enum<?> identifier, boolean remove) {
+    private List<SubmittedTask> getSubmittedTasksByIdentifier(
+            Object identifier, boolean remove) {
         Preconditions.checkNotNull(identifier, "can't lookup tasks by 'null' identifier");
-        List<SubmittedTask<?>> results = Lists.newArrayList();
+        List<SubmittedTask> results = Lists.newArrayList();
         synchronized (mLock) {
-            Iterator<SubmittedTask<?>> iter = mSubmittedTasks.iterator();
+            Iterator<SubmittedTask> iter = mSubmittedTasks.iterator();
             while (iter.hasNext()) {
-                SubmittedTask<?> task = iter.next();
+                SubmittedTask task = iter.next();
                 if (identifier.equals(task.getIdentifier())) {
                     results.add(task);
                     iter.remove();
diff --git a/tests/src/com/android/contacts/util/IntegrationTestUtils.java b/tests/src/com/android/contacts/util/IntegrationTestUtils.java
index afea349..66dd4ef 100644
--- a/tests/src/com/android/contacts/util/IntegrationTestUtils.java
+++ b/tests/src/com/android/contacts/util/IntegrationTestUtils.java
@@ -90,7 +90,7 @@
      * Waits for an idle sync on the main thread (see {@link Instrumentation#waitForIdle(Runnable)})
      * before executing this callable.
      */
-    private <T> T runOnUiThreadAndGetTheResult(Callable<T> callable) throws Throwable {
+    public <T> T runOnUiThreadAndGetTheResult(Callable<T> callable) throws Throwable {
         FutureTask<T> future = new FutureTask<T>(callable);
         mInstrumentation.waitForIdle(future);
         try {