Merge "Follow up to EditSchema parser"
diff --git a/res/drawable-hdpi/ic_launcher_folder_live_contacts.png b/res/drawable-hdpi/ic_launcher_folder_live_contacts.png
deleted file mode 100644
index 84babe2..0000000
--- a/res/drawable-hdpi/ic_launcher_folder_live_contacts.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_folder_live_contacts_phone.png b/res/drawable-hdpi/ic_launcher_folder_live_contacts_phone.png
deleted file mode 100644
index 004e849..0000000
--- a/res/drawable-hdpi/ic_launcher_folder_live_contacts_phone.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_folder_live_contacts_starred.png b/res/drawable-hdpi/ic_launcher_folder_live_contacts_starred.png
deleted file mode 100644
index 73b4fa5..0000000
--- a/res/drawable-hdpi/ic_launcher_folder_live_contacts_starred.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_see_contacts_holo_dark.png b/res/drawable-hdpi/ic_see_contacts_holo_dark.png
deleted file mode 100644
index 8e2182f..0000000
--- a/res/drawable-hdpi/ic_see_contacts_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_unheard_voicemail_holo_dark.png b/res/drawable-hdpi/ic_unheard_voicemail_holo_dark.png
deleted file mode 100644
index 4f48e77..0000000
--- a/res/drawable-hdpi/ic_unheard_voicemail_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_folder_live_contacts.png b/res/drawable-mdpi/ic_launcher_folder_live_contacts.png
deleted file mode 100644
index d49cc7b..0000000
--- a/res/drawable-mdpi/ic_launcher_folder_live_contacts.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_folder_live_contacts_phone.png b/res/drawable-mdpi/ic_launcher_folder_live_contacts_phone.png
deleted file mode 100644
index 0127f84..0000000
--- a/res/drawable-mdpi/ic_launcher_folder_live_contacts_phone.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_folder_live_contacts_starred.png b/res/drawable-mdpi/ic_launcher_folder_live_contacts_starred.png
deleted file mode 100644
index 8d56b31..0000000
--- a/res/drawable-mdpi/ic_launcher_folder_live_contacts_starred.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_see_contacts_holo_dark.png b/res/drawable-mdpi/ic_see_contacts_holo_dark.png
deleted file mode 100644
index ef1d2df..0000000
--- a/res/drawable-mdpi/ic_see_contacts_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_unheard_voicemail_holo_dark.png b/res/drawable-mdpi/ic_unheard_voicemail_holo_dark.png
deleted file mode 100644
index 8992be2..0000000
--- a/res/drawable-mdpi/ic_unheard_voicemail_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher_folder_live_contacts.png b/res/drawable-xhdpi/ic_launcher_folder_live_contacts.png
deleted file mode 100644
index 99bb9f8..0000000
--- a/res/drawable-xhdpi/ic_launcher_folder_live_contacts.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher_folder_live_contacts_phone.png b/res/drawable-xhdpi/ic_launcher_folder_live_contacts_phone.png
deleted file mode 100644
index fd416b8..0000000
--- a/res/drawable-xhdpi/ic_launcher_folder_live_contacts_phone.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher_folder_live_contacts_starred.png b/res/drawable-xhdpi/ic_launcher_folder_live_contacts_starred.png
deleted file mode 100644
index ff9e326..0000000
--- a/res/drawable-xhdpi/ic_launcher_folder_live_contacts_starred.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_see_contacts_holo_dark.png b/res/drawable-xhdpi/ic_see_contacts_holo_dark.png
deleted file mode 100644
index 279fff9..0000000
--- a/res/drawable-xhdpi/ic_see_contacts_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_unheard_voicemail_holo_dark.png b/res/drawable-xhdpi/ic_unheard_voicemail_holo_dark.png
deleted file mode 100644
index 75e771c..0000000
--- a/res/drawable-xhdpi/ic_unheard_voicemail_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/quickcontact_list_item_background.xml b/res/drawable/quickcontact_list_item_background.xml
deleted file mode 100644
index afb3947..0000000
--- a/res/drawable/quickcontact_list_item_background.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?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.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item
-        android:state_window_focused="false"
-        android:drawable="@drawable/list_background_holo" />
-    <item
-        android:state_focused="true"
-        android:state_pressed="true"
-        android:drawable="@drawable/list_pressed_holo" />
-    <item
-        android:state_focused="false"
-        android:state_pressed="true"
-        android:drawable="@drawable/list_pressed_holo" />
-    <item
-        android:state_focused="true"
-        android:drawable="@drawable/list_focused_holo" />
-    <item
-        android:drawable="@color/quickcontact_list_background" />
-</selector>
diff --git a/res/layout-sw580dp-w1000dp/group_detail_fragment.xml b/res/layout-sw580dp-w1000dp/group_detail_fragment.xml
index 45419a8..3011d69 100644
--- a/res/layout-sw580dp-w1000dp/group_detail_fragment.xml
+++ b/res/layout-sw580dp-w1000dp/group_detail_fragment.xml
@@ -81,4 +81,7 @@
         android:fadingEdge="none"
         android:divider="@null" />
 
+    <include
+        layout="@layout/group_detail_fragment_empty_view"/>
+
 </LinearLayout>
\ No newline at end of file
diff --git a/res/layout-sw580dp/group_detail_fragment.xml b/res/layout-sw580dp/group_detail_fragment.xml
index fbe695b..ec65582 100644
--- a/res/layout-sw580dp/group_detail_fragment.xml
+++ b/res/layout-sw580dp/group_detail_fragment.xml
@@ -61,4 +61,7 @@
         android:fadingEdge="none"
         android:divider="@null" />
 
+    <include
+        layout="@layout/group_detail_fragment_empty_view"/>
+
 </LinearLayout>
\ No newline at end of file
diff --git a/res/layout/group_detail_fragment.xml b/res/layout/group_detail_fragment.xml
index 2b020c9..735b29d 100644
--- a/res/layout/group_detail_fragment.xml
+++ b/res/layout/group_detail_fragment.xml
@@ -43,6 +43,9 @@
             android:scrollbarStyle="outsideOverlay"
             android:divider="@null"/>
 
+        <include
+            layout="@layout/group_detail_fragment_empty_view"/>
+
         <!--
           Shadow overlay over the list of group members (since we have a fake stacked
           action bar)
diff --git a/res/layout/group_detail_fragment_empty_view.xml b/res/layout/group_detail_fragment_empty_view.xml
new file mode 100644
index 0000000..89a6cf8
--- /dev/null
+++ b/res/layout/group_detail_fragment_empty_view.xml
@@ -0,0 +1,41 @@
+<?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="@android:id/empty"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:paddingTop="24dip"
+    android:visibility="gone">
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center_horizontal"
+        android:text="@string/emptyGroup"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:textColor="?android:attr/textColorSecondary"/>
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center_horizontal"
+        android:text="@string/addPeopleToGroup"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:textColor="?android:attr/textColorSecondary"/>
+
+</LinearLayout>
diff --git a/res/layout/join_contact_picker_show_all.xml b/res/layout/join_contact_picker_show_all.xml
deleted file mode 100644
index d332649..0000000
--- a/res/layout/join_contact_picker_show_all.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
- * Copyright 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.
- */
--->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:orientation="vertical">
-
-    <View
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:background="?android:attr/dividerHorizontal" />
-
-    <TextView
-        android:id="@+id/text"
-        android:layout_height="48dip"
-        android:layout_width="match_parent"
-        android:textAppearance="?android:attr/textAppearanceSmall"
-        android:textColor="?android:attr/textColorSecondary"
-        android:gravity="center_vertical"
-        android:text="@string/showAllContactsJoinItem" />
-
-    <View
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:background="?android:attr/dividerHorizontal" />
-</LinearLayout>
diff --git a/res/layout/status_bar_ongoing_event_progress_bar.xml b/res/layout/status_bar_ongoing_event_progress_bar.xml
deleted file mode 100644
index e3edf95..0000000
--- a/res/layout/status_bar_ongoing_event_progress_bar.xml
+++ /dev/null
@@ -1,94 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
- * Copyright 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.
- /
-TODO: This is copied from DownloadProvider, and looks similar to Bluetooth's.
-      It might be better to have this in framework.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical"
-    android:background="@android:drawable/status_bar_item_app_background"
-    >
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="horizontal"
-        >
-        <LinearLayout
-            android:layout_width="40dp"
-            android:layout_height="match_parent"
-            android:orientation="vertical"
-            android:paddingTop="8dp"
-            android:focusable="true"
-            >
-            <ImageView
-                android:id="@+id/status_icon"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_gravity="center"
-                android:src="@android:drawable/sym_def_app_icon"
-                />
-            <TextView android:id="@+id/status_progress_text"
-                android:layout_width="wrap_content" 
-                android:layout_height="wrap_content"
-                android:textColor="#ff000000"
-                android:singleLine="true"
-                android:textSize="14sp"
-                android:layout_gravity="center_horizontal" />
-        </LinearLayout>
-
-        <RelativeLayout
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:orientation="vertical"
-            android:focusable="true">
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:orientation="horizontal"
-                android:focusable="true"
-                android:layout_alignParentTop="true"
-                android:paddingTop="10dp">
-                <TextView android:id="@+id/status_description"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:textColor="?android:attr/textColorPrimary"
-                    android:singleLine="true"
-                    android:textSize="18sp"
-                    android:paddingLeft="5dp" />
-            </LinearLayout>
-            <ProgressBar
-               android:id="@+id/status_progress_bar"
-               style="?android:attr/progressBarStyleHorizontal"
-               android:layout_width="match_parent"
-               android:layout_height="wrap_content"
-               android:layout_alignParentBottom="true"
-               android:paddingBottom="8dp"
-               android:paddingRight="25dp" />
-        </RelativeLayout>
-    </LinearLayout>
-
-    <ImageView
-       android:layout_width="match_parent"
-       android:layout_height="wrap_content"
-       android:src="@android:drawable/divider_horizontal_bright"
-       />
-</LinearLayout>
-
diff --git a/res/values/strings.xml b/res/values/strings.xml
index accab64..a5e125d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -269,6 +269,12 @@
     <!-- The text displayed when the contacts list is empty while displaying only contacts that have phone numbers -->
     <string name="noContactsWithPhoneNumbers">No contacts with phone numbers.</string>
 
+    <!-- The text displayed when there are no members in the group while displaying the group detail page [CHAR LIMIT=40] -->
+    <string name="emptyGroup">No people in this group.</string>
+
+    <!-- The text displayed to instruct users to add members to a group (when viewing a group detail page for a group with no members) [CHAR LIMIT=50] -->
+    <string name="addPeopleToGroup">To add some, edit the group.</string>
+
     <!-- Displayed in a spinner dialog after the user creates a contact and it's being saved to the database -->
     <string name="savingContact">Saving contact\u2026</string>
 
diff --git a/src/com/android/contacts/CallDetailActivity.java b/src/com/android/contacts/CallDetailActivity.java
index 541b7bf..caa8bce 100644
--- a/src/com/android/contacts/CallDetailActivity.java
+++ b/src/com/android/contacts/CallDetailActivity.java
@@ -294,7 +294,8 @@
             public Void doInBackground(Void... params) {
                 ContentValues values = new ContentValues();
                 values.put(Voicemails.IS_READ, true);
-                getContentResolver().update(voicemailUri, values, null, null);
+                getContentResolver().update(voicemailUri, values,
+                        Voicemails.IS_READ + " = 0", null);
                 return null;
             }
         });
diff --git a/src/com/android/contacts/ContactLoader.java b/src/com/android/contacts/ContactLoader.java
index c9fbeae..c711b6c 100644
--- a/src/com/android/contacts/ContactLoader.java
+++ b/src/com/android/contacts/ContactLoader.java
@@ -19,6 +19,7 @@
 import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountTypeManager;
 import com.android.contacts.model.AccountTypeWithDataSet;
+import com.android.contacts.util.ContactLoaderUtils;
 import com.android.contacts.util.DataStatus;
 import com.android.contacts.util.StreamItemEntry;
 import com.android.contacts.util.StreamItemPhotoEntry;
@@ -681,7 +682,8 @@
         protected Result doInBackground(Void... args) {
             try {
                 final ContentResolver resolver = getContext().getContentResolver();
-                final Uri uriCurrentFormat = ensureIsContactUri(resolver, mLookupUri);
+                final Uri uriCurrentFormat = ContactLoaderUtils.ensureIsContactUri(
+                        resolver, mLookupUri);
                 Result result = loadContactEntity(resolver, uriCurrentFormat);
                 if (!result.isNotFound()) {
                     if (result.isDirectoryEntry()) {
@@ -706,47 +708,6 @@
             }
         }
 
-        /**
-         * Transforms the given Uri and returns a Lookup-Uri that represents the contact.
-         * For legacy contacts, a raw-contact lookup is performed.
-         * @param resolver
-         */
-        private Uri ensureIsContactUri(final ContentResolver resolver, final Uri uri) {
-            if (uri == null) throw new IllegalArgumentException("uri must not be null");
-
-            final String authority = uri.getAuthority();
-
-            // Current Style Uri?
-            if (ContactsContract.AUTHORITY.equals(authority)) {
-                final String type = resolver.getType(uri);
-                // Contact-Uri? Good, return it
-                if (Contacts.CONTENT_ITEM_TYPE.equals(type)) {
-                    return uri;
-                }
-
-                // RawContact-Uri? Transform it to ContactUri
-                if (RawContacts.CONTENT_ITEM_TYPE.equals(type)) {
-                    final long rawContactId = ContentUris.parseId(uri);
-                    return RawContacts.getContactLookupUri(getContext().getContentResolver(),
-                            ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
-                }
-
-                // Anything else? We don't know what this is
-                throw new IllegalArgumentException("uri format is unknown");
-            }
-
-            // Legacy Style? Convert to RawContact
-            final String OBSOLETE_AUTHORITY = "contacts";
-            if (OBSOLETE_AUTHORITY.equals(authority)) {
-                // Legacy Format. Convert to RawContact-Uri and then lookup the contact
-                final long rawContactId = ContentUris.parseId(uri);
-                return RawContacts.getContactLookupUri(resolver,
-                        ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
-            }
-
-            throw new IllegalArgumentException("uri authority is unknown");
-        }
-
         private Result loadContactEntity(ContentResolver resolver, Uri contactUri) {
             Uri entityUri = Uri.withAppendedPath(contactUri, Contacts.Entity.CONTENT_DIRECTORY);
             Cursor cursor = resolver.query(entityUri, ContactQuery.COLUMNS, null, null,
diff --git a/src/com/android/contacts/activities/ActionBarAdapter.java b/src/com/android/contacts/activities/ActionBarAdapter.java
index 6d55184..77caaf7 100644
--- a/src/com/android/contacts/activities/ActionBarAdapter.java
+++ b/src/com/android/contacts/activities/ActionBarAdapter.java
@@ -75,6 +75,7 @@
     private final MyTabListener mTabListener = new MyTabListener();
 
     private boolean mShowHomeIcon;
+    private boolean mShowTabsAsText;
 
     public enum TabState {
         GROUPS,
@@ -98,7 +99,8 @@
     private static final TabState DEFAULT_TAB = TabState.ALL;
     private TabState mCurrentTab = DEFAULT_TAB;
 
-    public ActionBarAdapter(Context context, Listener listener, ActionBar actionBar) {
+    public ActionBarAdapter(Context context, Listener listener, ActionBar actionBar,
+            boolean isUsingTwoPanes) {
         mContext = context;
         mListener = listener;
         mActionBar = actionBar;
@@ -106,6 +108,9 @@
 
         mShowHomeIcon = mContext.getResources().getBoolean(R.bool.show_home_icon);
 
+        // On wide screens, show the tabs as text (instead of icons)
+        mShowTabsAsText = isUsingTwoPanes;
+
         // Set up search view.
         View customSearchView = LayoutInflater.from(mActionBar.getThemedContext()).inflate(
                 R.layout.custom_action_bar, null);
@@ -153,12 +158,16 @@
         mListener = listener;
     }
 
-    private void addTab(TabState tabState, int icon, int contentDescription) {
+    private void addTab(TabState tabState, int icon, int description) {
         final Tab tab = mActionBar.newTab();
         tab.setTag(tabState);
-        tab.setIcon(icon);
-        tab.setContentDescription(contentDescription);
         tab.setTabListener(mTabListener);
+        if (mShowTabsAsText) {
+            tab.setText(description);
+        } else {
+            tab.setIcon(icon);
+            tab.setContentDescription(description);
+        }
         mActionBar.addTab(tab);
     }
 
@@ -211,14 +220,16 @@
         return mCurrentTab;
     }
 
+    /**
+     * @return Whether in search mode, i.e. if the search view is visible/expanded.
+     *
+     * Note even if the action bar is in search mode, if the query is empty, the search fragment
+     * will not be in search mode.
+     */
     public boolean isSearchMode() {
         return mSearchMode;
     }
 
-    public boolean shouldShowSearchResult() {
-        return mSearchMode && !TextUtils.isEmpty(mQueryString);
-    }
-
     public void setSearchMode(boolean flag) {
         if (mSearchMode != flag) {
             mSearchMode = flag;
@@ -235,7 +246,7 @@
     }
 
     public String getQueryString() {
-        return mQueryString;
+        return mSearchMode ? mQueryString : null;
     }
 
     public void setQueryString(String query) {
diff --git a/src/com/android/contacts/activities/ContactSelectionActivity.java b/src/com/android/contacts/activities/ContactSelectionActivity.java
index 6305d8e..e68c7bc 100644
--- a/src/com/android/contacts/activities/ContactSelectionActivity.java
+++ b/src/com/android/contacts/activities/ContactSelectionActivity.java
@@ -327,7 +327,6 @@
 
             case ContactsRequest.ACTION_PICK_CONTACT: {
                 ContactPickerFragment fragment = new ContactPickerFragment();
-                fragment.setSearchMode(mRequest.isSearchMode());
                 fragment.setIncludeProfile(mRequest.shouldIncludeProfile());
                 mListFragment = fragment;
                 break;
@@ -341,8 +340,6 @@
 
             case ContactsRequest.ACTION_CREATE_SHORTCUT_CONTACT: {
                 ContactPickerFragment fragment = new ContactPickerFragment();
-                fragment.setSearchMode(mRequest.isSearchMode());
-                fragment.setQueryString(mRequest.getQueryString(), false);
                 fragment.setShortcutRequested(true);
                 mListFragment = fragment;
                 break;
@@ -362,7 +359,6 @@
             case ContactsRequest.ACTION_CREATE_SHORTCUT_CALL: {
                 PhoneNumberPickerFragment fragment = new PhoneNumberPickerFragment();
                 fragment.setShortcutAction(Intent.ACTION_CALL);
-                fragment.setSearchMode(mRequest.isSearchMode());
 
                 mListFragment = fragment;
                 break;
@@ -387,9 +383,6 @@
         }
 
         mListFragment.setLegacyCompatibilityMode(mRequest.isLegacyCompatibilityMode());
-        mListFragment.setContactsRequest(mRequest);
-        mListFragment.setSearchMode(mRequest.isSearchMode());
-        mListFragment.setQueryString(mRequest.getQueryString(), false);
         mListFragment.setDirectoryResultLimit(DEFAULT_DIRECTORY_RESULT_LIMIT);
 
         getFragmentManager().beginTransaction()
@@ -537,7 +530,6 @@
     @Override
     public boolean onQueryTextChange(String newText) {
         mListFragment.setQueryString(newText, true);
-        mListFragment.setSearchMode(!TextUtils.isEmpty(newText));
         return false;
     }
 
diff --git a/src/com/android/contacts/activities/DialtactsActivity.java b/src/com/android/contacts/activities/DialtactsActivity.java
index 571a988..15724c1 100644
--- a/src/com/android/contacts/activities/DialtactsActivity.java
+++ b/src/com/android/contacts/activities/DialtactsActivity.java
@@ -363,7 +363,6 @@
                 public boolean onQueryTextChange(String newText) {
                     // Show search result with non-empty text. Show a bare list otherwise.
                     mSearchFragment.setQueryString(newText, true);
-                    mSearchFragment.setSearchMode(!TextUtils.isEmpty(newText));
                     return true;
                 }
     };
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index b68f566..6f4089c 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -317,7 +317,8 @@
         final FragmentTransaction transaction = fragmentManager.beginTransaction();
 
         // Prepare the fragments which are used both on 1-pane and on 2-pane.
-        if (PhoneCapabilityTester.isUsingTwoPanes(this)) {
+        boolean isUsingTwoPanes = PhoneCapabilityTester.isUsingTwoPanes(this);
+        if (isUsingTwoPanes) {
             mFavoritesFragment = getFragment(R.id.favorites_fragment);
             mAllFragment = getFragment(R.id.all_fragment);
             mGroupsFragment = getFragment(R.id.groups_fragment);
@@ -367,7 +368,7 @@
         transaction.hide(mAllFragment);
         transaction.hide(mGroupsFragment);
 
-        if (PhoneCapabilityTester.isUsingTwoPanes(this)) {
+        if (isUsingTwoPanes) {
             // Prepare 2-pane only fragments/views...
 
             // Container views for fragments
@@ -410,7 +411,7 @@
         }
 
         // Configure action bar
-        mActionBarAdapter = new ActionBarAdapter(this, this, getActionBar());
+        mActionBarAdapter = new ActionBarAdapter(this, this, getActionBar(), isUsingTwoPanes);
         mActionBarAdapter.initialize(savedState, mRequest);
 
         invalidateOptionsMenuIfNeeded();
@@ -566,12 +567,12 @@
                 invalidateOptionsMenu();
                 break;
             case STOP_SEARCH_MODE:
-                clearSearch();
+                setQueryTextToFragment("");
                 updateFragmentsVisibility();
                 invalidateOptionsMenu();
                 break;
             case CHANGE_SEARCH_QUERY:
-                loadSearch(mActionBarAdapter.getQueryString());
+                setQueryTextToFragment(mActionBarAdapter.getQueryString());
                 break;
             default:
                 throw new IllegalStateException("Unkonwn ActionBarAdapter action: " + action);
@@ -842,26 +843,19 @@
         }
     }
 
-    private void clearSearch() {
-        loadSearch("");
-    }
-
-    private void loadSearch(String query) {
-        configureFragments(false /* from request */);
+    private void setQueryTextToFragment(String query) {
         mAllFragment.setQueryString(query, true);
+        mAllFragment.setVisibleScrollbarEnabled(!mAllFragment.isSearchMode());
     }
 
     private void configureContactListFragmentForRequest() {
-        mAllFragment.setContactsRequest(mRequest);
-
         Uri contactUri = mRequest.getContactUri();
         if (contactUri != null) {
             mAllFragment.setSelectedContactUri(contactUri);
         }
 
         mAllFragment.setFilter(mContactListFilterController.getFilter());
-        mAllFragment.setSearchMode(mActionBarAdapter.isSearchMode());
-        mAllFragment.setQueryString(mActionBarAdapter.getQueryString(), false);
+        setQueryTextToFragment(mActionBarAdapter.getQueryString());
 
         if (mRequest.isDirectorySearchEnabled()) {
             mAllFragment.setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_DEFAULT);
@@ -874,11 +868,7 @@
         // Filter may be changed when this Activity is in background.
         mAllFragment.setFilter(mContactListFilterController.getFilter());
 
-        final boolean showSearchResult = mActionBarAdapter.shouldShowSearchResult();
-        mAllFragment.setSearchMode(showSearchResult);
-
         final boolean useTwoPane = PhoneCapabilityTester.isUsingTwoPanes(this);
-        mAllFragment.setVisibleScrollbarEnabled(!showSearchResult);
         mAllFragment.setVerticalScrollbarPosition(
                 useTwoPane
                         ? View.SCROLLBAR_POSITION_LEFT
diff --git a/src/com/android/contacts/detail/ContactDetailLayoutController.java b/src/com/android/contacts/detail/ContactDetailLayoutController.java
index 4b31a6e..2bcd1a0 100644
--- a/src/com/android/contacts/detail/ContactDetailLayoutController.java
+++ b/src/com/android/contacts/detail/ContactDetailLayoutController.java
@@ -324,7 +324,7 @@
                 // Update ViewPager to disable swipe so that it only shows the detail fragment
                 // and switch to the detail fragment
                 mViewPagerAdapter.enableSwipe(false);
-                mViewPager.setCurrentItem(0);
+                mViewPager.setCurrentItem(0, false /* smooth transition */);
                 break;
             case FRAGMENT_CAROUSEL: {
                 // Disable swipe so only the detail fragment shows
diff --git a/src/com/android/contacts/group/GroupDetailFragment.java b/src/com/android/contacts/group/GroupDetailFragment.java
index d4f6e29..b3472ab 100644
--- a/src/com/android/contacts/group/GroupDetailFragment.java
+++ b/src/com/android/contacts/group/GroupDetailFragment.java
@@ -98,6 +98,7 @@
     private TextView mGroupTitle;
     private TextView mGroupSize;
     private ListView mMemberListView;
+    private View mEmptyView;
 
     private Listener mListener;
 
@@ -149,6 +150,7 @@
         mGroupSize = (TextView) mRootView.findViewById(R.id.group_size);
         mGroupSourceViewContainer = (ViewGroup) mRootView.findViewById(
                 R.id.group_source_view_container);
+        mEmptyView = mRootView.findViewById(android.R.id.empty);
         mMemberListView = (ListView) mRootView.findViewById(android.R.id.list);
         mMemberListView.setAdapter(mAdapter);
 
@@ -258,6 +260,7 @@
         public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
             updateSize(data.getCount());
             mAdapter.setContactCursor(data);
+            mMemberListView.setEmptyView(mEmptyView);
         }
 
         @Override
diff --git a/src/com/android/contacts/list/ContactBrowseListFragment.java b/src/com/android/contacts/list/ContactBrowseListFragment.java
index 2d5fed1..061646e 100644
--- a/src/com/android/contacts/list/ContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/ContactBrowseListFragment.java
@@ -17,10 +17,10 @@
 
 import com.android.common.widget.CompositeCursorAdapter.Partition;
 import com.android.contacts.R;
+import com.android.contacts.util.ContactLoaderUtils;
 import com.android.contacts.widget.AutoScrollListView;
 
 import android.app.Activity;
-import android.content.AsyncQueryHandler;
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.Loader;
@@ -28,6 +28,7 @@
 import android.content.SharedPreferences.Editor;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
@@ -92,51 +93,64 @@
     private String mPersistentSelectionPrefix = PERSISTENT_SELECTION_PREFIX;
 
     protected OnContactBrowserActionListener mListener;
+    private ContactLookupTask mContactLookupTask;
 
-    /**
-     * Refreshes a contact URI: it may have changed as a result of aggregation
-     * activity.
-     */
-    private class ContactUriQueryHandler extends AsyncQueryHandler {
+    private final class ContactLookupTask extends AsyncTask<Void, Void, Uri> {
 
-        public ContactUriQueryHandler(ContentResolver cr) {
-            super(cr);
-        }
+        private final Uri mUri;
+        private boolean mIsCancelled;
 
-        public void runQuery() {
-            startQuery(0, mSelectedContactUri, mSelectedContactUri,
-                    new String[] { Contacts._ID, Contacts.LOOKUP_KEY }, null, null, null);
+        public ContactLookupTask(Uri uri) {
+            mUri = uri;
         }
 
         @Override
-        protected void onQueryComplete(int token, Object cookie, Cursor data) {
-            long contactId = 0;
-            String lookupKey = null;
-            if (data != null) {
-                if (data.moveToFirst()) {
-                    contactId = data.getLong(0);
-                    lookupKey = data.getString(1);
-                }
-                data.close();
-            }
+        protected Uri doInBackground(Void... args) {
+            Cursor cursor = null;
+            try {
+                final ContentResolver resolver = getContext().getContentResolver();
+                final Uri uriCurrentFormat = ContactLoaderUtils.ensureIsContactUri(resolver, mUri);
+                cursor = resolver.query(uriCurrentFormat,
+                        new String[] { Contacts._ID, Contacts.LOOKUP_KEY }, null, null, null);
 
-            if (!cookie.equals(mSelectedContactUri)) {
+                if (cursor != null && cursor.moveToFirst()) {
+                    final long contactId = cursor.getLong(0);
+                    final String lookupKey = cursor.getString(1);
+                    if (contactId != 0 && !TextUtils.isEmpty(lookupKey)) {
+                        return Contacts.getLookupUri(contactId, lookupKey);
+                    }
+                }
+
+                Log.e(TAG, "Error: No contact ID or lookup key for contact " + mUri);
+                return null;
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
+            }
+        }
+
+        public void cancel() {
+            super.cancel(true);
+            // Use a flag to keep track of whether the {@link AsyncTask} was cancelled or not in
+            // order to ensure onPostExecute() is not executed after the cancel request. The flag is
+            // necessary because {@link AsyncTask} still calls onPostExecute() if the cancel request
+            // came after the worker thread was finished.
+            mIsCancelled = true;
+        }
+
+        @Override
+        protected void onPostExecute(Uri uri) {
+            // Make sure the {@link Fragment} is at least still attached to the {@link Activity}
+            // before continuing.
+            if (mIsCancelled || !isAdded() || uri == null) {
                 return;
             }
-
-            Uri uri;
-            if (contactId != 0 && lookupKey != null) {
-                uri = Contacts.getLookupUri(contactId, lookupKey);
-            } else {
-                uri = null;
-            }
-
-            onContactUriQueryFinished(uri);
+            mSelectedContactUri = uri;
+            onContactUriQueryFinished(mSelectedContactUri);
         }
     }
 
-    private ContactUriQueryHandler mQueryHandler;
-
     private boolean mDelaySelection;
 
     private Handler getHandler() {
@@ -158,14 +172,13 @@
     @Override
     public void onAttach(Activity activity) {
         super.onAttach(activity);
-        mQueryHandler = new ContactUriQueryHandler(activity.getContentResolver());
         mPrefs = PreferenceManager.getDefaultSharedPreferences(activity);
         restoreFilter();
         restoreSelectedUri(false);
     }
 
     @Override
-    public void setSearchMode(boolean flag) {
+    protected void setSearchMode(boolean flag) {
         if (isSearchMode() != flag) {
             if (!flag) {
                 restoreSelectedUri(true);
@@ -228,12 +241,10 @@
     }
 
     protected void refreshSelectedContactUri() {
-        if (mQueryHandler == null) {
-            return;
+        if (mContactLookupTask != null) {
+            mContactLookupTask.cancel();
         }
 
-        mQueryHandler.cancelOperation(0);
-
         if (!isSelectionVisible()) {
             return;
         }
@@ -249,7 +260,8 @@
                 && mSelectedContactDirectoryId != Directory.LOCAL_INVISIBLE) {
             onContactUriQueryFinished(mSelectedContactUri);
         } else {
-            mQueryHandler.runQuery();
+            mContactLookupTask = new ContactLookupTask(mSelectedContactUri);
+            mContactLookupTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null);
         }
     }
 
diff --git a/src/com/android/contacts/list/ContactEntryListFragment.java b/src/com/android/contacts/list/ContactEntryListFragment.java
index 91c582c..8b9aaf5 100644
--- a/src/com/android/contacts/list/ContactEntryListFragment.java
+++ b/src/com/android/contacts/list/ContactEntryListFragment.java
@@ -108,7 +108,6 @@
     private String mQueryString;
     private int mDirectorySearchMode = DirectoryListLoader.SEARCH_MODE_NONE;
     private boolean mSelectionVisible;
-    private ContactsRequest mRequest;
     private boolean mLegacyCompatibility;
 
     private boolean mEnabled = true;
@@ -249,7 +248,6 @@
         outState.putBoolean(KEY_LEGACY_COMPATIBILITY, mLegacyCompatibility);
         outState.putString(KEY_QUERY_STRING, mQueryString);
         outState.putInt(KEY_DIRECTORY_RESULT_LIMIT, mDirectoryResultLimit);
-        outState.putParcelable(KEY_REQUEST, mRequest);
         outState.putBoolean(KEY_DARK_THEME, mDarkTheme);
 
         if (mListView != null) {
@@ -282,27 +280,12 @@
         mLegacyCompatibility = savedState.getBoolean(KEY_LEGACY_COMPATIBILITY);
         mQueryString = savedState.getString(KEY_QUERY_STRING);
         mDirectoryResultLimit = savedState.getInt(KEY_DIRECTORY_RESULT_LIMIT);
-        mRequest = savedState.getParcelable(KEY_REQUEST);
         mDarkTheme = savedState.getBoolean(KEY_DARK_THEME);
 
         // Retrieve list state. This will be applied in onLoadFinished
         mListState = savedState.getParcelable(KEY_LIST_STATE);
     }
 
-    /**
-     * Returns the parsed intent that started the activity hosting this fragment.
-     */
-    public ContactsRequest getContactsRequest() {
-        return mRequest;
-    }
-
-    /**
-     * Sets a parsed intent that started the activity hosting this fragment.
-     */
-    public void setContactsRequest(ContactsRequest request) {
-        mRequest = request;
-    }
-
     @Override
     public void onStart() {
         super.onStart();
@@ -608,7 +591,14 @@
         }
     }
 
-    public void setSearchMode(boolean flag) {
+    /**
+     * Enter/exit search mode.  By design, a fragment enters search mode only when it has a
+     * non-empty query text, so the mode must be tightly related to the current query.
+     * For this reason this method must only be called by {@link #setQueryString}.
+     *
+     * Also note this method doesn't call {@link #reloadData()}; {@link #setQueryString} does it.
+     */
+    protected void setSearchMode(boolean flag) {
         if (mSearchMode != flag) {
             mSearchMode = flag;
             setSectionHeaderDisplayEnabled(!mSearchMode);
@@ -632,7 +622,6 @@
                     }
                 }
                 mAdapter.configureDefaultPartition(false, flag);
-                reloadData();
             }
 
             if (mListView != null) {
@@ -641,17 +630,22 @@
         }
     }
 
-    public boolean isSearchMode() {
+    public final boolean isSearchMode() {
         return mSearchMode;
     }
 
-    public String getQueryString() {
+    public final String getQueryString() {
         return mQueryString;
     }
 
     public void setQueryString(String queryString, boolean delaySelection) {
+        // Normalize the empty query.
+        if (TextUtils.isEmpty(queryString)) queryString = null;
+
         if (!TextUtils.equals(mQueryString, queryString)) {
             mQueryString = queryString;
+            setSearchMode(!TextUtils.isEmpty(mQueryString));
+
             if (mAdapter != null) {
                 mAdapter.setQueryString(queryString);
                 reloadData();
diff --git a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
index 30c3c48..a6700b5 100644
--- a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
@@ -120,7 +120,7 @@
     }
 
     @Override
-    public void setSearchMode(boolean flag) {
+    protected void setSearchMode(boolean flag) {
         super.setSearchMode(flag);
         checkHeaderViewVisibility();
     }
diff --git a/src/com/android/contacts/list/PhoneFavoriteMergedAdapter.java b/src/com/android/contacts/list/PhoneFavoriteMergedAdapter.java
index f817b4c..0df4b3d 100644
--- a/src/com/android/contacts/list/PhoneFavoriteMergedAdapter.java
+++ b/src/com/android/contacts/list/PhoneFavoriteMergedAdapter.java
@@ -186,7 +186,7 @@
         } else if (position == contactTileAdapterCount) {
             return mAccountFilterHeaderContainer.isEnabled();
         } else {
-            final int localPosition = position - contactTileAdapterCount + 1;
+            final int localPosition = position - contactTileAdapterCount - 1;
             return mContactEntryListAdapter.isEnabled(localPosition);
         }
     }
diff --git a/src/com/android/contacts/list/PhoneNumberPickerFragment.java b/src/com/android/contacts/list/PhoneNumberPickerFragment.java
index 64d5f13..602b06a 100644
--- a/src/com/android/contacts/list/PhoneNumberPickerFragment.java
+++ b/src/com/android/contacts/list/PhoneNumberPickerFragment.java
@@ -104,7 +104,7 @@
     }
 
     @Override
-    public void setSearchMode(boolean flag) {
+    protected void setSearchMode(boolean flag) {
         super.setSearchMode(flag);
         updateFilterHeaderView();
     }
diff --git a/src/com/android/contacts/util/ContactLoaderUtils.java b/src/com/android/contacts/util/ContactLoaderUtils.java
new file mode 100644
index 0000000..91c683f
--- /dev/null
+++ b/src/com/android/contacts/util/ContactLoaderUtils.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.util;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.net.Uri;
+import android.provider.Contacts;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.RawContacts;
+
+/**
+ * Utility methods for the {@link ContactLoader}.
+ */
+public final class ContactLoaderUtils {
+
+    /** Static helper, not instantiable. */
+    private ContactLoaderUtils() {}
+
+    /**
+     * Transforms the given Uri and returns a Lookup-Uri that represents the contact.
+     * For legacy contacts, a raw-contact lookup is performed. An {@link IllegalArgumentException}
+     * can be thrown if the URI is null or the authority is not recognized.
+     *
+     * Do not call from the UI thread.
+     */
+    @SuppressWarnings("deprecation")
+    public static Uri ensureIsContactUri(final ContentResolver resolver, final Uri uri)
+            throws IllegalArgumentException {
+        if (uri == null) throw new IllegalArgumentException("uri must not be null");
+
+        final String authority = uri.getAuthority();
+
+        // Current Style Uri?
+        if (ContactsContract.AUTHORITY.equals(authority)) {
+            final String type = resolver.getType(uri);
+            // Contact-Uri? Good, return it
+            if (ContactsContract.Contacts.CONTENT_ITEM_TYPE.equals(type)) {
+                return uri;
+            }
+
+            // RawContact-Uri? Transform it to ContactUri
+            if (RawContacts.CONTENT_ITEM_TYPE.equals(type)) {
+                final long rawContactId = ContentUris.parseId(uri);
+                return RawContacts.getContactLookupUri(resolver,
+                        ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
+            }
+
+            // Anything else? We don't know what this is
+            throw new IllegalArgumentException("uri format is unknown");
+        }
+
+        // Legacy Style? Convert to RawContact
+        final String OBSOLETE_AUTHORITY = Contacts.AUTHORITY;
+        if (OBSOLETE_AUTHORITY.equals(authority)) {
+            // Legacy Format. Convert to RawContact-Uri and then lookup the contact
+            final long rawContactId = ContentUris.parseId(uri);
+            return RawContacts.getContactLookupUri(resolver,
+                    ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
+        }
+
+        throw new IllegalArgumentException("uri authority is unknown");
+    }
+}
diff --git a/src/com/android/contacts/vcard/NotificationImportExportListener.java b/src/com/android/contacts/vcard/NotificationImportExportListener.java
index fb83cec..06cf73f 100644
--- a/src/com/android/contacts/vcard/NotificationImportExportListener.java
+++ b/src/com/android/contacts/vcard/NotificationImportExportListener.java
@@ -197,30 +197,6 @@
     /* package */ static Notification constructProgressNotification(
             Context context, int type, String description, String tickerText,
             int jobId, String displayName, int totalCount, int currentCount) {
-        final RemoteViews remoteViews =
-                new RemoteViews(context.getPackageName(),
-                        R.layout.status_bar_ongoing_event_progress_bar);
-        remoteViews.setTextViewText(R.id.status_description, description);
-        remoteViews.setProgressBar(R.id.status_progress_bar, totalCount, currentCount,
-                totalCount == -1);
-        final String percentage;
-        if (totalCount > 0) {
-            percentage = context.getString(R.string.percentage,
-                    String.valueOf(currentCount * 100/totalCount));
-        } else {
-            percentage = "";
-        }
-        remoteViews.setTextViewText(R.id.status_progress_text, percentage);
-        final int icon = (type == VCardService.TYPE_IMPORT ? android.R.drawable.stat_sys_download :
-                android.R.drawable.stat_sys_upload);
-        remoteViews.setImageViewResource(R.id.status_icon, icon);
-
-        final Notification notification = new Notification();
-        notification.icon = icon;
-        notification.tickerText = tickerText;
-        notification.contentView = remoteViews;
-        notification.flags |= Notification.FLAG_ONGOING_EVENT;
-
         // Note: We cannot use extra values here (like setIntExtra()), as PendingIntent doesn't
         // preserve them across multiple Notifications. PendingIntent preserves the first extras
         // (when flag is not set), or update them when PendingIntent#getActivity() is called
@@ -238,8 +214,20 @@
                 .appendQueryParameter(CancelActivity.TYPE, String.valueOf(type)).build();
         intent.setData(uri);
 
-        notification.contentIntent = PendingIntent.getActivity(context, 0, intent, 0);
-        return notification;
+        final Notification.Builder builder = new Notification.Builder(context);
+        builder.setOngoing(true)
+                .setProgress(totalCount, currentCount, totalCount == - 1)
+                .setTicker(tickerText)
+                .setContentTitle(description)
+                .setSmallIcon(type == VCardService.TYPE_IMPORT
+                        ? android.R.drawable.stat_sys_download
+                        : android.R.drawable.stat_sys_upload)
+                .setContentIntent(PendingIntent.getActivity(context, 0, intent, 0));
+        if (totalCount > 0) {
+            builder.setContentText(context.getString(R.string.percentage,
+                    String.valueOf(currentCount * 100 / totalCount)));
+        }
+        return builder.getNotification();
     }
 
     /**