am 543e96aa: am 3289d8fe: reconcile main tree with open-source eclair

Merge commit '543e96aa39ab408aaaf83a2de364a01ca07f3689'

* commit '543e96aa39ab408aaaf83a2de364a01ca07f3689':
  android-2.1_r1 snapshot
diff --git a/Android.mk b/Android.mk
index 2632f47..7a35bf3 100644
--- a/Android.mk
+++ b/Android.mk
@@ -1,11 +1,12 @@
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
 
-LOCAL_MODULE_TAGS := user
+LOCAL_MODULE_TAGS := optional
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_STATIC_JAVA_LIBRARIES := googlelogin-client
+LOCAL_STATIC_JAVA_LIBRARIES := gsf-client \
+                               com.android.phone.common
 
 LOCAL_PACKAGE_NAME := Contacts
 LOCAL_CERTIFICATE := shared
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 9d6ff4b..b54d417 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -28,6 +28,7 @@
     <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.mail" />
     <uses-permission android:name="android.permission.WAKE_LOCK" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
     <uses-permission android:name="android.permission.USE_CREDENTIALS" />
     <uses-permission android:name="android.permission.VIBRATE" />
 
@@ -248,7 +249,7 @@
 
 
         <!-- Used to select display and sync groups -->
-        <activity android:name=".ui.DisplayGroupsActivity" android:label="@string/displayGroups" />
+        <activity android:name=".ui.ContactsPreferencesActivity" android:label="@string/displayGroups" />
 
         <activity
             android:name=".ui.ShowOrCreateActivity"
@@ -342,7 +343,6 @@
         <!-- Edit or insert details for a contact -->
         <activity
             android:name=".ui.EditContactActivity"
-            android:label="@string/editContactDescription"
             android:windowSoftInputMode="stateHidden|adjustResize">
 
             <intent-filter android:label="@string/editContactDescription">
diff --git a/res/drawable-hdpi-finger/ic_btn_round_less.png b/res/drawable-hdpi-finger/ic_btn_round_less.png
new file mode 100644
index 0000000..819440e
--- /dev/null
+++ b/res/drawable-hdpi-finger/ic_btn_round_less.png
Binary files differ
diff --git a/res/drawable-mdpi-finger/ic_btn_round_less.png b/res/drawable-mdpi-finger/ic_btn_round_less.png
new file mode 100644
index 0000000..a9b5bed
--- /dev/null
+++ b/res/drawable-mdpi-finger/ic_btn_round_less.png
Binary files differ
diff --git a/res/drawable/call_background_secondary.xml b/res/drawable/call_background_secondary.xml
new file mode 100644
index 0000000..b784862
--- /dev/null
+++ b/res/drawable/call_background_secondary.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_window_focused="false"
+        android:drawable="@android:color/transparent" />
+    <item android:state_focused="false" android:state_pressed="true"
+        android:drawable="@*android:drawable/list_selector_background_transition" />
+    <item android:state_focused="false" android:state_pressed="false"
+        android:drawable="@color/background_secondary"/>
+
+</selector>
diff --git a/res/drawable/ic_launcher_folder_live_contacts.png b/res/drawable/ic_launcher_folder_live_contacts.png
deleted file mode 100644
index 5e222ec..0000000
--- a/res/drawable/ic_launcher_folder_live_contacts.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/ic_launcher_folder_live_contacts_phone.png b/res/drawable/ic_launcher_folder_live_contacts_phone.png
deleted file mode 100644
index 02637da..0000000
--- a/res/drawable/ic_launcher_folder_live_contacts_phone.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/ic_launcher_folder_live_contacts_starred.png b/res/drawable/ic_launcher_folder_live_contacts_starred.png
deleted file mode 100644
index 927b343..0000000
--- a/res/drawable/ic_launcher_folder_live_contacts_starred.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/ic_launcher_phone.png b/res/drawable/ic_launcher_phone.png
deleted file mode 100644
index 4e613ec..0000000
--- a/res/drawable/ic_launcher_phone.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/ic_launcher_shortcut_contact.png b/res/drawable/ic_launcher_shortcut_contact.png
deleted file mode 100644
index ed7025c..0000000
--- a/res/drawable/ic_launcher_shortcut_contact.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/list_item_background_secondary.xml b/res/drawable/list_item_background_secondary.xml
new file mode 100644
index 0000000..0a27206
--- /dev/null
+++ b/res/drawable/list_item_background_secondary.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:drawable="@android:color/transparent"/>
+    <item android:state_selected="true" android:drawable="@android:color/transparent"/>
+    <item android:drawable="@color/background_secondary"/> <!-- not selected -->
+</selector>
diff --git a/res/drawable/quickcontact.9.png b/res/drawable/quickcontact.9.png
deleted file mode 100644
index fa0b917..0000000
--- a/res/drawable/quickcontact.9.png
+++ /dev/null
Binary files differ
diff --git a/res/layout-finger/call_detail.xml b/res/layout-finger/call_detail.xml
index ad151c1..3b230b5 100644
--- a/res/layout-finger/call_detail.xml
+++ b/res/layout-finger/call_detail.xml
@@ -15,13 +15,13 @@
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
     android:orientation="vertical"
 >
 
     <LinearLayout
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:background="@android:drawable/title_bar_tall"
         android:orientation="horizontal"
@@ -70,22 +70,22 @@
 
     <FrameLayout
         android:layout_height="0dip"
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_weight="1"
     >
         <ListView android:id="@android:id/list"
-            android:layout_width="fill_parent" 
-            android:layout_height="fill_parent"
+            android:layout_width="match_parent" 
+            android:layout_height="match_parent"
             android:scrollbarStyle="outsideOverlay"
         />
 
         <ScrollView android:id="@android:id/empty"
-                android:layout_width="fill_parent"
-                android:layout_height="fill_parent"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
                 android:fillViewport="true">
 
             <TextView android:id="@+id/emptyText"
-                android:layout_width="fill_parent"
+                android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:text="@string/unknown"
                 android:textSize="20sp"
@@ -99,7 +99,7 @@
         </ScrollView>
 
         <View
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:background="@drawable/title_bar_shadow"
         />
diff --git a/res/layout-finger/call_detail_list_item.xml b/res/layout-finger/call_detail_list_item.xml
index 6c61690..e476de1 100644
--- a/res/layout-finger/call_detail_list_item.xml
+++ b/res/layout-finger/call_detail_list_item.xml
@@ -18,7 +18,7 @@
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:minHeight="?android:attr/listPreferredItemHeight"
     android:orientation="horizontal"
diff --git a/res/layout-finger/contact_card_layout.xml b/res/layout-finger/contact_card_layout.xml
index 6e70561..38ce17e 100644
--- a/res/layout-finger/contact_card_layout.xml
+++ b/res/layout-finger/contact_card_layout.xml
@@ -17,27 +17,27 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/card_root_view"
     android:orientation="vertical"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent">
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
     
     <com.android.internal.widget.ContactHeaderWidget
         android:id="@+id/contact_header_widget"
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"/>
         
     <ListView android:id="@+id/contact_data"
-        android:layout_width="fill_parent"
-        android:layout_height="fill_parent"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
         android:background="@drawable/title_bar_shadow"
     />
     
     <ScrollView android:id="@android:id/empty"
-        android:layout_width="fill_parent"
-        android:layout_height="fill_parent"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
         android:fillViewport="true"
     >
         <TextView android:id="@+id/emptyText"
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:text="@string/no_contact_details"
             android:textSize="20sp"
diff --git a/res/layout-finger/contact_options.xml b/res/layout-finger/contact_options.xml
index e484324..5bd8836 100644
--- a/res/layout-finger/contact_options.xml
+++ b/res/layout-finger/contact_options.xml
@@ -15,14 +15,14 @@
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
     android:orientation="vertical"
 >
 
-    <include layout="@layout/edit_contact_entry_ringtone" android:id="@+id/ringtone" />
+    <include layout="@layout/preference_with_more_button" android:id="@+id/ringtone" />
     <View
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="1dip"
         android:background="?android:attr/listDivider"
     />
diff --git a/res/layout-finger/contacts_list_content.xml b/res/layout-finger/contacts_list_content.xml
index f736efd..cae4762 100644
--- a/res/layout-finger/contacts_list_content.xml
+++ b/res/layout-finger/contacts_list_content.xml
@@ -15,23 +15,23 @@
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="fill_parent"
-        android:layout_height="fill_parent"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
         android:orientation="vertical">
 
     <com.android.contacts.FocusRequestingListView android:id="@android:id/list"
-            android:layout_width="fill_parent"
-            android:layout_height="fill_parent"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
             android:fastScrollEnabled="true"
     />
 
     <ScrollView android:id="@android:id/empty"
-        android:layout_width="fill_parent"
-        android:layout_height="fill_parent"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
         android:fillViewport="true"
     >
         <TextView android:id="@+id/emptyText"
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:text="@string/noContacts"
             android:textSize="20sp"
diff --git a/res/layout-finger/contacts_list_content_join.xml b/res/layout-finger/contacts_list_content_join.xml
index b59c1b7..b50713b 100644
--- a/res/layout-finger/contacts_list_content_join.xml
+++ b/res/layout-finger/contacts_list_content_join.xml
@@ -16,12 +16,12 @@
 
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="fill_parent"
-        android:layout_height="fill_parent"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
         android:orientation="vertical">
 
     <LinearLayout
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content" 
             android:orientation="horizontal"
             android:background="@*android:drawable/title_bar_medium"
@@ -38,12 +38,12 @@
             android:scaleType="fitCenter"
         />
         <LinearLayout
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:orientation="vertical"
             android:paddingLeft="10dip">
             <TextView
-                android:layout_width="fill_parent"
+                android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:text="@string/titleJoinContactDataWith"
                 android:textAppearance="?android:attr/textAppearanceMedium"
@@ -52,7 +52,7 @@
             />
             <TextView
                 android:id="@+id/join_contact_blurb"
-                android:layout_width="fill_parent"
+                android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:layout_marginTop="-2dip"
                 android:maxLines="2"
@@ -62,8 +62,8 @@
     </LinearLayout>
 
     <ListView android:id="@android:id/list"
-            android:layout_width="fill_parent"
-            android:layout_height="fill_parent"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
             android:fastScrollEnabled="true"
     />
 </LinearLayout>
diff --git a/res/layout-finger/contacts_list_item.xml b/res/layout-finger/contacts_list_item.xml
index 61cc839..7520334 100644
--- a/res/layout-finger/contacts_list_item.xml
+++ b/res/layout-finger/contacts_list_item.xml
@@ -18,7 +18,7 @@
 -->
 
 <LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="vertical"
 >
@@ -28,14 +28,14 @@
     />
 
     <RelativeLayout
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="?android:attr/listPreferredItemHeight"
         android:paddingLeft="14dip"
     >
 
         <LinearLayout android:id="@+id/right_side"
             android:layout_width="wrap_content"
-            android:layout_height="fill_parent"
+            android:layout_height="match_parent"
             android:orientation="horizontal"
             android:layout_marginLeft="11dip"
             android:layout_alignParentRight="true">
@@ -52,12 +52,12 @@
 
             <LinearLayout android:id="@+id/call_view"
                 android:layout_width="wrap_content"
-                android:layout_height="fill_parent"
+                android:layout_height="match_parent"
                 android:orientation="horizontal">
 
                 <View android:id="@+id/divider"
                     android:layout_width="1px"
-                    android:layout_height="fill_parent"
+                    android:layout_height="match_parent"
                     android:layout_marginTop="5dip"
                     android:layout_marginBottom="5dip"
                     android:background="@drawable/divider_vertical_dark"
@@ -65,7 +65,7 @@
 
                 <com.android.contacts.ui.widget.DontPressWithParentImageView android:id="@+id/call_button"
                     android:layout_width="wrap_content"
-                    android:layout_height="fill_parent"
+                    android:layout_height="match_parent"
                     android:paddingLeft="14dip"
                     android:paddingRight="14dip"
                     android:layout_centerVertical="true"
@@ -124,7 +124,7 @@
     </RelativeLayout>
 
     <View android:id="@+id/list_divider"
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:background="@*android:drawable/divider_horizontal_dark_opaque"
     />
diff --git a/res/layout-finger/contacts_list_item_photo.xml b/res/layout-finger/contacts_list_item_photo.xml
index c9b4c1c..9dfeb80 100644
--- a/res/layout-finger/contacts_list_item_photo.xml
+++ b/res/layout-finger/contacts_list_item_photo.xml
@@ -18,7 +18,7 @@
 -->
 
 <LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="vertical"
 >
@@ -28,14 +28,14 @@
     />
 
     <RelativeLayout
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="?android:attr/listPreferredItemHeight"
         android:paddingLeft="4dip"
     >
 
         <LinearLayout android:id="@+id/right_side"
             android:layout_width="wrap_content"
-            android:layout_height="fill_parent"
+            android:layout_height="match_parent"
             android:orientation="horizontal"
             android:layout_marginLeft="11dip"
             android:layout_alignParentRight="true">
@@ -52,12 +52,12 @@
 
             <LinearLayout android:id="@+id/call_view"
                 android:layout_width="wrap_content"
-                android:layout_height="fill_parent"
+                android:layout_height="match_parent"
                 android:orientation="horizontal">
 
                 <View android:id="@+id/divider"
                     android:layout_width="1px"
-                    android:layout_height="fill_parent"
+                    android:layout_height="match_parent"
                     android:layout_marginTop="5dip"
                     android:layout_marginBottom="5dip"
                     android:background="@drawable/divider_vertical_dark"
@@ -65,7 +65,7 @@
 
                 <com.android.contacts.ui.widget.DontPressWithParentImageView android:id="@+id/call_button"
                     android:layout_width="wrap_content"
-                    android:layout_height="fill_parent"
+                    android:layout_height="match_parent"
                     android:paddingLeft="14dip"
                     android:paddingRight="14dip"
                     android:layout_centerVertical="true"
@@ -81,15 +81,15 @@
             android:layout_alignParentLeft="true"
             android:layout_centerVertical="true"
             android:layout_marginRight="8dip"
-            style="@*android:style/Widget.QuickContactBadge.WindowMedium" />
+            style="?android:attr/quickContactBadgeStyleWindowMedium"
         />
 
         <ImageView android:id="@+id/noQuickContactPhoto"
             android:layout_alignParentLeft="true"
             android:layout_centerVertical="true"
             android:layout_marginRight="8dip"
-            style="@*android:style/Widget.QuickContactBadge.WindowMedium"
-            android:background="@null" />
+            android:background="@null"
+            style="?android:attr/quickContactBadgeStyleWindowMedium"
         />
 
         <TextView android:id="@+id/label"
@@ -138,7 +138,7 @@
     </RelativeLayout>
 
     <View android:id="@+id/list_divider"
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:background="@*android:drawable/divider_horizontal_dark_opaque"
     />
diff --git a/res/layout-finger/contacts_list_show_all_item.xml b/res/layout-finger/contacts_list_show_all_item.xml
index 5937b9f..4111a8f 100644
--- a/res/layout-finger/contacts_list_show_all_item.xml
+++ b/res/layout-finger/contacts_list_show_all_item.xml
@@ -18,12 +18,12 @@
 -->
 
 <LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="vertical"
 >
     <TextView
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="?android:attr/listPreferredItemHeight"
         android:gravity="center_vertical|left"
         android:textAppearance="?android:attr/textAppearanceLarge"
@@ -32,7 +32,7 @@
     />
 
     <View android:id="@+id/list_divider"
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:background="@*android:drawable/divider_horizontal_dark_opaque"
     />
diff --git a/res/layout-finger/create_new_contact.xml b/res/layout-finger/create_new_contact.xml
index 776c482..2c36017 100644
--- a/res/layout-finger/create_new_contact.xml
+++ b/res/layout-finger/create_new_contact.xml
@@ -15,7 +15,7 @@
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:minHeight="?android:attr/listPreferredItemHeight"
         android:orientation="horizontal"
diff --git a/res/layout-finger/dialer_activity.xml b/res/layout-finger/dialer_activity.xml
index 29189c7..14a6b39 100644
--- a/res/layout-finger/dialer_activity.xml
+++ b/res/layout-finger/dialer_activity.xml
@@ -16,21 +16,21 @@
 
 <TabHost xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@android:id/tabhost"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent">
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
 
     <LinearLayout
         android:orientation="vertical"
-        android:layout_width="fill_parent"
-        android:layout_height="fill_parent">
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
 
         <TabWidget android:id="@android:id/tabs"
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
         />
 
         <FrameLayout android:id="@android:id/tabcontent"
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
             android:layout_height="0dip"
             android:layout_weight="1"
         />
diff --git a/res/layout-finger/dialpad.xml b/res/layout-finger/dialpad.xml
index 1eb653d..2c14877 100644
--- a/res/layout-finger/dialpad.xml
+++ b/res/layout-finger/dialpad.xml
@@ -17,7 +17,7 @@
 <!-- Dialpad in the Contact app.
  -->
 
-<com.android.contacts.ButtonGridLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.phone.ButtonGridLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/dialpad"
     android:paddingLeft="7dp"
     android:paddingRight="7dp"
@@ -135,4 +135,4 @@
             android:soundEffectsEnabled="false"
             android:contentDescription="@string/description_image_button_pound"
         />
-</com.android.contacts.ButtonGridLayout>
+</com.android.phone.ButtonGridLayout>
diff --git a/res/layout-finger/dialpad_chooser_list_item.xml b/res/layout-finger/dialpad_chooser_list_item.xml
index c836f5d..853ca47 100644
--- a/res/layout-finger/dialpad_chooser_list_item.xml
+++ b/res/layout-finger/dialpad_chooser_list_item.xml
@@ -17,8 +17,8 @@
 <!-- Layout of a single item in the Dialer's "Dialpad chooser" UI. -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:orientation="horizontal"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent">
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
 
     <ImageView android:id="@+id/icon"
         android:layout_width="64dp"
diff --git a/res/layout-finger/display_child.xml b/res/layout-finger/display_child.xml
index ed4856a..65ef405 100644
--- a/res/layout-finger/display_child.xml
+++ b/res/layout-finger/display_child.xml
@@ -15,7 +15,7 @@
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:minHeight="?android:attr/listPreferredItemHeight"
     android:gravity="center_vertical"
diff --git a/res/layout-finger/display_group.xml b/res/layout-finger/display_group.xml
index 7d36450..b19ae2d 100644
--- a/res/layout-finger/display_group.xml
+++ b/res/layout-finger/display_group.xml
@@ -15,7 +15,7 @@
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:minHeight="?android:attr/listPreferredItemHeight"
     android:gravity="center_vertical"
diff --git a/res/layout-finger/display_header.xml b/res/layout-finger/display_options_phones_only.xml
similarity index 96%
rename from res/layout-finger/display_header.xml
rename to res/layout-finger/display_options_phones_only.xml
index 65f82aa..35965da 100644
--- a/res/layout-finger/display_header.xml
+++ b/res/layout-finger/display_options_phones_only.xml
@@ -15,7 +15,7 @@
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:minHeight="?android:attr/listPreferredItemHeight"
     android:gravity="center_vertical"
@@ -25,7 +25,7 @@
     <RelativeLayout
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginLeft="20dip"
+        android:layout_marginLeft="14dip"
         android:layout_marginRight="6dip"
         android:layout_marginTop="6dip"
         android:layout_marginBottom="6dip"
diff --git a/res/layout-finger/edit_contact.xml b/res/layout-finger/edit_contact.xml
index e91f7e9..d343318 100644
--- a/res/layout-finger/edit_contact.xml
+++ b/res/layout-finger/edit_contact.xml
@@ -14,25 +14,25 @@
      limitations under the License.
 -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
     android:orientation="vertical"
     android:fillViewport="true">
 
     <ScrollView
-        android:layout_width="fill_parent"
-        android:layout_height="fill_parent"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
         android:layout_weight="1"
         android:orientation="vertical">
 
         <LinearLayout
-            android:layout_width="fill_parent"
-            android:layout_height="fill_parent"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
             android:orientation="vertical"
         >
 
             <LinearLayout android:id="@+id/banner"
-                android:layout_width="fill_parent"
+                android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:orientation="horizontal"
                 android:padding="0dip"
@@ -49,13 +49,14 @@
                     android:layout_marginRight="2dip"
                 >
                     <ImageView android:id="@+id/photoImage"
-                        android:layout_width="fill_parent"
-                        android:layout_height="fill_parent"
+                        android:layout_width="match_parent"
+                        android:layout_height="match_parent"
                         android:clickable="true"
                         android:focusable="true"
                         android:src="@drawable/ic_menu_add_picture"
                         android:scaleType="center"
                         android:background="@drawable/btn_contact_picture"
+                        android:contentDescription="@string/description_contact_photo"
                     />
                 </FrameLayout>
 
@@ -80,7 +81,7 @@
             <include layout="@layout/edit_phonetic_name"/>
 
             <LinearLayout
-                android:layout_width="fill_parent"
+                android:layout_width="match_parent"
                 android:layout_height="0dip"
                 android:layout_weight="1"
                 android:orientation="vertical"
@@ -88,7 +89,7 @@
 
                 <!-- The edit items -->
                 <LinearLayout android:id="@+id/list"
-                    android:layout_width="fill_parent"
+                    android:layout_width="match_parent"
                     android:layout_height="wrap_content"
                     android:orientation="vertical"
                 />
@@ -98,7 +99,7 @@
     </ScrollView>
 
     <LinearLayout
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:orientation="horizontal"
         style="@android:style/ButtonBar"
diff --git a/res/layout-finger/edit_contact_entry.xml b/res/layout-finger/edit_contact_entry.xml
index d17fdf7..cf9edda 100644
--- a/res/layout-finger/edit_contact_entry.xml
+++ b/res/layout-finger/edit_contact_entry.xml
@@ -15,7 +15,7 @@
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:layout_marginLeft="4dip"
     android:layout_marginBottom="3dip"
diff --git a/res/layout-finger/edit_contact_entry_group.xml b/res/layout-finger/edit_contact_entry_group.xml
index b233ca8..eeab395 100644
--- a/res/layout-finger/edit_contact_entry_group.xml
+++ b/res/layout-finger/edit_contact_entry_group.xml
@@ -16,7 +16,7 @@
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/entry_group"
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:paddingRight="?android:attr/scrollbarSize"
     android:minHeight="?android:attr/listPreferredItemHeight"
diff --git a/res/layout-finger/edit_contact_entry_org.xml b/res/layout-finger/edit_contact_entry_org.xml
index 9daf3d1..6b16998 100644
--- a/res/layout-finger/edit_contact_entry_org.xml
+++ b/res/layout-finger/edit_contact_entry_org.xml
@@ -15,7 +15,7 @@
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="horizontal"
     android:baselineAligned="false"
@@ -39,13 +39,13 @@
         >
 
         <EditText android:id="@+id/data"
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_marginLeft="4dip"
             />
 
         <EditText android:id="@+id/data2"
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_marginLeft="4dip"
             />
diff --git a/res/layout-finger/edit_contact_entry_static_label.xml b/res/layout-finger/edit_contact_entry_static_label.xml
index 5e40631..c0e89bb 100644
--- a/res/layout-finger/edit_contact_entry_static_label.xml
+++ b/res/layout-finger/edit_contact_entry_static_label.xml
@@ -15,7 +15,7 @@
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:layout_marginLeft="4dip"
     android:layout_marginBottom="3dip"
diff --git a/res/layout-finger/edit_contact_entry_voicemail.xml b/res/layout-finger/edit_contact_entry_voicemail.xml
index 7b66500..73c65d9 100644
--- a/res/layout-finger/edit_contact_entry_voicemail.xml
+++ b/res/layout-finger/edit_contact_entry_voicemail.xml
@@ -16,7 +16,7 @@
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/checkable"
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:paddingRight="?android:attr/scrollbarSize"
     android:minHeight="?android:attr/listPreferredItemHeight"
@@ -62,7 +62,7 @@
 
     <CheckBox android:id="@+id/checkbox"
         android:layout_width="wrap_content"
-        android:layout_height="fill_parent"
+        android:layout_height="match_parent"
         android:focusable="false"
         android:clickable="false"
         />
diff --git a/res/layout-finger/edit_divider.xml b/res/layout-finger/edit_divider.xml
index 1dbb563..eb2a49a 100644
--- a/res/layout-finger/edit_divider.xml
+++ b/res/layout-finger/edit_divider.xml
@@ -15,7 +15,7 @@
 -->
 
 <View xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:background="?android:attr/listDivider"
     />
diff --git a/res/layout-finger/edit_phonetic_name.xml b/res/layout-finger/edit_phonetic_name.xml
index afbbb1d..418f8fa 100644
--- a/res/layout-finger/edit_phonetic_name.xml
+++ b/res/layout-finger/edit_phonetic_name.xml
@@ -32,7 +32,7 @@
      by temporarily removing the visibility="gone" attribute below.
 -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="?android:attr/listPreferredItemHeight"
     android:orientation="horizontal"
     android:padding="0dip"
@@ -42,7 +42,7 @@
     >
     <TextView
         android:layout_width="wrap_content"
-        android:layout_height="fill_parent"
+        android:layout_height="match_parent"
         android:paddingLeft="4dip"
         android:gravity="left|center_vertical"
         android:text="@string/label_phonetic_name"
diff --git a/res/layout-finger/edit_separator.xml b/res/layout-finger/edit_separator.xml
index 1fe799d..62e1c64 100644
--- a/res/layout-finger/edit_separator.xml
+++ b/res/layout-finger/edit_separator.xml
@@ -17,7 +17,7 @@
 <!-- Layout used for edit separators. -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:layout_marginBottom="3dip"
     android:orientation="vertical"
@@ -25,7 +25,7 @@
 
     <LinearLayout
         android:id="@+id/separator"
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginLeft="14dip"
         android:layout_marginTop="3dip"
diff --git a/res/layout-finger/edit_separator_alone.xml b/res/layout-finger/edit_separator_alone.xml
index 7d4aa88..dbe293e 100644
--- a/res/layout-finger/edit_separator_alone.xml
+++ b/res/layout-finger/edit_separator_alone.xml
@@ -18,7 +18,7 @@
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/separator"
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:paddingLeft="14dip"
     android:paddingRight="?android:attr/scrollbarSize"
diff --git a/res/layout-finger/horizontal_divider.xml b/res/layout-finger/horizontal_divider.xml
new file mode 100644
index 0000000..202e663
--- /dev/null
+++ b/res/layout-finger/horizontal_divider.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<View xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@*android:drawable/divider_horizontal_dark_opaque"
+/>
diff --git a/res/layout-finger/list_item_text_icons.xml b/res/layout-finger/list_item_text_icons.xml
index 8d4b7a8..69e064e 100644
--- a/res/layout-finger/list_item_text_icons.xml
+++ b/res/layout-finger/list_item_text_icons.xml
@@ -18,7 +18,7 @@
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:minHeight="?android:attr/listPreferredItemHeight"
     android:orientation="horizontal"
@@ -95,7 +95,7 @@
     
     <View android:id="@+id/divider"
         android:layout_width="1px"
-        android:layout_height="fill_parent"
+        android:layout_height="match_parent"
         android:layout_marginTop="5dip"
         android:layout_marginBottom="5dip"
         android:background="@drawable/divider_vertical_dark"
@@ -103,7 +103,7 @@
 
     <ImageView android:id="@+id/secondary_action_button"
         android:layout_width="wrap_content"
-        android:layout_height="fill_parent"
+        android:layout_height="match_parent"
         android:layout_centerVertical="true"
         android:paddingLeft="14dip"
         android:paddingRight="14dip"
diff --git a/res/layout-finger/list_section.xml b/res/layout-finger/list_section.xml
index 06a9ffe..f7ba693 100644
--- a/res/layout-finger/list_section.xml
+++ b/res/layout-finger/list_section.xml
@@ -17,7 +17,7 @@
 <!-- Layout used for list section separators. -->
 <RelativeLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="25dip"
     android:background="@*android:drawable/dark_header"
     >
diff --git a/res/layout-finger/list_separator.xml b/res/layout-finger/list_separator.xml
index 2ab4859..1257935 100644
--- a/res/layout-finger/list_separator.xml
+++ b/res/layout-finger/list_separator.xml
@@ -4,9 +4,9 @@
      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.
@@ -18,5 +18,5 @@
 <TextView xmlns:android="http://schemas.android.com/apk/res/android"
     style="?android:attr/listSeparatorTextViewStyle"
     android:textColor="@*android:color/dim_foreground_dark"
-    android:gravity="center_horizontal"
+    android:gravity="left|center_vertical"
 />
diff --git a/res/layout-finger/edit_contact_entry_ringtone.xml b/res/layout-finger/preference_with_more_button.xml
similarity index 96%
rename from res/layout-finger/edit_contact_entry_ringtone.xml
rename to res/layout-finger/preference_with_more_button.xml
index f2a171f..8599cee 100644
--- a/res/layout-finger/edit_contact_entry_ringtone.xml
+++ b/res/layout-finger/preference_with_more_button.xml
@@ -15,8 +15,8 @@
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/entry_ringtone"
-    android:layout_width="fill_parent"
+    android:id="@+id/preference"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:paddingRight="?android:attr/scrollbarSize"
     android:minHeight="?android:attr/listPreferredItemHeight"
diff --git a/res/layout-finger/quickcontact.xml b/res/layout-finger/quickcontact.xml
index 13b5c20..a895a54 100644
--- a/res/layout-finger/quickcontact.xml
+++ b/res/layout-finger/quickcontact.xml
@@ -16,7 +16,7 @@
 
 <RelativeLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:paddingLeft="@dimen/quickcontact_shadow_horiz"
     android:paddingRight="@dimen/quickcontact_shadow_horiz"
@@ -24,28 +24,28 @@
 
     <FrameLayout
         android:id="@+id/header"
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginTop="10dip">
 
         <ViewStub
             android:id="@+id/header_small"
             android:inflatedId="@+id/header_small"
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout="@layout/quickcontact_header_small" />
 
         <ViewStub
             android:id="@+id/header_medium"
             android:inflatedId="@+id/header_medium"
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout="@layout/quickcontact_header_med" />
 
         <ViewStub
             android:id="@+id/header_large"
             android:inflatedId="@+id/header_large"
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout="@layout/quickcontact_header_large" />
 
@@ -53,7 +53,7 @@
 
     <HorizontalScrollView
         android:id="@+id/scroll"
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_below="@id/header"
         android:fadingEdgeLength="0dip"
@@ -84,14 +84,14 @@
 
     <FrameLayout
         android:id="@+id/footer"
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_below="@id/scroll"
         android:background="@drawable/quickcontact_bottom_frame" />
 
     <LinearLayout
         android:id="@+id/footer_disambig"
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_below="@id/scroll"
         android:background="@drawable/quickcontact_disambig_bottom_bg"
@@ -100,7 +100,7 @@
 
         <ListView
             android:id="@android:id/list"
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
             android:layout_height="0dip"
             android:layout_weight="1"
             android:background="@color/quickcontact_disambig"
@@ -109,7 +109,7 @@
 
         <CheckBox
             android:id="@android:id/checkbox"
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_marginLeft="19dip"
             android:layout_marginRight="19dip"
diff --git a/res/layout-finger/quickcontact_header_large.xml b/res/layout-finger/quickcontact_header_large.xml
index 2271ff1..d9d4875 100644
--- a/res/layout-finger/quickcontact_header_large.xml
+++ b/res/layout-finger/quickcontact_header_large.xml
@@ -17,7 +17,7 @@
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/header_large"
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:minHeight="87dip"
     android:background="@drawable/quickcontact_top_frame"
@@ -41,7 +41,7 @@
 
         <TextView
             android:id="@+id/name"
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:singleLine="true"
             android:ellipsize="end"
@@ -51,7 +51,7 @@
 
         <TextView
             android:id="@+id/status"
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:singleLine="true"
             android:ellipsize="end"
@@ -61,7 +61,7 @@
 
         <TextView
             android:id="@+id/timestamp"
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:singleLine="true"
             android:ellipsize="end"
diff --git a/res/layout-finger/quickcontact_header_med.xml b/res/layout-finger/quickcontact_header_med.xml
index c9ef2be..bed886d 100644
--- a/res/layout-finger/quickcontact_header_med.xml
+++ b/res/layout-finger/quickcontact_header_med.xml
@@ -17,7 +17,7 @@
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/header_medium"
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:minHeight="51dip"
     android:background="@drawable/quickcontact_top_frame"
@@ -34,7 +34,7 @@
 
         <TextView
             android:id="@+id/status"
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:singleLine="true"
             android:ellipsize="end"
@@ -43,7 +43,7 @@
 
         <TextView
             android:id="@+id/timestamp"
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:singleLine="true"
             android:ellipsize="end"
diff --git a/res/layout-finger/quickcontact_header_small.xml b/res/layout-finger/quickcontact_header_small.xml
index 3711dcc..9dfe0a1 100644
--- a/res/layout-finger/quickcontact_header_small.xml
+++ b/res/layout-finger/quickcontact_header_small.xml
@@ -17,7 +17,7 @@
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/header_small"
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:background="@drawable/quickcontact_top_frame"
     android:orientation="horizontal" />
diff --git a/res/layout-finger/quickcontact_resolve_item.xml b/res/layout-finger/quickcontact_resolve_item.xml
index 9156a59..db683a0 100755
--- a/res/layout-finger/quickcontact_resolve_item.xml
+++ b/res/layout-finger/quickcontact_resolve_item.xml
@@ -15,7 +15,7 @@
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="vertical"
     android:paddingLeft="30dip"
diff --git a/res/layout-finger/recent_calls.xml b/res/layout-finger/recent_calls.xml
index 986d913..f054b70 100644
--- a/res/layout-finger/recent_calls.xml
+++ b/res/layout-finger/recent_calls.xml
@@ -15,18 +15,18 @@
 -->
 
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
 >
     <ListView android:id="@android:id/list"
-        android:layout_width="fill_parent" 
-        android:layout_height="fill_parent"
+        android:layout_width="match_parent" 
+        android:layout_height="match_parent"
         android:scrollbarStyle="outsideOverlay"
     />
     
     <TextView android:id="@android:id/empty"
-        android:layout_width="fill_parent"
-        android:layout_height="fill_parent"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
         android:text="@string/recentCalls_empty"
         android:gravity="center"
         android:textAppearance="?android:attr/textAppearanceLarge"
diff --git a/res/layout-finger/recent_calls_list_child_item.xml b/res/layout-finger/recent_calls_list_child_item.xml
new file mode 100644
index 0000000..14eb24d
--- /dev/null
+++ b/res/layout-finger/recent_calls_list_child_item.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="?android:attr/listPreferredItemHeight"
+    android:paddingLeft="7dip"
+    android:background="@drawable/list_item_background_secondary"
+>
+
+    <com.android.contacts.ui.widget.DontPressWithParentImageView android:id="@+id/call_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:paddingLeft="14dip"
+        android:paddingRight="14dip"
+        android:layout_alignParentRight="true"
+
+        android:gravity="center_vertical"
+        android:src="@android:drawable/sym_action_call"
+        android:background="@drawable/call_background_secondary"
+    />
+
+    <include layout="@layout/recent_calls_list_item_layout"/>
+
+</RelativeLayout>
diff --git a/res/layout-finger/recent_calls_list_group_item.xml b/res/layout-finger/recent_calls_list_group_item.xml
new file mode 100644
index 0000000..acaecaa
--- /dev/null
+++ b/res/layout-finger/recent_calls_list_group_item.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="?android:attr/listPreferredItemHeight"
+    android:paddingLeft="7dip"
+>
+
+    <com.android.contacts.ui.widget.DontPressWithParentImageView android:id="@+id/call_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:paddingLeft="14dip"
+        android:paddingRight="14dip"
+        android:layout_alignParentRight="true"
+
+        android:gravity="center_vertical"
+        android:src="@android:drawable/sym_action_call"
+        android:background="@drawable/call_background"
+    />
+
+    <View android:id="@+id/divider"
+        android:layout_width="1px"
+        android:layout_height="match_parent"
+        android:layout_marginTop="5dip"
+        android:layout_marginBottom="5dip"
+        android:layout_toLeftOf="@id/call_icon"
+        android:layout_marginLeft="11dip"
+        android:background="@drawable/divider_vertical_dark"
+    />
+
+    <TextView android:id="@+id/date"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_toLeftOf="@id/divider"
+        android:layout_alignParentBottom="true"
+        android:layout_marginBottom="8dip"
+
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:singleLine="true"
+    />
+
+    <TextView android:id="@+id/label"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentBottom="true"
+        android:layout_marginLeft="36dip"
+        android:layout_alignBaseline="@id/date"
+
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:textStyle="bold"
+    />
+
+    <TextView android:id="@+id/number"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="5dip"
+        android:layout_toRightOf="@id/label"
+        android:layout_toLeftOf="@id/date"
+        android:layout_alignBaseline="@id/label"
+        android:layout_alignWithParentIfMissing="true"
+
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+    />
+
+    <TextView android:id="@+id/groupSize"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:layout_toLeftOf="@id/divider"
+        android:layout_above="@id/label"
+        android:layout_alignWithParentIfMissing="true"
+
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:singleLine="true"
+        android:gravity="center_vertical"
+    />
+
+    <TextView android:id="@+id/line1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentTop="true"
+        android:layout_toLeftOf="@+id/groupSize"
+        android:layout_above="@id/date"
+        android:layout_alignWithParentIfMissing="true"
+        android:layout_marginLeft="36dip"
+        android:layout_marginBottom="-10dip"
+
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        android:gravity="center_vertical"
+    />
+
+    <ImageView
+        android:id="@+id/groupIndicator"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_centerVertical="true"
+        android:src="@*android:drawable/expander_ic_minimized"
+        android:gravity="center_vertical"
+    />
+</RelativeLayout>
diff --git a/res/layout-finger/recent_calls_list_item.xml b/res/layout-finger/recent_calls_list_item.xml
index 5439ff3..8efa23c 100644
--- a/res/layout-finger/recent_calls_list_item.xml
+++ b/res/layout-finger/recent_calls_list_item.xml
@@ -15,14 +15,14 @@
 -->
 
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="?android:attr/listPreferredItemHeight"
     android:paddingLeft="7dip"
 >
 
     <com.android.contacts.ui.widget.DontPressWithParentImageView android:id="@+id/call_icon"
         android:layout_width="wrap_content"
-        android:layout_height="fill_parent"
+        android:layout_height="match_parent"
         android:paddingLeft="14dip"
         android:paddingRight="14dip"
         android:layout_alignParentRight="true"
@@ -32,75 +32,6 @@
         android:background="@drawable/call_background"
     />
 
-    <View android:id="@+id/divider"
-        android:layout_width="1px"
-        android:layout_height="fill_parent"
-        android:layout_marginTop="5dip"
-        android:layout_marginBottom="5dip"
-        android:layout_toLeftOf="@id/call_icon"
-        android:layout_marginLeft="11dip"
-        android:background="@drawable/divider_vertical_dark"
-    />
-
-    <ImageView android:id="@+id/call_type_icon"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_alignParentTop="true"
-        android:layout_toLeftOf="@id/divider"
-    />
-
-    <TextView android:id="@+id/date"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_toLeftOf="@id/divider"
-        android:layout_alignParentBottom="true"
-        android:layout_marginBottom="8dip"
-
-        android:textAppearance="?android:attr/textAppearanceSmall"
-        android:singleLine="true"
-    />
-
-    <TextView android:id="@+id/label"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_alignParentLeft="true"
-        android:layout_alignParentBottom="true"
-        android:layout_marginBottom="8dip"
-        android:layout_marginTop="-10dip"
-
-        android:singleLine="true"
-        android:ellipsize="marquee"
-        android:textAppearance="?android:attr/textAppearanceSmall"
-        android:textStyle="bold"
-    />
-
-    <TextView android:id="@+id/number"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginLeft="5dip"
-        android:layout_toRightOf="@id/label"
-        android:layout_toLeftOf="@id/date"
-        android:layout_alignBaseline="@id/label"
-        android:layout_alignWithParentIfMissing="true"
-
-        android:singleLine="true"
-        android:ellipsize="marquee"
-        android:textAppearance="?android:attr/textAppearanceSmall"
-    />
-
-    <TextView android:id="@+id/line1"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_alignParentLeft="true"
-        android:layout_alignParentTop="true"
-        android:layout_toLeftOf="@+id/call_type_icon"
-        android:layout_above="@id/label"
-        android:layout_alignWithParentIfMissing="true"
-
-        android:textAppearance="?android:attr/textAppearanceLarge"
-        android:singleLine="true"
-        android:ellipsize="marquee"
-        android:gravity="center_vertical"
-    />
+    <include layout="@layout/recent_calls_list_item_layout"/>
 
 </RelativeLayout>
diff --git a/res/layout-finger/recent_calls_list_item_layout.xml b/res/layout-finger/recent_calls_list_item_layout.xml
new file mode 100644
index 0000000..29ec59e
--- /dev/null
+++ b/res/layout-finger/recent_calls_list_item_layout.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <View android:id="@+id/divider"
+        android:layout_width="1px"
+        android:layout_height="match_parent"
+        android:layout_marginTop="5dip"
+        android:layout_marginBottom="5dip"
+        android:layout_toLeftOf="@id/call_icon"
+        android:layout_marginLeft="11dip"
+        android:background="@drawable/divider_vertical_dark"
+    />
+
+    <ImageView android:id="@+id/call_type_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:layout_alignParentLeft="true"
+        android:layout_marginLeft="4dip"
+    />
+
+    <TextView android:id="@+id/date"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_toLeftOf="@id/divider"
+        android:layout_alignParentBottom="true"
+        android:layout_marginBottom="8dip"
+
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:singleLine="true"
+    />
+
+    <TextView android:id="@+id/label"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentBottom="true"
+        android:layout_marginLeft="36dip"
+        android:layout_alignBaseline="@id/date"
+
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:textStyle="bold"
+    />
+
+    <TextView android:id="@+id/number"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="5dip"
+        android:layout_toRightOf="@id/label"
+        android:layout_toLeftOf="@id/date"
+        android:layout_alignBaseline="@id/label"
+        android:layout_alignWithParentIfMissing="true"
+
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+    />
+
+    <TextView android:id="@+id/line1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentTop="true"
+        android:layout_toLeftOf="@id/divider"
+        android:layout_above="@id/date"
+        android:layout_alignWithParentIfMissing="true"
+        android:layout_marginLeft="36dip"
+        android:layout_marginBottom="-10dip"
+
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        android:gravity="center_vertical"
+    />
+</merge>
diff --git a/res/layout-finger/set_primary_checkbox.xml b/res/layout-finger/set_primary_checkbox.xml
index bba8cf9..00b0865 100644
--- a/res/layout-finger/set_primary_checkbox.xml
+++ b/res/layout-finger/set_primary_checkbox.xml
@@ -16,7 +16,7 @@
 
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:paddingLeft="14dip"
     android:paddingRight="15dip"
diff --git a/res/layout-finger/split_aggregate_list_item.xml b/res/layout-finger/split_aggregate_list_item.xml
index 5bd4270..5c72eda 100644
--- a/res/layout-finger/split_aggregate_list_item.xml
+++ b/res/layout-finger/split_aggregate_list_item.xml
@@ -16,7 +16,7 @@
 -->
 
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="?android:attr/listPreferredItemHeight"
     android:paddingLeft="12dip"
     android:paddingRight="12dip"
diff --git a/res/layout-finger/sync_settings.xml b/res/layout-finger/sync_settings.xml
index 50d0d47..01ed000 100644
--- a/res/layout-finger/sync_settings.xml
+++ b/res/layout-finger/sync_settings.xml
@@ -18,19 +18,19 @@
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
     android:orientation="vertical"
 >
 
     <ListView android:id="@android:id/list"
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="0dip"
         android:layout_weight="1"
     />
 
     <LinearLayout
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:background="@android:drawable/bottom_bar"
         android:orientation="horizontal"
diff --git a/res/layout-finger/tab_account_name.xml b/res/layout-finger/tab_account_name.xml
index dc99af1..8707d98 100644
--- a/res/layout-finger/tab_account_name.xml
+++ b/res/layout-finger/tab_account_name.xml
@@ -18,7 +18,7 @@
 <TextView  xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/account_name"
     android:background="@drawable/bg_infobar_new"
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="@dimen/account_name_height"
     android:layout_below="@+id/tab_scroll_view"
     android:textStyle="normal"
diff --git a/res/layout-finger/tab_layout.xml b/res/layout-finger/tab_layout.xml
index 9a793c3..42461f6 100644
--- a/res/layout-finger/tab_layout.xml
+++ b/res/layout-finger/tab_layout.xml
@@ -16,7 +16,7 @@
 
 <HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/tab_scroll_view"
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:layout_alignParentLeft="true"
     android:scrollbars="none"
@@ -24,7 +24,7 @@
     
     <com.android.contacts.TabStripView
         android:id="@android:id/tabs"
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
     />
 </HorizontalScrollView>
\ No newline at end of file
diff --git a/res/layout-finger/total_contacts.xml b/res/layout-finger/total_contacts.xml
index 12badb6..e1588d4 100644
--- a/res/layout-finger/total_contacts.xml
+++ b/res/layout-finger/total_contacts.xml
@@ -16,7 +16,7 @@
 
 <TextView xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/totalContactsText"
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="25dip"
     android:textColor="#ffbfbfbf"
     android:textSize="14sp"
diff --git a/res/layout-finger/twelve_key_dialer.xml b/res/layout-finger/twelve_key_dialer.xml
index bd2c7a8..d4c9d8f 100644
--- a/res/layout-finger/twelve_key_dialer.xml
+++ b/res/layout-finger/twelve_key_dialer.xml
@@ -16,8 +16,8 @@
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/top"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
     android:orientation="vertical"
 >
 
@@ -27,7 +27,7 @@
     -->
     <!-- TODO: Use a textAppearance to control the display of the number -->
     <EditText android:id="@+id/digits"
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="67dip"
         android:gravity="center"
         android:maxLines="1"
@@ -40,6 +40,7 @@
         android:editable="true"
         android:cursorVisible="false"
         android:layout_weight="0"
+        android:contentDescription="@string/description_digits_edittext"
     />
 
     <!-- Keypad section -->
@@ -53,7 +54,7 @@
          When this UI is visible, the other Dialer elements
          (the textfield/button and the dialpad) are hidden. -->
     <ListView android:id="@+id/dialpadChooser"
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="1dip"
         android:layout_weight="1"
     />
diff --git a/res/layout-finger/voicemail_dial_delete.xml b/res/layout-finger/voicemail_dial_delete.xml
index 910aaff..384981c 100644
--- a/res/layout-finger/voicemail_dial_delete.xml
+++ b/res/layout-finger/voicemail_dial_delete.xml
@@ -34,6 +34,7 @@
         android:layout_gravity="center_vertical"
         android:state_enabled="false"
         android:background="@drawable/btn_dial_voicemail"
+        android:contentDescription="@string/description_voicemail_button"
         android:src="@drawable/ic_dial_action_voice_mail" />
 
     <!-- Onscreen "Dial" button, used on all platforms by
@@ -45,6 +46,7 @@
         android:layout_gravity="center_vertical"
         android:state_enabled="false"
         android:background="@drawable/btn_dial_action"
+        android:contentDescription="@string/description_dial_button"
         android:src="@drawable/ic_dial_action_call" />
 
     <!-- Onscreen "Backspace/Delete" button
@@ -57,6 +59,7 @@
         android:layout_gravity="center_vertical"
         android:state_enabled="false"
         android:background="@drawable/btn_dial_delete"
+        android:contentDescription="@string/description_delete_button"
         android:src="@drawable/ic_dial_action_delete" />
 </LinearLayout>
 
diff --git a/res/layout-ja-finger/edit_phonetic_name.xml b/res/layout-ja-finger/edit_phonetic_name.xml
index 41a4059..012f85f 100644
--- a/res/layout-ja-finger/edit_phonetic_name.xml
+++ b/res/layout-ja-finger/edit_phonetic_name.xml
@@ -17,7 +17,7 @@
 <!-- "Phonetic name" field on the Edit contact screen, for
      Japanese-language locales (i.e. the "furigana" or "yomi" field.) -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="?android:attr/listPreferredItemHeight"
     android:orientation="horizontal"
     android:padding="0dip"
@@ -26,7 +26,7 @@
     >
     <TextView
         android:layout_width="wrap_content"
-        android:layout_height="fill_parent"
+        android:layout_height="match_parent"
         android:paddingLeft="4dip"
         android:gravity="left|center_vertical"
         android:text="@string/label_phonetic_name"
diff --git a/res/layout-land-finger/twelve_key_dialer.xml b/res/layout-land-finger/twelve_key_dialer.xml
index 92906ba..985d047 100644
--- a/res/layout-land-finger/twelve_key_dialer.xml
+++ b/res/layout-land-finger/twelve_key_dialer.xml
@@ -16,8 +16,8 @@
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/top"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
     android:orientation="vertical"
 >
 
@@ -27,7 +27,7 @@
     -->
     <!-- TODO: Use a textAppearance to control the display of the number -->
     <EditText android:id="@+id/digits"
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="66dip"
         android:layout_marginBottom="50dip"
         android:layout_marginTop="1dip"
@@ -49,7 +49,7 @@
          When this UI is visible, the other Dialer elements
          (the textfield and button) are hidden. -->
     <ListView android:id="@+id/dialpadChooser"
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:footerDividersEnabled="true"
     />
diff --git a/res/layout-long-finger/dialpad.xml b/res/layout-long-finger/dialpad.xml
index 9fea6d2..066392d 100644
--- a/res/layout-long-finger/dialpad.xml
+++ b/res/layout-long-finger/dialpad.xml
@@ -18,7 +18,7 @@
      Tall screen version with taller buttons.
  -->
 
-<com.android.contacts.ButtonGridLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.phone.ButtonGridLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/dialpad"
     android:paddingLeft="7dp"
     android:paddingRight="7dp"
@@ -136,4 +136,4 @@
             android:soundEffectsEnabled="false"
             android:contentDescription="@string/description_image_button_pound"
         />
-</com.android.contacts.ButtonGridLayout>
+</com.android.phone.ButtonGridLayout>
diff --git a/res/layout-long-finger/twelve_key_dialer.xml b/res/layout-long-finger/twelve_key_dialer.xml
index 07771cc..bd90df4 100644
--- a/res/layout-long-finger/twelve_key_dialer.xml
+++ b/res/layout-long-finger/twelve_key_dialer.xml
@@ -16,8 +16,8 @@
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/top"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
     android:orientation="vertical"
 >
 
@@ -27,7 +27,7 @@
     -->
     <!-- TODO: Use a textAppearance to control the display of the number -->
     <EditText android:id="@+id/digits"
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="74dip"
         android:gravity="center"
         android:maxLines="1"
@@ -53,7 +53,7 @@
          When this UI is visible, the other Dialer elements
          (the textfield/button and the dialpad) are hidden. -->
     <ListView android:id="@+id/dialpadChooser"
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="1dip"
         android:layout_weight="1"
     />
diff --git a/res/layout-long-finger/voicemail_dial_delete.xml b/res/layout-long-finger/voicemail_dial_delete.xml
index 8c42e65..23ff289 100644
--- a/res/layout-long-finger/voicemail_dial_delete.xml
+++ b/res/layout-long-finger/voicemail_dial_delete.xml
@@ -35,6 +35,7 @@
         android:layout_gravity="center_vertical"
         android:state_enabled="false"
         android:background="@drawable/btn_dial_voicemail"
+        android:contentDescription="@string/description_voicemail_button"
         android:src="@drawable/ic_dial_action_voice_mail" />
 
     <!-- Onscreen "Dial" button, used on all platforms by
@@ -46,6 +47,7 @@
         android:layout_gravity="center_vertical"
         android:state_enabled="false"
         android:background="@drawable/btn_dial_action"
+        android:contentDescription="@string/description_dial_button"
         android:src="@drawable/ic_dial_action_call" />
 
     <!-- Onscreen "Backspace/Delete" button
@@ -58,6 +60,7 @@
         android:layout_gravity="center_vertical"
         android:state_enabled="false"
         android:background="@drawable/btn_dial_delete"
+        android:contentDescription="@string/description_delete_button"
         android:src="@drawable/ic_dial_action_delete" />
 </LinearLayout>
 
diff --git a/res/layout-long-land-finger/twelve_key_dialer.xml b/res/layout-long-land-finger/twelve_key_dialer.xml
index 18c9a0f..1cf9690 100644
--- a/res/layout-long-land-finger/twelve_key_dialer.xml
+++ b/res/layout-long-land-finger/twelve_key_dialer.xml
@@ -16,8 +16,8 @@
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/top"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
     android:orientation="vertical"
 >
 
@@ -27,7 +27,7 @@
     -->
     <!-- TODO: Use a textAppearance to control the display of the number -->
     <EditText android:id="@+id/digits"
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="74dip"
         android:layout_marginBottom="30dip"
         android:layout_marginTop="1dip"
@@ -49,7 +49,7 @@
          When this UI is visible, the other Dialer elements
          (the textfield and button) are hidden. -->
     <ListView android:id="@+id/dialpadChooser"
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:footerDividersEnabled="true"
     />
diff --git a/res/layout/act_edit.xml b/res/layout/act_edit.xml
index e56ec20..2a71717 100644
--- a/res/layout/act_edit.xml
+++ b/res/layout/act_edit.xml
@@ -15,28 +15,28 @@
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
     android:orientation="vertical"
 >
 
     <ScrollView 
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="1px"
         android:layout_weight="1"
         android:fillViewport="true"
     >
 
         <LinearLayout android:id="@+id/editors"
-            android:layout_width="fill_parent"
-            android:layout_height="fill_parent"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
             android:orientation="vertical"
         />
 
     </ScrollView>
 
     <LinearLayout
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:orientation="horizontal"
         style="@android:style/ButtonBar"
diff --git a/res/layout/act_display_groups.xml b/res/layout/contacts_preferences.xml
similarity index 90%
rename from res/layout/act_display_groups.xml
rename to res/layout/contacts_preferences.xml
index 5ee93e7..c8a1bcf 100644
--- a/res/layout/act_display_groups.xml
+++ b/res/layout/contacts_preferences.xml
@@ -16,19 +16,19 @@
 
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
     android:orientation="vertical"
     android:fillViewport="true">
 
     <ExpandableListView
         android:id="@android:id/list"
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="0dip"
         android:layout_weight="1" />
 
     <LinearLayout
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:orientation="horizontal"
         style="@android:style/ButtonBar">
diff --git a/res/layout/item_contact_editor.xml b/res/layout/item_contact_editor.xml
index a7d5df5..f5e7bd3 100644
--- a/res/layout/item_contact_editor.xml
+++ b/res/layout/item_contact_editor.xml
@@ -17,7 +17,7 @@
 <!-- placed inside act_edit as tabcontent -->
 <com.android.contacts.ui.widget.ContactEditorView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="horizontal"
 >
@@ -26,7 +26,7 @@
     <ImageView
         android:id="@+id/color_bar"
         android:layout_width="4dip"
-        android:layout_height="fill_parent"
+        android:layout_height="match_parent"
         android:visibility="gone"
     />
 
@@ -41,11 +41,11 @@
         <!-- Account info header -->
         <RelativeLayout android:id="@+id/header"
             android:layout_height="64dip"
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
         >
 
             <ImageView android:id="@+id/header_color_bar"
-                android:layout_width="fill_parent"
+                android:layout_width="match_parent"
                 android:layout_height="4dip"
                 android:layout_marginBottom="5dip"
                 android:background="@color/edit_divider"
@@ -85,7 +85,7 @@
             />
 
             <View
-                android:layout_width="fill_parent"
+                android:layout_width="match_parent"
                 android:layout_height="1px"
                 android:layout_alignParentBottom="true"
 
@@ -96,7 +96,7 @@
 
         <FrameLayout
             android:id="@+id/stub_photo"
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:paddingLeft="12dip"
             android:paddingTop="10dip">
@@ -109,7 +109,7 @@
 
         <include
             android:id="@+id/edit_name"
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_below="@id/stub_photo"
             android:layout_marginTop="6dip"
@@ -117,7 +117,7 @@
             layout="@layout/item_generic_editor" />
 
         <TextView android:id="@+id/read_only_name"
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_marginTop="6dip"
             android:layout_marginBottom="6dip"
@@ -128,19 +128,19 @@
 
         <LinearLayout
             android:id="@+id/sect_general"
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:orientation="vertical"
         />
 
         <View android:id="@+id/head_secondary_divider"
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
             android:layout_height="1px"
             android:background="?android:attr/listDivider" />
 
         <TextView
             android:id="@+id/head_secondary"
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
 
             android:gravity="center_vertical"
@@ -157,13 +157,13 @@
 
         <LinearLayout
             android:id="@+id/sect_secondary"
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:orientation="vertical" />
 
         <TextView
             android:id="@+id/edit_read_only"
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_marginTop="10dip"
             android:layout_marginBottom="10dip"
diff --git a/res/layout/item_editor_field.xml b/res/layout/item_editor_field.xml
index 1e77068..9ad8689 100644
--- a/res/layout/item_editor_field.xml
+++ b/res/layout/item_editor_field.xml
@@ -16,5 +16,5 @@
 
 <EditText
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content" />
diff --git a/res/layout/item_generic_editor.xml b/res/layout/item_generic_editor.xml
index 01fa980..e672eba 100644
--- a/res/layout/item_generic_editor.xml
+++ b/res/layout/item_generic_editor.xml
@@ -56,4 +56,13 @@
         android:visibility="gone"
         style="@style/MoreButton" />
 
+    <ImageButton
+        android:id="@+id/edit_less"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentRight="true"
+        android:layout_alignBottom="@id/edit_fields"
+        android:visibility="gone"
+        style="@style/LessButton" />
+
 </com.android.contacts.ui.widget.GenericEditorView>
diff --git a/res/layout/item_kind_section.xml b/res/layout/item_kind_section.xml
index 73912aa..ebfeddf 100644
--- a/res/layout/item_kind_section.xml
+++ b/res/layout/item_kind_section.xml
@@ -18,19 +18,19 @@
 
 <com.android.contacts.ui.widget.KindSectionView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:minHeight="?android:attr/listPreferredItemHeight"
     android:orientation="vertical">
 
     <View
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:background="?android:attr/listDivider" />
 
     <LinearLayout
         android:id="@+id/kind_header"
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_weight="1"
         android:layout_marginLeft="14dip"
@@ -63,7 +63,7 @@
 
     <LinearLayout
         android:id="@+id/kind_editors"
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:paddingBottom="6dip"
         android:orientation="vertical" />
diff --git a/res/layout/item_photo_editor.xml b/res/layout/item_photo_editor.xml
index 7544439..b981131 100644
--- a/res/layout/item_photo_editor.xml
+++ b/res/layout/item_photo_editor.xml
@@ -24,4 +24,5 @@
     android:cropToPadding="true"
     android:scaleType="center"
     android:background="@drawable/btn_contact_picture"
+    android:contentDescription="@string/description_contact_photo"
     android:gravity="center" />
diff --git a/res/layout/item_read_only_contact_editor.xml b/res/layout/item_read_only_contact_editor.xml
index 0b1dc14..df442c8 100644
--- a/res/layout/item_read_only_contact_editor.xml
+++ b/res/layout/item_read_only_contact_editor.xml
@@ -17,7 +17,7 @@
 <!-- placed inside act_edit as tabcontent -->
 <com.android.contacts.ui.widget.ReadOnlyContactEditorView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="horizontal"
 >
@@ -26,7 +26,7 @@
     <ImageView
         android:id="@+id/color_bar"
         android:layout_width="4dip"
-        android:layout_height="fill_parent"
+        android:layout_height="match_parent"
         android:visibility="gone"
     />
 
@@ -41,11 +41,11 @@
         <!-- Account info header -->
         <RelativeLayout android:id="@+id/header"
             android:layout_height="64dip"
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
         >
 
             <ImageView android:id="@+id/header_color_bar"
-                android:layout_width="fill_parent"
+                android:layout_width="match_parent"
                 android:layout_height="4dip"
                 android:layout_marginBottom="5dip"
                 android:background="@color/edit_divider"
@@ -85,7 +85,7 @@
             />
 
             <View
-                android:layout_width="fill_parent"
+                android:layout_width="match_parent"
                 android:layout_height="1px"
                 android:layout_alignParentBottom="true"
 
@@ -96,7 +96,7 @@
 
         <FrameLayout
             android:id="@+id/stub_photo"
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:paddingLeft="12dip"
             android:paddingTop="10dip">
@@ -108,7 +108,7 @@
         </FrameLayout>
 
         <TextView android:id="@+id/read_only_name"
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_marginTop="6dip"
             android:layout_marginBottom="6dip"
@@ -118,14 +118,14 @@
         />
 
         <View
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
             android:layout_height="1px"
             android:background="?android:attr/listDivider"
         />
 
         <TextView
             android:id="@+id/read_only_warning"
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_marginTop="13dip"
             android:layout_marginBottom="13dip"
@@ -138,13 +138,13 @@
         />
 
         <View
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
             android:layout_height="1px"
             android:background="?android:attr/listDivider"
         />
 
         <LinearLayout android:id="@+id/sect_general"
-            android:layout_width="fill_parent"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:orientation="vertical"
         />
diff --git a/res/layout/item_read_only_field.xml b/res/layout/item_read_only_field.xml
index a0b20c4..9e0967e 100644
--- a/res/layout/item_read_only_field.xml
+++ b/res/layout/item_read_only_field.xml
@@ -15,7 +15,7 @@
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
+    android:layout_width="match_parent"
     android:layout_height="?android:attr/listPreferredItemHeight"
 
     android:orientation="vertical"
@@ -43,7 +43,7 @@
     />
 
     <View
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="1px"
 
         android:background="?android:attr/listDivider"
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index ca39832..446634e 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -264,7 +264,6 @@
     <string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"Analýza karty vCard se z neznámého důvodu nezdařila."</string>
     <string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"Analýza karty vCard se nezdařila. Přestože se zřejmě jedná o správný formát, aktuální implementace jej nepodporuje."</string>
     <string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"Na kartě SD nebyl nalezen žádný soubor vCard"</string>
-    <string name="fail_reason_no_vcard_entry" msgid="4733290752474073143">"Ve vašem výběru nebyla nalezena žádná platná položka karty vCard"</string>
     <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"Import jednoho nebo více souborů se nezdařil (%s)."</string>
     <string name="fail_reason_unknown" msgid="999034019513096768">"Neznámá chyba"</string>
     <string name="select_vcard_title" msgid="3968948173786172468">"Výběr souboru vCard"</string>
@@ -293,7 +292,8 @@
     <string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"Nelze spustit exportní program: <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
     <string name="fail_reason_error_occurred_during_export" msgid="2151165129433831202">"Při exportu došlo k chybě: <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
     <string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"Načtení informací z databáze se nezdařilo"</string>
-    <string name="composer_has_no_exportable_contact" msgid="2239503301380653777">"Nelze exportovat žádný kontakt. Pravděpodobně jste zvolili data, která nelze exportovat."</string>
+    <!-- no translation found for composer_has_no_exportable_contact (754734132189369094) -->
+    <skip />
     <string name="composer_not_initialized" msgid="8041534450748388843">"Editor karty vCard není správně inicializován"</string>
     <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"Soubor <xliff:g id="FILE_NAME">%s</xliff:g> nelze otevřít: <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
     <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g> z <xliff:g id="TOTAL_NUMBER">%s</xliff:g> kontaktů"</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 513129e..71b7135 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -264,7 +264,6 @@
     <string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"vCard kunne ikke parses pga. en uventet årsag"</string>
     <string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"vCard kunne ikke parses, selv om det ser ud til at være et gyldigt format, da den nuværende implementering ikke understøtter det"</string>
     <string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"Der blev ikke fundet nogen VCard-fil på SD-kortet"</string>
-    <string name="fail_reason_no_vcard_entry" msgid="4733290752474073143">"Der blev ikke fundet nogen gyldig vCard-post for dit valg"</string>
     <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"En eller flere filer blev ikke importeret (%s)."</string>
     <string name="fail_reason_unknown" msgid="999034019513096768">"Ukendt fejl"</string>
     <string name="select_vcard_title" msgid="3968948173786172468">"Vælg vCard-fil"</string>
@@ -293,7 +292,8 @@
     <string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"Eksportfunktionen kunne ikke startes: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
     <string name="fail_reason_error_occurred_during_export" msgid="2151165129433831202">"Der opstod en fejl under eksport: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
     <string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"Kunne ikke hente databaseoplysninger"</string>
-    <string name="composer_has_no_exportable_contact" msgid="2239503301380653777">"Der er ingen kontakter, som kan eksporteres. Du har måske valgt data, som ikke kan eksporteres"</string>
+    <!-- no translation found for composer_has_no_exportable_contact (754734132189369094) -->
+    <skip />
     <string name="composer_not_initialized" msgid="8041534450748388843">"vCard-oprettelse ikke korrekt initialiseret"</string>
     <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"\"<xliff:g id="FILE_NAME">%s</xliff:g>\" kunne ikke åbnes: <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
     <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g> af <xliff:g id="TOTAL_NUMBER">%s</xliff:g> kontakter"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 4e2a2b9..70dddd5 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -264,7 +264,6 @@
     <string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"Die vCard konnte aus einem unbekannten Grund nicht geparst werden."</string>
     <string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"Beim Parsen der vCard ist ein Fehler aufgetreten. Obwohl die vCard anscheinend ein gültiges Format aufweist, wird sie von der aktuellen Implementierung nicht unterstützt."</string>
     <string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"Keine VCard-Datei auf der SD-Karte gefunden"</string>
-    <string name="fail_reason_no_vcard_entry" msgid="4733290752474073143">"Es wurde kein gültiger vCard-Eintrag für Ihre Auswahl gefunden."</string>
     <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"Fehler beim Import einer oder mehrerer Dateien (%s)"</string>
     <string name="fail_reason_unknown" msgid="999034019513096768">"Unbekannter Fehler"</string>
     <string name="select_vcard_title" msgid="3968948173786172468">"vCard-Datei auswählen"</string>
@@ -293,7 +292,8 @@
     <string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"Exportprogramm konnte nicht initialisiert werden: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
     <string name="fail_reason_error_occurred_during_export" msgid="2151165129433831202">"Fehler beim Exportieren: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
     <string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"Fehler bei der Ermittlung von Datenbank-Informationen"</string>
-    <string name="composer_has_no_exportable_contact" msgid="2239503301380653777">"Es ist kein exportierbarer Kontakt vorhanden. Möglicherweise können Sie nicht exportierbare Daten auswählen."</string>
+    <!-- no translation found for composer_has_no_exportable_contact (754734132189369094) -->
+    <skip />
     <string name="composer_not_initialized" msgid="8041534450748388843">"Das Programm zum Erstellen der vCard wurde nicht richtig initialisiert."</string>
     <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"\"<xliff:g id="FILE_NAME">%s</xliff:g>\" konnte nicht geöffnet werden: <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
     <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g> von <xliff:g id="TOTAL_NUMBER">%s</xliff:g> Kontakten"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 099912d..8fb90d2 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -264,7 +264,6 @@
     <string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"Η ανάλυση της κάρτας vCard απέτυχε εξαιτίας μη αναμενόμενου συμβάντος"</string>
     <string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"Αν και το VCard φαίνεται να βρίσκεται σε έγκυρη μορφή, η ανάλυσή του απέτυχε, εφόσον η τρέχουσα εφαρμογή δεν το υποστηρίζει"</string>
     <string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"Δεν βρέθηκε αρχείο VCard στην κάρτα SD"</string>
-    <string name="fail_reason_no_vcard_entry" msgid="4733290752474073143">"Δεν βρέθηκε έγκυρη καταχώρηση VCard για την επιλογή σας"</string>
     <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"Η εισαγωγή ενός ή περισσοτέρων αρχείων απέτυχε (%s)."</string>
     <string name="fail_reason_unknown" msgid="999034019513096768">"Άγνωστο σφάλμα"</string>
     <string name="select_vcard_title" msgid="3968948173786172468">"Επιλογή αρχείου vCard"</string>
@@ -293,7 +292,8 @@
     <string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"Δεν ήταν δυνατή η εκκίνηση της εξαγωγής: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
     <string name="fail_reason_error_occurred_during_export" msgid="2151165129433831202">"Προέκυψε σφάλμα κατά την εξαγωγή: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
     <string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"Αποτυχία λήψης πληροφοριών βάσης δεδομένων"</string>
-    <string name="composer_has_no_exportable_contact" msgid="2239503301380653777">"Δεν υπάρχει επαφή με δυνατότητα εξαγωγής. Ενδέχεται να επιλέξατε μη εξαγώγιμα δεδομένα"</string>
+    <!-- no translation found for composer_has_no_exportable_contact (754734132189369094) -->
+    <skip />
     <string name="composer_not_initialized" msgid="8041534450748388843">"Δεν έχει γίνει σωστή εκκίνηση του προγράμματος σύνθεσης της vCard"</string>
     <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"Δεν ήταν δυνατό το άνοιγμα του \"<xliff:g id="FILE_NAME">%s</xliff:g>\": <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
     <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g> από <xliff:g id="TOTAL_NUMBER">%s</xliff:g> επαφές"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index bae6b90..fb2b408 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -264,7 +264,6 @@
     <string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"No se ha podido analizar vCard debido a un motivo inesperado"</string>
     <string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"No se ha podido analizar vCard aunque el formato parece válido, ya que la implementación actual no lo admite"</string>
     <string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"No se ha encontrado un archivo de vCard en la Tarjeta SD."</string>
-    <string name="fail_reason_no_vcard_entry" msgid="4733290752474073143">"No se ha encontrado ninguna entrada válida de vCard para tu selección"</string>
     <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"No se pudieron importar uno o más archivos (%s)."</string>
     <string name="fail_reason_unknown" msgid="999034019513096768">"Error desconocido"</string>
     <string name="select_vcard_title" msgid="3968948173786172468">"Seleccionar archivo de vCard"</string>
@@ -293,7 +292,8 @@
     <string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"No se ha podido inicializar el exportador: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
     <string name="fail_reason_error_occurred_during_export" msgid="2151165129433831202">"Se produjo un error durante la exportación: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
     <string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"No se ha podido obtener información de la base de datos"</string>
-    <string name="composer_has_no_exportable_contact" msgid="2239503301380653777">"No hay ningún contacto exportable. Puedes elegir datos no exportables"</string>
+    <!-- no translation found for composer_has_no_exportable_contact (754734132189369094) -->
+    <skip />
     <string name="composer_not_initialized" msgid="8041534450748388843">"El redactor de vCard no se ha inicializado correctamente"</string>
     <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"No se pudo abrir \"<xliff:g id="FILE_NAME">%s</xliff:g>\": <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
     <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g> de <xliff:g id="TOTAL_NUMBER">%s</xliff:g> contactos"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 3852e80..9f55484 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -264,7 +264,6 @@
     <string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"No se ha podido analizar el archivo de vCard debido a un error inesperado."</string>
     <string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"No se ha podido analizar el archivo de vCard (a pesar de que su formato parece ser válido) porque no es compatible con la implementación actual."</string>
     <string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"No se ha encontrado ningún archivo de vCard en la tarjeta SD."</string>
-    <string name="fail_reason_no_vcard_entry" msgid="4733290752474073143">"No se ha encontrado ninguna entrada de vCard válida para la selección realizada."</string>
     <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"No ha sido posible importar uno o varios archivos (%s)."</string>
     <string name="fail_reason_unknown" msgid="999034019513096768">"Error desconocido"</string>
     <string name="select_vcard_title" msgid="3968948173786172468">"Seleccionar archivo de vCard"</string>
@@ -293,7 +292,8 @@
     <string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"No se ha podido inicializar el exportador: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
     <string name="fail_reason_error_occurred_during_export" msgid="2151165129433831202">"Se ha producido un error durante la exportación: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
     <string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"Error al obtener información de la base de datos"</string>
-    <string name="composer_has_no_exportable_contact" msgid="2239503301380653777">"No hay contactos que exportar. Puedes seleccionar datos no exportables."</string>
+    <!-- no translation found for composer_has_no_exportable_contact (754734132189369094) -->
+    <skip />
     <string name="composer_not_initialized" msgid="8041534450748388843">"El redactor de vCard no se ha inicializado correctamente."</string>
     <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"No se ha podido abrir el archivo \"<xliff:g id="FILE_NAME">%s</xliff:g>\": <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
     <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g> de <xliff:g id="TOTAL_NUMBER">%s</xliff:g> contactos"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index bae53ed..1b007c0 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -264,7 +264,6 @@
     <string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"Échec de l\'analyse des données VCard pour une raison inattendue"</string>
     <string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"Échec de l\'analyse des données VCard. Le format semble valide, mais l\'implémentation actuelle ne le prend pas en charge."</string>
     <string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"Aucun fichier VCard n\'a été trouvé sur la carte SD."</string>
-    <string name="fail_reason_no_vcard_entry" msgid="4733290752474073143">"Aucune entrée VCard valide n\'a été trouvée pour votre sélection."</string>
     <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"Échec de l\'importation d\'un ou de plusieurs fichiers (%s)"</string>
     <string name="fail_reason_unknown" msgid="999034019513096768">"Erreur inconnue"</string>
     <string name="select_vcard_title" msgid="3968948173786172468">"Sélectionner un fichier VCard"</string>
@@ -293,7 +292,8 @@
     <string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"Échec de l\'initialisation du module d\'exportation : \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
     <string name="fail_reason_error_occurred_during_export" msgid="2151165129433831202">"Une erreur s\'est produite lors de l\'exportation : \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
     <string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"Impossible d\'obtenir les informations concernant la base de données"</string>
-    <string name="composer_has_no_exportable_contact" msgid="2239503301380653777">"Aucun contact exportable. Vous pouvez choisir des données non exportables."</string>
+    <!-- no translation found for composer_has_no_exportable_contact (754734132189369094) -->
+    <skip />
     <string name="composer_not_initialized" msgid="8041534450748388843">"Le système de composition vCard n\'est pas correctement initialisé."</string>
     <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"Impossible d\'ouvrir \"<xliff:g id="FILE_NAME">%s</xliff:g>\" : <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
     <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g> sur <xliff:g id="TOTAL_NUMBER">%s</xliff:g> contacts"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 6615020..69c41d7 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -264,7 +264,6 @@
     <string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"Impossibile analizzare la vCard per motivi imprevisti"</string>
     <string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"Impossibile analizzare la vCard nonostante sembri avere un formato valido perché l\'implementazione corrente non la supporta"</string>
     <string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"Nessun file vCard trovato sulla scheda SD"</string>
-    <string name="fail_reason_no_vcard_entry" msgid="4733290752474073143">"Nessuna voce vCard valida trovata per la tua selezione"</string>
     <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"Impossibile importare uno o più file (%s)."</string>
     <string name="fail_reason_unknown" msgid="999034019513096768">"Errore sconosciuto"</string>
     <string name="select_vcard_title" msgid="3968948173786172468">"Seleziona file vCard"</string>
@@ -293,7 +292,8 @@
     <string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"Impossibile inizializzare l\'utilità di esportazione: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
     <string name="fail_reason_error_occurred_during_export" msgid="2151165129433831202">"Si è verificato un errore durante l\'esportazione: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
     <string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"Recupero informazioni database non riuscito"</string>
-    <string name="composer_has_no_exportable_contact" msgid="2239503301380653777">"Nessun contatto esportabile. Potresti scegliere dati non esportabili"</string>
+    <!-- no translation found for composer_has_no_exportable_contact (754734132189369094) -->
+    <skip />
     <string name="composer_not_initialized" msgid="8041534450748388843">"Il compositore di vCard non è correttamente inizializzato"</string>
     <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"Impossibile aprire \"<xliff:g id="FILE_NAME">%s</xliff:g>\": <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
     <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g> contatti su <xliff:g id="TOTAL_NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index fdb7858..611f25f 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -264,7 +264,6 @@
     <string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"予期しない理由によりvCardの解析に失敗しました"</string>
     <string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"vCardの解析に失敗しました。正しいフォーマットですが、現在サポートされていません。"</string>
     <string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"SDカードでvCardファイルが見つかりません"</string>
-    <string name="fail_reason_no_vcard_entry" msgid="4733290752474073143">"選択した内容の有効なvCardエントリが見つかりません"</string>
     <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"1つ以上のファイルをインポートできませんでした(%s)。"</string>
     <string name="fail_reason_unknown" msgid="999034019513096768">"不明なエラー"</string>
     <string name="select_vcard_title" msgid="3968948173786172468">"vCardファイルの選択"</string>
@@ -293,7 +292,8 @@
     <string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"エクスポータを初期化できませんでした: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
     <string name="fail_reason_error_occurred_during_export" msgid="2151165129433831202">"エクスポート中にエラーが発生しました: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
     <string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"データベース情報の取得に失敗しました"</string>
-    <string name="composer_has_no_exportable_contact" msgid="2239503301380653777">"エクスポートできる連絡先がありません。エクスポートできないデータを選択している可能性があります。"</string>
+    <!-- no translation found for composer_has_no_exportable_contact (754734132189369094) -->
+    <skip />
     <string name="composer_not_initialized" msgid="8041534450748388843">"vCardコンポーザーが正しく初期化されていません"</string>
     <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"\"<xliff:g id="FILE_NAME">%s</xliff:g>\"を開けませんでした: <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
     <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g>/<xliff:g id="TOTAL_NUMBER">%s</xliff:g>件のファイル"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 58ac6ef..8104c5c 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -264,7 +264,6 @@
     <string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"예기치 못한 이유로 인해 vCard를 구문분석하지 못했습니다."</string>
     <string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"올바른 형식처럼 보이지만 현재 구현 환경에서는 VCard를 지원하지 않기 때문에 VCard 구문 분석에 실패했습니다."</string>
     <string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"SD 카드에 VCard 파일이 없습니다."</string>
-    <string name="fail_reason_no_vcard_entry" msgid="4733290752474073143">"선택한 항목에 유효한 VCard 항목이 없습니다."</string>
     <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"하나 이상의 파일을 가져오지 못했습니다(%s)."</string>
     <string name="fail_reason_unknown" msgid="999034019513096768">"알 수 없는 오류"</string>
     <string name="select_vcard_title" msgid="3968948173786172468">"vCard 파일 선택"</string>
@@ -293,7 +292,8 @@
     <string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"내보내기를 초기화하지 못했습니다. \'<xliff:g id="EXACT_REASON">%s</xliff:g>\'"</string>
     <string name="fail_reason_error_occurred_during_export" msgid="2151165129433831202">"내보내는 중에 오류가 발생했습니다. \'<xliff:g id="EXACT_REASON">%s</xliff:g>\'"</string>
     <string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"데이터베이스 정보를 가져오지 못했습니다."</string>
-    <string name="composer_has_no_exportable_contact" msgid="2239503301380653777">"내보낼 수 있는 연락처가 없습니다. 내보낼 수 없는 데이터를 선택할 수 있습니다."</string>
+    <!-- no translation found for composer_has_no_exportable_contact (754734132189369094) -->
+    <skip />
     <string name="composer_not_initialized" msgid="8041534450748388843">"vCard 작성기가 바르게 초기화되지 않았습니다."</string>
     <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"\'<xliff:g id="FILE_NAME">%s</xliff:g>\'을(를) 열 수 없습니다. <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
     <string name="exporting_contact_list_progress" msgid="560522409559101193">"연락처 <xliff:g id="CURRENT_NUMBER">%s</xliff:g>개(총 <xliff:g id="TOTAL_NUMBER">%s</xliff:g>개) 내보내는 중"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 2f14dd8..8aac478 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -264,7 +264,6 @@
     <string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"Kan ikke analysere VCard av uventet årsak"</string>
     <string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"Kan ikke analysere VCard selv om det ser ut til å være i riktig format, fordi den aktuelle implementeringen ikke støtter det."</string>
     <string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"Fant ingen VCard-filer på minnekortet"</string>
-    <string name="fail_reason_no_vcard_entry" msgid="4733290752474073143">"Fant ingen VCard-filer for valget ditt"</string>
     <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"Importeringen av én eller flere filer mislyktes (%s)"</string>
     <string name="fail_reason_unknown" msgid="999034019513096768">"Ukjent feil"</string>
     <string name="select_vcard_title" msgid="3968948173786172468">"Velg VCard-fil"</string>
@@ -293,7 +292,8 @@
     <string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"Kunne ikke starte eksporteringen: «<xliff:g id="EXACT_REASON">%s</xliff:g>»"</string>
     <string name="fail_reason_error_occurred_during_export" msgid="2151165129433831202">"Det oppstod feil ved eksport: «<xliff:g id="EXACT_REASON">%s</xliff:g>»"</string>
     <string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"Klarte ikke å hente databaseinformasjon"</string>
-    <string name="composer_has_no_exportable_contact" msgid="2239503301380653777">"Det finnes ingen eksporterbar kontakt."</string>
+    <!-- no translation found for composer_has_no_exportable_contact (754734132189369094) -->
+    <skip />
     <string name="composer_not_initialized" msgid="8041534450748388843">"Objektet for vCard-redigering er ikke riktig initialisert"</string>
     <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"Kunne ikke åpne «<xliff:g id="FILE_NAME">%s</xliff:g>»: <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
     <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g> av <xliff:g id="TOTAL_NUMBER">%s</xliff:g> kontakter"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 6c41e23..9b03bef 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -264,7 +264,6 @@
     <string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"Parseren van vCard is mislukt om onbekende reden"</string>
     <string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"Kan vCard niet parseren, ook al lijkt de indeling geldig. vCard wordt niet ondersteund door de huidige implementatie"</string>
     <string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"Geen vCard-bestand gevonden op SD-kaart"</string>
-    <string name="fail_reason_no_vcard_entry" msgid="4733290752474073143">"Geen geldig vCard-item gevonden voor uw selectie"</string>
     <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"Een of meer bestanden kunnen niet worden geïmporteerd (%s)"</string>
     <string name="fail_reason_unknown" msgid="999034019513096768">"Onbekende fout"</string>
     <string name="select_vcard_title" msgid="3968948173786172468">"vCard-bestand selecteren"</string>
@@ -293,7 +292,8 @@
     <string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"Kan het exportprogramma niet initialiseren: \'<xliff:g id="EXACT_REASON">%s</xliff:g>\'"</string>
     <string name="fail_reason_error_occurred_during_export" msgid="2151165129433831202">"Fout opgetreden tijdens export: \'<xliff:g id="EXACT_REASON">%s</xliff:g>\'"</string>
     <string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"Databasegegevens ophalen mislukt"</string>
-    <string name="composer_has_no_exportable_contact" msgid="2239503301380653777">"Er is geen contact dat geëxporteerd kan worden. Mogelijk heeft u niet-exporteerbare gegevens gekozen."</string>
+    <!-- no translation found for composer_has_no_exportable_contact (754734132189369094) -->
+    <skip />
     <string name="composer_not_initialized" msgid="8041534450748388843">"De vCard-editor is niet juist geïnitialiseerd"</string>
     <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"Kan \'<xliff:g id="FILE_NAME">%s</xliff:g>\' niet openen: <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
     <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g> van <xliff:g id="TOTAL_NUMBER">%s</xliff:g> contacten"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 0769bfc..2428b5a 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -264,7 +264,6 @@
     <string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"Nie można zanalizować pliku vCard z nieoczekiwanego powodu"</string>
     <string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"Nie można zanalizować pliku vCard, mimo że prawdopodobnie ma on prawidłowy format. Bieżąca wersja go nie obsługuje."</string>
     <string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"Na karcie SD nie znaleziono żadnego pliku vCard"</string>
-    <string name="fail_reason_no_vcard_entry" msgid="4733290752474073143">"W wybranym zakresie nie znaleziono prawidłowego wpisu vCard"</string>
     <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"Nie można zaimportować co najmniej jednego pliku (%s)."</string>
     <string name="fail_reason_unknown" msgid="999034019513096768">"Nieznany błąd"</string>
     <string name="select_vcard_title" msgid="3968948173786172468">"Wybierz plik vCard"</string>
@@ -293,7 +292,8 @@
     <string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"Nie można zainicjować programu eksportującego: „<xliff:g id="EXACT_REASON">%s</xliff:g>”"</string>
     <string name="fail_reason_error_occurred_during_export" msgid="2151165129433831202">"Wystąpił błąd podczas eksportowania: „<xliff:g id="EXACT_REASON">%s</xliff:g>”"</string>
     <string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"Nie można pobrać informacji o bazie danych"</string>
-    <string name="composer_has_no_exportable_contact" msgid="2239503301380653777">"Brak kontaktów, które można wyeksportować. Być może wybrano dane, których nie można eksportować."</string>
+    <!-- no translation found for composer_has_no_exportable_contact (754734132189369094) -->
+    <skip />
     <string name="composer_not_initialized" msgid="8041534450748388843">"Obiekt tworzenia danych vCard nie został poprawnie zainicjowany"</string>
     <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"Nie można otworzyć pliku „<xliff:g id="FILE_NAME">%s</xliff:g>”: <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
     <string name="exporting_contact_list_progress" msgid="560522409559101193">"Kontakt <xliff:g id="CURRENT_NUMBER">%s</xliff:g> z <xliff:g id="TOTAL_NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index ae35da1..8133070 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -264,7 +264,6 @@
     <string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"Falha na análise do vCard por motivo inesperado"</string>
     <string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"Falha na análise do vCard embora o formato pareça válido, uma vez que não é suportado pela implementação actual"</string>
     <string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"Não foi encontrado nenhum ficheiro vCard no cartão SD"</string>
-    <string name="fail_reason_no_vcard_entry" msgid="4733290752474073143">"Não foi encontrada nenhuma entrada vCard válida para a sua selecção"</string>
     <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"A importação de um ou vários ficheiros falhou (%s)."</string>
     <string name="fail_reason_unknown" msgid="999034019513096768">"Erro desconhecido"</string>
     <string name="select_vcard_title" msgid="3968948173786172468">"Seleccionar ficheiro vCard"</string>
@@ -293,7 +292,8 @@
     <string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"Não foi possível iniciar o exportador: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
     <string name="fail_reason_error_occurred_during_export" msgid="2151165129433831202">"Erro ocorrido durante a exportação: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
     <string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"Falha na obtenção de informações da base de dados"</string>
-    <string name="composer_has_no_exportable_contact" msgid="2239503301380653777">"Não existe nenhum contacto exportável. Pode ter escolhido dados não exportáveis"</string>
+    <!-- no translation found for composer_has_no_exportable_contact (754734132189369094) -->
+    <skip />
     <string name="composer_not_initialized" msgid="8041534450748388843">"O compositor vCard não foi inicializado correctamente"</string>
     <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"Não foi possível abrir \"<xliff:g id="FILE_NAME">%s</xliff:g>\": <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
     <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g> de <xliff:g id="TOTAL_NUMBER">%s</xliff:g> contactos"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 2ea7a02..b740918 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -264,7 +264,6 @@
     <string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"Falha ao analisar o vCard por razão inesperada"</string>
     <string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"Falha ao analisar o vCard. Embora o formato pareça ser válido, a implementação atual não oferece suporte a ele."</string>
     <string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"Nenhum arquivo de vCard encontrado no cartão SD"</string>
-    <string name="fail_reason_no_vcard_entry" msgid="4733290752474073143">"Nenhuma entrada válida no vCard foi encontrada para a sua seleção"</string>
     <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"Falha na importação de um ou mais arquivos (%s)."</string>
     <string name="fail_reason_unknown" msgid="999034019513096768">"Erro desconhecido"</string>
     <string name="select_vcard_title" msgid="3968948173786172468">"Selecionar arquivo do vCard"</string>
@@ -293,7 +292,8 @@
     <string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"Não foi possível iniciar o exportador: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
     <string name="fail_reason_error_occurred_during_export" msgid="2151165129433831202">"Ocorreu um erro durante a exportação: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
     <string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"Falha ao acessar informações do banco de dados"</string>
-    <string name="composer_has_no_exportable_contact" msgid="2239503301380653777">"Não há contatos exportáveis. Você precisa escolher dados não exportáveis."</string>
+    <!-- no translation found for composer_has_no_exportable_contact (754734132189369094) -->
+    <skip />
     <string name="composer_not_initialized" msgid="8041534450748388843">"O vCard não foi iniciado corretamente"</string>
     <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"Não foi possível abrir \"<xliff:g id="FILE_NAME">%s</xliff:g>\": <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
     <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g> de <xliff:g id="TOTAL_NUMBER">%s</xliff:g> contatos"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 82dc007..0d54145 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -264,7 +264,6 @@
     <string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"Не удалось обработать vCard по неизвестной причине."</string>
     <string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"Не удалось обработать vCard. Файл правильного формата, но не поддерживается в текущей реализации."</string>
     <string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"На SD-карте не найдены файлы VCard"</string>
-    <string name="fail_reason_no_vcard_entry" msgid="4733290752474073143">"Не найдено правильных записей vCard."</string>
     <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"Не удалось импортировать один или несколько файлов (%s)."</string>
     <string name="fail_reason_unknown" msgid="999034019513096768">"Неизвестная ошибка"</string>
     <string name="select_vcard_title" msgid="3968948173786172468">"Выберите файл vCard"</string>
@@ -293,7 +292,8 @@
     <string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"Не удается запустить функцию экспорта: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
     <string name="fail_reason_error_occurred_during_export" msgid="2151165129433831202">"Ошибка при экспорте: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
     <string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"Не удалось получить информацию из базы данных."</string>
-    <string name="composer_has_no_exportable_contact" msgid="2239503301380653777">"Нет контактов, пригодных для экспорта. Можно выбрать неэкспортируемые данные."</string>
+    <!-- no translation found for composer_has_no_exportable_contact (754734132189369094) -->
+    <skip />
     <string name="composer_not_initialized" msgid="8041534450748388843">"Редактор vCard запущен некорректно"</string>
     <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"Не удается открыть \"<xliff:g id="FILE_NAME">%s</xliff:g>\": <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
     <string name="exporting_contact_list_progress" msgid="560522409559101193">"Контакт <xliff:g id="CURRENT_NUMBER">%s</xliff:g> из <xliff:g id="TOTAL_NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index c42fe16..f47346f 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -264,7 +264,6 @@
     <string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"Det gick inte att analysera vCard av okänd anledning"</string>
     <string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"Det gick inte att analysera vCard trots att formatet verkar vara giltigt. Den aktuella implementeringen stöder inte det."</string>
     <string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"Ingen vCard-fil hittades på SD-kortet"</string>
-    <string name="fail_reason_no_vcard_entry" msgid="4733290752474073143">"Det finns ingen vCard-post i den valda filen"</string>
     <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"En eller flera filer kunde inte importeras: (%s)."</string>
     <string name="fail_reason_unknown" msgid="999034019513096768">"Okänt fel"</string>
     <string name="select_vcard_title" msgid="3968948173786172468">"Välj vCard-fil"</string>
@@ -293,7 +292,8 @@
     <string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"Det gick inte att starta exportverktyget: <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
     <string name="fail_reason_error_occurred_during_export" msgid="2151165129433831202">"Ett fel uppstod under export: <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
     <string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"Det gick inte att hämta databasinformation"</string>
-    <string name="composer_has_no_exportable_contact" msgid="2239503301380653777">"Det finns ingen kontakt som kan exporteras. Du kan välja data som inte kan exporteras"</string>
+    <!-- no translation found for composer_has_no_exportable_contact (754734132189369094) -->
+    <skip />
     <string name="composer_not_initialized" msgid="8041534450748388843">"vCard-kompositören är inte korrekt initierad"</string>
     <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"Det gick inte att öppna <xliff:g id="FILE_NAME">%s</xliff:g>: <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
     <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g> av <xliff:g id="TOTAL_NUMBER">%s</xliff:g> kontakter"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index df91cdc..e6becea 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -264,7 +264,6 @@
     <string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"Beklenmedik bir nedenden dolayı vCard ayrıştırılamadı"</string>
     <string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"Geçerli biçimde görünmesine rağmen vCard ayrıştırılamadı, çünkü şu anki uygulama vCard\'ı desteklemiyor"</string>
     <string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"SD kartta hiçbir vCard dosyası bulunamadı"</string>
-    <string name="fail_reason_no_vcard_entry" msgid="4733290752474073143">"Seçiminiz için geçerli vCard girdisi bulunamadı"</string>
     <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"İçe aktarılamayan bir veya birden fazla dosya (%s)."</string>
     <string name="fail_reason_unknown" msgid="999034019513096768">"Bilinmeyen hata"</string>
     <string name="select_vcard_title" msgid="3968948173786172468">"vCard dosyasını seç"</string>
@@ -293,7 +292,8 @@
     <string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"Dışa aktarıcı başlatılamadı: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
     <string name="fail_reason_error_occurred_during_export" msgid="2151165129433831202">"Dışa aktarma sırasında hata oluştu: \"<xliff:g id="EXACT_REASON">%s</xliff:g>\""</string>
     <string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"Veri tabanı bilgileri alınamadı"</string>
-    <string name="composer_has_no_exportable_contact" msgid="2239503301380653777">"Dışa aktarılabilir kişi yok. Dışa aktarılamayan veriler seçmiş olabilirsiniz"</string>
+    <!-- no translation found for composer_has_no_exportable_contact (754734132189369094) -->
+    <skip />
     <string name="composer_not_initialized" msgid="8041534450748388843">"vCard oluşturucu doğru bir şekilde başlatılmamış"</string>
     <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"\"<xliff:g id="FILE_NAME">%s</xliff:g>\" dosyası açılamadı: <xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
     <string name="exporting_contact_list_progress" msgid="560522409559101193">"<xliff:g id="CURRENT_NUMBER">%s</xliff:g> / <xliff:g id="TOTAL_NUMBER">%s</xliff:g> kişi"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 20cbf18..858dab0 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -264,7 +264,6 @@
     <string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"由于意外原因而无法解析 vCard"</string>
     <string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"虽然 vCard 似乎使用了有效的格式,但系统无法对其进行解析,因为当前的实现方案不支持该格式。"</string>
     <string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"未在 SD 卡上找到 vCard 文件"</string>
-    <string name="fail_reason_no_vcard_entry" msgid="4733290752474073143">"未找到与所选内容对应的有效 vCard 条目"</string>
     <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"一个或多个文件导入失败 (%s)。"</string>
     <string name="fail_reason_unknown" msgid="999034019513096768">"未知错误"</string>
     <string name="select_vcard_title" msgid="3968948173786172468">"选择 vCard 文件"</string>
@@ -293,7 +292,8 @@
     <string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"无法初始化导出程序:“<xliff:g id="EXACT_REASON">%s</xliff:g>”"</string>
     <string name="fail_reason_error_occurred_during_export" msgid="2151165129433831202">"导出时出错:“<xliff:g id="EXACT_REASON">%s</xliff:g>”"</string>
     <string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"获取数据库信息失败"</string>
-    <string name="composer_has_no_exportable_contact" msgid="2239503301380653777">"没有可导出的联系人。您可能选择了无法导出的数据。"</string>
+    <!-- no translation found for composer_has_no_exportable_contact (754734132189369094) -->
+    <skip />
     <string name="composer_not_initialized" msgid="8041534450748388843">"vCard 制作程序未正确初始化"</string>
     <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"无法打开“<xliff:g id="FILE_NAME">%s</xliff:g>”:<xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
     <string name="exporting_contact_list_progress" msgid="560522409559101193">"第 <xliff:g id="CURRENT_NUMBER">%s</xliff:g> 个联系人(共 <xliff:g id="TOTAL_NUMBER">%s</xliff:g> 个)"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 601583a..649f120 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -264,7 +264,6 @@
     <string name="fail_reason_vcard_parse_error" msgid="1201233722762680214">"因未預期原因,無法剖析 VCard"</string>
     <string name="fail_reason_vcard_not_supported_error" msgid="655208100451286027">"VCard 格式正確,但目前的實作系統不支援此格式,因此無法剖析"</string>
     <string name="fail_reason_no_vcard_file" msgid="6376516175882881595">"SD 卡上沒有 vCard 檔案"</string>
-    <string name="fail_reason_no_vcard_entry" msgid="4733290752474073143">"所選的檔案中沒有有效的 VCard 項目"</string>
     <string name="fail_reason_failed_to_read_files" msgid="3659521123567134029">"無法匯入一或多個檔案 (%s)。"</string>
     <string name="fail_reason_unknown" msgid="999034019513096768">"未知的錯誤"</string>
     <string name="select_vcard_title" msgid="3968948173786172468">"選取 VCard 檔案"</string>
@@ -293,7 +292,8 @@
     <string name="fail_reason_could_not_initialize_exporter" msgid="4943708332700987376">"無法初始化匯出程式:「<xliff:g id="EXACT_REASON">%s</xliff:g>」"</string>
     <string name="fail_reason_error_occurred_during_export" msgid="2151165129433831202">"匯出時發生錯誤:「<xliff:g id="EXACT_REASON">%s</xliff:g>」"</string>
     <string name="composer_failed_to_get_database_infomation" msgid="3723109558155169053">"無法取得資料庫資訊"</string>
-    <string name="composer_has_no_exportable_contact" msgid="2239503301380653777">"沒有可匯出的聯絡人,您可能選擇了不可匯出的資料。"</string>
+    <!-- no translation found for composer_has_no_exportable_contact (754734132189369094) -->
+    <skip />
     <string name="composer_not_initialized" msgid="8041534450748388843">"VCard 編輯器並未正確初始化"</string>
     <string name="fail_reason_could_not_open_file" msgid="4013520943128739511">"無法開啟「<xliff:g id="FILE_NAME">%s</xliff:g>」:<xliff:g id="EXACT_REASON">%s</xliff:g>"</string>
     <string name="exporting_contact_list_progress" msgid="560522409559101193">"第 <xliff:g id="CURRENT_NUMBER">%s</xliff:g> 位聯絡人,共 <xliff:g id="TOTAL_NUMBER">%s</xliff:g> 位"</string>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 69ab865..3ba7106 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -22,4 +22,5 @@
     <color name="quickcontact_disambig_divider">#afafaf</color>
 
     <color name="edit_divider">#ff666666</color>
+    <color name="background_secondary">#ff202020</color>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index e37c118..7990406 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -776,6 +776,48 @@
     -->
     <string name="description_image_button_pound">pound</string>
 
+    <!-- String describing the Voicemail ImageButton
+
+         Used by AccessibilityService to announce the purpose of the button.
+    -->
+    <string name="description_voicemail_button">voicemail</string>
+
+    <!-- String describing the Dial ImageButton
+
+         Used by AccessibilityService to announce the purpose of the button.
+    -->
+    <string name="description_dial_button">dial</string>
+
+    <!-- String describing the Delete/Backspace ImageButton
+
+         Used by AccessibilityService to announce the purpose of the button.
+    -->
+    <string name="description_delete_button">backspace</string>
+
+    <!-- String describing the digits text box containing the number to dial.
+
+         Used by AccessibilityService to announce the purpose of the view.
+    -->
+    <string name="description_digits_edittext">number to dial</string>
+
+    <!-- String describing the Contact Photo Image
+
+         Used by AccessibilityService to announce the purpose of the view.
+    -->
+    <string name="description_contact_photo">contact photo</string>
+
+    <!-- String describing the Contact Editor Minus button
+
+         Used by AccessibilityService to announce the purpose of the button.
+    -->
+    <string name="description_minus_button">minus</string>
+
+    <!-- String describing the Contact Editor Plus button
+
+         Used by AccessibilityService to announce the purpose of the button.
+    -->
+    <string name="description_plus_button">plus</string>
+
     <!-- Dialog title shown when SD Card does not exist -->
     <string name="no_sdcard_title">No SD card</string>
 
@@ -797,44 +839,55 @@
     <!-- Action that exports all contacts to SD Card -->
     <string name="export_to_sdcard">Export to SD card</string>
 
-    <!-- "Import one vCard file" -->
+    <!-- One of alternatives shown when the system allows a user to select how many vCard files
+         should be imported. This message shows only when the system is certain that there's more
+         than one vCard files available in the system. -->
     <string name="import_one_vcard_string">Import one vCard file</string>
 
-    <!-- "Import more than one vCard -->
+    <!-- One of alternatives shown when the system allows a user to select how many vCard files
+         should be imported. This message shows only when the system is certain that there's more
+         than one vCard files available in the system. -->
     <string name="import_multiple_vcard_string">Import multiple vCard files</string>
 
-    <!-- "Import all vCard files" -->
+    <!-- One of alternatives shown when the system allows a user to select how many vCard files
+         should be imported. This message shows only when the system is certain that there's more
+         than one vCard files available in the system. -->
     <string name="import_all_vcard_string">Import all vCard files</string>
 
     <!-- Dialog message shown when searching VCard data from SD Card -->
     <string name="searching_vcard_message">Searching for vCard data on SD card</string>
 
-    <!-- Dialog title shown when searching VCard data failed. -->
+    <!-- Dialog title shown when scanning VCard data failed. -->
     <string name="scanning_sdcard_failed_title">Scanning SD card failed</string>
 
-    <!-- Dialog message shown when searching VCard data failed. -->
+    <!-- Dialog message shown when searching VCard data failed.
+         An exact reason for the failure should -->
     <string name="scanning_sdcard_failed_message">Scanning SD card failed (Reason: \"<xliff:g id="fail_reason">%s</xliff:g>\")</string>
 
-    <!-- The failed reason: "I/O Error" -->
+    <!-- The failed reason shown when Contacts app (especially vCard importer/exporter)
+         emitted some I/O error. Exact reason will be appended by the system. -->
     <string name="fail_reason_io_error">I/O Error</string>
 
-    <!-- The failed reason: "Failed to parse VCard data" -->
+    <!-- The failed reason shown when vCard parser was not able to be parsed by the current vCard
+         implementation. This might happen even when the input vCard is completely valid, though
+         we believe it is rather rare in the actual world. -->
     <string name="fail_reason_vcard_parse_error">Failed to parse vCard for unexpected reason</string>
 
-    <!-- The failed reason: "The VCard is not supported right now, but may be supported in the future" -->
+    <!-- The failed reason shown when the current vCard importer cannot parse the file since the
+         parser is incomplete (actually, there's missing part in the current vCard parser, though
+         our understanding is that the functionality missed by the current vCard parser
+         is rarely needed in the actual vCard...). -->
     <string name="fail_reason_vcard_not_supported_error">Failed to parse vCard though it seems in valid format, since the current implementation does not support it</string>
 
-    <!-- The failed reason: "There is no VCard file" -->
+    <!-- The failed reason shown when the system could not find any vCard file
+         (with extension ".vcf" in SDCard.) -->
     <string name="fail_reason_no_vcard_file">No vCard file found on the SD card</string>
 
-    <!-- The failed reason: "There is no valid VCard entry in the file(s)" -->
-    <string name="fail_reason_no_vcard_entry">No valid vCard entry found for your selection</string>
-
-    <!-- The failed reason: "One or more files failed to be imported. (&lt;a list of file names&gt;)" -->
+    <!-- The failed reason shown when the import of some of vCard files failed during multiple vCard
+         files import. It includes the case where all files were failed to be imported. -->
     <string name="fail_reason_failed_to_read_files">One or more files failed to be imported (%s).</string>
 
-    <!-- The failed reason: "Unknown error". This message should not be shown
-         but it may in some buggy condition. -->
+    <!-- The failed reason which should not be shown but it may in some buggy condition. -->
     <string name="fail_reason_unknown">Unknown error</string>
 
     <!-- Dialog title shown when a user is asked to select VCard file -->
@@ -890,16 +943,22 @@
     <!-- Dialog message shown when exporting Contact data failed -->
     <string name="exporting_contact_failed_message">Failed to export contact data.\nReason for failure: \"<xliff:g id="fail_reason">%s</xliff:g>\"</string>
 
-    <!-- The failed reason: "There is no exportable contact" -->
+    <!-- The failed reason shown when there's no contact which is allowed to be exported.
+         Note that user may have contacts data but all of them are probably not allowed to be
+         exported because of security/permission reasons. -->
     <string name="fail_reason_no_exportable_contact">There is no exportable contact</string>
 
-    <!-- The failed reason: "Too many vcard files on the SD Card" -->
+    <!-- The failed reason shown when vCard exporter could not create a file for the vCard since
+         there are too many files relevant to vCard. -->
     <string name="fail_reason_too_many_vcard">Too many vCard files on the SD card</string>
 
-    <!-- The failed reason: "Too long filename". This error usually does not happen. -->
+    <!-- The failed reason shown when the given file name is too long for the system.
+         The length limit of each file is different in each Android device, so we don't need to
+         mention it here. -->
     <string name="fail_reason_too_long_filename">Required filename is too long (\"<xliff:g id="filename">%s</xliff:g>\")</string>
 
-    <!-- The failed reason: "Cannot open or create the destination directory" -->
+    <!-- The reason why the contacts import/export failed, meaning a designated file or directory
+         cannot be opened/created because of some permission, security, or any other reasons. -->
     <string name="fail_reason_cannot_open_destination_dir">Cannot open or create the destination directory\"<xliff:g id="dir_name">%s</xliff:g>\"</string>
 
     <!-- Dialog title shown when the application is exporting contact data outside -->
@@ -908,22 +967,30 @@
     <!-- Message shown when the application is exporting contact data outside -->
     <string name="exporting_contact_list_message">Exporting contact data to \"<xliff:g id="file_name">%s</xliff:g>\"</string>
 
-    <!-- The failed reason: "Could not initialize the exporter" -->
+    <!-- The failed reason shown when contacts exporter fails to be initialized.
+         Some exact reason must follow this. -->
     <string name="fail_reason_could_not_initialize_exporter">Could not initialize the exporter: \"<xliff:g id="exact_reason">%s</xliff:g>\"</string>
 
-    <!-- The failed reason: "Error occured during export" -->
+    <!-- The failed reason shown when some error happend during contacts export.
+         Some exact reason must follow this. -->
     <string name="fail_reason_error_occurred_during_export">Error occured during export: \"<xliff:g id="exact_reason">%s</xliff:g>\"</string>
 
-    <!-- The error reason the vCard composer emits: "Failed to get database information" -->
+    <!-- The error reason the vCard composer "may" emit when database is corrupted or
+         something is going wrong. Usually users should not see this text. -->
     <string name="composer_failed_to_get_database_infomation">Failed to get database information</string>
 
-    <!-- The error reason the vCard composer emits: "There is no exportable contact. You might choose un-exportable data" -->
-    <string name="composer_has_no_exportable_contact">There is no exportable contact. You might choose un-exportable data</string>
+    <!-- This error message shown when the user actually have no contact
+         (e.g. just after data-wiping), or, data providers of the contact list prohibits their
+         contacts from being exported to outside world via vcard exporter, etc. -->
+    <string name="composer_has_no_exportable_contact">There is no exportable contact. If you have actualy have contacts on your phone, all the contacts may be prohibited from being exported to outside the phone by some data provider.</string>
 
-    <!-- The error reason the vCard composer emits: "The vCard composer object is not correctly initialized" -->
+    <!-- The error reason the vCard composer may emit when vCard composer is not initialized
+         even when needed.
+         Users should not usually see this error message. -->
     <string name="composer_not_initialized">The vCard composer is not correctly initialized</string>
 
-    <!-- The failed reason: "Could not open a specific file" -->
+    <!-- The failed reason shown when vCard importer/exporter could not open the file
+         specified by a user. The file name should be in the message. -->
     <string name="fail_reason_could_not_open_file">Could not open \"<xliff:g id="file_name">%s</xliff:g>\": <xliff:g id="exact_reason">%s</xliff:g></string>
 
     <!-- Message in progress bar while exporting contact list to a file "(current number) of (total number) contacts" The order of "current number" and "total number" cannot be changed (like "total: (total number), current: (current number)")-->
@@ -1217,4 +1284,22 @@
 
     <!-- Text describing that a contact has no information available other than name and photo -->
     <string name="no_contact_details">No additional information for this contact</string>
+    
+    <!-- Label of the "sort list by" display option -->
+    <string name="display_options_sort_list_by">Sort list by</string>
+    
+    <!-- An allowable value for the "sort list by" contact display option  -->
+    <string name="display_options_sort_by_given_name">Given name</string>
+    
+    <!-- An allowable value for the "sort list by" contact display option  -->
+    <string name="display_options_sort_by_family_name">Family name</string>
+           
+    <!-- Label of the "view names as" display option -->
+    <string name="display_options_view_names_as">View contact names as</string>
+    
+    <!-- An allowable value for the "view names as" contact display option  -->
+    <string name="display_options_view_given_name_first">Given name first</string>
+    
+    <!-- An allowable value for the "view names as" contact display option  -->
+    <string name="display_options_view_family_name_first">Family name first</string>
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 8c5ae35..0a6fe2f 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -23,11 +23,13 @@
     <style name="MinusButton">
         <item name="android:background">@drawable/btn_circle</item>
         <item name="android:src">@drawable/ic_btn_round_minus</item>
+        <item name="android:contentDescription">@string/description_minus_button</item>
     </style>
 
     <style name="PlusButton">
         <item name="android:background">@drawable/btn_circle</item>
         <item name="android:src">@drawable/ic_btn_round_plus</item>
+        <item name="android:contentDescription">@string/description_plus_button</item>
     </style>
 
     <style name="MoreButton">
@@ -35,6 +37,11 @@
         <item name="android:src">@drawable/ic_btn_round_more</item>
     </style>
 
+    <style name="LessButton">
+        <item name="android:background">@drawable/btn_circle</item>
+        <item name="android:src">@drawable/ic_btn_round_less</item>
+    </style>
+
     <style name="TallTitleBarTheme" parent="android:Theme.NoTitleBar">
         <item name="android:windowContentOverlay">@null</item>
     </style>
diff --git a/res/xml/searchable.xml b/res/xml/searchable.xml
index af8b3ef..17ef37f 100644
--- a/res/xml/searchable.xml
+++ b/res/xml/searchable.xml
@@ -4,9 +4,9 @@
      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.
@@ -27,7 +27,7 @@
     android:includeInGlobalSearch="true"
     android:searchSuggestAuthority="com.android.contacts"
     android:searchSuggestIntentAction="android.provider.Contacts.SEARCH_SUGGESTION_CLICKED"
-    android:searchSuggestIntentData="content://com.android.contacts/contacts"
+    android:searchSuggestIntentData="content://com.android.contacts/contacts/lookup"
     android:searchSettingsDescription="@string/search_settings_description"
 >
 
diff --git a/src/com/android/contacts/ButtonGridLayout.java b/src/com/android/contacts/ButtonGridLayout.java
deleted file mode 100644
index 6ce3e71..0000000
--- a/src/com/android/contacts/ButtonGridLayout.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.contacts;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View.MeasureSpec;
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * Create a 4x3 grid of dial buttons.
- *
- * It was easier and more efficient to do it this way than use
- * standard layouts. It's perfectly fine (and actually encouraged) to
- * use custom layouts rather than piling up standard layouts.
- *
- * The horizontal and vertical spacings between buttons are controlled
- * by the amount of padding (attributes on the ButtonGridLayout element):
- *   - horizontal = left + right padding and
- *   - vertical = top + bottom padding.
- *
- * This class assumes that all the buttons have the same size.
- * The buttons will be bottom aligned in their view on layout.
- *
- * Invocation: onMeasure is called first by the framework to know our
- * size. Then onLayout is invoked to layout the buttons.
- */
-// TODO: Blindly layout the buttons w/o checking if we overrun the
-// bottom-right corner.
-public class ButtonGridLayout extends ViewGroup {
-    private final int COLUMNS = 3;
-    private final int ROWS = 4;
-
-    // Width and height of a button
-    private int mButtonWidth;
-    private int mButtonHeight;
-
-    // Width and height of a button + padding.
-    private int mWidthInc;
-    private int mHeightInc;
-
-    // Height of the dialpad. Used to align it at the bottom of the
-    // view.
-    private int mHeight;
-
-    public ButtonGridLayout(Context context) {
-        super(context);
-    }
-
-    public ButtonGridLayout(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public ButtonGridLayout(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        int i = 0;
-        // The last row is bottom aligned.
-        int y = (b - t) - mHeight + mPaddingTop;
-        for (int row = 0; row < ROWS; row++) {
-            int x = mPaddingLeft;
-            for (int col = 0; col < COLUMNS; col++) {
-                View child = getChildAt(i);
-
-                child.layout(x, y, x + mButtonWidth, y + mButtonHeight);
-
-                x += mWidthInc;
-                i++;
-            }
-            y += mHeightInc;
-        }
-      }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        // Measure the first child and get it's size
-        View child = getChildAt(0);
-        child.measure(MeasureSpec.UNSPECIFIED , MeasureSpec.UNSPECIFIED);
-
-        // Make sure the other children are measured as well, to initialize
-        for (int i = 1; i < getChildCount(); i++) {
-            getChildAt(i).measure(MeasureSpec.UNSPECIFIED , MeasureSpec.UNSPECIFIED);
-        }
-
-        // Store these to be reused in onLayout.
-        mButtonWidth = child.getMeasuredWidth();
-        mButtonHeight = child.getMeasuredHeight();
-        mWidthInc = mButtonWidth + mPaddingLeft + mPaddingRight;
-        mHeightInc = mButtonHeight + mPaddingTop + mPaddingBottom;
-        mHeight = ROWS * mHeightInc;
-
-        final int width = resolveSize(COLUMNS * mWidthInc, widthMeasureSpec);
-        final int height = resolveSize(mHeight, heightMeasureSpec);
-
-        setMeasuredDimension(width, height);
-    }
-}
diff --git a/src/com/android/contacts/ContactsListActivity.java b/src/com/android/contacts/ContactsListActivity.java
index 24c7100..40e048f 100644
--- a/src/com/android/contacts/ContactsListActivity.java
+++ b/src/com/android/contacts/ContactsListActivity.java
@@ -16,10 +16,12 @@
 
 package com.android.contacts;
 
+import com.android.contacts.TextHighlightingAnimation.TextWithHighlighting;
 import com.android.contacts.model.ContactsSource;
 import com.android.contacts.model.Sources;
-import com.android.contacts.ui.DisplayGroupsActivity;
-import com.android.contacts.ui.DisplayGroupsActivity.Prefs;
+import com.android.contacts.ui.ContactsPreferences;
+import com.android.contacts.ui.ContactsPreferencesActivity;
+import com.android.contacts.ui.ContactsPreferencesActivity.Prefs;
 import com.android.contacts.util.AccountSelectionUtil;
 import com.android.contacts.util.Constants;
 
@@ -77,7 +79,6 @@
 import android.provider.ContactsContract.Intents.UI;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
-import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.ContextMenu;
 import android.view.ContextThemeWrapper;
@@ -103,7 +104,6 @@
 import android.widget.SectionIndexer;
 import android.widget.TextView;
 import android.widget.AbsListView.OnScrollListener;
-import android.*;
 
 import java.lang.ref.SoftReference;
 import java.lang.ref.WeakReference;
@@ -152,6 +152,8 @@
     private static final int SUBACTIVITY_VIEW_CONTACT = 2;
     private static final int SUBACTIVITY_DISPLAY_GROUP = 3;
 
+    private static final int TEXT_HIGHLIGHTING_ANIMATION_DURATION = 350;
+
     /**
      * The action for the join contact activity.
      * <p>
@@ -219,11 +221,11 @@
     static final int MODE_PICK_OR_CREATE_CONTACT = 42 | MODE_MASK_PICKER | MODE_MASK_CREATE_NEW
             | MODE_MASK_SHOW_PHOTOS | MODE_MASK_DISABLE_QUIKCCONTACT;
     /** Show all people through the legacy provider and pick them when clicking */
-    static final int MODE_LEGACY_PICK_PERSON = 43 | MODE_MASK_PICKER | MODE_MASK_SHOW_PHOTOS
+    static final int MODE_LEGACY_PICK_PERSON = 43 | MODE_MASK_PICKER
             | MODE_MASK_DISABLE_QUIKCCONTACT;
     /** Show all people through the legacy provider as well as the option to create a new one */
     static final int MODE_LEGACY_PICK_OR_CREATE_PERSON = 44 | MODE_MASK_PICKER
-            | MODE_MASK_CREATE_NEW | MODE_MASK_SHOW_PHOTOS | MODE_MASK_DISABLE_QUIKCCONTACT;
+            | MODE_MASK_CREATE_NEW | MODE_MASK_DISABLE_QUIKCCONTACT;
     /** Show all contacts and pick them when clicking, and allow creating a new contact */
     static final int MODE_INSERT_OR_EDIT_CONTACT = 45 | MODE_MASK_PICKER | MODE_MASK_CREATE_NEW;
     /** Show all phone numbers and pick them when clicking */
@@ -250,44 +252,49 @@
     /** Maximum number of suggestions shown for joining aggregates */
     static final int MAX_SUGGESTIONS = 4;
 
-    static final String NAME_COLUMN = Contacts.DISPLAY_NAME;
-    //static final String SORT_STRING = People.SORT_STRING;
-
     static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
-        Contacts._ID, // 0
-        Contacts.DISPLAY_NAME, // 1
-        Contacts.STARRED, //2
-        Contacts.TIMES_CONTACTED, //3
-        Contacts.CONTACT_PRESENCE, //4
-        Contacts.PHOTO_ID, //5
-        Contacts.LOOKUP_KEY, //6
-        Contacts.HAS_PHONE_NUMBER, //7
+        Contacts._ID,                       // 0
+        Contacts.DISPLAY_NAME_PRIMARY,      // 1
+        Contacts.DISPLAY_NAME_ALTERNATIVE,  // 2
+        Contacts.SORT_KEY_PRIMARY,          // 3
+        Contacts.STARRED,                   // 4
+        Contacts.TIMES_CONTACTED,           // 5
+        Contacts.CONTACT_PRESENCE,          // 6
+        Contacts.PHOTO_ID,                  // 7
+        Contacts.LOOKUP_KEY,                // 8
+        Contacts.HAS_PHONE_NUMBER,          // 9
     };
     static final String[] CONTACTS_SUMMARY_PROJECTION_FROM_EMAIL = new String[] {
-        Contacts._ID, // 0
-        Contacts.DISPLAY_NAME, // 1
-        Contacts.STARRED, //2
-        Contacts.TIMES_CONTACTED, //3
-        Contacts.CONTACT_PRESENCE, //4
-        Contacts.PHOTO_ID, //5
-        Contacts.LOOKUP_KEY, //6
+        Contacts._ID,                       // 0
+        Contacts.DISPLAY_NAME_PRIMARY,      // 1
+        Contacts.DISPLAY_NAME_ALTERNATIVE,  // 2
+        Contacts.SORT_KEY_PRIMARY,          // 3
+        Contacts.STARRED,                   // 4
+        Contacts.TIMES_CONTACTED,           // 5
+        Contacts.CONTACT_PRESENCE,          // 6
+        Contacts.PHOTO_ID,                  // 7
+        Contacts.LOOKUP_KEY,                // 8
         // email lookup doesn't included HAS_PHONE_NUMBER OR LOOKUP_KEY in projection
     };
     static final String[] LEGACY_PEOPLE_PROJECTION = new String[] {
-        People._ID, // 0
-        People.DISPLAY_NAME, // 1
-        People.STARRED, //2
-        PeopleColumns.TIMES_CONTACTED, //3
-        People.PRESENCE_STATUS, //4
+        People._ID,                         // 0
+        People.DISPLAY_NAME,                // 1
+        People.DISPLAY_NAME,                // 2
+        People.DISPLAY_NAME,                // 3
+        People.STARRED,                     // 4
+        PeopleColumns.TIMES_CONTACTED,      // 5
+        People.PRESENCE_STATUS,             // 6
     };
     static final int SUMMARY_ID_COLUMN_INDEX = 0;
-    static final int SUMMARY_NAME_COLUMN_INDEX = 1;
-    static final int SUMMARY_STARRED_COLUMN_INDEX = 2;
-    static final int SUMMARY_TIMES_CONTACTED_COLUMN_INDEX = 3;
-    static final int SUMMARY_PRESENCE_STATUS_COLUMN_INDEX = 4;
-    static final int SUMMARY_PHOTO_ID_COLUMN_INDEX = 5;
-    static final int SUMMARY_LOOKUP_KEY = 6;
-    static final int SUMMARY_HAS_PHONE_COLUMN_INDEX = 7;
+    static final int SUMMARY_DISPLAY_NAME_PRIMARY_COLUMN_INDEX = 1;
+    static final int SUMMARY_DISPLAY_NAME_ALTERNATIVE_COLUMN_INDEX = 2;
+    static final int SUMMARY_SORT_KEY_PRIMARY_COLUMN_INDEX = 3;
+    static final int SUMMARY_STARRED_COLUMN_INDEX = 4;
+    static final int SUMMARY_TIMES_CONTACTED_COLUMN_INDEX = 5;
+    static final int SUMMARY_PRESENCE_STATUS_COLUMN_INDEX = 6;
+    static final int SUMMARY_PHOTO_ID_COLUMN_INDEX = 7;
+    static final int SUMMARY_LOOKUP_KEY_COLUMN_INDEX = 8;
+    static final int SUMMARY_HAS_PHONE_COLUMN_INDEX = 9;
 
     static final String[] PHONES_PROJECTION = new String[] {
         Phone._ID, //0
@@ -417,6 +424,52 @@
         }
     }
 
+    /**
+     * A {@link TextHighlightingAnimation} that redraws just the contact display name in a
+     * list item.
+     */
+    private static class NameHighlightingAnimation extends TextHighlightingAnimation {
+        private final ListView mListView;
+
+        private NameHighlightingAnimation(ListView listView, int duration) {
+            super(duration);
+            this.mListView = listView;
+        }
+
+        /**
+         * Redraws all visible items of the list corresponding to contacts
+         */
+        @Override
+        protected void invalidate() {
+            int childCount = mListView.getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                View listItem = mListView.getChildAt(i);
+                Object tag = listItem.getTag();
+                if (tag instanceof ContactListItemCache) {
+                    ((ContactListItemCache)tag).nameView.invalidate();
+                }
+            }
+        }
+
+        @Override
+        protected void onAnimationStarted() {
+            mListView.setScrollingCacheEnabled(false);
+        }
+
+        @Override
+        protected void onAnimationEnded() {
+            mListView.setScrollingCacheEnabled(true);
+        }
+    }
+
+    // The size of a home screen shortcut icon.
+    private int mIconSize;
+    private ContactsPreferences mContactsPrefs;
+    private int mDisplayOrder;
+    private int mSortOrder;
+    private boolean mHighlightWhenScrolling;
+    private TextHighlightingAnimation mHighlightingAnimation;
+
     @Override
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
@@ -424,6 +477,9 @@
         // Resolve the intent
         final Intent intent = getIntent();
 
+        mIconSize = getResources().getDimensionPixelSize(android.R.dimen.app_icon_size);
+        mContactsPrefs = new ContactsPreferences(this);
+
         // Allow the title to be set to a custom String using an extra on the intent
         String title = intent.getStringExtra(UI.TITLE_EXTRA_KEY);
         if (title != null) {
@@ -607,6 +663,8 @@
 
         // Setup the UI
         final ListView list = getListView();
+        mHighlightingAnimation =
+                new NameHighlightingAnimation(list, TEXT_HIGHLIGHTING_ANIMATION_DURATION);
 
         // Tell list view to not show dividers. We'll do it ourself so that we can *not* show
         // them when an A-Z headers is visible.
@@ -680,8 +738,14 @@
         return contactName;
     }
 
-    private int[] mLocation = new int[2];
-    private Rect mRect = new Rect();
+
+    private int getSummaryDisplayNameColumnIndex() {
+        if (mDisplayOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
+            return SUMMARY_DISPLAY_NAME_PRIMARY_COLUMN_INDEX;
+        } else {
+            return SUMMARY_DISPLAY_NAME_ALTERNATIVE_COLUMN_INDEX;
+        }
+    }
 
     /** {@inheritDoc} */
     public void onClick(View v) {
@@ -866,7 +930,7 @@
     public boolean onOptionsItemSelected(MenuItem item) {
         switch (item.getItemId()) {
             case R.id.menu_display_groups: {
-                final Intent intent = new Intent(this, DisplayGroupsActivity.class);
+                final Intent intent = new Intent(this, ContactsPreferencesActivity.class);
                 startActivityForResult(intent, SUBACTIVITY_DISPLAY_GROUP);
                 return true;
             }
@@ -1086,7 +1150,7 @@
         Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
 
         // Setup the menu header
-        menu.setHeaderTitle(cursor.getString(SUMMARY_NAME_COLUMN_INDEX));
+        menu.setHeaderTitle(cursor.getString(getSummaryDisplayNameColumnIndex()));
 
         // View contact details
         menu.add(0, MENU_ITEM_VIEW_CONTACT, 0, R.string.menu_viewContact)
@@ -1274,7 +1338,7 @@
                     || mMode == MODE_LEGACY_PICK_OR_CREATE_PERSON) {
                 if (mShortcutAction != null) {
                     Cursor c = (Cursor) mAdapter.getItem(position);
-                    returnPickerResult(c, c.getString(SUMMARY_NAME_COLUMN_INDEX), uri, id);
+                    returnPickerResult(c, c.getString(getSummaryDisplayNameColumnIndex()), uri, id);
                 } else {
                     returnPickerResult(null, null, uri, id);
                 }
@@ -1313,7 +1377,7 @@
                 shortcutIntent = new Intent(ContactsContract.QuickContact.ACTION_QUICK_CONTACT);
                 shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
                         Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
-    
+
                 shortcutIntent.setData(uri);
                 shortcutIntent.putExtra(ContactsContract.QuickContact.EXTRA_MODE,
                         ContactsContract.QuickContact.MODE_LARGE);
@@ -1322,7 +1386,7 @@
 
                 final Bitmap icon = framePhoto(loadContactPhoto(id, null));
                 if (icon != null) {
-                    intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, icon);
+                    intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, scaleToAppIconSize(icon));
                 } else {
                     intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
                             Intent.ShortcutIconResource.fromContext(this,
@@ -1415,8 +1479,7 @@
         }
 
         // Setup the drawing classes
-        int iconSize = (int) r.getDimension(android.R.dimen.app_icon_size);
-        Bitmap icon = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
+        Bitmap icon = createShortcutBitmap();
         Canvas canvas = new Canvas(icon);
 
         // Copy in the photo
@@ -1424,7 +1487,7 @@
         photoPaint.setDither(true);
         photoPaint.setFilterBitmap(true);
         Rect src = new Rect(0,0, photo.getWidth(),photo.getHeight());
-        Rect dst = new Rect(0,0, iconSize,iconSize);
+        Rect dst = new Rect(0,0, mIconSize, mIconSize);
         canvas.drawBitmap(photo, src, dst, photoPaint);
 
         // Create an overlay for the phone number type
@@ -1474,6 +1537,26 @@
         return icon;
     }
 
+    private Bitmap scaleToAppIconSize(Bitmap photo) {
+        // Setup the drawing classes
+        Bitmap icon = createShortcutBitmap();
+        Canvas canvas = new Canvas(icon);
+
+        // Copy in the photo
+        Paint photoPaint = new Paint();
+        photoPaint.setDither(true);
+        photoPaint.setFilterBitmap(true);
+        Rect src = new Rect(0,0, photo.getWidth(),photo.getHeight());
+        Rect dst = new Rect(0,0, mIconSize, mIconSize);
+        canvas.drawBitmap(photo, src, dst, photoPaint);
+
+        return icon;
+    }
+
+    private Bitmap createShortcutBitmap() {
+        return Bitmap.createBitmap(mIconSize, mIconSize, Bitmap.Config.ARGB_8888);
+    }
+
     /**
      * Returns the icon for the phone call action.
      *
@@ -1561,7 +1644,7 @@
             default: {
                 // Build and return soft, lookup reference
                 final long contactId = cursor.getLong(SUMMARY_ID_COLUMN_INDEX);
-                final String lookupKey = cursor.getString(SUMMARY_LOOKUP_KEY);
+                final String lookupKey = cursor.getString(SUMMARY_LOOKUP_KEY_COLUMN_INDEX);
                 return Contacts.getLookupUri(contactId, lookupKey);
             }
         }
@@ -1714,15 +1797,12 @@
         return builder.build();
     }
 
-    private static String getSortOrder(String[] projectionType) {
-        /* if (Locale.getDefault().equals(Locale.JAPAN) &&
-                projectionType == AGGREGATES_PRIMARY_PHONE_PROJECTION) {
-            return SORT_STRING + " ASC";
+    private String getSortOrder(String[] projectionType) {
+        if (mSortOrder == ContactsContract.Preferences.SORT_ORDER_PRIMARY) {
+            return Contacts.SORT_KEY_PRIMARY;
         } else {
-            return NAME_COLUMN + " COLLATE LOCALIZED ASC";
-        } */
-
-        return NAME_COLUMN + " COLLATE LOCALIZED ASC";
+            return Contacts.SORT_KEY_ALTERNATIVE;
+        }
     }
 
     void startQuery() {
@@ -1732,6 +1812,20 @@
         mQueryHandler.cancelOperation(QUERY_TOKEN);
         mQueryHandler.setLoadingJoinSuggestions(false);
 
+        mSortOrder = mContactsPrefs.getSortOrder();
+        mDisplayOrder = mContactsPrefs.getDisplayOrder();
+
+        // When sort order and display order contradict each other, we want to
+        // highlight the part of the name used for sorting.
+        mHighlightWhenScrolling = false;
+        if (mSortOrder == ContactsContract.Preferences.SORT_ORDER_PRIMARY &&
+                mDisplayOrder == ContactsContract.Preferences.DISPLAY_ORDER_ALTERNATIVE) {
+            mHighlightWhenScrolling = true;
+        } else if (mSortOrder == ContactsContract.Preferences.SORT_ORDER_ALTERNATIVE &&
+                mDisplayOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
+            mHighlightWhenScrolling = true;
+        }
+
         String[] projection = getProjectionForQuery();
         String callingPackage = getCallingPackage();
         Uri uri = getUriToQuery();
@@ -2051,7 +2145,7 @@
                                 CONTACTS_SUMMARY_PROJECTION,
                                 Contacts._ID + " != " + activity.mQueryAggregateId
                                         + " AND " + CLAUSE_ONLY_VISIBLE, null,
-                                getSortOrder(CONTACTS_SUMMARY_PROJECTION));
+                                activity.getSortOrder(CONTACTS_SUMMARY_PROJECTION));
                         return;
                     }
 
@@ -2086,12 +2180,13 @@
         public ImageView callButton;
         public CharArrayBuffer nameBuffer = new CharArrayBuffer(128);
         public TextView labelView;
-        public CharArrayBuffer labelBuffer = new CharArrayBuffer(128);
         public TextView dataView;
         public CharArrayBuffer dataBuffer = new CharArrayBuffer(128);
         public ImageView presenceView;
         public QuickContactBadge photoView;
         public ImageView nonQuickContactPhotoView;
+        public CharArrayBuffer highlightedTextBuffer = new CharArrayBuffer(128);
+        public TextWithHighlighting textWithHighlighting;
     }
 
     final static class PhotoInfo {
@@ -2123,6 +2218,7 @@
         private int mSuggestionsCursorCount;
         private ImageFetchHandler mHandler;
         private ImageDbFetcher mImageFetcher;
+
         private static final int FETCH_IMAGE_MSG = 1;
 
         public ContactItemListAdapter(Context context) {
@@ -2280,11 +2376,12 @@
         }
 
         private SectionIndexer getNewIndexer(Cursor cursor) {
-            /* if (Locale.getDefault().getLanguage().equals(Locale.JAPAN.getLanguage())) {
-                return new JapaneseContactListIndexer(cursor, SORT_STRING_INDEX);
-            } else { */
-                return new AlphabetIndexer(cursor, SUMMARY_NAME_COLUMN_INDEX, mAlphabet);
-            /* } */
+            if (Locale.getDefault().getLanguage().equals(Locale.JAPAN.getLanguage())) {
+                return new JapaneseContactListIndexer(cursor,
+                        SUMMARY_SORT_KEY_PRIMARY_COLUMN_INDEX);
+            } else {
+                return new AlphabetIndexer(cursor, getSummaryDisplayNameColumnIndex(), mAlphabet);
+            }
         }
 
         /**
@@ -2384,7 +2481,7 @@
             }
 
             View v;
-            if (convertView == null) {
+            if (convertView == null || convertView.getTag() == null) {
                 v = newView(mContext, cursor, parent);
             } else {
                 v = convertView;
@@ -2466,9 +2563,12 @@
             cache.dataView = (TextView) view.findViewById(R.id.data);
             cache.presenceView = (ImageView) view.findViewById(R.id.presence);
             cache.photoView = (QuickContactBadge) view.findViewById(R.id.photo);
+            if (cache.photoView != null) {
+                cache.photoView.setExcludeMimes(new String[] {Contacts.CONTENT_ITEM_TYPE});
+            }
             cache.nonQuickContactPhotoView = (ImageView) view.findViewById(R.id.noQuickContactPhoto);
+            cache.textWithHighlighting = mHighlightingAnimation.createTextWithHighlighting();
             view.setTag(cache);
-
             return view;
         }
 
@@ -2484,6 +2584,7 @@
             int defaultType;
             int nameColumnIndex;
             boolean displayAdditionalData = mDisplayAdditionalData;
+            boolean highlightingEnabled = false;
             switch(mMode) {
                 case MODE_PICK_PHONE:
                 case MODE_LEGACY_PICK_PHONE: {
@@ -2504,12 +2605,13 @@
                     break;
                 }
                 default: {
-                    nameColumnIndex = SUMMARY_NAME_COLUMN_INDEX;
+                    nameColumnIndex = getSummaryDisplayNameColumnIndex();
                     dataColumnIndex = -1;
                     typeColumnIndex = -1;
                     labelColumnIndex = -1;
                     defaultType = Phone.TYPE_HOME;
                     displayAdditionalData = false;
+                    highlightingEnabled = mHighlightWhenScrolling && mMode != MODE_STREQUENT;
                 }
             }
 
@@ -2517,13 +2619,21 @@
             cursor.copyStringToBuffer(nameColumnIndex, cache.nameBuffer);
             int size = cache.nameBuffer.sizeCopied;
             if (size != 0) {
-                cache.nameView.setText(cache.nameBuffer.data, 0, size);
+                if (highlightingEnabled) {
+                    buildDisplayNameWithHighlighting(cache.nameView, cursor, cache.nameBuffer,
+                            cache.highlightedTextBuffer, cache.textWithHighlighting);
+                } else {
+                    cache.nameView.setText(cache.nameBuffer.data, 0, size);
+                }
             } else {
                 cache.nameView.setText(mUnknownNameText);
             }
 
+            boolean hasPhone = cursor.getColumnCount() >= SUMMARY_HAS_PHONE_COLUMN_INDEX
+                    && cursor.getInt(SUMMARY_HAS_PHONE_COLUMN_INDEX) != 0;
+
             // Make the call button visible if requested.
-            if (mDisplayCallButton) {
+            if (mDisplayCallButton && hasPhone) {
                 int pos = cursor.getPosition();
                 cache.callView.setVisibility(View.VISIBLE);
                 cache.callButton.setTag(pos);
@@ -2545,7 +2655,7 @@
                     viewToUse = cache.photoView;
                     // Build soft lookup reference
                     final long contactId = cursor.getLong(SUMMARY_ID_COLUMN_INDEX);
-                    final String lookupKey = cursor.getString(SUMMARY_LOOKUP_KEY);
+                    final String lookupKey = cursor.getString(SUMMARY_LOOKUP_KEY_COLUMN_INDEX);
                     cache.photoView.assignContactUri(Contacts.getLookupUri(contactId, lookupKey));
                     cache.photoView.setVisibility(View.VISIBLE);
                     cache.nonQuickContactPhotoView.setVisibility(View.INVISIBLE);
@@ -2645,6 +2755,25 @@
             }
         }
 
+        /**
+         * Computes the span of the display name that has highlighted parts and configures
+         * the display name text view accordingly.
+         */
+        private void buildDisplayNameWithHighlighting(TextView textView, Cursor cursor,
+                CharArrayBuffer buffer1, CharArrayBuffer buffer2,
+                TextWithHighlighting textWithHighlighting) {
+            int oppositeDisplayOrderColumnIndex;
+            if (mDisplayOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
+                oppositeDisplayOrderColumnIndex = SUMMARY_DISPLAY_NAME_ALTERNATIVE_COLUMN_INDEX;
+            } else {
+                oppositeDisplayOrderColumnIndex = SUMMARY_DISPLAY_NAME_PRIMARY_COLUMN_INDEX;
+            }
+            cursor.copyStringToBuffer(oppositeDisplayOrderColumnIndex, buffer2);
+
+            textWithHighlighting.setText(buffer1, buffer2);
+            textView.setText(textWithHighlighting);
+        }
+
         private void bindSectionHeader(View view, int position, boolean displaySectionHeaders) {
             final ContactListItemCache cache = (ContactListItemCache) view.getTag();
             if (!displaySectionHeaders) {
@@ -2908,6 +3037,14 @@
         }
 
         public void onScrollStateChanged(AbsListView view, int scrollState) {
+            if (mHighlightWhenScrolling) {
+                if (scrollState != OnScrollListener.SCROLL_STATE_IDLE) {
+                    mHighlightingAnimation.startHighlighting();
+                } else {
+                    mHighlightingAnimation.stopHighlighting();
+                }
+            }
+
             mScrollState = scrollState;
             if (scrollState == OnScrollListener.SCROLL_STATE_FLING) {
                 // If we are in a fling, stop loading images.
diff --git a/src/com/android/contacts/ContactsUtils.java b/src/com/android/contacts/ContactsUtils.java
index 1e3b8ad..9ad0aa8 100644
--- a/src/com/android/contacts/ContactsUtils.java
+++ b/src/com/android/contacts/ContactsUtils.java
@@ -17,6 +17,9 @@
 package com.android.contacts;
 
 
+import com.android.contacts.model.ContactsSource;
+import com.android.contacts.util.Constants;
+
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
@@ -36,7 +39,7 @@
 import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.provider.ContactsContract.CommonDataKinds.Photo;
 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
-import android.provider.Im.ProviderNames;
+import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -44,13 +47,9 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
-import com.android.contacts.model.ContactsSource;
-import com.android.contacts.util.Constants;
-
 import java.util.ArrayList;
 
 public class ContactsUtils {
-
     private static final String TAG = "ContactsUtils";
     /**
      * Build the display title for the {@link Data#CONTENT_URI} entry in the
@@ -191,9 +190,22 @@
         return photoBm;
     }
 
+    // TODO find a proper place for the canonical version of these
+    public interface ProviderNames {
+        String YAHOO = "Yahoo";
+        String GTALK = "GTalk";
+        String MSN = "MSN";
+        String ICQ = "ICQ";
+        String AIM = "AIM";
+        String XMPP = "XMPP";
+        String JABBER = "JABBER";
+        String SKYPE = "SKYPE";
+        String QQ = "QQ";
+    }
+
     /**
      * This looks up the provider name defined in
-     * {@link android.provider.Im.ProviderNames} from the predefined IM protocol id.
+     * ProviderNames from the predefined IM protocol id.
      * This is used for interacting with the IM application.
      *
      * @param protocol the protocol ID
@@ -386,7 +398,6 @@
     /**
      * Utility for creating a standard tab indicator view.
      *
-     * @param context The label to display in the tab indicator. If null, not label will be displayed.
      * @param parent The parent ViewGroup to attach the new view to.
      * @param source The {@link ContactsSource} to build the tab view from.
      * @return The tab indicator View.
@@ -401,6 +412,9 @@
 
     /**
      * Kick off an intent to initiate a call.
+     *
+     * @param phoneNumber must not be null.
+     * @throws NullPointerException when the given argument is null.
      */
     public static void initiateCall(Context context, CharSequence phoneNumber) {
         Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
@@ -410,6 +424,9 @@
 
     /**
      * Kick off an intent to initiate an Sms/Mms message.
+     *
+     * @param phoneNumber must not be null.
+     * @throws NullPointerException when the given argument is null.
      */
     public static void initiateSms(Context context, CharSequence phoneNumber) {
         Intent intent = new Intent(Intent.ACTION_SENDTO,
@@ -424,4 +441,47 @@
     public static boolean isGraphic(CharSequence str) {
         return !TextUtils.isEmpty(str) && TextUtils.isGraphic(str);
     }
+
+    /**
+     * Returns true if two objects are considered equal.  Two null references are equal here.
+     */
+    public static boolean areObjectsEqual(Object a, Object b) {
+        return a == b || (a != null && a.equals(b));
+    }
+
+    /**
+     * Returns true if two data with mimetypes which represent values in contact entries are
+     * considered equal.
+     */
+    public static final boolean areDataEqual(Context context, CharSequence mimetype1,
+            CharSequence data1, CharSequence mimetype2, CharSequence data2) {
+        if (TextUtils.equals(Phone.CONTENT_ITEM_TYPE, mimetype1)
+                && TextUtils.equals(Phone.CONTENT_ITEM_TYPE, mimetype2)) {
+            if (data1 == data2) {
+                return true;
+            }
+            if (data1 == null || data2 == null) {
+                return false;
+            }
+            return PhoneNumberUtils.compare(context, data1.toString(), data2.toString());
+        } else {
+            if (mimetype1 == mimetype2 && data1 == data2) {
+                return true;
+            }
+            return TextUtils.equals(mimetype1, mimetype2) && TextUtils.equals(data1, data2);
+        }
+    }
+
+    /**
+     * Returns true if two {@link Intent}s are both null, or have the same action.
+     */
+    public static final boolean areIntentActionEqual(Intent a, Intent b) {
+        if (a == b) {
+            return true;
+        }
+        if (a == null || b == null) {
+            return false;
+        }
+        return TextUtils.equals(a.getAction(), b.getAction());
+    }
 }
diff --git a/src/com/android/contacts/ExportVCardActivity.java b/src/com/android/contacts/ExportVCardActivity.java
index baf2371..5bccc7a 100644
--- a/src/com/android/contacts/ExportVCardActivity.java
+++ b/src/com/android/contacts/ExportVCardActivity.java
@@ -36,6 +36,14 @@
 import java.util.HashSet;
 import java.util.Set;
 
+/**
+ * Class for exporting vCard.
+ *
+ * Note that this Activity assumes that the instance is a "one-shot Activity", which will be
+ * finished (with the method {@link Activity#finish()}) after the export and never reuse
+ * any Dialog in the instance. So this code is careless about the management around managed
+ * dialogs stuffs (like how onCreateDialog() is used).
+ */
 public class ExportVCardActivity extends Activity {
     private static final String LOG_TAG = "ExportVCardActivity";
 
@@ -102,7 +110,6 @@
         public void onClick(DialogInterface dialog, int which) {
             if (which == DialogInterface.BUTTON_POSITIVE) {
                 mActualExportThread = new ActualExportThread(mFileName);
-                mActualExportThread.start();
                 showDialog(R.id.dialog_exporting_vcard);
             }
         }
@@ -287,26 +294,23 @@
                 return builder.create();
             }
             case R.id.dialog_exporting_vcard: {
-                return getExportingVCardDialog();
+                if (mProgressDialog == null) {
+                    String title = getString(R.string.exporting_contact_list_title);
+                    String message = getString(R.string.exporting_contact_list_message,
+                            mExportingFileName);
+                    mProgressDialog = new ProgressDialog(ExportVCardActivity.this);
+                    mProgressDialog.setTitle(title);
+                    mProgressDialog.setMessage(message);
+                    mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
+                    mProgressDialog.setOnCancelListener(mActualExportThread);
+                    mActualExportThread.start();
+                }
+                return mProgressDialog;
             }
         }
         return super.onCreateDialog(id);
     }
 
-    private Dialog getExportingVCardDialog() {
-        if (mProgressDialog == null) {
-            String title = getString(R.string.exporting_contact_list_title);
-            String message = getString(R.string.exporting_contact_list_message,
-                    mExportingFileName);
-            mProgressDialog = new ProgressDialog(ExportVCardActivity.this);
-            mProgressDialog.setTitle(title);
-            mProgressDialog.setMessage(message);
-            mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
-            mProgressDialog.setOnCancelListener(mActualExportThread);
-        }
-        return mProgressDialog;
-    }
-
     @Override
     protected void onPrepareDialog(int id, Dialog dialog) {
         if (id == R.id.dialog_fail_to_export_with_reason) {
diff --git a/src/com/android/contacts/GroupingListAdapter.java b/src/com/android/contacts/GroupingListAdapter.java
new file mode 100644
index 0000000..9d494a3
--- /dev/null
+++ b/src/com/android/contacts/GroupingListAdapter.java
@@ -0,0 +1,471 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts;
+
+import com.android.internal.util.ArrayUtils;
+
+import android.content.Context;
+import android.database.CharArrayBuffer;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.database.DataSetObserver;
+import android.os.Handler;
+import android.util.SparseIntArray;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+
+/**
+ * Maintains a list that groups adjacent items sharing the same value of
+ * a "group-by" field.  The list has three types of elements: stand-alone, group header and group
+ * child. Groups are collapsible and collapsed by default.
+ */
+public abstract class GroupingListAdapter extends BaseAdapter {
+
+    private static final int GROUP_METADATA_ARRAY_INITIAL_SIZE = 16;
+    private static final int GROUP_METADATA_ARRAY_INCREMENT = 128;
+    private static final long GROUP_OFFSET_MASK    = 0x00000000FFFFFFFFL;
+    private static final long GROUP_SIZE_MASK     = 0x7FFFFFFF00000000L;
+    private static final long EXPANDED_GROUP_MASK = 0x8000000000000000L;
+
+    public static final int ITEM_TYPE_STANDALONE = 0;
+    public static final int ITEM_TYPE_GROUP_HEADER = 1;
+    public static final int ITEM_TYPE_IN_GROUP = 2;
+
+    /**
+     * Information about a specific list item: is it a group, if so is it expanded.
+     * Otherwise, is it a stand-alone item or a group member.
+     */
+    protected static class PositionMetadata {
+        int itemType;
+        boolean isExpanded;
+        int cursorPosition;
+        int childCount;
+        private int groupPosition;
+        private int listPosition = -1;
+    }
+
+    private Context mContext;
+    private Cursor mCursor;
+
+    /**
+     * Count of list items.
+     */
+    private int mCount;
+
+    private int mRowIdColumnIndex;
+
+    /**
+     * Count of groups in the list.
+     */
+    private int mGroupCount;
+
+    /**
+     * Information about where these groups are located in the list, how large they are
+     * and whether they are expanded.
+     */
+    private long[] mGroupMetadata;
+
+    private SparseIntArray mPositionCache = new SparseIntArray();
+    private int mLastCachedListPosition;
+    private int mLastCachedCursorPosition;
+    private int mLastCachedGroup;
+
+    /**
+     * A reusable temporary instance of PositionMetadata
+     */
+    private PositionMetadata mPositionMetadata = new PositionMetadata();
+
+    protected ContentObserver mChangeObserver = new ContentObserver(new Handler()) {
+
+        @Override
+        public boolean deliverSelfNotifications() {
+            return true;
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            onContentChanged();
+        }
+    };
+
+    protected DataSetObserver mDataSetObserver = new DataSetObserver() {
+
+        @Override
+        public void onChanged() {
+            notifyDataSetChanged();
+        }
+
+        @Override
+        public void onInvalidated() {
+            notifyDataSetInvalidated();
+        }
+    };
+
+    public GroupingListAdapter(Context context) {
+        mContext = context;
+        resetCache();
+    }
+
+    /**
+     * Finds all groups of adjacent items in the cursor and calls {@link #addGroup} for
+     * each of them.
+     */
+    protected abstract void addGroups(Cursor cursor);
+
+    protected abstract View newStandAloneView(Context context, ViewGroup parent);
+    protected abstract void bindStandAloneView(View view, Context context, Cursor cursor);
+
+    protected abstract View newGroupView(Context context, ViewGroup parent);
+    protected abstract void bindGroupView(View view, Context context, Cursor cursor, int groupSize,
+            boolean expanded);
+
+    protected abstract View newChildView(Context context, ViewGroup parent);
+    protected abstract void bindChildView(View view, Context context, Cursor cursor);
+
+    /**
+     * Cache should be reset whenever the cursor changes or groups are expanded or collapsed.
+     */
+    private void resetCache() {
+        mCount = -1;
+        mLastCachedListPosition = -1;
+        mLastCachedCursorPosition = -1;
+        mLastCachedGroup = -1;
+        mPositionMetadata.listPosition = -1;
+        mPositionCache.clear();
+    }
+
+    protected void onContentChanged() {
+    }
+
+    public void changeCursor(Cursor cursor) {
+        if (cursor == mCursor) {
+            return;
+        }
+
+        if (mCursor != null) {
+            mCursor.unregisterContentObserver(mChangeObserver);
+            mCursor.unregisterDataSetObserver(mDataSetObserver);
+            mCursor.close();
+        }
+        mCursor = cursor;
+        if (cursor != null) {
+            cursor.registerContentObserver(mChangeObserver);
+            cursor.registerDataSetObserver(mDataSetObserver);
+            mRowIdColumnIndex = cursor.getColumnIndexOrThrow("_id");
+            notifyDataSetChanged();
+        } else {
+            // notify the observers about the lack of a data set
+            notifyDataSetInvalidated();
+        }
+
+        resetCache();
+        findGroups();
+    }
+
+    public Cursor getCursor() {
+        return mCursor;
+    }
+
+    /**
+     * Scans over the entire cursor looking for duplicate phone numbers that need
+     * to be collapsed.
+     */
+    private void findGroups() {
+        mGroupCount = 0;
+        mGroupMetadata = new long[GROUP_METADATA_ARRAY_INITIAL_SIZE];
+
+        if (mCursor == null) {
+            return;
+        }
+
+        addGroups(mCursor);
+    }
+
+    /**
+     * Records information about grouping in the list.  Should be called by the overridden
+     * {@link #addGroups} method.
+     */
+    protected void addGroup(int cursorPosition, int size, boolean expanded) {
+        if (mGroupCount >= mGroupMetadata.length) {
+            int newSize = ArrayUtils.idealLongArraySize(
+                    mGroupMetadata.length + GROUP_METADATA_ARRAY_INCREMENT);
+            long[] array = new long[newSize];
+            System.arraycopy(mGroupMetadata, 0, array, 0, mGroupCount);
+            mGroupMetadata = array;
+        }
+
+        long metadata = ((long)size << 32) | cursorPosition;
+        if (expanded) {
+            metadata |= EXPANDED_GROUP_MASK;
+        }
+        mGroupMetadata[mGroupCount++] = metadata;
+    }
+
+    public int getCount() {
+        if (mCursor == null) {
+            return 0;
+        }
+
+        if (mCount != -1) {
+            return mCount;
+        }
+
+        int cursorPosition = 0;
+        int count = 0;
+        for (int i = 0; i < mGroupCount; i++) {
+            long metadata = mGroupMetadata[i];
+            int offset = (int)(metadata & GROUP_OFFSET_MASK);
+            boolean expanded = (metadata & EXPANDED_GROUP_MASK) != 0;
+            int size = (int)((metadata & GROUP_SIZE_MASK) >> 32);
+
+            count += (offset - cursorPosition);
+
+            if (expanded) {
+                count += size + 1;
+            } else {
+                count++;
+            }
+
+            cursorPosition = offset + size;
+        }
+
+        mCount = count + mCursor.getCount() - cursorPosition;
+        return mCount;
+    }
+
+    /**
+     * Figures out whether the item at the specified position represents a
+     * stand-alone element, a group or a group child. Also computes the
+     * corresponding cursor position.
+     */
+    public void obtainPositionMetadata(PositionMetadata metadata, int position) {
+
+        // If the description object already contains requested information, just return
+        if (metadata.listPosition == position) {
+            return;
+        }
+
+        int listPosition = 0;
+        int cursorPosition = 0;
+        int firstGroupToCheck = 0;
+
+        // Check cache for the supplied position.  What we are looking for is
+        // the group descriptor immediately preceding the supplied position.
+        // Once we have that, we will be able to tell whether the position
+        // is the header of the group, a member of the group or a standalone item.
+        if (mLastCachedListPosition != -1) {
+            if (position <= mLastCachedListPosition) {
+
+                // Have SparceIntArray do a binary search for us.
+                int index = mPositionCache.indexOfKey(position);
+
+                // If we get back a positive number, the position corresponds to
+                // a group header.
+                if (index < 0) {
+
+                    // We had a cache miss, but we did obtain valuable information anyway.
+                    // The negative number will allow us to compute the location of
+                    // the group header immediately preceding the supplied position.
+                    index = ~index - 1;
+
+                    if (index >= mPositionCache.size()) {
+                        index--;
+                    }
+                }
+
+                // A non-negative index gives us the position of the group header
+                // corresponding or preceding the position, so we can
+                // search for the group information at the supplied position
+                // starting with the cached group we just found
+                if (index >= 0) {
+                    listPosition = mPositionCache.keyAt(index);
+                    firstGroupToCheck = mPositionCache.valueAt(index);
+                    long descriptor = mGroupMetadata[firstGroupToCheck];
+                    cursorPosition = (int)(descriptor & GROUP_OFFSET_MASK);
+                }
+            } else {
+
+                // If we haven't examined groups beyond the supplied position,
+                // we will start where we left off previously
+                firstGroupToCheck = mLastCachedGroup;
+                listPosition = mLastCachedListPosition;
+                cursorPosition = mLastCachedCursorPosition;
+            }
+        }
+
+        for (int i = firstGroupToCheck; i < mGroupCount; i++) {
+            long group = mGroupMetadata[i];
+            int offset = (int)(group & GROUP_OFFSET_MASK);
+
+            // Move pointers to the beginning of the group
+            listPosition += (offset - cursorPosition);
+            cursorPosition = offset;
+
+            if (i > mLastCachedGroup) {
+                mPositionCache.append(listPosition, i);
+                mLastCachedListPosition = listPosition;
+                mLastCachedCursorPosition = cursorPosition;
+                mLastCachedGroup = i;
+            }
+
+            // Now we have several possibilities:
+            // A) The requested position precedes the group
+            if (position < listPosition) {
+                metadata.itemType = ITEM_TYPE_STANDALONE;
+                metadata.cursorPosition = cursorPosition - (listPosition - position);
+                return;
+            }
+
+            boolean expanded = (group & EXPANDED_GROUP_MASK) != 0;
+            int size = (int) ((group & GROUP_SIZE_MASK) >> 32);
+
+            // B) The requested position is a group header
+            if (position == listPosition) {
+                metadata.itemType = ITEM_TYPE_GROUP_HEADER;
+                metadata.groupPosition = i;
+                metadata.isExpanded = expanded;
+                metadata.childCount = size;
+                metadata.cursorPosition = offset;
+                return;
+            }
+
+            if (expanded) {
+                // C) The requested position is an element in the expanded group
+                if (position < listPosition + size + 1) {
+                    metadata.itemType = ITEM_TYPE_IN_GROUP;
+                    metadata.cursorPosition = cursorPosition + (position - listPosition) - 1;
+                    return;
+                }
+
+                // D) The element is past the expanded group
+                listPosition += size + 1;
+            } else {
+
+                // E) The element is past the collapsed group
+                listPosition++;
+            }
+
+            // Move cursor past the group
+            cursorPosition += size;
+        }
+
+        // The required item is past the last group
+        metadata.itemType = ITEM_TYPE_STANDALONE;
+        metadata.cursorPosition = cursorPosition + (position - listPosition);
+    }
+
+    /**
+     * Returns true if the specified position in the list corresponds to a
+     * group header.
+     */
+    public boolean isGroupHeader(int position) {
+        obtainPositionMetadata(mPositionMetadata, position);
+        return mPositionMetadata.itemType == ITEM_TYPE_GROUP_HEADER;
+    }
+
+    /**
+     * Given a position of a groups header in the list, returns the size of
+     * the corresponding group.
+     */
+    public int getGroupSize(int position) {
+        obtainPositionMetadata(mPositionMetadata, position);
+        return mPositionMetadata.childCount;
+    }
+
+    /**
+     * Mark group as expanded if it is collapsed and vice versa.
+     */
+    public void toggleGroup(int position) {
+        obtainPositionMetadata(mPositionMetadata, position);
+        if (mPositionMetadata.itemType != ITEM_TYPE_GROUP_HEADER) {
+            throw new IllegalArgumentException("Not a group at position " + position);
+        }
+
+
+        if (mPositionMetadata.isExpanded) {
+            mGroupMetadata[mPositionMetadata.groupPosition] &= ~EXPANDED_GROUP_MASK;
+        } else {
+            mGroupMetadata[mPositionMetadata.groupPosition] |= EXPANDED_GROUP_MASK;
+        }
+        resetCache();
+        notifyDataSetChanged();
+    }
+
+    @Override
+    public int getViewTypeCount() {
+        return 3;
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        obtainPositionMetadata(mPositionMetadata, position);
+        return mPositionMetadata.itemType;
+    }
+
+    public Object getItem(int position) {
+        obtainPositionMetadata(mPositionMetadata, position);
+        if (mCursor.moveToPosition(mPositionMetadata.cursorPosition)) {
+            return mCursor;
+        } else {
+            return null;
+        }
+    }
+
+    public long getItemId(int position) {
+        Object item = getItem(position);
+        if (item != null) {
+            return mCursor.getLong(mRowIdColumnIndex);
+        } else {
+            return -1;
+        }
+    }
+
+    public View getView(int position, View convertView, ViewGroup parent) {
+        obtainPositionMetadata(mPositionMetadata, position);
+        View view = convertView;
+        if (view == null) {
+            switch (mPositionMetadata.itemType) {
+                case ITEM_TYPE_STANDALONE:
+                    view = newStandAloneView(mContext, parent);
+                    break;
+                case ITEM_TYPE_GROUP_HEADER:
+                    view = newGroupView(mContext, parent);
+                    break;
+                case ITEM_TYPE_IN_GROUP:
+                    view = newChildView(mContext, parent);
+                    break;
+            }
+        }
+
+        mCursor.moveToPosition(mPositionMetadata.cursorPosition);
+        switch (mPositionMetadata.itemType) {
+            case ITEM_TYPE_STANDALONE:
+                bindStandAloneView(view, mContext, mCursor);
+                break;
+            case ITEM_TYPE_GROUP_HEADER:
+                bindGroupView(view, mContext, mCursor, mPositionMetadata.childCount,
+                        mPositionMetadata.isExpanded);
+                break;
+            case ITEM_TYPE_IN_GROUP:
+                bindChildView(view, mContext, mCursor);
+                break;
+
+        }
+        return view;
+    }
+}
diff --git a/src/com/android/contacts/ImportVCardActivity.java b/src/com/android/contacts/ImportVCardActivity.java
index 8fd9c0d..b306528 100644
--- a/src/com/android/contacts/ImportVCardActivity.java
+++ b/src/com/android/contacts/ImportVCardActivity.java
@@ -30,11 +30,11 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.PowerManager;
-import android.pim.vcard.EntryCommitter;
-import android.pim.vcard.VCardBuilder;
-import android.pim.vcard.VCardBuilderCollection;
+import android.pim.vcard.VCardEntryCommitter;
+import android.pim.vcard.VCardInterpreter;
+import android.pim.vcard.VCardInterpreterCollection;
 import android.pim.vcard.VCardConfig;
-import android.pim.vcard.VCardDataBuilder;
+import android.pim.vcard.VCardEntryConstructor;
 import android.pim.vcard.VCardEntryCounter;
 import android.pim.vcard.VCardParser_V21;
 import android.pim.vcard.VCardParser_V30;
@@ -90,6 +90,11 @@
 /**
  * Class for importing vCard. Several user interaction will be required while reading
  * (selecting a file, waiting a moment, etc.)
+ *
+ * Note that this Activity assumes that the instance is a "one-shot Activity", which will be
+ * finished (with the method {@link Activity#finish()}) after the import and never reuse
+ * any Dialog in the instance. So this code is careless about the management around managed
+ * dialogs stuffs (like how onCreateDialog() is used).
  */
 public class ImportVCardActivity extends Activity {
     private static final String LOG_TAG = "ImportVCardActivity";
@@ -197,7 +202,7 @@
                     }
                     VCardEntryCounter counter = new VCardEntryCounter();
                     VCardSourceDetector detector = new VCardSourceDetector();
-                    VCardBuilderCollection builderCollection = new VCardBuilderCollection(
+                    VCardInterpreterCollection builderCollection = new VCardInterpreterCollection(
                             Arrays.asList(counter, detector));
 
                     boolean result;
@@ -290,17 +295,17 @@
                 String charset, boolean showEntryParseProgress,
                 VCardSourceDetector detector, List<String> errorFileNameList) {
             final Context context = ImportVCardActivity.this;
-            VCardDataBuilder builder;
+            VCardEntryConstructor builder;
             final String currentLanguage = Locale.getDefault().getLanguage();
             int vcardType = VCardConfig.getVCardTypeFromString(
                     context.getString(R.string.config_import_vcard_type));
             if (charset != null) {
-                builder = new VCardDataBuilder(charset, charset, false, vcardType, mAccount);
+                builder = new VCardEntryConstructor(charset, charset, false, vcardType, mAccount);
             } else {
                 charset = VCardConfig.DEFAULT_CHARSET;
-                builder = new VCardDataBuilder(null, null, false, vcardType, mAccount);
+                builder = new VCardEntryConstructor(null, null, false, vcardType, mAccount);
             }
-            builder.addEntryHandler(new EntryCommitter(mResolver));
+            builder.addEntryHandler(new VCardEntryCommitter(mResolver));
             if (showEntryParseProgress) {
                 builder.addEntryHandler(new ProgressShower(mProgressDialogForReadVCard,
                         context.getString(R.string.reading_vcard_message),
@@ -319,7 +324,7 @@
         }
 
         private boolean readOneVCardFile(String canonicalPath, String charset,
-                VCardBuilder builder, VCardSourceDetector detector,
+                VCardInterpreter builder, VCardSourceDetector detector,
                 boolean throwNestedException, List<String> errorFileNameList)
                 throws VCardNestedException {
             FileInputStream is;
@@ -334,6 +339,10 @@
                         is.close();
                     } catch (IOException e) {
                     }
+                    if (builder instanceof VCardEntryConstructor) {
+                        // Let the object clean up internal temporal objects,
+                        ((VCardEntryConstructor)builder).clear();
+                    }
                     is = new FileInputStream(canonicalPath);
 
                     try {
@@ -673,20 +682,6 @@
         return builder.create();
     }
 
-    private Dialog getReadingVCardDialog() {
-        if (mProgressDialogForReadVCard == null) {
-            String title = getString(R.string.reading_vcard_title);
-            String message = getString(R.string.reading_vcard_message);
-            mProgressDialogForReadVCard = new ProgressDialog(this);
-            mProgressDialogForReadVCard.setTitle(title);
-            mProgressDialogForReadVCard.setMessage(message);
-            mProgressDialogForReadVCard.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
-            mProgressDialogForReadVCard.setOnCancelListener(mVCardReadThread);
-            mVCardReadThread.start();
-        }
-        return mProgressDialogForReadVCard;
-    }
-
     @Override
     protected void onCreate(Bundle bundle) {
         super.onCreate(bundle);
@@ -748,7 +743,17 @@
                 return getVCardFileSelectDialog(false);
             }
             case R.id.dialog_reading_vcard: {
-                return getReadingVCardDialog();
+                if (mProgressDialogForReadVCard == null) {
+                    String title = getString(R.string.reading_vcard_title);
+                    String message = getString(R.string.reading_vcard_message);
+                    mProgressDialogForReadVCard = new ProgressDialog(this);
+                    mProgressDialogForReadVCard.setTitle(title);
+                    mProgressDialogForReadVCard.setMessage(message);
+                    mProgressDialogForReadVCard.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
+                    mProgressDialogForReadVCard.setOnCancelListener(mVCardReadThread);
+                    mVCardReadThread.start();
+                }
+                return mProgressDialogForReadVCard;
             }
             case R.id.dialog_io_exception: {
                 String message = (getString(R.string.scanning_sdcard_failed_message,
diff --git a/src/com/android/contacts/PhoneDisambigDialog.java b/src/com/android/contacts/PhoneDisambigDialog.java
index b727c77..e0295e3 100644
--- a/src/com/android/contacts/PhoneDisambigDialog.java
+++ b/src/com/android/contacts/PhoneDisambigDialog.java
@@ -136,11 +136,11 @@
 
     private class PhoneItem implements Collapsible<PhoneItem> {
 
-        String phoneNumber;
-        long id;
+        final String phoneNumber;
+        final long id;
 
         public PhoneItem(String newPhoneNumber, long newId) {
-            phoneNumber = newPhoneNumber;
+            phoneNumber = (newPhoneNumber != null ? newPhoneNumber : "");
             id = newId;
         }
 
@@ -160,6 +160,7 @@
             return false;
         }
 
+        @Override
         public String toString() {
             return phoneNumber;
         }
diff --git a/src/com/android/contacts/ProgressShower.java b/src/com/android/contacts/ProgressShower.java
index c1a2493..a5ad2a2 100644
--- a/src/com/android/contacts/ProgressShower.java
+++ b/src/com/android/contacts/ProgressShower.java
@@ -18,12 +18,12 @@
 import android.app.ProgressDialog;
 import android.content.Context;
 import android.os.Handler;
-import android.pim.vcard.ContactStruct;
-import android.pim.vcard.EntryHandler;
+import android.pim.vcard.VCardEntry;
+import android.pim.vcard.VCardEntryHandler;
 import android.pim.vcard.VCardConfig;
 import android.util.Log;
 
-public class ProgressShower implements EntryHandler {
+public class ProgressShower implements VCardEntryHandler {
     public static final String LOG_TAG = "vcard.ProgressShower"; 
 
     private final Context mContext;
@@ -34,9 +34,9 @@
     private long mTime;
     
     private class ShowProgressRunnable implements Runnable {
-        private ContactStruct mContact;
+        private VCardEntry mContact;
         
-        public ShowProgressRunnable(ContactStruct contact) {
+        public ShowProgressRunnable(VCardEntry contact) {
             mContact = contact;
         }
         
@@ -57,10 +57,10 @@
         mProgressMessage = progressMessage;
     }
 
-    public void onParsingStart() {
+    public void onStart() {
     }
 
-    public void onEntryCreated(ContactStruct contactStruct) {
+    public void onEntryCreated(VCardEntry contactStruct) {
         long start = System.currentTimeMillis();
         
         if (!contactStruct.isIgnorable()) {
@@ -78,7 +78,7 @@
         mTime += System.currentTimeMillis() - start;
     }
 
-    public void onParsingEnd() {
+    public void onEnd() {
         if (VCardConfig.showPerformanceLog()) {
             Log.d(LOG_TAG,
                     String.format("Time to progress a dialog: %d ms", mTime));
diff --git a/src/com/android/contacts/RecentCallsListActivity.java b/src/com/android/contacts/RecentCallsListActivity.java
index 547cd45..8e553d1 100644
--- a/src/com/android/contacts/RecentCallsListActivity.java
+++ b/src/com/android/contacts/RecentCallsListActivity.java
@@ -16,6 +16,9 @@
 
 package com.android.contacts;
 
+import com.android.internal.telephony.CallerInfo;
+import com.android.internal.telephony.ITelephony;
+
 import android.app.ListActivity;
 import android.content.ActivityNotFoundException;
 import android.content.AsyncQueryHandler;
@@ -23,9 +26,11 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
+import android.database.CharArrayBuffer;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabaseCorruptException;
 import android.database.sqlite.SQLiteDiskIOException;
+import android.database.sqlite.SQLiteException;
 import android.database.sqlite.SQLiteFullException;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
@@ -38,10 +43,10 @@
 import android.os.SystemClock;
 import android.provider.CallLog;
 import android.provider.CallLog.Calls;
+import android.provider.ContactsContract.Intents.Insert;
 import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.PhoneLookup;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.Contacts.Intents.Insert;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.TelephonyManager;
 import android.text.SpannableStringBuilder;
@@ -50,6 +55,7 @@
 import android.util.Log;
 import android.view.ContextMenu;
 import android.view.KeyEvent;
+import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
@@ -60,16 +66,12 @@
 import android.widget.AdapterView;
 import android.widget.ImageView;
 import android.widget.ListView;
-import android.widget.ResourceCursorAdapter;
 import android.widget.TextView;
 
-import com.android.internal.telephony.CallerInfo;
-import com.android.internal.telephony.ITelephony;
-
+import java.lang.ref.WeakReference;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.Locale;
-import java.lang.ref.WeakReference;
 
 /**
  * Displays a list of call log entries.
@@ -143,6 +145,8 @@
         TextView dateView;
         ImageView iconView;
         View callView;
+        ImageView groupIndicator;
+        TextView groupSize;
     }
 
     static final class CallerInfoQuery {
@@ -171,7 +175,7 @@
     private static int sFormattingType = FORMATTING_TYPE_INVALID;
 
     /** Adapter class to fill in data for the Call Log */
-    final class RecentCallsAdapter extends ResourceCursorAdapter
+    final class RecentCallsAdapter extends GroupingListAdapter
             implements Runnable, ViewTreeObserver.OnPreDrawListener, View.OnClickListener {
         HashMap<String,ContactInfo> mContactInfo;
         private final LinkedList<CallerInfoQuery> mRequests;
@@ -189,6 +193,12 @@
         private Drawable mDrawableOutgoing;
         private Drawable mDrawableMissed;
 
+        /**
+         * Reusable char array buffers.
+         */
+        private CharArrayBuffer mBuffer1 = new CharArrayBuffer(128);
+        private CharArrayBuffer mBuffer2 = new CharArrayBuffer(128);
+
         public void onClick(View view) {
             String number = (String) view.getTag();
             if (!TextUtils.isEmpty(number)) {
@@ -220,7 +230,7 @@
         };
 
         public RecentCallsAdapter() {
-            super(RecentCallsListActivity.this, R.layout.recent_calls_list_item, null);
+            super(RecentCallsListActivity.this);
 
             mContactInfo = new HashMap<String,ContactInfo>();
             mRequests = new LinkedList<CallerInfoQuery>();
@@ -388,8 +398,116 @@
         }
 
         @Override
-        public View newView(Context context, Cursor cursor, ViewGroup parent) {
-            View view = super.newView(context, cursor, parent);
+        protected void addGroups(Cursor cursor) {
+
+            int count = cursor.getCount();
+            if (count == 0) {
+                return;
+            }
+
+            int groupItemCount = 1;
+
+            CharArrayBuffer currentValue = mBuffer1;
+            CharArrayBuffer value = mBuffer2;
+            cursor.moveToFirst();
+            cursor.copyStringToBuffer(NUMBER_COLUMN_INDEX, currentValue);
+            int currentCallType = cursor.getInt(CALL_TYPE_COLUMN_INDEX);
+            for (int i = 1; i < count; i++) {
+                cursor.moveToNext();
+                cursor.copyStringToBuffer(NUMBER_COLUMN_INDEX, value);
+                boolean sameNumber = equalPhoneNumbers(value, currentValue);
+
+                // Group adjacent calls with the same number. Make an exception
+                // for the latest item if it was a missed call.  We don't want
+                // a missed call to be hidden inside a group.
+                if (sameNumber && currentCallType != Calls.MISSED_TYPE) {
+                    groupItemCount++;
+                } else {
+                    if (groupItemCount > 1) {
+                        addGroup(i - groupItemCount, groupItemCount, false);
+                    }
+
+                    groupItemCount = 1;
+
+                    // Swap buffers
+                    CharArrayBuffer temp = currentValue;
+                    currentValue = value;
+                    value = temp;
+
+                    // If we have just examined a row following a missed call, make
+                    // sure that it is grouped with subsequent calls from the same number
+                    // even if it was also missed.
+                    if (sameNumber && currentCallType == Calls.MISSED_TYPE) {
+                        currentCallType = 0;       // "not a missed call"
+                    } else {
+                        currentCallType = cursor.getInt(CALL_TYPE_COLUMN_INDEX);
+                    }
+                }
+            }
+            if (groupItemCount > 1) {
+                addGroup(count - groupItemCount, groupItemCount, false);
+            }
+        }
+
+        protected boolean equalPhoneNumbers(CharArrayBuffer buffer1, CharArrayBuffer buffer2) {
+
+            // TODO add PhoneNumberUtils.compare(CharSequence, CharSequence) to avoid
+            // string allocation
+            return PhoneNumberUtils.compare(new String(buffer1.data, 0, buffer1.sizeCopied),
+                    new String(buffer2.data, 0, buffer2.sizeCopied));
+        }
+
+
+        @Override
+        protected View newStandAloneView(Context context, ViewGroup parent) {
+            LayoutInflater inflater =
+                    (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+            View view = inflater.inflate(R.layout.recent_calls_list_item, parent, false);
+            findAndCacheViews(view);
+            return view;
+        }
+
+        @Override
+        protected void bindStandAloneView(View view, Context context, Cursor cursor) {
+            bindView(context, view, cursor);
+        }
+
+        @Override
+        protected View newChildView(Context context, ViewGroup parent) {
+            LayoutInflater inflater =
+                    (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+            View view = inflater.inflate(R.layout.recent_calls_list_child_item, parent, false);
+            findAndCacheViews(view);
+            return view;
+        }
+
+        @Override
+        protected void bindChildView(View view, Context context, Cursor cursor) {
+            bindView(context, view, cursor);
+        }
+
+        @Override
+        protected View newGroupView(Context context, ViewGroup parent) {
+            LayoutInflater inflater =
+                    (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+            View view = inflater.inflate(R.layout.recent_calls_list_group_item, parent, false);
+            findAndCacheViews(view);
+            return view;
+        }
+
+        @Override
+        protected void bindGroupView(View view, Context context, Cursor cursor, int groupSize,
+                boolean expanded) {
+            final RecentCallsListItemViews views = (RecentCallsListItemViews) view.getTag();
+            int groupIndicator = expanded
+                    ? com.android.internal.R.drawable.expander_ic_maximized
+                    : com.android.internal.R.drawable.expander_ic_minimized;
+            views.groupIndicator.setImageResource(groupIndicator);
+            views.groupSize.setText("(" + groupSize + ")");
+            bindView(context, view, cursor);
+        }
+
+        private void findAndCacheViews(View view) {
 
             // Get the views to bind to
             RecentCallsListItemViews views = new RecentCallsListItemViews();
@@ -400,15 +518,12 @@
             views.iconView = (ImageView) view.findViewById(R.id.call_type_icon);
             views.callView = view.findViewById(R.id.call_icon);
             views.callView.setOnClickListener(this);
-
+            views.groupIndicator = (ImageView) view.findViewById(R.id.groupIndicator);
+            views.groupSize = (TextView) view.findViewById(R.id.groupSize);
             view.setTag(views);
-
-            return view;
         }
 
-
-        @Override
-        public void bindView(View view, Context context, Cursor c) {
+        public void bindView(Context context, View view, Cursor c) {
             final RecentCallsListItemViews views = (RecentCallsListItemViews) view.getTag();
 
             String number = c.getString(NUMBER_COLUMN_INDEX);
@@ -501,7 +616,6 @@
                 views.labelView.setVisibility(View.GONE);
             }
 
-            int type = c.getInt(CALL_TYPE_COLUMN_INDEX);
             long date = c.getLong(DATE_COLUMN_INDEX);
 
             // Set the date/time field by mixing relative and absolute times.
@@ -510,19 +624,22 @@
             views.dateView.setText(DateUtils.getRelativeTimeSpanString(date,
                     System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, flags));
 
-            // Set the icon
-            switch (type) {
-                case Calls.INCOMING_TYPE:
-                    views.iconView.setImageDrawable(mDrawableIncoming);
-                    break;
+            if (views.iconView != null) {
+                int type = c.getInt(CALL_TYPE_COLUMN_INDEX);
+                // Set the icon
+                switch (type) {
+                    case Calls.INCOMING_TYPE:
+                        views.iconView.setImageDrawable(mDrawableIncoming);
+                        break;
 
-                case Calls.OUTGOING_TYPE:
-                    views.iconView.setImageDrawable(mDrawableOutgoing);
-                    break;
+                    case Calls.OUTGOING_TYPE:
+                        views.iconView.setImageDrawable(mDrawableOutgoing);
+                        break;
 
-                case Calls.MISSED_TYPE:
-                    views.iconView.setImageDrawable(mDrawableMissed);
-                    break;
+                    case Calls.MISSED_TYPE:
+                        views.iconView.setImageDrawable(mDrawableMissed);
+                        break;
+                }
             }
 
             // Listen for the first draw
@@ -819,12 +936,24 @@
 
         switch (item.getItemId()) {
             case MENU_ITEM_DELETE: {
-                Cursor cursor = mAdapter.getCursor();
-                if (cursor != null) {
-                    cursor.moveToPosition(menuInfo.position);
-                    cursor.deleteRow();
+                Cursor cursor = (Cursor)mAdapter.getItem(menuInfo.position);
+                int groupSize = 1;
+                if (mAdapter.isGroupHeader(menuInfo.position)) {
+                    groupSize = mAdapter.getGroupSize(menuInfo.position);
                 }
-                return true;
+
+                StringBuilder sb = new StringBuilder();
+                for (int i = 0; i < groupSize; i++) {
+                    if (i != 0) {
+                        sb.append(",");
+                        cursor.moveToNext();
+                    }
+                    long id = cursor.getLong(ID_COLUMN_INDEX);
+                    sb.append(id);
+                }
+
+                getContentResolver().delete(Calls.CONTENT_URI, Calls._ID + " IN (" + sb + ")",
+                        null);
             }
         }
         return super.onContextItemSelected(item);
@@ -946,8 +1075,12 @@
 
     @Override
     protected void onListItemClick(ListView l, View v, int position, long id) {
-        Intent intent = new Intent(this, CallDetailActivity.class);
-        intent.setData(ContentUris.withAppendedId(CallLog.Calls.CONTENT_URI, id));
-        startActivity(intent);
+        if (mAdapter.isGroupHeader(position)) {
+            mAdapter.toggleGroup(position);
+        } else {
+            Intent intent = new Intent(this, CallDetailActivity.class);
+            intent.setData(ContentUris.withAppendedId(CallLog.Calls.CONTENT_URI, id));
+            startActivity(intent);
+        }
     }
 }
diff --git a/src/com/android/contacts/SplitAggregateView.java b/src/com/android/contacts/SplitAggregateView.java
index ed78fd9..b85a4ab 100644
--- a/src/com/android/contacts/SplitAggregateView.java
+++ b/src/com/android/contacts/SplitAggregateView.java
@@ -18,7 +18,6 @@
 
 import com.android.contacts.model.ContactsSource;
 import com.android.contacts.model.Sources;
-import com.google.common.util.text.TextUtil;
 
 import android.content.Context;
 import android.content.pm.PackageManager;
diff --git a/src/com/android/contacts/TextHighlightingAnimation.java b/src/com/android/contacts/TextHighlightingAnimation.java
new file mode 100644
index 0000000..3bf6499
--- /dev/null
+++ b/src/com/android/contacts/TextHighlightingAnimation.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.contacts;
+
+import com.android.internal.R;
+
+import android.database.CharArrayBuffer;
+import android.graphics.Color;
+import android.os.Handler;
+import android.text.Spanned;
+import android.text.TextPaint;
+import android.text.style.CharacterStyle;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+
+/**
+ * An animation that alternately dims and brightens the non-highlighted portion of text.
+ */
+public abstract class TextHighlightingAnimation implements Runnable {
+
+    private static final int MAX_ALPHA = 255;
+    private static final int MIN_ALPHA = 50;
+
+    private AccelerateInterpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator();
+    private DecelerateInterpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator();
+
+    private final static DimmingSpan[] sEmptySpans = new DimmingSpan[0];
+
+    private DimmingSpan mDimmingSpan;
+    private Handler mHandler;
+    private boolean mAnimating;
+    private boolean mDimming;
+    private long mTargetTime;
+    private final int mDuration;
+
+    /**
+     * A Spanned that highlights a part of text by dimming another part of that text.
+     */
+    public class TextWithHighlighting implements Spanned {
+
+        private final DimmingSpan[] mSpans;
+        private boolean mDimmingEnabled;
+        private CharArrayBuffer mText;
+        private int mDimmingSpanStart;
+        private int mDimmingSpanEnd;
+        private String mString;
+
+        public TextWithHighlighting() {
+            mSpans = new DimmingSpan[] { mDimmingSpan };
+        }
+
+        public void setText(CharArrayBuffer baseText, CharArrayBuffer highlightedText) {
+            mText = baseText;
+
+            // TODO figure out a way to avoid string allocation
+            mString = new String(mText.data, 0, mText.sizeCopied);
+
+            int index = indexOf(baseText, highlightedText);
+
+            if (index == 0 || index == -1) {
+                mDimmingEnabled = false;
+            } else {
+                mDimmingEnabled = true;
+                mDimmingSpanStart = 0;
+                mDimmingSpanEnd = index;
+            }
+        }
+
+        /**
+         * An implementation of indexOf on CharArrayBuffers that finds the first match of
+         * the start of buffer2 in buffer1.  For example, indexOf("abcd", "cdef") == 2
+         */
+        private int indexOf(CharArrayBuffer buffer1, CharArrayBuffer buffer2) {
+            char[] string1 = buffer1.data;
+            char[] string2 = buffer2.data;
+            int count1 = buffer1.sizeCopied;
+            int count2 = buffer2.sizeCopied;
+            int size = count2;
+            for (int i = 0; i < count1; i++) {
+                if (i + size > count1) {
+                    size = count1 - i;
+                }
+                int j;
+                for (j = 0; j < size; j++) {
+                    if (string1[i+j] != string2[j]) {
+                        break;
+                    }
+                }
+                if (j == size) {
+                    return i;
+                }
+            }
+
+            return -1;
+        }
+
+
+        @SuppressWarnings("unchecked")
+        public <T> T[] getSpans(int start, int end, Class<T> type) {
+            if (mDimmingEnabled) {
+                return (T[])mSpans;
+            } else {
+                return (T[])sEmptySpans;
+            }
+        }
+
+        public int getSpanStart(Object tag) {
+            // We only have one span - no need to check the tag parameter
+            return mDimmingSpanStart;
+        }
+
+        public int getSpanEnd(Object tag) {
+            // We only have one span - no need to check the tag parameter
+            return mDimmingSpanEnd;
+        }
+
+        public int getSpanFlags(Object tag) {
+            // String is immutable - flags not needed
+            return 0;
+        }
+
+        public int nextSpanTransition(int start, int limit, Class type) {
+            // Never called since we only have one span
+            return 0;
+        }
+
+        public char charAt(int index) {
+            return mText.data[index];
+        }
+
+        public int length() {
+            return mText.sizeCopied;
+        }
+
+        public CharSequence subSequence(int start, int end) {
+            // Never called - implementing for completeness
+            return new String(mText.data, start, end);
+        }
+
+        @Override
+        public String toString() {
+            return mString;
+        }
+    }
+
+    /**
+     * A Span that modifies alpha of the default foreground color.
+     */
+    private static class DimmingSpan extends CharacterStyle {
+        private int mAlpha;
+
+        public void setAlpha(int alpha) {
+            mAlpha = alpha;
+        }
+
+        @Override
+        public void updateDrawState(TextPaint ds) {
+
+            // Only dim the text in the basic state; not selected, focused or pressed
+            int[] states = ds.drawableState;
+            if (states != null) {
+                int count = states.length;
+                for (int i = 0; i < count; i++) {
+                    switch (states[i]) {
+                        case R.attr.state_pressed:
+                        case R.attr.state_selected:
+                        case R.attr.state_focused:
+                            // We can simply return, because the supplied text
+                            // paint is already configured with defaults.
+                            return;
+                    }
+                }
+            }
+
+            int color = ds.getColor();
+            color = Color.argb(mAlpha, Color.red(color), Color.green(color), Color.blue(color));
+            ds.setColor(color);
+        }
+    }
+
+    /**
+     * Constructor.
+     */
+    public TextHighlightingAnimation(int duration) {
+        mDuration = duration;
+        mHandler = new Handler();
+        mDimmingSpan = new DimmingSpan();
+        mDimmingSpan.setAlpha(MAX_ALPHA);
+    }
+
+    /**
+     * Returns a Spanned that can be used by a text view to show text with highlighting.
+     */
+    public TextWithHighlighting createTextWithHighlighting() {
+        return new TextWithHighlighting();
+    }
+
+    /**
+     * Override and invalidate (redraw) TextViews showing {@link TextWithHighlighting}.
+     */
+    protected abstract void invalidate();
+
+    /**
+     * Starts the highlighting animation, which will dim portions of text.
+     */
+    public void startHighlighting() {
+        startAnimation(true);
+    }
+
+    /**
+     * Starts un-highlighting animation, which will brighten the dimmed portions of text
+     * to the brightness level of the rest of text.
+     */
+    public void stopHighlighting() {
+        startAnimation(false);
+    }
+
+    /**
+     * Called when the animation starts.
+     */
+    protected void onAnimationStarted() {
+    }
+
+    /**
+     * Called when the animation has stopped.
+     */
+    protected void onAnimationEnded() {
+    }
+
+    private void startAnimation(boolean dim) {
+        if (mDimming != dim) {
+            mDimming = dim;
+            long now = System.currentTimeMillis();
+            if (!mAnimating) {
+                mAnimating = true;
+                mTargetTime = now + mDuration;
+                onAnimationStarted();
+                mHandler.post(this);
+            } else  {
+
+                // If we have started dimming, reverse the direction and adjust the target
+                // time accordingly.
+                mTargetTime = (now + mDuration) - (mTargetTime - now);
+            }
+        }
+    }
+
+    /**
+     * Animation step.
+     */
+    public void run() {
+        long now = System.currentTimeMillis();
+        long timeLeft = mTargetTime - now;
+        if (timeLeft < 0) {
+            mDimmingSpan.setAlpha(mDimming ? MIN_ALPHA : MAX_ALPHA);
+            mAnimating = false;
+            onAnimationEnded();
+            return;
+        }
+
+        // Start=1, end=0
+        float virtualTime = (float)timeLeft / mDuration;
+        if (mDimming) {
+            float interpolatedTime = DECELERATE_INTERPOLATOR.getInterpolation(virtualTime);
+            mDimmingSpan.setAlpha((int)(MIN_ALPHA + (MAX_ALPHA-MIN_ALPHA) * interpolatedTime));
+        } else {
+            float interpolatedTime = ACCELERATE_INTERPOLATOR.getInterpolation(virtualTime);
+            mDimmingSpan.setAlpha((int)(MIN_ALPHA + (MAX_ALPHA-MIN_ALPHA) * (1-interpolatedTime)));
+        }
+
+        invalidate();
+
+        // Repeat
+        mHandler.post(this);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/TwelveKeyDialer.java b/src/com/android/contacts/TwelveKeyDialer.java
index 2d8b4e6..c0390eb 100644
--- a/src/com/android/contacts/TwelveKeyDialer.java
+++ b/src/com/android/contacts/TwelveKeyDialer.java
@@ -34,7 +34,6 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
-import android.os.Vibrator;
 import android.provider.Contacts.Intents.Insert;
 import android.provider.Contacts.People;
 import android.provider.Contacts.Phones;
@@ -66,6 +65,8 @@
 import android.widget.TextView;
 
 import com.android.internal.telephony.ITelephony;
+import com.android.phone.CallLogAsync;
+import com.android.phone.HapticFeedback;
 
 /**
  * Dialer activity that displays the typical twelve key interface.
@@ -73,7 +74,7 @@
 public class TwelveKeyDialer extends Activity implements View.OnClickListener,
         View.OnLongClickListener, View.OnKeyListener,
         AdapterView.OnItemClickListener, TextWatcher {
-
+    private static final String EMPTY_NUMBER = "";
     private static final String TAG = "TwelveKeyDialer";
 
     /** The length of DTMF tones in milliseconds */
@@ -85,9 +86,6 @@
     /** Stream type used to play the DTMF tones off call, and mapped to the volume control keys */
     private static final int DIAL_TONE_STREAM_TYPE = AudioManager.STREAM_MUSIC;
 
-    /** Play the vibrate pattern only once. */
-    private static final int VIBRATE_NO_REPEAT = -1;
-
     private EditText mDigits;
     private View mDelete;
     private MenuItem mAddToContactMenuItem;
@@ -108,14 +106,17 @@
     private static final int MENU_2S_PAUSE = 2;
     private static final int MENU_WAIT = 3;
 
+    // Last number dialed, retrieved asynchronously from the call DB
+    // in onCreate. This number is displayed when the user hits the
+    // send key and cleared in onPause.
+    CallLogAsync mCallLog = new CallLogAsync();
+    private String mLastNumberDialed = EMPTY_NUMBER;
+
     // determines if we want to playback local DTMF tones.
     private boolean mDTMFToneEnabled;
 
     // Vibration (haptic feedback) for dialer key presses.
-    private Vibrator mVibrator;
-    private boolean mVibrateOn;
-    private long[] mVibratePattern;
-
+    private HapticFeedback mHaptic = new HapticFeedback();
 
     /** Identifier for the "Add Call" intent extra. */
     static final String ADD_CALL_MODE_KEY = "add_call_mode";
@@ -175,15 +176,14 @@
             mDigits.getText().clear();
         }
 
-        final boolean notEmpty = mDigits.length() != 0;
-        if (notEmpty) {
+        if (!isDigitsEmpty()) {
             mDigits.setBackgroundDrawable(mDigitsBackground);
         } else {
             mDigits.setCursorVisible(false);
             mDigits.setBackgroundDrawable(mDigitsEmptyBackground);
         }
 
-        updateDialAndDeleteButtonStateEnabledAttr();
+        updateDialAndDeleteButtonEnabledState();
     }
 
     @Override
@@ -249,10 +249,12 @@
             super.onRestoreInstanceState(icicle);
         }
 
-        // TODO: We might eventually need to make mVibrateOn come from a
-        // user preference rather than a per-platform resource, in which
-        // case we would need to update it in onResume() rather than here.
-        initVibrationPattern(r);
+        try {
+            mHaptic.init(this, r.getBoolean(R.bool.config_enable_dialer_key_vibration));
+        } catch (Resources.NotFoundException nfe) {
+             Log.e(TAG, "Vibrate control bool missing.", nfe);
+        }
+
     }
 
     @Override
@@ -299,7 +301,7 @@
             if (uri != null) {
                 if ("tel".equals(uri.getScheme())) {
                     // Put the requested number into the input area
-                    String data = uri.getSchemeSpecificPart();
+                    String data = uri.toString().substring("tel:".length());
                     setFormattedDigits(data);
                 } else {
                     String type = intent.getType();
@@ -397,11 +399,17 @@
     @Override
     protected void onResume() {
         super.onResume();
+        // Query the last dialed number. Do it first because hitting
+        // the DB is 'slow'. This call is asynchronous.
+        queryLastOutgoingCall();
 
         // retrieve the DTMF tone play back setting.
         mDTMFToneEnabled = Settings.System.getInt(getContentResolver(),
                 Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
 
+        // Retrieve the haptic feedback setting.
+        mHaptic.checkSystemSetting();
+
         // if the mToneGenerator creation fails, just continue without it.  It is
         // a local audio signal, and is not as important as the dtmf tone itself.
         synchronized(mToneGeneratorLock) {
@@ -455,7 +463,7 @@
             showDialpadChooser(false);
         }
 
-        updateDialAndDeleteButtonStateEnabledAttr();
+        updateDialAndDeleteButtonEnabledState();
     }
 
     @Override
@@ -485,6 +493,9 @@
                 mToneGenerator = null;
             }
         }
+        // TODO: I wonder if we should not check if the AsyncTask that
+        // lookup the last dialed number has completed.
+        mLastNumberDialed = EMPTY_NUMBER;  // Since we are going to query again, free stale number.
     }
 
     @Override
@@ -505,15 +516,16 @@
             return false;
         }
 
-        CharSequence digits = mDigits.getText();
-        if (digits == null || !TextUtils.isGraphic(digits)) {
+        if (isDigitsEmpty()) {
             mAddToContactMenuItem.setVisible(false);
             m2SecPauseMenuItem.setVisible(false);
             mWaitMenuItem.setVisible(false);
         } else {
+            CharSequence digits = mDigits.getText();
+
             // Put the current digits string into an intent
             Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
-            intent.putExtra(Insert.PHONE, mDigits.getText());
+            intent.putExtra(Insert.PHONE, digits);
             intent.setType(People.CONTENT_ITEM_TYPE);
             mAddToContactMenuItem.setIntent(intent);
             mAddToContactMenuItem.setVisible(true);
@@ -588,21 +600,26 @@
     public boolean onKeyUp(int keyCode, KeyEvent event) {
         switch (keyCode) {
             case KeyEvent.KEYCODE_CALL: {
-                if (phoneIsCdma()) {
-                    // If we're CDMA, regardless of where we are adding a call from (either
-                    // InCallScreen or Dialtacts), the user may need to send an empty
-                    // flash command to the network. So let's call placeCall() regardless
-                    // and placeCall will handle this functionality for us.
-                    placeCall();
-                } else if (mIsAddCallMode && (TextUtils.isEmpty(mDigits.getText().toString()))) {
-                    // if we are adding a call from the InCallScreen and the phone
-                    // number entered is empty, we just close the dialer to expose
-                    // the InCallScreen under it.
+                // TODO: In dialButtonPressed we do some of these
+                // tests again. We should try to consolidate them in
+                // one place.
+                if (!phoneIsCdma() && mIsAddCallMode && isDigitsEmpty()) {
+                    // For CDMA phones, we always call
+                    // dialButtonPressed() because we may need to send
+                    // an empty flash command to the network.
+                    // Otherwise, if we are adding a call from the
+                    // InCallScreen and the phone number entered is
+                    // empty, we just close the dialer to expose the
+                    // InCallScreen under it.
                     finish();
-                } else {
-                    // otherwise, we place the call.
-                    placeCall();
                 }
+
+                // If we're CDMA, regardless of where we are adding a call from (either
+                // InCallScreen or Dialtacts), the user may need to send an empty
+                // flash command to the network. So let's call dialButtonPressed() regardless
+                // and dialButtonPressed will handle this functionality for us.
+                // otherwise, we place the call.
+                dialButtonPressed();
                 return true;
             }
         }
@@ -610,7 +627,7 @@
     }
 
     private void keyPressed(int keyCode) {
-        vibrate();
+        mHaptic.vibrate();
         KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
         mDigits.onKeyDown(keyCode, event);
     }
@@ -619,7 +636,7 @@
         switch (view.getId()) {
             case R.id.digits:
                 if (keyCode == KeyEvent.KEYCODE_ENTER) {
-                    placeCall();
+                    dialButtonPressed();
                     return true;
                 }
                 break;
@@ -694,17 +711,17 @@
                 return;
             }
             case R.id.dialButton: {
-                vibrate();  // Vibrate here too, just like we do for the regular keys
-                placeCall();
+                mHaptic.vibrate();  // Vibrate here too, just like we do for the regular keys
+                dialButtonPressed();
                 return;
             }
             case R.id.voicemailButton: {
                 callVoicemail();
-                vibrate();
+                mHaptic.vibrate();
                 return;
             }
             case R.id.digits: {
-                if (mDigits.length() != 0) {
+                if (!isDigitsEmpty()) {
                     mDigits.setCursorVisible(true);
                 }
                 return;
@@ -725,7 +742,7 @@
                 return true;
             }
             case R.id.one: {
-                if (digits.length() == 0) {
+                if (isDigitsEmpty()) {
                     callVoicemail();
                     return true;
                 }
@@ -741,30 +758,39 @@
 
     void callVoicemail() {
         Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
-                Uri.fromParts("voicemail", "", null));
+                Uri.fromParts("voicemail", EMPTY_NUMBER, null));
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         startActivity(intent);
         mDigits.getText().clear();
         finish();
     }
 
-    void placeCall() {
+    void dialButtonPressed() {
         final String number = mDigits.getText().toString();
         boolean sendEmptyFlash = false;
-        Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
-                Uri.fromParts("tel", number, null));
-        if (number == null || !TextUtils.isGraphic(number)) {
-            // There is no number entered.
+        Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED);
+        if (isDigitsEmpty()) { // There is no number entered.
             if (phoneIsCdma() && phoneIsOffhook()) {
                 // We only want to send this empty flash extra if we're CDMA and the
                 // phone is offhook (don't want to send if ringing or dialing)
+                intent.setData(Uri.fromParts("tel", EMPTY_NUMBER, null));
                 intent.putExtra(EXTRA_SEND_EMPTY_FLASH, true);
                 sendEmptyFlash = true;
+            } else if (!phoneIsOffhook() && !TextUtils.isEmpty(mLastNumberDialed)) {
+                mDigits.setText(mLastNumberDialed);
+                return;
             } else {
+                // TODO: Is this dead code? Hit only if phoneIsOffHook
+                // and dial button is pressed. Can this happen? How
+                // does this compare to the finish() called in
+                // onKeyUp? Should this play tone be moved there?
                 playTone(ToneGenerator.TONE_PROP_NACK);
                 return;
             }
+        } else {  // There is a number.
+            intent.setData(Uri.fromParts("tel", number, null));
         }
+
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         startActivity(intent);
         mDigits.getText().clear();
@@ -1062,18 +1088,6 @@
         return phoneOffhook;
     }
 
-    /**
-     * Triggers haptic feedback (if enabled) for dialer key presses.
-     */
-    private synchronized void vibrate() {
-        if (!mVibrateOn) {
-            return;
-        }
-        if (mVibrator == null) {
-            mVibrator = new Vibrator();
-        }
-        mVibrator.vibrate(mVibratePattern, VIBRATE_NO_REPEAT);
-    }
 
     /**
      * Returns true whenever any one of the options from the menu is selected.
@@ -1127,20 +1141,23 @@
     /**
      * Update the enabledness of the "Dial" and "Backspace" buttons if applicable.
      */
-    private void updateDialAndDeleteButtonStateEnabledAttr() {
-        final boolean notEmpty = mDigits.length() != 0;
+    private void updateDialAndDeleteButtonEnabledState() {
+        final boolean digitsNotEmpty = !isDigitsEmpty();
 
-        // If we're already on a CDMA call, then we want to enable the Call button
-        if (phoneIsCdma() && phoneIsOffhook()) {
-            if (mDialButton != null) {
-                mDialButton.setEnabled(true);
-            }
-        } else {
-            if (mDialButton != null) {
-                mDialButton.setEnabled(notEmpty);
+        if (mDialButton != null) {
+            // If we're already on a CDMA call, then we want to enable
+            // the Call button so we can send a flash.
+            if (phoneIsOffhook()) {
+                mDialButton.setEnabled(phoneIsCdma());
+            } else {
+                // Not in a call, enable the button if digits have
+                // been entered or if there is a last dialed number
+                // that could be redialed.
+                mDialButton.setEnabled(digitsNotEmpty ||
+                                       !TextUtils.isEmpty(mLastNumberDialed));
             }
         }
-        mDelete.setEnabled(notEmpty);
+        mDelete.setEnabled(digitsNotEmpty);
     }
 
 
@@ -1164,35 +1181,6 @@
     }
 
     /**
-     * Initialize the vibration parameters.
-     * @param r The Resources with the vibration parameters.
-     */
-    private void initVibrationPattern(Resources r) {
-        int[] pattern = null;
-        try {
-            mVibrateOn = r.getBoolean(R.bool.config_enable_dialer_key_vibration);
-            pattern = r.getIntArray(com.android.internal.R.array.config_virtualKeyVibePattern);
-            if (null == pattern) {
-                Log.e(TAG, "Vibrate pattern is null.");
-                mVibrateOn = false;
-            }
-        } catch (Resources.NotFoundException nfe) {
-            Log.e(TAG, "Vibrate control bool or pattern missing.", nfe);
-            mVibrateOn = false;
-        }
-
-        if (!mVibrateOn) {
-            return;
-        }
-
-        // int[] to long[] conversion.
-        mVibratePattern = new long[pattern.length];
-        for (int i = 0; i < pattern.length; i++) {
-            mVibratePattern[i] = pattern[i];
-        }
-    }
-
-    /**
      * This function return true if Wait menu item can be shown
      * otherwise returns false. Assumes the passed string is non-empty
      * and the 0th index check is not required.
@@ -1217,4 +1205,34 @@
         }
         return true;
     }
+
+    /**
+     * @return true if the widget with the phone number digits is empty.
+     */
+    private boolean isDigitsEmpty() {
+        return mDigits.length() == 0;
+    }
+
+    /**
+     * Starts the asyn query to get the last dialed/outgoing
+     * number. When the background query finishes, mLastNumberDialed
+     * is set to the last dialed number or an empty string if none
+     * exists yet.
+     */
+    private void queryLastOutgoingCall() {
+        mLastNumberDialed = EMPTY_NUMBER;
+        CallLogAsync.GetLastOutgoingCallArgs lastCallArgs =
+                new CallLogAsync.GetLastOutgoingCallArgs(
+                    this,
+                    new CallLogAsync.OnLastOutgoingCallComplete() {
+                        public void lastOutgoingCall(String number) {
+                            // TODO: Filter out emergency numbers if
+                            // the carrier does not want redial for
+                            // these.
+                            mLastNumberDialed = number;
+                            updateDialAndDeleteButtonEnabledState();
+                        }
+                    });
+        mCallLog.getLastOutgoingCall(lastCallArgs);
+    }
 }
diff --git a/src/com/android/contacts/ViewContactActivity.java b/src/com/android/contacts/ViewContactActivity.java
index ca3c08a..8402b3f 100644
--- a/src/com/android/contacts/ViewContactActivity.java
+++ b/src/com/android/contacts/ViewContactActivity.java
@@ -46,7 +46,9 @@
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.graphics.drawable.Drawable;
+import android.net.ParseException;
 import android.net.Uri;
+import android.net.WebAddress;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.RemoteException;
@@ -57,6 +59,7 @@
 import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.RawContactsEntity;
 import android.provider.ContactsContract.StatusUpdates;
 import android.provider.ContactsContract.CommonDataKinds.Email;
 import android.provider.ContactsContract.CommonDataKinds.Im;
@@ -65,6 +68,7 @@
 import android.provider.ContactsContract.CommonDataKinds.Organization;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
 import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
 import android.util.Log;
@@ -140,6 +144,8 @@
     protected int mWritableSourcesCnt;
     protected boolean mAllRestricted;
 
+    protected Uri mPrimaryPhoneUri = null;
+
     protected ArrayList<Long> mWritableRawContactIds = new ArrayList<Long>();
 
     private static final int TOKEN_ENTITIES = 0;
@@ -151,6 +157,13 @@
     private ArrayList<Entity> mEntities = Lists.newArrayList();
     private HashMap<Long, DataStatus> mStatuses = Maps.newHashMap();
 
+    /**
+     * The view shown if the detail list is empty.
+     * We set this to the list view when first bind the adapter, so that it won't be shown while
+     * we're loading data.
+     */
+    private View mEmptyView;
+
     private ContentObserver mObserver = new ContentObserver(new Handler()) {
         @Override
         public boolean deliverSelfNotifications() {
@@ -206,7 +219,8 @@
         mListView.setOnCreateContextMenuListener(this);
         mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY);
         mListView.setOnItemClickListener(this);
-        mListView.setEmptyView((ScrollView) findViewById(android.R.id.empty));
+        // Don't set it to mListView yet.  We do so later when we bind the adapter.
+        mEmptyView = findViewById(android.R.id.empty);
 
         mResolver = getContentResolver();
 
@@ -286,31 +300,27 @@
         return null;
     }
 
-    // QUERY CODE //
     /** {@inheritDoc} */
-    public void onQueryEntitiesComplete(int token, Object cookie, EntityIterator iterator) {
-        try {
-            // Read incoming entities and consider binding
-            readEntities(iterator);
-            considerBindData();
-        } finally {
-            if (iterator != null) {
+    public void onQueryComplete(int token, Object cookie, Cursor cursor) {
+        if (token == TOKEN_STATUSES) {
+            try {
+                // Read available social rows and consider binding
+                readStatuses(cursor);
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
+            }
+        } else {
+            EntityIterator iterator = RawContacts.newEntityIterator(cursor);
+            try {
+                // Read incoming entities and consider binding
+                readEntities(iterator);
+            } finally {
                 iterator.close();
             }
         }
-    }
-
-    /** {@inheritDoc} */
-    public void onQueryComplete(int token, Object cookie, Cursor cursor) {
-        try {
-            // Read available social rows and consider binding
-            readStatuses(cursor);
-            considerBindData();
-        } finally {
-            if (cursor != null) {
-                cursor.close();
-            }
-        }
+        considerBindData();
     }
 
     private long getRefreshedContactId() {
@@ -388,7 +398,7 @@
         mHasEntities = false;
         mHasStatuses = false;
 
-        mHandler.startQueryEntities(TOKEN_ENTITIES, null, RawContacts.CONTENT_URI,
+        mHandler.startQuery(TOKEN_ENTITIES, null, RawContactsEntity.CONTENT_URI, null,
                 RawContacts.CONTACT_ID + "=" + contactId, null, null);
         mHandler.startQuery(TOKEN_STATUSES, null, dataUri, StatusQuery.PROJECTION,
                         StatusUpdates.PRESENCE + " IS NOT NULL OR " + StatusUpdates.STATUS
@@ -434,6 +444,7 @@
         } else {
             mAdapter.setSections(mSections, SHOW_SEPARATORS);
         }
+        mListView.setEmptyView(mEmptyView);
     }
 
     @Override
@@ -705,15 +716,11 @@
                     if (entry.intent.getAction() == Intent.ACTION_CALL_PRIVILEGED) {
                         startActivity(entry.intent);
                     }
-                } else if (mNumPhoneNumbers != 0) {
+                } else if (mPrimaryPhoneUri != null) {
                     // There isn't anything selected, call the default number
-                    long freshContactId = getRefreshedContactId();
-                    if (freshContactId > 0) {
-                        Uri hardContacUri = ContentUris.withAppendedId(
-                                Contacts.CONTENT_URI, freshContactId);
-                        Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, hardContacUri);
-                        startActivity(intent);
-                    }
+                    final Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
+                            mPrimaryPhoneUri);
+                    startActivity(intent);
                 }
                 return true;
             }
@@ -778,6 +785,7 @@
         mReadOnlySourcesCnt = 0;
         mWritableSourcesCnt = 0;
         mAllRestricted = true;
+        mPrimaryPhoneUri = null;
 
         mWritableRawContactIds.clear();
 
@@ -836,6 +844,9 @@
                         entry.secondaryIntent = new Intent(Intent.ACTION_SENDTO,
                                 Uri.fromParts(Constants.SCHEME_SMSTO, entry.data, null));
 
+                        // Remember super-primary phone
+                        if (isSuperPrimary) mPrimaryPhoneUri = entry.uri;
+
                         entry.isPrimary = isSuperPrimary;
                         mPhoneEntries.add(entry);
 
@@ -893,6 +904,18 @@
                         entry.uri = null;
                         entry.maxLines = 10;
                         mOtherEntries.add(entry);
+                    } else if (Website.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
+                        // Build note entries
+                        entry.uri = null;
+                        entry.maxLines = 10;
+                        try {
+                            WebAddress webAddress = new WebAddress(entry.data);
+                            entry.intent = new Intent(Intent.ACTION_VIEW,
+                                    Uri.parse(webAddress.toString()));
+                        } catch (ParseException e) {
+                            Log.e(TAG, "Couldn't parse website: " + entry.data);
+                        }
+                        mOtherEntries.add(entry);
                     } else {
                         // Handle showing custom rows
                         entry.intent = new Intent(Intent.ACTION_VIEW, entry.uri);
@@ -1040,39 +1063,19 @@
                 return false;
             }
 
-            if (Phone.CONTENT_ITEM_TYPE.equals(mimetype)
-                    && Phone.CONTENT_ITEM_TYPE.equals(entry.mimetype)) {
-                if (!PhoneNumberUtils.compare(this.context, data, entry.data)) {
-                    return false;
-                }
-            } else {
-                if (!equals(data, entry.data)) {
-                    return false;
-                }
+            if (!ContactsUtils.areDataEqual(context, mimetype, data, entry.mimetype, entry.data)) {
+                return false;
             }
 
-            if (!equals(mimetype, entry.mimetype)
-                    || !intentCollapsible(intent, entry.intent)
-                    || !intentCollapsible(secondaryIntent, entry.secondaryIntent)
+            if (!TextUtils.equals(mimetype, entry.mimetype)
+                    || !ContactsUtils.areIntentActionEqual(intent, entry.intent)
+                    || !ContactsUtils.areIntentActionEqual(secondaryIntent, entry.secondaryIntent)
                     || actionIcon != entry.actionIcon) {
                 return false;
             }
 
             return true;
         }
-
-        private boolean equals(Object a, Object b) {
-            return a==b || (a != null && a.equals(b));
-        }
-
-        private boolean intentCollapsible(Intent a, Intent b) {
-            if (a == b) {
-                return true;
-            } else if ((a != null && b != null) && equals(a.getAction(), b.getAction())) {
-                return true;
-            }
-            return false;
-        }
     }
 
     /** Cache of the children views of a row */
diff --git a/src/com/android/contacts/model/Editor.java b/src/com/android/contacts/model/Editor.java
index b3e8443..473f0d3 100644
--- a/src/com/android/contacts/model/Editor.java
+++ b/src/com/android/contacts/model/Editor.java
@@ -18,6 +18,7 @@
 
 import com.android.contacts.model.ContactsSource.DataKind;
 import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.android.contacts.ui.ViewIdGenerator;
 
 import android.provider.ContactsContract.Data;
 
@@ -50,7 +51,8 @@
      * builds any needed views. Any changes performed by the user will be
      * written back to that same object.
      */
-    public void setValues(DataKind kind, ValuesDelta values, EntityDelta state, boolean readOnly);
+    public void setValues(DataKind kind, ValuesDelta values, EntityDelta state, boolean readOnly,
+            ViewIdGenerator vig);
 
     /**
      * Add a specific {@link EditorListener} to this {@link Editor}.
diff --git a/src/com/android/contacts/model/EntityDelta.java b/src/com/android/contacts/model/EntityDelta.java
index ae30806..1f152e1 100644
--- a/src/com/android/contacts/model/EntityDelta.java
+++ b/src/com/android/contacts/model/EntityDelta.java
@@ -571,14 +571,6 @@
             return getAsLong(mIdColumn);
         }
 
-        /**
-         * Return a valid integer value suitable for {@link View#setId(int)}.
-         */
-        public int getViewId() {
-            final Long id = this.getId();
-            return (id == null) ? View.NO_ID : id.intValue();
-        }
-
         public void setIdColumn(String idColumn) {
             mIdColumn = idColumn;
         }
diff --git a/src/com/android/contacts/model/EntityModifier.java b/src/com/android/contacts/model/EntityModifier.java
index fb3eba7..2e6899e 100644
--- a/src/com/android/contacts/model/EntityModifier.java
+++ b/src/com/android/contacts/model/EntityModifier.java
@@ -33,12 +33,13 @@
 import android.provider.ContactsContract.CommonDataKinds.BaseTypes;
 import android.provider.ContactsContract.CommonDataKinds.Email;
 import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.provider.ContactsContract.CommonDataKinds.Note;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.provider.ContactsContract.CommonDataKinds.Photo;
 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
 import android.provider.ContactsContract.Intents.Insert;
-import android.provider.ContactsContract;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseIntArray;
@@ -484,6 +485,36 @@
             fixupLegacyImType(extras);
             parseExtras(state, kind, extras, Insert.IM_PROTOCOL, Insert.IM_HANDLE, Im.DATA);
         }
+
+        // Organization
+        final boolean hasOrg = extras.containsKey(Insert.COMPANY)
+                || extras.containsKey(Insert.JOB_TITLE);
+        final DataKind kindOrg = source.getKindForMimetype(Organization.CONTENT_ITEM_TYPE);
+        if (hasOrg && EntityModifier.canInsert(state, kindOrg)) {
+            final ValuesDelta child = EntityModifier.insertChild(state, kindOrg);
+
+            final String company = extras.getString(Insert.COMPANY);
+            if (ContactsUtils.isGraphic(company)) {
+                child.put(Organization.COMPANY, company);
+            }
+
+            final String title = extras.getString(Insert.JOB_TITLE);
+            if (ContactsUtils.isGraphic(title)) {
+                child.put(Organization.TITLE, title);
+            }
+        }
+
+        // Notes
+        final boolean hasNotes = extras.containsKey(Insert.NOTES);
+        final DataKind kindNotes = source.getKindForMimetype(Note.CONTENT_ITEM_TYPE);
+        if (hasNotes && EntityModifier.canInsert(state, kindNotes)) {
+            final ValuesDelta child = EntityModifier.insertChild(state, kindNotes);
+
+            final String notes = extras.getString(Insert.NOTES);
+            if (ContactsUtils.isGraphic(notes)) {
+                child.put(Note.NOTE, notes);
+            }
+        }
     }
 
     /**
diff --git a/src/com/android/contacts/model/EntitySet.java b/src/com/android/contacts/model/EntitySet.java
index be2f70f..ac53611 100644
--- a/src/com/android/contacts/model/EntitySet.java
+++ b/src/com/android/contacts/model/EntitySet.java
@@ -21,15 +21,13 @@
 import android.content.Entity;
 import android.content.EntityIterator;
 import android.content.ContentProviderOperation.Builder;
-import android.graphics.BitmapFactory;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.provider.ContactsContract.AggregationExceptions;
 import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.RawContacts;
-import android.provider.ContactsContract.CommonDataKinds.Photo;
-import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.RawContactsEntity;
 
 import com.google.android.collect.Lists;
 
@@ -65,26 +63,24 @@
      */
     public static EntitySet fromQuery(ContentResolver resolver, String selection,
             String[] selectionArgs, String sortOrder) {
-        EntityIterator iterator = null;
-        final EntitySet state = new EntitySet();
+        EntityIterator iterator = RawContacts.newEntityIterator(resolver.query(
+                RawContactsEntity.CONTENT_URI, null, selection, selectionArgs,
+                sortOrder));
         try {
+            final EntitySet state = new EntitySet();
             // Perform background query to pull contact details
-            iterator = resolver.queryEntities(RawContacts.CONTENT_URI, selection, selectionArgs,
-                    sortOrder);
             while (iterator.hasNext()) {
                 // Read all contacts into local deltas to prepare for edits
                 final Entity before = iterator.next();
                 final EntityDelta entity = EntityDelta.fromBefore(before);
                 state.add(entity);
             }
+            return state;
         } catch (RemoteException e) {
             throw new IllegalStateException("Problem querying contact details", e);
         } finally {
-            if (iterator != null) {
-                iterator.close();
-            }
+            iterator.close();
         }
-        return state;
     }
 
     /**
diff --git a/src/com/android/contacts/model/ExchangeSource.java b/src/com/android/contacts/model/ExchangeSource.java
index b46824c..b26bdeb 100644
--- a/src/com/android/contacts/model/ExchangeSource.java
+++ b/src/com/android/contacts/model/ExchangeSource.java
@@ -32,6 +32,8 @@
 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
 import android.provider.ContactsContract.CommonDataKinds.Website;
 
+import java.util.Locale;
+
 public class ExchangeSource extends FallbackSource {
 
     public static final String ACCOUNT_TYPE = "com.android.exchange";
@@ -64,23 +66,40 @@
         final DataKind kind = super.inflateStructuredName(ContactsSource.LEVEL_MIMETYPES);
 
         if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+            final boolean useJapaneseOrder =
+                Locale.JAPANESE.getLanguage().equals(Locale.getDefault().getLanguage());
             kind.typeOverallMax = 1;
 
             kind.fieldList = Lists.newArrayList();
             kind.fieldList.add(new EditField(StructuredName.PREFIX, R.string.name_prefix,
                     FLAGS_PERSON_NAME).setOptional(true));
-            kind.fieldList.add(new EditField(StructuredName.GIVEN_NAME, R.string.name_given,
-                    FLAGS_PERSON_NAME));
-            kind.fieldList.add(new EditField(StructuredName.MIDDLE_NAME, R.string.name_middle,
-                    FLAGS_PERSON_NAME).setOptional(true));
-            kind.fieldList.add(new EditField(StructuredName.FAMILY_NAME, R.string.name_family,
-                    FLAGS_PERSON_NAME));
-            kind.fieldList.add(new EditField(StructuredName.SUFFIX, R.string.name_suffix,
-                    FLAGS_PERSON_NAME).setOptional(true));
-            kind.fieldList.add(new EditField(StructuredName.PHONETIC_GIVEN_NAME,
-                    R.string.name_phonetic_given, FLAGS_PHONETIC).setOptional(true));
-            kind.fieldList.add(new EditField(StructuredName.PHONETIC_FAMILY_NAME,
-                    R.string.name_phonetic_family, FLAGS_PHONETIC).setOptional(true));
+            if (useJapaneseOrder) {
+                kind.fieldList.add(new EditField(StructuredName.FAMILY_NAME,
+                        R.string.name_family, FLAGS_PERSON_NAME));
+                kind.fieldList.add(new EditField(StructuredName.MIDDLE_NAME,
+                        R.string.name_middle, FLAGS_PERSON_NAME).setOptional(true));
+                kind.fieldList.add(new EditField(StructuredName.GIVEN_NAME,
+                        R.string.name_given, FLAGS_PERSON_NAME));
+                kind.fieldList.add(new EditField(StructuredName.SUFFIX,
+                        R.string.name_suffix, FLAGS_PERSON_NAME).setOptional(true));
+                kind.fieldList.add(new EditField(StructuredName.PHONETIC_FAMILY_NAME,
+                        R.string.name_phonetic_family, FLAGS_PHONETIC).setOptional(true));
+                kind.fieldList.add(new EditField(StructuredName.PHONETIC_GIVEN_NAME,
+                        R.string.name_phonetic_given, FLAGS_PHONETIC).setOptional(true));
+            } else {
+                kind.fieldList.add(new EditField(StructuredName.GIVEN_NAME,
+                        R.string.name_given, FLAGS_PERSON_NAME));
+                kind.fieldList.add(new EditField(StructuredName.MIDDLE_NAME,
+                        R.string.name_middle, FLAGS_PERSON_NAME).setOptional(true));
+                kind.fieldList.add(new EditField(StructuredName.FAMILY_NAME,
+                        R.string.name_family, FLAGS_PERSON_NAME));
+                kind.fieldList.add(new EditField(StructuredName.SUFFIX,
+                        R.string.name_suffix, FLAGS_PERSON_NAME).setOptional(true));
+                kind.fieldList.add(new EditField(StructuredName.PHONETIC_GIVEN_NAME,
+                        R.string.name_phonetic_given, FLAGS_PHONETIC).setOptional(true));
+                kind.fieldList.add(new EditField(StructuredName.PHONETIC_FAMILY_NAME,
+                        R.string.name_phonetic_family, FLAGS_PHONETIC).setOptional(true));
+            }
         }
 
         return kind;
@@ -152,6 +171,8 @@
         final DataKind kind = super.inflateStructuredPostal(ContactsSource.LEVEL_MIMETYPES);
 
         if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+            final boolean useJapaneseOrder =
+                Locale.JAPANESE.getLanguage().equals(Locale.getDefault().getLanguage());
             kind.typeColumn = StructuredPostal.TYPE;
             kind.typeList = Lists.newArrayList();
             kind.typeList.add(buildPostalType(StructuredPostal.TYPE_WORK).setSpecificMax(1));
@@ -159,16 +180,29 @@
             kind.typeList.add(buildPostalType(StructuredPostal.TYPE_OTHER).setSpecificMax(1));
 
             kind.fieldList = Lists.newArrayList();
-            kind.fieldList.add(new EditField(StructuredPostal.STREET, R.string.postal_street,
-                    FLAGS_POSTAL));
-            kind.fieldList.add(new EditField(StructuredPostal.CITY, R.string.postal_city,
-                    FLAGS_POSTAL));
-            kind.fieldList.add(new EditField(StructuredPostal.REGION, R.string.postal_region,
-                    FLAGS_POSTAL));
-            kind.fieldList.add(new EditField(StructuredPostal.POSTCODE, R.string.postal_postcode,
-                    FLAGS_POSTAL));
-            kind.fieldList.add(new EditField(StructuredPostal.COUNTRY, R.string.postal_country,
-                    FLAGS_POSTAL).setOptional(true));
+            if (useJapaneseOrder) {
+                kind.fieldList.add(new EditField(StructuredPostal.COUNTRY,
+                        R.string.postal_country, FLAGS_POSTAL).setOptional(true));
+                kind.fieldList.add(new EditField(StructuredPostal.POSTCODE,
+                        R.string.postal_postcode, FLAGS_POSTAL));
+                kind.fieldList.add(new EditField(StructuredPostal.REGION,
+                        R.string.postal_region, FLAGS_POSTAL));
+                kind.fieldList.add(new EditField(StructuredPostal.CITY,
+                        R.string.postal_city,FLAGS_POSTAL));
+                kind.fieldList.add(new EditField(StructuredPostal.STREET,
+                        R.string.postal_street, FLAGS_POSTAL));
+            } else {
+                kind.fieldList.add(new EditField(StructuredPostal.STREET,
+                        R.string.postal_street, FLAGS_POSTAL));
+                kind.fieldList.add(new EditField(StructuredPostal.CITY,
+                        R.string.postal_city,FLAGS_POSTAL));
+                kind.fieldList.add(new EditField(StructuredPostal.REGION,
+                        R.string.postal_region, FLAGS_POSTAL));
+                kind.fieldList.add(new EditField(StructuredPostal.POSTCODE,
+                        R.string.postal_postcode, FLAGS_POSTAL));
+                kind.fieldList.add(new EditField(StructuredPostal.COUNTRY,
+                        R.string.postal_country, FLAGS_POSTAL).setOptional(true));
+            }
         }
 
         return kind;
diff --git a/src/com/android/contacts/model/FallbackSource.java b/src/com/android/contacts/model/FallbackSource.java
index ca405eb..8c3a9d2 100644
--- a/src/com/android/contacts/model/FallbackSource.java
+++ b/src/com/android/contacts/model/FallbackSource.java
@@ -16,9 +16,6 @@
 
 package com.android.contacts.model;
 
-import com.android.contacts.R;
-import com.google.android.collect.Lists;
-
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.res.Resources;
@@ -35,9 +32,15 @@
 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
 import android.provider.ContactsContract.CommonDataKinds.Website;
-import android.text.TextUtils;
+import android.util.Log;
 import android.view.inputmethod.EditorInfo;
 
+import com.google.android.collect.Lists;
+
+import com.android.contacts.R;
+
+import java.util.Locale;
+
 public class FallbackSource extends ContactsSource {
     protected static final int FLAGS_PHONE = EditorInfo.TYPE_CLASS_PHONE;
     protected static final int FLAGS_EMAIL = EditorInfo.TYPE_CLASS_TEXT
@@ -109,23 +112,42 @@
         }
 
         if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+            final boolean useJapaneseOrder =
+                Locale.JAPANESE.getLanguage().equals(Locale.getDefault().getLanguage());
             kind.fieldList = Lists.newArrayList();
             kind.fieldList.add(new EditField(StructuredName.PREFIX, R.string.name_prefix,
                     FLAGS_PERSON_NAME).setOptional(true));
-            kind.fieldList.add(new EditField(StructuredName.GIVEN_NAME, R.string.name_given,
-                    FLAGS_PERSON_NAME));
-            kind.fieldList.add(new EditField(StructuredName.MIDDLE_NAME, R.string.name_middle,
-                    FLAGS_PERSON_NAME).setOptional(true));
-            kind.fieldList.add(new EditField(StructuredName.FAMILY_NAME, R.string.name_family,
-                    FLAGS_PERSON_NAME));
-            kind.fieldList.add(new EditField(StructuredName.SUFFIX, R.string.name_suffix,
-                    FLAGS_PERSON_NAME).setOptional(true));
-            kind.fieldList.add(new EditField(StructuredName.PHONETIC_GIVEN_NAME,
-                    R.string.name_phonetic_given, FLAGS_PHONETIC).setOptional(true));
-            kind.fieldList.add(new EditField(StructuredName.PHONETIC_MIDDLE_NAME,
-                    R.string.name_phonetic_middle, FLAGS_PHONETIC).setOptional(true));
-            kind.fieldList.add(new EditField(StructuredName.PHONETIC_FAMILY_NAME,
-                    R.string.name_phonetic_family, FLAGS_PHONETIC).setOptional(true));
+            if (useJapaneseOrder) {
+                kind.fieldList.add(new EditField(StructuredName.FAMILY_NAME, R.string.name_family,
+                        FLAGS_PERSON_NAME));
+                kind.fieldList.add(new EditField(StructuredName.MIDDLE_NAME, R.string.name_middle,
+                        FLAGS_PERSON_NAME).setOptional(true));
+                kind.fieldList.add(new EditField(StructuredName.GIVEN_NAME, R.string.name_given,
+                        FLAGS_PERSON_NAME));
+                kind.fieldList.add(new EditField(StructuredName.SUFFIX, R.string.name_suffix,
+                        FLAGS_PERSON_NAME).setOptional(true));
+                kind.fieldList.add(new EditField(StructuredName.PHONETIC_FAMILY_NAME,
+                        R.string.name_phonetic_family, FLAGS_PHONETIC).setOptional(true));
+                kind.fieldList.add(new EditField(StructuredName.PHONETIC_MIDDLE_NAME,
+                        R.string.name_phonetic_middle, FLAGS_PHONETIC).setOptional(true));
+                kind.fieldList.add(new EditField(StructuredName.PHONETIC_GIVEN_NAME,
+                        R.string.name_phonetic_given, FLAGS_PHONETIC).setOptional(true));
+            } else {
+                kind.fieldList.add(new EditField(StructuredName.GIVEN_NAME, R.string.name_given,
+                        FLAGS_PERSON_NAME));
+                kind.fieldList.add(new EditField(StructuredName.MIDDLE_NAME, R.string.name_middle,
+                        FLAGS_PERSON_NAME).setOptional(true));
+                kind.fieldList.add(new EditField(StructuredName.FAMILY_NAME, R.string.name_family,
+                        FLAGS_PERSON_NAME));
+                kind.fieldList.add(new EditField(StructuredName.SUFFIX, R.string.name_suffix,
+                        FLAGS_PERSON_NAME).setOptional(true));
+                kind.fieldList.add(new EditField(StructuredName.PHONETIC_GIVEN_NAME,
+                        R.string.name_phonetic_given, FLAGS_PHONETIC).setOptional(true));
+                kind.fieldList.add(new EditField(StructuredName.PHONETIC_MIDDLE_NAME,
+                        R.string.name_phonetic_middle, FLAGS_PHONETIC).setOptional(true));
+                kind.fieldList.add(new EditField(StructuredName.PHONETIC_FAMILY_NAME,
+                        R.string.name_phonetic_family, FLAGS_PHONETIC).setOptional(true));
+            }
         }
 
         return kind;
@@ -234,6 +256,8 @@
         }
 
         if (inflateLevel >= ContactsSource.LEVEL_CONSTRAINTS) {
+            final boolean useJapaneseOrder =
+                Locale.JAPANESE.getLanguage().equals(Locale.getDefault().getLanguage());
             kind.typeColumn = StructuredPostal.TYPE;
             kind.typeList = Lists.newArrayList();
             kind.typeList.add(buildPostalType(StructuredPostal.TYPE_HOME));
@@ -243,20 +267,38 @@
                     .setCustomColumn(StructuredPostal.LABEL));
 
             kind.fieldList = Lists.newArrayList();
-            kind.fieldList.add(new EditField(StructuredPostal.STREET, R.string.postal_street,
-                    FLAGS_POSTAL));
-            kind.fieldList.add(new EditField(StructuredPostal.POBOX, R.string.postal_pobox,
-                    FLAGS_POSTAL).setOptional(true));
-            kind.fieldList.add(new EditField(StructuredPostal.NEIGHBORHOOD,
-                    R.string.postal_neighborhood, FLAGS_POSTAL).setOptional(true));
-            kind.fieldList.add(new EditField(StructuredPostal.CITY, R.string.postal_city,
-                    FLAGS_POSTAL));
-            kind.fieldList.add(new EditField(StructuredPostal.REGION, R.string.postal_region,
-                    FLAGS_POSTAL));
-            kind.fieldList.add(new EditField(StructuredPostal.POSTCODE, R.string.postal_postcode,
-                    FLAGS_POSTAL));
-            kind.fieldList.add(new EditField(StructuredPostal.COUNTRY, R.string.postal_country,
-                    FLAGS_POSTAL).setOptional(true));
+
+            if (useJapaneseOrder) {
+                kind.fieldList.add(new EditField(StructuredPostal.COUNTRY,
+                        R.string.postal_country, FLAGS_POSTAL).setOptional(true));
+                kind.fieldList.add(new EditField(StructuredPostal.POSTCODE,
+                        R.string.postal_postcode, FLAGS_POSTAL));
+                kind.fieldList.add(new EditField(StructuredPostal.REGION,
+                        R.string.postal_region, FLAGS_POSTAL));
+                kind.fieldList.add(new EditField(StructuredPostal.CITY,
+                        R.string.postal_city, FLAGS_POSTAL));
+                kind.fieldList.add(new EditField(StructuredPostal.NEIGHBORHOOD,
+                        R.string.postal_neighborhood, FLAGS_POSTAL).setOptional(true));
+                kind.fieldList.add(new EditField(StructuredPostal.STREET,
+                        R.string.postal_street, FLAGS_POSTAL));
+                kind.fieldList.add(new EditField(StructuredPostal.POBOX,
+                        R.string.postal_pobox, FLAGS_POSTAL).setOptional(true));
+            } else {
+                kind.fieldList.add(new EditField(StructuredPostal.STREET,
+                        R.string.postal_street, FLAGS_POSTAL));
+                kind.fieldList.add(new EditField(StructuredPostal.POBOX,
+                        R.string.postal_pobox, FLAGS_POSTAL).setOptional(true));
+                kind.fieldList.add(new EditField(StructuredPostal.NEIGHBORHOOD,
+                        R.string.postal_neighborhood, FLAGS_POSTAL).setOptional(true));
+                kind.fieldList.add(new EditField(StructuredPostal.CITY,
+                        R.string.postal_city, FLAGS_POSTAL));
+                kind.fieldList.add(new EditField(StructuredPostal.REGION,
+                        R.string.postal_region, FLAGS_POSTAL));
+                kind.fieldList.add(new EditField(StructuredPostal.POSTCODE,
+                        R.string.postal_postcode, FLAGS_POSTAL));
+                kind.fieldList.add(new EditField(StructuredPostal.COUNTRY,
+                        R.string.postal_country, FLAGS_POSTAL).setOptional(true));
+            }
         }
 
         return kind;
@@ -267,6 +309,7 @@
         if (kind == null) {
             kind = addKind(new DataKind(Im.CONTENT_ITEM_TYPE, R.string.imLabelsGroup,
                     android.R.drawable.sym_action_chat, 20, true));
+            kind.secondary = true;
             kind.actionHeader = new ImActionInflater();
             kind.actionBody = new SimpleInflater(Im.DATA);
         }
diff --git a/src/com/android/contacts/model/Sources.java b/src/com/android/contacts/model/Sources.java
index f664fb1..b6422fb 100644
--- a/src/com/android/contacts/model/Sources.java
+++ b/src/com/android/contacts/model/Sources.java
@@ -42,6 +42,7 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Locale;
 
 /**
  * Singleton holder for all parsed {@link ContactsSource} available on the
@@ -87,12 +88,17 @@
         queryAccounts();
 
         // Request updates when packages or accounts change
-        final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
         filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
         filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
         filter.addDataScheme("package");
-
         mApplicationContext.registerReceiver(this, filter);
+
+        // Request updates when locale is changed so that the order of each field will
+        // be able to be changed on the locale change.
+        filter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
+        mApplicationContext.registerReceiver(this, filter);
+
         mAccountManager.addOnAccountsUpdatedListener(this, null, false);
     }
 
@@ -112,11 +118,11 @@
     @Override
     public void onReceive(Context context, Intent intent) {
         final String action = intent.getAction();
-        final String packageName = intent.getData().getSchemeSpecificPart();
 
         if (Intent.ACTION_PACKAGE_REMOVED.equals(action)
                 || Intent.ACTION_PACKAGE_ADDED.equals(action)
                 || Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
+            final String packageName = intent.getData().getSchemeSpecificPart();
             final boolean knownPackage = mKnownPackages.contains(packageName);
             if (knownPackage) {
                 // Invalidate cache of existing source
@@ -125,6 +131,8 @@
                 // Unknown source, so reload from scratch
                 queryAccounts();
             }
+        } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
+            invalidateAllCache();
         }
     }
 
@@ -137,6 +145,13 @@
         }
     }
 
+    protected void invalidateAllCache() {
+        mFallbackSource.invalidateCache();
+        for (ContactsSource source : mSources.values()) {
+            source.invalidateCache();
+        }
+    }
+
     /** {@inheritDoc} */
     public void onAccountsUpdated(Account[] accounts) {
         // Refresh to catch any changed accounts
@@ -233,6 +248,8 @@
      * Find the best {@link DataKind} matching the requested
      * {@link ContactsSource#accountType} and {@link DataKind#mimeType}. If no
      * direct match found, we try searching {@link #mFallbackSource}.
+     * When fourceRefresh is set to true, cache is refreshed and inflation of each
+     * EditField will occur.
      */
     public DataKind getKindOrFallback(String accountType, String mimeType, Context context,
             int inflateLevel) {
diff --git a/src/com/android/contacts/ui/ContactsPreferences.java b/src/com/android/contacts/ui/ContactsPreferences.java
new file mode 100644
index 0000000..187df37
--- /dev/null
+++ b/src/com/android/contacts/ui/ContactsPreferences.java
@@ -0,0 +1,125 @@
+/*
+ * 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.ui;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.provider.ContactsContract;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+
+/**
+ * Manages user preferences for contacts.
+ */
+public final class ContactsPreferences  {
+
+    private Context mContext;
+    private ContentResolver mContentResolver;
+    private int mSortOrder = -1;
+    private int mDisplayOrder = -1;
+    private SettingsObserver mSettingsObserver;
+
+    // TODO listen to locale changes
+    public ContactsPreferences(Context context) {
+        mContext = context;
+        mContentResolver = context.getContentResolver();
+
+        mSettingsObserver = new SettingsObserver();
+        mSettingsObserver.register();
+    }
+
+    public boolean isSortOrderUserChangeable() {
+        // TODO this should be locale-specific
+        return true;
+    }
+
+    private int getDefaultSortOrder() {
+        // TODO this should be locale-specific
+        return ContactsContract.Preferences.SORT_ORDER_PRIMARY;
+    }
+
+    public int getSortOrder() {
+        if (mSortOrder == -1) {
+            try {
+                mSortOrder = Settings.System.getInt(mContext.getContentResolver(),
+                        ContactsContract.Preferences.SORT_ORDER);
+            } catch (SettingNotFoundException e) {
+                mSortOrder = getDefaultSortOrder();
+            }
+        }
+        return mSortOrder;
+    }
+
+    public void setSortOrder(int sortOrder) {
+        mSortOrder = sortOrder;
+        Settings.System.putInt(mContext.getContentResolver(),
+                ContactsContract.Preferences.SORT_ORDER, sortOrder);
+    }
+
+    public boolean isDisplayOrderUserChangeable() {
+        // TOD this should be locale-specific
+        return true;
+    }
+
+    private int getDefaultDisplayOrder() {
+        // TODO this should be locale-specific
+        return ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY;
+    }
+
+    public int getDisplayOrder() {
+        if (mDisplayOrder == -1) {
+            try {
+                mDisplayOrder = Settings.System.getInt(mContext.getContentResolver(),
+                        ContactsContract.Preferences.DISPLAY_ORDER);
+            } catch (SettingNotFoundException e) {
+                mDisplayOrder = getDefaultDisplayOrder();
+            }
+        }
+        return mDisplayOrder;
+    }
+
+    public void setDisplayOrder(int displayOrder) {
+        mDisplayOrder = displayOrder;
+        Settings.System.putInt(mContext.getContentResolver(),
+                ContactsContract.Preferences.DISPLAY_ORDER, displayOrder);
+    }
+
+    private class SettingsObserver extends ContentObserver {
+
+        public SettingsObserver() {
+            super(null);
+        }
+
+        public void register() {
+            mContentResolver.registerContentObserver(
+                    Settings.System.getUriFor(
+                            ContactsContract.Preferences.SORT_ORDER), false, this);
+            mContentResolver.registerContentObserver(
+                    Settings.System.getUriFor(
+                            ContactsContract.Preferences.DISPLAY_ORDER), false, this);
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            mSortOrder = -1;
+            mDisplayOrder = -1;
+
+            // TODO send a message to parent context to notify of the change
+        }
+    }
+}
diff --git a/src/com/android/contacts/ui/DisplayGroupsActivity.java b/src/com/android/contacts/ui/ContactsPreferencesActivity.java
similarity index 78%
rename from src/com/android/contacts/ui/DisplayGroupsActivity.java
rename to src/com/android/contacts/ui/ContactsPreferencesActivity.java
index ce68dcb..aa6aa14 100644
--- a/src/com/android/contacts/ui/DisplayGroupsActivity.java
+++ b/src/com/android/contacts/ui/ContactsPreferencesActivity.java
@@ -28,6 +28,7 @@
 import android.accounts.Account;
 import android.app.Activity;
 import android.app.AlertDialog;
+import android.app.Dialog;
 import android.app.ExpandableListActivity;
 import android.app.ProgressDialog;
 import android.content.ContentProviderOperation;
@@ -61,6 +62,7 @@
 import android.widget.CheckBox;
 import android.widget.ExpandableListAdapter;
 import android.widget.ExpandableListView;
+import android.widget.ListView;
 import android.widget.TextView;
 import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
 
@@ -74,7 +76,7 @@
  * Shows a list of all available {@link Groups} available, letting the user
  * select which ones they want to be visible.
  */
-public final class DisplayGroupsActivity extends ExpandableListActivity implements
+public final class ContactsPreferencesActivity extends ExpandableListActivity implements
         AdapterView.OnItemClickListener, View.OnClickListener {
     private static final String TAG = "DisplayGroupsActivity";
 
@@ -84,28 +86,63 @@
 
     }
 
+    private static final int DIALOG_SORT_ORDER = 1;
+    private static final int DIALOG_DISPLAY_ORDER = 2;
+
     private ExpandableListView mList;
     private DisplayAdapter mAdapter;
 
     private SharedPreferences mPrefs;
+    private ContactsPreferences mContactsPrefs;
 
     private CheckBox mDisplayPhones;
 
     private View mHeaderPhones;
     private View mHeaderSeparator;
 
+    private View mSortOrderView;
+    private TextView mSortOrderTextView;
+    private int mSortOrder;
+
+    private View mDisplayOrderView;
+    private TextView mDisplayOrderTextView;
+    private int mDisplayOrder;
+
     @Override
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
-        setContentView(R.layout.act_display_groups);
+        setContentView(R.layout.contacts_preferences);
 
         mList = getExpandableListView();
         mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
+        mContactsPrefs = new ContactsPreferences(this);
 
         final LayoutInflater inflater = getLayoutInflater();
 
+        addWithPhonesOnlyPreferenceView(inflater);
+        addDivider(inflater);
+        addSortOrderPreferenceView(inflater);
+        addDivider(inflater);
+        addDisplayOrderPreferenceView(inflater);
+        addDisplayGroupHeader(inflater);
+
+        findViewById(R.id.btn_done).setOnClickListener(this);
+        findViewById(R.id.btn_discard).setOnClickListener(this);
+
+        // Catch clicks on the header views
+        mList.setOnItemClickListener(this);
+        mList.setOnCreateContextMenuListener(this);
+
+        mSortOrder = mContactsPrefs.getSortOrder();
+        mDisplayOrder = mContactsPrefs.getDisplayOrder();
+
+        // Start background query to find account details
+        new QueryGroupsTask(this).execute();
+    }
+
+    private LayoutInflater addWithPhonesOnlyPreferenceView(LayoutInflater inflater) {
         // Add the "Only contacts with phones" header modifier.
-        mHeaderPhones = inflater.inflate(R.layout.display_header, mList, false);
+        mHeaderPhones = inflater.inflate(R.layout.display_options_phones_only, mList, false);
         mHeaderPhones.setId(R.id.header_phones);
         mDisplayPhones = (CheckBox) mHeaderPhones.findViewById(android.R.id.checkbox);
         mDisplayPhones.setChecked(mPrefs.getBoolean(Prefs.DISPLAY_ONLY_PHONES,
@@ -116,8 +153,52 @@
             text1.setText(R.string.showFilterPhones);
             text2.setText(R.string.showFilterPhonesDescrip);
         }
-        mList.addHeaderView(mHeaderPhones, null, true);
 
+        mList.addHeaderView(mHeaderPhones, null, true);
+        return inflater;
+    }
+
+    private void addSortOrderPreferenceView(LayoutInflater inflater) {
+        mSortOrderView = inflater.inflate(R.layout.preference_with_more_button, mList, false);
+
+        View preferenceLayout = mSortOrderView.findViewById(R.id.preference);
+        preferenceLayout.setOnClickListener(new View.OnClickListener() {
+
+            public void onClick(View v) {
+                showDialog(DIALOG_SORT_ORDER);
+            }
+        });
+
+        TextView label = (TextView)preferenceLayout.findViewById(R.id.label);
+        label.setText(getString(R.string.display_options_sort_list_by));
+
+        mSortOrderTextView = (TextView)preferenceLayout.findViewById(R.id.data);
+        mList.addHeaderView(mSortOrderView, null, false);
+    }
+
+    private void addDisplayOrderPreferenceView(LayoutInflater inflater) {
+        mDisplayOrderView = inflater.inflate(R.layout.preference_with_more_button, mList, false);
+        View preferenceLayout = mDisplayOrderView.findViewById(R.id.preference);
+        preferenceLayout.setOnClickListener(new View.OnClickListener() {
+
+            public void onClick(View v) {
+                showDialog(DIALOG_DISPLAY_ORDER);
+            }
+        });
+
+        TextView label = (TextView)preferenceLayout.findViewById(R.id.label);
+        label.setText(getString(R.string.display_options_view_names_as));
+
+        mDisplayOrderTextView = (TextView)preferenceLayout.findViewById(R.id.data);
+        mList.addHeaderView(mDisplayOrderView, null, false);
+    }
+
+    private void addDivider(LayoutInflater inflater) {
+        View divider = inflater.inflate(R.layout.horizontal_divider, mList, false);
+        mList.addHeaderView(divider, null, false);
+    }
+
+    private void addDisplayGroupHeader(LayoutInflater inflater) {
         // Add the separator before showing the detailed group list.
         mHeaderSeparator = inflater.inflate(R.layout.list_separator, mList, false);
         {
@@ -125,16 +206,124 @@
             text1.setText(R.string.headerContactGroups);
         }
         mList.addHeaderView(mHeaderSeparator, null, false);
+    }
 
-        findViewById(R.id.btn_done).setOnClickListener(this);
-        findViewById(R.id.btn_discard).setOnClickListener(this);
+    @Override
+    protected void onResume() {
+        super.onResume();
+        bindView();
+    }
 
-        // Catch clicks on the header views
-        mList.setOnItemClickListener(this);
-        mList.setOnCreateContextMenuListener(this);
+    private void bindView() {
+        mSortOrderTextView.setText(
+                mSortOrder == ContactsContract.Preferences.SORT_ORDER_PRIMARY
+                        ? getString(R.string.display_options_sort_by_given_name)
+                        : getString(R.string.display_options_sort_by_family_name));
 
-        // Start background query to find account details
-        new QueryGroupsTask(this).execute();
+        mDisplayOrderTextView.setText(
+                mDisplayOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY
+                        ? getString(R.string.display_options_view_given_name_first)
+                        : getString(R.string.display_options_view_family_name_first));
+    }
+
+    @Override
+    protected Dialog onCreateDialog(int id, Bundle args) {
+        switch (id) {
+            case DIALOG_SORT_ORDER:
+                return createSortOrderDialog();
+            case DIALOG_DISPLAY_ORDER:
+                return createDisplayOrderDialog();
+        }
+
+        return null;
+    }
+
+    private Dialog createSortOrderDialog() {
+        String[] items = new String[] {
+                getString(R.string.display_options_sort_by_given_name),
+                getString(R.string.display_options_sort_by_family_name),
+        };
+
+        return new AlertDialog.Builder(this)
+            .setIcon(com.android.internal.R.drawable.ic_dialog_menu_generic)
+            .setTitle(R.string.display_options_sort_list_by)
+            .setSingleChoiceItems(items, -1, new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int whichButton) {
+                        setSortOrder(dialog);
+                        dialog.dismiss();
+                    }
+                })
+            .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int whichButton) {
+                        setSortOrder(dialog);
+                    }
+                })
+            .setNegativeButton(android.R.string.cancel, null)
+            .create();
+    }
+
+    private Dialog createDisplayOrderDialog() {
+        String[] items = new String[] {
+                getString(R.string.display_options_view_given_name_first),
+                getString(R.string.display_options_view_family_name_first),
+        };
+
+        return new AlertDialog.Builder(this)
+            .setIcon(com.android.internal.R.drawable.ic_dialog_menu_generic)
+            .setTitle(R.string.display_options_view_names_as)
+            .setSingleChoiceItems(items, -1, new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int whichButton) {
+                        setDisplayOrder(dialog);
+                        dialog.dismiss();
+                    }
+                })
+            .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int whichButton) {
+                        setDisplayOrder(dialog);
+                    }
+                })
+            .setNegativeButton(android.R.string.cancel, null)
+            .create();
+    }
+
+    @Override
+    protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {
+        switch (id) {
+            case DIALOG_SORT_ORDER:
+                setCheckedItem(dialog,
+                        mSortOrder == ContactsContract.Preferences.SORT_ORDER_PRIMARY ? 0 : 1);
+                break;
+            case DIALOG_DISPLAY_ORDER:
+                setCheckedItem(dialog,
+                        mSortOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY ? 0 : 1);
+                break;
+        }
+    }
+
+    private void setCheckedItem(Dialog dialog, int position) {
+        ListView listView = ((AlertDialog)dialog).getListView();
+        listView.setItemChecked(position, true);
+        listView.setSelection(position);
+    }
+
+    protected void setSortOrder(DialogInterface dialog) {
+        ListView listView = ((AlertDialog)dialog).getListView();
+        int checked = listView.getCheckedItemPosition();
+        mSortOrder = checked == 0
+                ? ContactsContract.Preferences.SORT_ORDER_PRIMARY
+                : ContactsContract.Preferences.SORT_ORDER_ALTERNATIVE;
+
+        bindView();
+    }
+
+    protected void setDisplayOrder(DialogInterface dialog) {
+        ListView listView = ((AlertDialog)dialog).getListView();
+        int checked = listView.getCheckedItemPosition();
+        mDisplayOrder = checked == 0
+                ? ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY
+                : ContactsContract.Preferences.DISPLAY_ORDER_ALTERNATIVE;
+
+        bindView();
     }
 
     /**
@@ -142,13 +331,13 @@
      * {@link Sources#getAccounts(boolean)} that provides groups.
      */
     private static class QueryGroupsTask extends
-            WeakAsyncTask<Void, Void, AccountSet, DisplayGroupsActivity> {
-        public QueryGroupsTask(DisplayGroupsActivity target) {
+            WeakAsyncTask<Void, Void, AccountSet, ContactsPreferencesActivity> {
+        public QueryGroupsTask(ContactsPreferencesActivity target) {
             super(target);
         }
 
         @Override
-        protected AccountSet doInBackground(DisplayGroupsActivity target,
+        protected AccountSet doInBackground(ContactsPreferencesActivity target,
                 Void... params) {
             final Context context = target;
             final Sources sources = Sources.getInstance(context);
@@ -164,7 +353,7 @@
         }
 
         @Override
-        protected void onPostExecute(DisplayGroupsActivity target, AccountSet result) {
+        protected void onPostExecute(ContactsPreferencesActivity target, AccountSet result) {
             // Build adapter to show available groups
             final Context context = target;
             final DisplayAdapter adapter = new DisplayAdapter(context, result);
@@ -326,7 +515,7 @@
     private static Uri addCallerIsSyncAdapterParameter(Uri uri) {
         return uri.buildUpon()
 	        .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
-		.build();
+	        .build();
     }
 
     /**
@@ -334,7 +523,15 @@
      */
     private static Comparator<GroupDelta> sIdComparator = new Comparator<GroupDelta>() {
         public int compare(GroupDelta object1, GroupDelta object2) {
-            return object1.getViewId() - object2.getViewId();
+            final long id1 = object1.getId();
+            final long id2 = object2.getId();
+            if (id1 < id2) {
+                return -1;
+            } else if (id1 > id2) {
+                return 1;
+            } else {
+                return 0;
+            }
         }
     };
 
@@ -371,30 +568,29 @@
             mName = accountName;
             mType = accountType;
 
-            boolean hasGroups = false;
-
             final Uri groupsUri = Groups.CONTENT_URI.buildUpon()
                     .appendQueryParameter(Groups.ACCOUNT_NAME, accountName)
                     .appendQueryParameter(Groups.ACCOUNT_TYPE, accountType).build();
-            EntityIterator iterator = null;
+            EntityIterator iterator = ContactsContract.Groups.newEntityIterator(resolver.query(
+                    groupsUri, null, null, null, null));
             try {
+                boolean hasGroups = false;
+
                 // Create entries for each known group
-                iterator = resolver.queryEntities(groupsUri, null, null, null);
                 while (iterator.hasNext()) {
                     final ContentValues values = iterator.next().getEntityValues();
                     final GroupDelta group = GroupDelta.fromBefore(values);
                     addGroup(group);
                     hasGroups = true;
                 }
+                // Create single entry handling ungrouped status
+                mUngrouped = GroupDelta.fromSettings(resolver, accountName, accountType, hasGroups);
+                addGroup(mUngrouped);
             } catch (RemoteException e) {
                 Log.w(TAG, "Problem reading groups: " + e.toString());
             } finally {
-                if (iterator != null) iterator.close();
+                iterator.close();
             }
-
-            // Create single entry handling ungrouped status
-            mUngrouped = GroupDelta.fromSettings(resolver, accountName, accountType, hasGroups);
-            addGroup(mUngrouped);
         }
 
         /**
@@ -792,6 +988,9 @@
     }
 
     private void doSaveAction() {
+        mContactsPrefs.setSortOrder(mSortOrder);
+        mContactsPrefs.setDisplayOrder(mDisplayOrder);
+
         if (mAdapter == null) return;
         setDisplayOnlyPhones(mDisplayPhones.isChecked());
         new UpdateTask(this).execute(mAdapter.mAccounts);
diff --git a/src/com/android/contacts/ui/EditContactActivity.java b/src/com/android/contacts/ui/EditContactActivity.java
index 8368714..89f9b12 100644
--- a/src/com/android/contacts/ui/EditContactActivity.java
+++ b/src/com/android/contacts/ui/EditContactActivity.java
@@ -30,6 +30,7 @@
 import com.android.contacts.model.Editor.EditorListener;
 import com.android.contacts.model.EntityDelta.ValuesDelta;
 import com.android.contacts.ui.widget.BaseContactEditorView;
+import com.android.contacts.ui.widget.ContactEditorView;
 import com.android.contacts.ui.widget.PhotoEditorView;
 import com.android.contacts.util.EmptyService;
 import com.android.contacts.util.WeakAsyncTask;
@@ -99,6 +100,7 @@
 
     private static final String KEY_EDIT_STATE = "state";
     private static final String KEY_RAW_CONTACT_ID_REQUESTING_PHOTO = "photorequester";
+    private static final String KEY_VIEW_ID_GENERATOR = "viewidgenerator";
 
     /** The result code when view activity should close after edit returns */
     public static final int RESULT_CLOSE_VIEW_ACTIVITY = 777;
@@ -124,6 +126,8 @@
 
     private ArrayList<Dialog> mManagedDialogs = Lists.newArrayList();
 
+    private ViewIdGenerator mViewIdGenerator;
+
     @Override
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
@@ -143,12 +147,19 @@
         final boolean hasIncomingState = icicle != null && icicle.containsKey(KEY_EDIT_STATE);
 
         if (Intent.ACTION_EDIT.equals(action) && !hasIncomingState) {
+            setTitle(R.string.editContact_title_edit);
             // Read initial state from database
             new QueryEntitiesTask(this).execute(intent);
         } else if (Intent.ACTION_INSERT.equals(action) && !hasIncomingState) {
+            setTitle(R.string.editContact_title_insert);
             // Trigger dialog to pick account type
             doAddAction();
         }
+
+        if (icicle == null) {
+            // If icicle is non-null, onRestoreInstanceState() will restore the generator.
+            mViewIdGenerator = new ViewIdGenerator();
+        }
     }
 
     private static class QueryEntitiesTask extends
@@ -223,6 +234,7 @@
         }
 
         outState.putLong(KEY_RAW_CONTACT_ID_REQUESTING_PHOTO, mRawContactIdRequestingPhoto);
+        outState.putParcelable(KEY_VIEW_ID_GENERATOR, mViewIdGenerator);
         super.onSaveInstanceState(outState);
     }
 
@@ -232,6 +244,7 @@
         mState = savedInstanceState.<EntitySet> getParcelable(KEY_EDIT_STATE);
         mRawContactIdRequestingPhoto = savedInstanceState.getLong(
                 KEY_RAW_CONTACT_ID_REQUESTING_PHOTO);
+        mViewIdGenerator = savedInstanceState.getParcelable(KEY_VIEW_ID_GENERATOR);
         bindEditors();
 
         super.onRestoreInstanceState(savedInstanceState);
@@ -242,9 +255,7 @@
         super.onDestroy();
 
         for (Dialog dialog : mManagedDialogs) {
-            if (dialog.isShowing()) {
-                dialog.dismiss();
-            }
+            dismissDialog(dialog);
         }
     }
 
@@ -308,6 +319,20 @@
     }
 
     /**
+     * Dismiss the given {@link Dialog}.
+     */
+    static void dismissDialog(Dialog dialog) {
+        try {
+            // Only dismiss when valid reference and still showing
+            if (dialog != null && dialog.isShowing()) {
+                dialog.dismiss();
+            }
+        } catch (Exception e) {
+            Log.w(TAG, "Ignoring exception while dismissing dialog: " + e.toString());
+        }
+    }
+
+    /**
      * Check if our internal {@link #mState} is valid, usually checked before
      * performing user actions.
      */
@@ -357,7 +382,7 @@
                     photoEditor));
 
             mContent.addView(editor);
-            editor.setState(entity, source);
+            editor.setState(entity, source, mViewIdGenerator);
         }
 
         // Show editor now that we've loaded state
@@ -682,10 +707,7 @@
                 Toast.makeText(context, R.string.contactSavedErrorToast, Toast.LENGTH_LONG).show();
             }
 
-            // Only dismiss when valid reference and still showing
-            if (progress != null && progress.isShowing()) {
-                progress.dismiss();
-            }
+            dismissDialog(progress);
 
             // Stop the service that was protecting us
             context.stopService(new Intent(context, EmptyService.class));
@@ -783,6 +805,20 @@
         startActivityForResult(intent, REQUEST_JOIN_CONTACT);
     }
 
+    private interface JoinContactQuery {
+        String[] PROJECTION = {
+                RawContacts._ID,
+                RawContacts.CONTACT_ID,
+                RawContacts.NAME_VERIFIED,
+        };
+
+        String SELECTION = RawContacts.CONTACT_ID + "=? OR " + RawContacts.CONTACT_ID + "=?";
+
+        int _ID = 0;
+        int CONTACT_ID = 1;
+        int NAME_VERIFIED = 2;
+    }
+
     /**
      * Performs aggregation with the contact selected by the user from suggestions or A-Z list.
      */
@@ -792,16 +828,24 @@
         // Load raw contact IDs for all raw contacts involved - currently edited and selected
         // in the join UIs
         Cursor c = resolver.query(RawContacts.CONTENT_URI,
-                new String[] {RawContacts._ID},
-                RawContacts.CONTACT_ID + "=" + contactId
-                + " OR " + RawContacts.CONTACT_ID + "=" + mContactIdForJoin, null, null);
+                JoinContactQuery.PROJECTION,
+                JoinContactQuery.SELECTION,
+                new String[]{String.valueOf(contactId), String.valueOf(mContactIdForJoin)}, null);
 
         long rawContactIds[];
+        long verifiedNameRawContactId = -1;
         try {
             rawContactIds = new long[c.getCount()];
             for (int i = 0; i < rawContactIds.length; i++) {
                 c.moveToNext();
-                rawContactIds[i] = c.getLong(0);
+                long rawContactId = c.getLong(JoinContactQuery._ID);
+                rawContactIds[i] = rawContactId;
+                if (c.getLong(JoinContactQuery.CONTACT_ID) == mContactIdForJoin) {
+                    if (verifiedNameRawContactId == -1
+                            || c.getInt(JoinContactQuery.NAME_VERIFIED) != 0) {
+                        verifiedNameRawContactId = rawContactId;
+                    }
+                }
             }
         } finally {
             c.close();
@@ -817,6 +861,13 @@
             }
         }
 
+        // Mark the original contact as "name verified" to make sure that the contact
+        // display name does not change as a result of the join
+        Builder builder = ContentProviderOperation.newUpdate(
+                    ContentUris.withAppendedId(RawContacts.CONTENT_URI, verifiedNameRawContactId));
+        builder.withValue(RawContacts.NAME_VERIFIED, 1);
+        operations.add(builder.build());
+
         // Apply all aggregation exceptions as one batch
         try {
             getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations);
diff --git a/src/com/android/contacts/ui/QuickContactActivity.java b/src/com/android/contacts/ui/QuickContactActivity.java
index b5e445f..897d912 100644
--- a/src/com/android/contacts/ui/QuickContactActivity.java
+++ b/src/com/android/contacts/ui/QuickContactActivity.java
@@ -33,7 +33,7 @@
         QuickContactWindow.OnDismissListener {
     private static final String TAG = "QuickContactActivity";
 
-    static final boolean LOGV = true;
+    static final boolean LOGV = false;
     static final boolean FORCE_CREATE = false;
 
     private QuickContactWindow mQuickContact;
@@ -65,30 +65,13 @@
         final Bundle extras = intent.getExtras();
 
         // Read requested parameters for displaying
-        final Rect target = getTargetRect(intent);
+        final Rect target = intent.getSourceBounds();
         final int mode = extras.getInt(QuickContact.EXTRA_MODE, QuickContact.MODE_MEDIUM);
         final String[] excludeMimes = extras.getStringArray(QuickContact.EXTRA_EXCLUDE_MIMES);
 
         mQuickContact.show(lookupUri, target, mode, excludeMimes);
     }
 
-    private Rect getTargetRect(Intent intent) {
-        Rect target = intent.getSourceBounds();
-        if (target != null) {
-            return target;
-        }
-        final Bundle extras = intent.getExtras();
-        try {
-            target = (Rect)extras.getParcelable(QuickContact.EXTRA_TARGET_RECT);
-            if (target != null) {
-                return target;
-            }
-        } catch (ClassCastException ex) {
-            // fall through
-        }
-        return new Rect(0, 0, 0, 0);
-    }
-
     /** {@inheritDoc} */
     @Override
     public void onBackPressed() {
diff --git a/src/com/android/contacts/ui/QuickContactWindow.java b/src/com/android/contacts/ui/QuickContactWindow.java
index 132b18c..69a2801 100644
--- a/src/com/android/contacts/ui/QuickContactWindow.java
+++ b/src/com/android/contacts/ui/QuickContactWindow.java
@@ -16,6 +16,7 @@
 
 package com.android.contacts.ui;
 
+import com.android.contacts.Collapser;
 import com.android.contacts.ContactsUtils;
 import com.android.contacts.R;
 import com.android.contacts.model.ContactsSource;
@@ -54,6 +55,8 @@
 import android.provider.ContactsContract.CommonDataKinds.Im;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.ContextThemeWrapper;
@@ -85,6 +88,7 @@
 import android.widget.Toast;
 
 import java.lang.ref.SoftReference;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -173,10 +177,17 @@
     private String[] mExcludeMimes;
 
     /**
-     * Specific MIME-types that should be bumped to the front of the dialog.
-     * Other MIME-types not appearing in this list follow in alphabetic order.
+     * {@link #PRECEDING_MIMETYPES} and {@link #FOLLOWING_MIMETYPES} are used to sort MIME-types.
+     *
+     * <p>The MIME-types in {@link #PRECEDING_MIMETYPES} appear in the front of the dialog,
+     * in the order in the array.
+     *
+     * <p>The ones in {@link #FOLLOWING_MIMETYPES} appear in the end of the dialog, in alphabetical
+     * order.
+     *
+     * <p>The rest go between them, in the order in the array.
      */
-    private static final String[] ORDERED_MIMETYPES = new String[] {
+    private static final String[] PRECEDING_MIMETYPES = new String[] {
             Phone.CONTENT_ITEM_TYPE,
             Contacts.CONTENT_ITEM_TYPE,
             Constants.MIME_SMS_ADDRESS,
@@ -184,6 +195,14 @@
     };
 
     /**
+     * See {@link #PRECEDING_MIMETYPES}.
+     */
+    private static final String[] FOLLOWING_MIMETYPES = new String[] {
+            StructuredPostal.CONTENT_ITEM_TYPE,
+            Website.CONTENT_ITEM_TYPE,
+    };
+
+    /**
      * Specific list {@link ApplicationInfo#packageName} of apps that are
      * prefered <strong>only</strong> for the purposes of default icons when
      * multiple {@link ResolveInfo} are found to match. This only happens when
@@ -195,7 +214,8 @@
             "com.android.calendar",
             "com.android.contacts",
             "com.android.mms",
-            "com.android.phone");
+            "com.android.phone",
+            "com.android.browser");
 
     private static final int TOKEN_DATA = 1;
 
@@ -303,6 +323,18 @@
             android.os.Debug.startMethodTracing(TRACE_TAG);
         }
 
+        // Validate incoming parameters
+        final boolean validMode = (mode == QuickContact.MODE_SMALL
+                || mode == QuickContact.MODE_MEDIUM || mode == QuickContact.MODE_LARGE);
+        if (!validMode) {
+            throw new IllegalArgumentException("Invalid mode, expecting MODE_LARGE, "
+                    + "MODE_MEDIUM, or MODE_SMALL");
+        }
+
+        if (anchor == null) {
+            throw new IllegalArgumentException("Missing anchor rectangle");
+        }
+
         // Prepare header view for requested mode
         mLookupUri = lookupUri;
         mAnchor = new Rect(anchor);
@@ -650,7 +682,7 @@
      * Abstract definition of an action that could be performed, along with
      * string description and icon.
      */
-    private interface Action {
+    private interface Action extends Collapser.Collapsible<Action> {
         public CharSequence getHeader();
         public CharSequence getBody();
 
@@ -739,6 +771,12 @@
                     mIntent = new Intent(Intent.ACTION_SENDTO, mailUri);
                 }
 
+            } else if (Website.CONTENT_ITEM_TYPE.equals(mimeType)) {
+                final String url = getAsString(cursor, Website.URL);
+                if (!TextUtils.isEmpty(url)) {
+                    mIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+                }
+
             } else if (Im.CONTENT_ITEM_TYPE.equals(mimeType)) {
                 final boolean isEmail = Email.CONTENT_ITEM_TYPE.equals(
                         getAsString(cursor, Data.MIMETYPE));
@@ -836,6 +874,39 @@
         public Intent getIntent() {
             return mIntent;
         }
+
+        /** {@inheritDoc} */
+        public boolean collapseWith(Action other) {
+            if (!shouldCollapseWith(other)) {
+                return false;
+            }
+            return true;
+        }
+
+        /** {@inheritDoc} */
+        public boolean shouldCollapseWith(Action t) {
+            if (t == null) {
+                return false;
+            }
+            if (!(t instanceof DataAction)) {
+                Log.e(TAG, "t must be DataAction");
+                return false;
+            }
+            DataAction other = (DataAction)t;
+            if (!ContactsUtils.areObjectsEqual(mKind, other.mKind)) {
+                return false;
+            }
+            if (!ContactsUtils.areDataEqual(mContext, mMimeType, mBody, other.mMimeType,
+                    other.mBody)) {
+                return false;
+            }
+            if (!TextUtils.equals(mMimeType, other.mMimeType)
+                    || !ContactsUtils.areIntentActionEqual(mIntent, other.mIntent)
+                    ) {
+                return false;
+            }
+            return true;
+        }
     }
 
     /**
@@ -887,6 +958,15 @@
             return null;
         }
 
+        /** {@inheritDoc} */
+        public boolean collapseWith(Action t) {
+            return false; // Never dup.
+        }
+
+        /** {@inheritDoc} */
+        public boolean shouldCollapseWith(Action t) {
+            return false; // Never dup.
+        }
     }
 
     /**
@@ -1029,7 +1109,7 @@
      * Provide a strongly-typed {@link LinkedList} that holds a list of
      * {@link Action} objects.
      */
-    private class ActionList extends LinkedList<Action> {
+    private class ActionList extends ArrayList<Action> {
     }
 
     /**
@@ -1154,22 +1234,39 @@
             setHeaderText(R.id.timestamp, status.getTimestampLabel(mContext));
         }
 
-        // Turn our list of actions into UI elements, starting with common types
-        final Set<String> containedTypes = mActions.keySet();
-        for (String mimeType : ORDERED_MIMETYPES) {
+        // Turn our list of actions into UI elements
+
+        // Index where we start adding child views.
+        int index = mTrack.getChildCount() - 1;
+
+        // All the mime-types to add.
+        final Set<String> containedTypes = new HashSet<String>(mActions.keySet());
+
+        // First, add PRECEDING_MIMETYPES, which are most common.
+        for (String mimeType : PRECEDING_MIMETYPES) {
             if (containedTypes.contains(mimeType)) {
-                final int index = mTrack.getChildCount() - 1;
-                mTrack.addView(inflateAction(mimeType), index);
+                mTrack.addView(inflateAction(mimeType), index++);
                 containedTypes.remove(mimeType);
             }
         }
 
-        // Then continue with remaining MIME-types in alphabetical order
+        // Keep the current index to append non PRECEDING/FOLLOWING items.
+        final int indexAfterPreceding = index;
+
+        // Then, add FOLLOWING_MIMETYPES, which are least common.
+        for (String mimeType : FOLLOWING_MIMETYPES) {
+            if (containedTypes.contains(mimeType)) {
+                mTrack.addView(inflateAction(mimeType), index++);
+                containedTypes.remove(mimeType);
+            }
+        }
+
+        // Go back to just after PRECEDING_MIMETYPES, and append the rest.
+        index = indexAfterPreceding;
         final String[] remainingTypes = containedTypes.toArray(new String[containedTypes.size()]);
         Arrays.sort(remainingTypes);
         for (String mimeType : remainingTypes) {
-            final int index = mTrack.getChildCount() - 1;
-            mTrack.addView(inflateAction(mimeType), index);
+            mTrack.addView(inflateAction(mimeType), index++);
         }
     }
 
@@ -1208,8 +1305,8 @@
     }
 
     /**
-     * Inflate the in-track view for the action of the given MIME-type. Will use
-     * the icon provided by the {@link DataKind}.
+     * Inflate the in-track view for the action of the given MIME-type, collapsing duplicate values.
+     * Will use the icon provided by the {@link DataKind}.
      */
     private View inflateAction(String mimeType) {
         final CheckableImageView view = (CheckableImageView)obtainView();
@@ -1217,6 +1314,9 @@
 
         // Add direct intent if single child, otherwise flag for multiple
         ActionList children = mActions.get(mimeType);
+        if (children.size() > 1) {
+            Collapser.collapseList(children);
+        }
         Action firstInfo = children.get(0);
         if (children.size() == 1) {
             view.setTag(firstInfo);
@@ -1507,11 +1607,6 @@
     }
 
     /** {@inheritDoc} */
-    public void onQueryEntitiesComplete(int token, Object cookie, EntityIterator iterator) {
-        // No actions
-    }
-
-    /** {@inheritDoc} */
     public void onAttachedToWindow() {
         // No actions
     }
diff --git a/src/com/android/contacts/ui/ShowOrCreateActivity.java b/src/com/android/contacts/ui/ShowOrCreateActivity.java
index 7728b36..0828b3f 100755
--- a/src/com/android/contacts/ui/ShowOrCreateActivity.java
+++ b/src/com/android/contacts/ui/ShowOrCreateActivity.java
@@ -221,11 +221,6 @@
 	return super.onCreateDialog(id);
     }
 
-    /** {@inheritDoc} */
-    public void onQueryEntitiesComplete(int token, Object cookie, EntityIterator iterator) {
-        // No actions
-    }
-
     /**
      * Listener for {@link DialogInterface} that launches a given {@link Intent}
      * when clicked. When clicked, this also closes the parent using
diff --git a/src/com/android/contacts/ui/ViewIdGenerator.java b/src/com/android/contacts/ui/ViewIdGenerator.java
new file mode 100644
index 0000000..f7281d4
--- /dev/null
+++ b/src/com/android/contacts/ui/ViewIdGenerator.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts.ui;
+
+import com.android.contacts.model.EntityDelta;
+import com.android.contacts.model.ContactsSource.DataKind;
+import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.android.contacts.ui.widget.GenericEditorView;
+import com.android.contacts.ui.widget.KindSectionView;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class that provides unique view ids for {@link ContentEditorView}, {@link KindSectionView},
+ * {@link GenericEditorView} and {@link EditView} on {@link EditContactActivity}.
+ * It is used to assign a unique but consistent id to each view across {@link EditContactActivity}'s
+ * lifecycle, so that we can re-construct view state (e.g. focused view) when the screen rotates.
+ *
+ * <p>This class is not thread safe.
+ */
+public final class ViewIdGenerator implements Parcelable {
+    private static final int INVALID_VIEW_ID = 0;
+    private static final int INITIAL_VIEW_ID = 1;
+
+    public static final int NO_VIEW_INDEX = -1;
+
+    private int mNextId;
+
+    /**
+     * Used as a map from the "key" of the views to actual ids.  {@link #getId()} generates keys for
+     * the views.
+     */
+    private Bundle mIdMap = new Bundle();
+
+    private static final char KEY_SEPARATOR = '*';
+
+    private final static StringBuilder sWorkStringBuilder = new StringBuilder();
+
+    public ViewIdGenerator() {
+        mNextId = INITIAL_VIEW_ID;
+    }
+
+    /** {@inheritDoc} */
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Returns an id for a view associated with specified contact field.
+     *
+     * @param entity {@link EntityDelta} associated with the view
+     * @param kind {@link DataKind} associated with the view, or null if none exists.
+     * @param values {@link ValuesDelta} associated with the view, or null if none exists.
+     * @param viewIndex index of the view in the parent {@link Editor}, if it's a leave view.
+     *     Otherwise, pass {@link #NO_VIEW_INDEX}.
+     */
+    public int getId(EntityDelta entity, DataKind kind, ValuesDelta values,
+            int viewIndex) {
+        final String k = getMapKey(entity, kind, values, viewIndex);
+
+        int id = mIdMap.getInt(k, INVALID_VIEW_ID);
+        if (id == INVALID_VIEW_ID) {
+            // Make sure the new id won't conflict with auto-generated ids by masking with 0xffff.
+            id = (mNextId++) & 0xFFFF;
+            mIdMap.putInt(k, id);
+        }
+        return id;
+    }
+
+    private static String getMapKey(EntityDelta entity, DataKind kind, ValuesDelta values,
+            int viewIndex) {
+        sWorkStringBuilder.setLength(0);
+        if (entity != null) {
+            sWorkStringBuilder.append(entity.getValues().getId());
+
+            if (kind != null) {
+                sWorkStringBuilder.append(KEY_SEPARATOR);
+                sWorkStringBuilder.append(kind.mimeType);
+
+                if (values != null) {
+                    sWorkStringBuilder.append(KEY_SEPARATOR);
+                    sWorkStringBuilder.append(values.getId());
+
+                    if (viewIndex != NO_VIEW_INDEX) {
+                        sWorkStringBuilder.append(KEY_SEPARATOR);
+                        sWorkStringBuilder.append(viewIndex);
+                    }
+                }
+            }
+        }
+        return sWorkStringBuilder.toString();
+    }
+
+    /** {@Override} */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mNextId);
+        dest.writeBundle(mIdMap);
+    }
+
+    private void readFromParcel(Parcel src) {
+        mNextId = src.readInt();
+        mIdMap = src.readBundle();
+    }
+
+    public static final Parcelable.Creator<ViewIdGenerator> CREATOR =
+            new Parcelable.Creator<ViewIdGenerator>() {
+        public ViewIdGenerator createFromParcel(Parcel in) {
+            final ViewIdGenerator vig = new ViewIdGenerator();
+            vig.readFromParcel(in);
+            return vig;
+        }
+
+        public ViewIdGenerator[] newArray(int size) {
+            return new ViewIdGenerator[size];
+        }
+    };
+}
diff --git a/src/com/android/contacts/ui/widget/BaseContactEditorView.java b/src/com/android/contacts/ui/widget/BaseContactEditorView.java
index 189e2d5..c2f9136 100644
--- a/src/com/android/contacts/ui/widget/BaseContactEditorView.java
+++ b/src/com/android/contacts/ui/widget/BaseContactEditorView.java
@@ -22,6 +22,7 @@
 import com.android.contacts.model.ContactsSource.EditType;
 import com.android.contacts.model.Editor.EditorListener;
 import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.android.contacts.ui.ViewIdGenerator;
 
 import android.content.Context;
 import android.content.Entity;
@@ -95,7 +96,7 @@
      * {@link EntityDelta} state and the {@link ContactsSource} that
      * apply to that state.
      */
-    public abstract void setState(EntityDelta state, ContactsSource source);
+    public abstract void setState(EntityDelta state, ContactsSource source, ViewIdGenerator vig);
 
     /**
      * Sets the {@link EditorListener} on the name field
diff --git a/src/com/android/contacts/ui/widget/ContactEditorView.java b/src/com/android/contacts/ui/widget/ContactEditorView.java
index 1720822..ebb6bbb 100644
--- a/src/com/android/contacts/ui/widget/ContactEditorView.java
+++ b/src/com/android/contacts/ui/widget/ContactEditorView.java
@@ -24,11 +24,14 @@
 import com.android.contacts.model.ContactsSource.EditType;
 import com.android.contacts.model.Editor.EditorListener;
 import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.android.contacts.ui.ViewIdGenerator;
 
 import android.content.Context;
 import android.content.Entity;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.RawContacts;
@@ -61,6 +64,7 @@
     private View mPhotoStub;
     private GenericEditorView mName;
 
+    private boolean mIsSourceReadOnly;
     private ViewGroup mGeneral;
     private ViewGroup mSecondary;
     private boolean mSecondaryVisible;
@@ -135,12 +139,22 @@
 
     /**
      * Set the visibility of secondary sections, along with header icon.
+     *
+     * <p>If the source is read-only and there's no secondary fields, the entire secondary section
+     * will be hidden.
      */
     private void setSecondaryVisible(boolean makeVisible) {
-        mSecondary.setVisibility(makeVisible ? View.VISIBLE : View.GONE);
-        mSecondaryHeader.setCompoundDrawablesWithIntrinsicBounds(makeVisible ? mSecondaryOpen
-                : mSecondaryClosed, null, null, null);
         mSecondaryVisible = makeVisible;
+
+        if (!mIsSourceReadOnly && mSecondary.getChildCount() > 0) {
+            mSecondaryHeader.setVisibility(View.VISIBLE);
+            mSecondaryHeader.setCompoundDrawablesWithIntrinsicBounds(
+                    makeVisible ? mSecondaryOpen : mSecondaryClosed, null, null, null);
+            mSecondary.setVisibility(makeVisible ? View.VISIBLE : View.GONE);
+        } else {
+            mSecondaryHeader.setVisibility(View.GONE);
+            mSecondary.setVisibility(View.GONE);
+        }
     }
 
     /**
@@ -149,7 +163,7 @@
      * apply to that state.
      */
     @Override
-    public void setState(EntityDelta state, ContactsSource source) {
+    public void setState(EntityDelta state, ContactsSource source, ViewIdGenerator vig) {
         // Remove any existing sections
         mGeneral.removeAllViews();
         mSecondary.removeAllViews();
@@ -157,6 +171,10 @@
         // Bail if invalid state or source
         if (state == null || source == null) return;
 
+        setId(vig.getId(state, null, null, ViewIdGenerator.NO_VIEW_INDEX));
+
+        mIsSourceReadOnly = source.readOnly;
+
         // Make sure we have StructuredName
         EntityModifier.ensureKindExists(state, source, StructuredName.CONTENT_ITEM_TYPE);
 
@@ -180,12 +198,11 @@
         EntityModifier.ensureKindExists(state, source, Photo.CONTENT_ITEM_TYPE);
         mHasPhotoEditor = (source.getKindForMimetype(Photo.CONTENT_ITEM_TYPE) != null);
         mPhoto.setVisibility(mHasPhotoEditor ? View.VISIBLE : View.GONE);
-        mPhoto.setEnabled(!source.readOnly);
-        mName.setEnabled(!source.readOnly);
+        mPhoto.setEnabled(!mIsSourceReadOnly);
+        mName.setEnabled(!mIsSourceReadOnly);
 
-        boolean readOnly = source.readOnly;
         // Show and hide the appropriate views
-        if (readOnly) {
+        if (mIsSourceReadOnly) {
             mGeneral.setVisibility(View.GONE);
             mName.setVisibility(View.GONE);
             mReadOnly.setVisibility(View.VISIBLE);
@@ -207,8 +224,8 @@
             if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
                 // Handle special case editor for structured name
                 final ValuesDelta primary = state.getPrimaryEntry(mimeType);
-                if (!readOnly) {
-                    mName.setValues(kind, primary, state, source.readOnly);
+                if (!mIsSourceReadOnly) {
+                    mName.setValues(kind, primary, state, mIsSourceReadOnly, vig);
                 } else {
                     String displayName = primary.getAsString(StructuredName.DISPLAY_NAME);
                     mReadOnlyName.setText(displayName);
@@ -216,37 +233,24 @@
             } else if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
                 // Handle special case editor for photos
                 final ValuesDelta primary = state.getPrimaryEntry(mimeType);
-                mPhoto.setValues(kind, primary, state, source.readOnly);
-                if (readOnly && !mPhoto.hasSetPhoto()) {
+                mPhoto.setValues(kind, primary, state, mIsSourceReadOnly, vig);
+                if (mIsSourceReadOnly && !mPhoto.hasSetPhoto()) {
                     mPhotoStub.setVisibility(View.GONE);
                 } else {
                     mPhotoStub.setVisibility(View.VISIBLE);
                 }
-            } else if (!readOnly) {
+            } else if (!mIsSourceReadOnly) {
                 // Otherwise use generic section-based editors
                 if (kind.fieldList == null) continue;
                 final ViewGroup parent = kind.secondary ? mSecondary : mGeneral;
                 final KindSectionView section = (KindSectionView)mInflater.inflate(
                         R.layout.item_kind_section, parent, false);
-                section.setState(kind, state, source.readOnly);
-                section.setId(kind.weight);
+                section.setState(kind, state, mIsSourceReadOnly, vig);
                 parent.addView(section);
             }
         }
 
-        if (!readOnly && mSecondary.getChildCount() > 0) {
-            // There exist secondary elements, show the header and honor mSecondaryVisible
-            mSecondaryHeader.setVisibility(View.VISIBLE);
-            if (mSecondaryVisible) {
-                mSecondary.setVisibility(View.VISIBLE);
-            } else {
-                mSecondary.setVisibility(View.GONE);
-            }
-        } else {
-            // There are no secondary elements, hide the whole thing
-            mSecondaryHeader.setVisibility(View.GONE);
-            mSecondary.setVisibility(View.GONE);
-        }
+        setSecondaryVisible(mSecondaryVisible);
     }
 
     /**
@@ -261,4 +265,57 @@
     public long getRawContactId() {
         return mRawContactId;
     }
+
+    private static class SavedState extends BaseSavedState {
+        public boolean mSecondaryVisible;
+
+        SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        private SavedState(Parcel in) {
+            super(in);
+            mSecondaryVisible = (in.readInt() == 0 ? false : true);
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            super.writeToParcel(out, flags);
+            out.writeInt(mSecondaryVisible ? 1 : 0);
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR
+                = new Parcelable.Creator<SavedState>() {
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+
+    /**
+     * Saves the visibility of the secondary field.
+     */
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        Parcelable superState = super.onSaveInstanceState();
+        SavedState ss = new SavedState(superState);
+
+        ss.mSecondaryVisible = mSecondaryVisible;
+        return ss;
+    }
+
+    /**
+     * Restores the visibility of the secondary field.
+     */
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        SavedState ss = (SavedState) state;
+        super.onRestoreInstanceState(ss.getSuperState());
+
+        setSecondaryVisible(ss.mSecondaryVisible);
+    }
 }
diff --git a/src/com/android/contacts/ui/widget/GenericEditorView.java b/src/com/android/contacts/ui/widget/GenericEditorView.java
index e1fdd71..40ed5cd 100644
--- a/src/com/android/contacts/ui/widget/GenericEditorView.java
+++ b/src/com/android/contacts/ui/widget/GenericEditorView.java
@@ -25,12 +25,15 @@
 import com.android.contacts.model.ContactsSource.EditField;
 import com.android.contacts.model.ContactsSource.EditType;
 import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.android.contacts.ui.ViewIdGenerator;
 
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Entity;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.telephony.PhoneNumberFormattingTextWatcher;
 import android.text.Editable;
 import android.text.InputType;
@@ -67,6 +70,7 @@
     protected ViewGroup mFields;
     protected View mDelete;
     protected View mMore;
+    protected View mLess;
 
     protected DataKind mKind;
     protected ValuesDelta mEntry;
@@ -79,6 +83,8 @@
     // Used only when a user tries to use custom label.
     private EditType mPendingType;
 
+    private ViewIdGenerator mViewIdGenerator;
+
     public GenericEditorView(Context context) {
         super(context);
     }
@@ -103,6 +109,9 @@
 
         mMore = findViewById(R.id.edit_more);
         mMore.setOnClickListener(this);
+
+        mLess = findViewById(R.id.edit_less);
+        mLess.setOnClickListener(this);
     }
 
     protected EditorListener mListener;
@@ -124,6 +133,7 @@
             v.setEnabled(enabled);
         }
         mMore.setEnabled(enabled);
+        mLess.setEnabled(enabled);
     }
 
     /**
@@ -160,18 +170,21 @@
     }
 
     private void rebuildValues() {
-        setValues(mKind, mEntry, mState, mReadOnly);
+        setValues(mKind, mEntry, mState, mReadOnly, mViewIdGenerator);
     }
 
     /**
      * Prepare this editor using the given {@link DataKind} for defining
      * structure and {@link ValuesDelta} describing the content to edit.
      */
-    public void setValues(DataKind kind, ValuesDelta entry, EntityDelta state, boolean readOnly) {
+    public void setValues(DataKind kind, ValuesDelta entry, EntityDelta state, boolean readOnly,
+            ViewIdGenerator vig) {
         mKind = kind;
         mEntry = entry;
         mState = state;
         mReadOnly = readOnly;
+        mViewIdGenerator = vig;
+        setId(vig.getId(state, kind, entry, ViewIdGenerator.NO_VIEW_INDEX));
 
         final boolean enabled = !readOnly;
 
@@ -195,9 +208,11 @@
         // Build out set of fields
         mFields.removeAllViews();
         boolean hidePossible = false;
+        int n = 0;
         for (EditField field : kind.fieldList) {
             // Inflate field from definition
             EditText fieldView = (EditText)mInflater.inflate(RES_FIELD, mFields, false);
+            fieldView.setId(vig.getId(state, kind, entry, n++));
             if (field.titleRes > 0) {
                 fieldView.setHint(field.titleRes);
             }
@@ -238,8 +253,15 @@
         }
 
         // When hiding fields, place expandable
-        mMore.setVisibility(hidePossible ? View.VISIBLE : View.GONE);
+        if (hidePossible) {
+            mMore.setVisibility(mHideOptional ? View.VISIBLE : View.GONE);
+            mLess.setVisibility(mHideOptional ? View.GONE : View.VISIBLE);
+        } else {
+            mMore.setVisibility(View.GONE);
+            mLess.setVisibility(View.GONE);
+        }
         mMore.setEnabled(enabled);
+        mLess.setEnabled(enabled);
     }
 
     /**
@@ -355,11 +377,80 @@
                 }
                 break;
             }
-            case R.id.edit_more: {
+            case R.id.edit_more:
+            case R.id.edit_less: {
                 mHideOptional = !mHideOptional;
                 rebuildValues();
                 break;
             }
         }
     }
+
+    private static class SavedState extends BaseSavedState {
+        public boolean mHideOptional;
+        public int[] mVisibilities;
+
+        SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        private SavedState(Parcel in) {
+            super(in);
+            mVisibilities = new int[in.readInt()];
+            in.readIntArray(mVisibilities);
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            super.writeToParcel(out, flags);
+            out.writeInt(mVisibilities.length);
+            out.writeIntArray(mVisibilities);
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR
+                = new Parcelable.Creator<SavedState>() {
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+
+    /**
+     * Saves the visibility of the child EditTexts, and mHideOptional.
+     */
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        Parcelable superState = super.onSaveInstanceState();
+        SavedState ss = new SavedState(superState);
+
+        ss.mHideOptional = mHideOptional;
+
+        final int numChildren = mFields.getChildCount();
+        ss.mVisibilities = new int[numChildren];
+        for (int i = 0; i < numChildren; i++) {
+            ss.mVisibilities[i] = mFields.getChildAt(i).getVisibility();
+        }
+
+        return ss;
+    }
+
+    /**
+     * Restores the visibility of the child EditTexts, and mHideOptional.
+     */
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        SavedState ss = (SavedState) state;
+        super.onRestoreInstanceState(ss.getSuperState());
+
+        mHideOptional = ss.mHideOptional;
+
+        int numChildren = Math.min(mFields.getChildCount(), ss.mVisibilities.length);
+        for (int i = 0; i < numChildren; i++) {
+            mFields.getChildAt(i).setVisibility(ss.mVisibilities[i]);
+        }
+    }
 }
diff --git a/src/com/android/contacts/ui/widget/KindSectionView.java b/src/com/android/contacts/ui/widget/KindSectionView.java
index b52cfd0..e379b69 100644
--- a/src/com/android/contacts/ui/widget/KindSectionView.java
+++ b/src/com/android/contacts/ui/widget/KindSectionView.java
@@ -23,6 +23,7 @@
 import com.android.contacts.model.ContactsSource.DataKind;
 import com.android.contacts.model.Editor.EditorListener;
 import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.android.contacts.ui.ViewIdGenerator;
 
 import android.content.Context;
 import android.provider.ContactsContract.Data;
@@ -52,6 +53,8 @@
     private EntityDelta mState;
     private boolean mReadOnly;
 
+    private ViewIdGenerator mViewIdGenerator;
+
     public KindSectionView(Context context) {
         super(context);
     }
@@ -88,10 +91,13 @@
         // Ignore requests
     }
 
-    public void setState(DataKind kind, EntityDelta state, boolean readOnly) {
+    public void setState(DataKind kind, EntityDelta state, boolean readOnly, ViewIdGenerator vig) {
         mKind = kind;
         mState = state;
         mReadOnly = readOnly;
+        mViewIdGenerator = vig;
+
+        setId(mViewIdGenerator.getId(state, kind, null, ViewIdGenerator.NO_VIEW_INDEX));
 
         // TODO: handle resources from remote packages
         mTitle.setText(kind.titleRes);
@@ -116,9 +122,8 @@
 
             final GenericEditorView editor = (GenericEditorView)mInflater.inflate(
                     R.layout.item_generic_editor, mEditors, false);
-            editor.setValues(mKind, entry, mState, mReadOnly);
+            editor.setValues(mKind, entry, mState, mReadOnly, mViewIdGenerator);
             editor.setEditorListener(this);
-            editor.setId(entry.getViewId());
             mEditors.addView(editor);
         }
     }
@@ -138,9 +143,16 @@
     /** {@inheritDoc} */
     public void onClick(View v) {
         // Insert a new child and rebuild
-        EntityModifier.insertChild(mState, mKind);
+        final ValuesDelta newValues = EntityModifier.insertChild(mState, mKind);
         this.rebuildFromState();
         this.updateAddEnabled();
         this.updateEditorsVisible();
+
+        // Find the newly added EditView and set focus.
+        final int newFieldId = mViewIdGenerator.getId(mState, mKind, newValues, 0);
+        final View newField = findViewById(newFieldId);
+        if (newField != null) {
+            newField.requestFocus();
+        }
     }
 }
diff --git a/src/com/android/contacts/ui/widget/PhotoEditorView.java b/src/com/android/contacts/ui/widget/PhotoEditorView.java
index f117091..eff39d0 100644
--- a/src/com/android/contacts/ui/widget/PhotoEditorView.java
+++ b/src/com/android/contacts/ui/widget/PhotoEditorView.java
@@ -21,6 +21,7 @@
 import com.android.contacts.model.ContactsSource.DataKind;
 import com.android.contacts.model.EntityDelta.ValuesDelta;
 import com.android.contacts.model.Editor;
+import com.android.contacts.ui.ViewIdGenerator;
 
 import android.content.Context;
 import android.graphics.Bitmap;
@@ -75,9 +76,13 @@
     }
 
     /** {@inheritDoc} */
-    public void setValues(DataKind kind, ValuesDelta values, EntityDelta state, boolean readOnly) {
+    public void setValues(DataKind kind, ValuesDelta values, EntityDelta state, boolean readOnly,
+            ViewIdGenerator vig) {
         mEntry = values;
         mReadOnly = readOnly;
+
+        setId(vig.getId(state, kind, values, 0));
+
         if (values != null) {
             // Try decoding photo if actual entry
             final byte[] photoBytes = values.getAsByteArray(Photo.PHOTO);
diff --git a/src/com/android/contacts/ui/widget/ReadOnlyContactEditorView.java b/src/com/android/contacts/ui/widget/ReadOnlyContactEditorView.java
index a5f8eb6..4635f6a 100644
--- a/src/com/android/contacts/ui/widget/ReadOnlyContactEditorView.java
+++ b/src/com/android/contacts/ui/widget/ReadOnlyContactEditorView.java
@@ -24,6 +24,7 @@
 import com.android.contacts.model.ContactsSource.EditType;
 import com.android.contacts.model.Editor.EditorListener;
 import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.android.contacts.ui.ViewIdGenerator;
 
 import android.content.Context;
 import android.content.Entity;
@@ -101,7 +102,7 @@
      * TODO: make this more generic using data from the source
      */
     @Override
-    public void setState(EntityDelta state, ContactsSource source) {
+    public void setState(EntityDelta state, ContactsSource source, ViewIdGenerator vig) {
         // Remove any existing sections
         mGeneral.removeAllViews();
 
@@ -135,7 +136,7 @@
             EntityModifier.ensureKindExists(state, source, Photo.CONTENT_ITEM_TYPE);
             mHasPhotoEditor = (source.getKindForMimetype(Photo.CONTENT_ITEM_TYPE) != null);
             primary = state.getPrimaryEntry(Photo.CONTENT_ITEM_TYPE);
-            mPhoto.setValues(kind, primary, state, source.readOnly);
+            mPhoto.setValues(kind, primary, state, source.readOnly, vig);
             if (!mHasPhotoEditor || !mPhoto.hasSetPhoto()) {
                 mPhotoStub.setVisibility(View.GONE);
             } else {
diff --git a/src/com/android/contacts/util/NotifyingAsyncQueryHandler.java b/src/com/android/contacts/util/NotifyingAsyncQueryHandler.java
index 795ac79..83fae29 100644
--- a/src/com/android/contacts/util/NotifyingAsyncQueryHandler.java
+++ b/src/com/android/contacts/util/NotifyingAsyncQueryHandler.java
@@ -18,7 +18,6 @@
 
 import android.content.AsyncQueryHandler;
 import android.content.Context;
-import android.content.EntityIterator;
 import android.database.Cursor;
 
 import java.lang.ref.WeakReference;
@@ -26,7 +25,7 @@
 /**
  * Slightly more abstract {@link AsyncQueryHandler} that helps keep a
  * {@link WeakReference} back to a listener. Will properly close any
- * {@link Cursor} or {@link EntityIterator} if the listener ceases to exist.
+ * {@link Cursor} if the listener ceases to exist.
  * <p>
  * This pattern can be used to perform background queries without leaking
  * {@link Context} objects.
@@ -41,7 +40,6 @@
      */
     public interface AsyncQueryListener {
         void onQueryComplete(int token, Object cookie, Cursor cursor);
-        void onQueryEntitiesComplete(int token, Object cookie, EntityIterator iterator);
     }
 
     public NotifyingAsyncQueryHandler(Context context, AsyncQueryListener listener) {
@@ -67,15 +65,4 @@
             cursor.close();
         }
     }
-
-    /** {@inheritDoc} */
-    @Override
-    protected void onQueryEntitiesComplete(int token, Object cookie, EntityIterator iterator) {
-        final AsyncQueryListener listener = mListener.get();
-        if (listener != null) {
-            listener.onQueryEntitiesComplete(token, cookie, iterator);
-        } else if (iterator != null) {
-            iterator.close();
-        }
-    }
 }
diff --git a/tests/src/com/android/contacts/ContactsUtilsTests.java b/tests/src/com/android/contacts/ContactsUtilsTests.java
index 01a1ef4..d4d4e5b 100644
--- a/tests/src/com/android/contacts/ContactsUtilsTests.java
+++ b/tests/src/com/android/contacts/ContactsUtilsTests.java
@@ -21,6 +21,7 @@
 import android.net.Uri;
 import android.provider.ContactsContract.CommonDataKinds.Email;
 import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.LargeTest;
 
@@ -98,4 +99,61 @@
     public void testIsGraphicPunctuation() throws Exception {
         assertTrue(ContactsUtils.isGraphic("."));
     }
+
+    public void testAreObjectsEqual() throws Exception {
+        assertTrue("null:null", ContactsUtils.areObjectsEqual(null, null));
+        assertTrue("1:1", ContactsUtils.areObjectsEqual(1, 1));
+
+        assertFalse("null:1", ContactsUtils.areObjectsEqual(null, 1));
+        assertFalse("1:null", ContactsUtils.areObjectsEqual(1, null));
+        assertFalse("1:2", ContactsUtils.areObjectsEqual(1, 2));
+    }
+
+    public void testAreDataEqual() throws Exception {
+        checkAreDataEqual("1", true, null, null, null, null);
+        checkAreDataEqual("2", true, "a", "b", "a", "b");
+
+        checkAreDataEqual("11", false, "a", null, null, null);
+        checkAreDataEqual("12", false, null, "a", null, null);
+        checkAreDataEqual("13", false, null, null, "a", null);
+        checkAreDataEqual("14", false, null, null, null, "a");
+
+        checkAreDataEqual("21", false, "a", "b", null, null);
+        checkAreDataEqual("22", false, "a", "b", "a", null);
+        checkAreDataEqual("23", false, "a", "b", null, "b");
+        checkAreDataEqual("24", false, "a", "b", "a", "x");
+        checkAreDataEqual("25", false, "a", "b", "x", "b");
+
+        checkAreDataEqual("31", false, null, null, "a", "b");
+        checkAreDataEqual("32", false, "a", null, "a", "b");
+        checkAreDataEqual("33", false, null, "b", "a", "b");
+        checkAreDataEqual("34", false, "a", "x", "a", "b");
+        checkAreDataEqual("35", false, "x", "b", "a", "b");
+
+        checkAreDataEqual("41", true, Phone.CONTENT_ITEM_TYPE, null, Phone.CONTENT_ITEM_TYPE,
+                null);
+        checkAreDataEqual("42", true, Phone.CONTENT_ITEM_TYPE, "1", Phone.CONTENT_ITEM_TYPE, "1");
+
+        checkAreDataEqual("51", false, Phone.CONTENT_ITEM_TYPE, "1", Phone.CONTENT_ITEM_TYPE, "2");
+        checkAreDataEqual("52", false, Phone.CONTENT_ITEM_TYPE, "1", Phone.CONTENT_ITEM_TYPE, null);
+        checkAreDataEqual("53", false, Phone.CONTENT_ITEM_TYPE, null, Phone.CONTENT_ITEM_TYPE, "2");
+    }
+
+    private void checkAreDataEqual(String message, boolean expected, CharSequence mimetype1,
+            CharSequence data1, CharSequence mimetype2, CharSequence data2) {
+        assertEquals(message, expected,
+                ContactsUtils.areDataEqual(mContext, mimetype1, data1, mimetype2, data2));
+    }
+
+    public void testAreIntentActionEqual() throws Exception {
+        assertTrue("1", ContactsUtils.areIntentActionEqual(null, null));
+        assertTrue("1", ContactsUtils.areIntentActionEqual(new Intent("a"), new Intent("a")));
+
+        assertFalse("11", ContactsUtils.areIntentActionEqual(new Intent("a"), null));
+        assertFalse("12", ContactsUtils.areIntentActionEqual(null, new Intent("a")));
+
+        assertFalse("21", ContactsUtils.areIntentActionEqual(new Intent("a"), new Intent()));
+        assertFalse("22", ContactsUtils.areIntentActionEqual(new Intent(), new Intent("b")));
+        assertFalse("23", ContactsUtils.areIntentActionEqual(new Intent("a"), new Intent("b")));
+    }
 }
diff --git a/tests/src/com/android/contacts/EntityModifierTests.java b/tests/src/com/android/contacts/EntityModifierTests.java
index 489bf04..4bc4622 100644
--- a/tests/src/com/android/contacts/EntityModifierTests.java
+++ b/tests/src/com/android/contacts/EntityModifierTests.java
@@ -34,10 +34,16 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Entity;
+import android.os.Bundle;
+import android.provider.ContactsContract.Intents.Insert;
 import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.RawContacts;
 import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.LargeTest;
 
@@ -56,6 +62,10 @@
 
     private static final long TEST_ID = 4;
     private static final String TEST_PHONE = "218-555-1212";
+    private static final String TEST_NAME = "Adam Young";
+    private static final String TEST_NAME2 = "Breanne Duren";
+    private static final String TEST_IM = "example@example.com";
+    private static final String TEST_POSTAL = "1600 Amphitheatre Parkway";
 
     private static final String TEST_ACCOUNT_NAME = "unittest@example.com";
     private static final String TEST_ACCOUNT_TYPE = "com.example.unittest";
@@ -95,12 +105,24 @@
 
             // Email is unlimited
             kind = new DataKind(Email.CONTENT_ITEM_TYPE, -1, -1, 10, true);
-
             kind.typeOverallMax = -1;
-
             kind.fieldList = Lists.newArrayList();
             kind.fieldList.add(new EditField(Email.DATA, -1, -1));
+            addKind(kind);
 
+            // IM is only one
+            kind = new DataKind(Im.CONTENT_ITEM_TYPE, -1, -1, 10, true);
+            kind.typeOverallMax = 1;
+            kind.fieldList = Lists.newArrayList();
+            kind.fieldList.add(new EditField(Im.DATA, -1, -1));
+            addKind(kind);
+
+            // Organization is only one
+            kind = new DataKind(Organization.CONTENT_ITEM_TYPE, -1, -1, 10, true);
+            kind.typeOverallMax = 1;
+            kind.fieldList = Lists.newArrayList();
+            kind.fieldList.add(new EditField(Organization.COMPANY, -1, -1));
+            kind.fieldList.add(new EditField(Organization.TITLE, -1, -1));
             addKind(kind);
         }
 
@@ -662,4 +684,73 @@
             assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
         }
     }
+
+    public void testParseExtrasExistingName() {
+        final ContactsSource source = getSource();
+        final DataKind kindName = source.getKindForMimetype(StructuredName.CONTENT_ITEM_TYPE);
+
+        // Build "before" name
+        final ContentValues first = new ContentValues();
+        first.put(Data._ID, TEST_ID);
+        first.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
+        first.put(StructuredName.GIVEN_NAME, TEST_NAME);
+
+        // Parse extras, making sure we keep single name
+        final EntityDelta state = getEntity(TEST_ID, first);
+        final Bundle extras = new Bundle();
+        extras.putString(Insert.NAME, TEST_NAME2);
+        EntityModifier.parseExtras(mContext, source, state, extras);
+
+        final int nameCount = state.getMimeEntriesCount(StructuredName.CONTENT_ITEM_TYPE, true);
+        assertEquals("Unexpected names", 1, nameCount);
+    }
+
+    public void testParseExtrasIgnoreLimit() {
+        final ContactsSource source = getSource();
+        final DataKind kindIm = source.getKindForMimetype(Im.CONTENT_ITEM_TYPE);
+
+        // Build "before" IM
+        final ContentValues first = new ContentValues();
+        first.put(Data._ID, TEST_ID);
+        first.put(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE);
+        first.put(Im.DATA, TEST_IM);
+
+        final EntityDelta state = getEntity(TEST_ID, first);
+        final int beforeCount = state.getMimeEntries(Im.CONTENT_ITEM_TYPE).size();
+
+        // We should ignore data that doesn't fit source rules, since source
+        // only allows single Im
+        final Bundle extras = new Bundle();
+        extras.putInt(Insert.IM_PROTOCOL, Im.PROTOCOL_GOOGLE_TALK);
+        extras.putString(Insert.IM_HANDLE, TEST_IM);
+        EntityModifier.parseExtras(mContext, source, state, extras);
+
+        final int afterCount = state.getMimeEntries(Im.CONTENT_ITEM_TYPE).size();
+        assertEquals("Broke source rules", beforeCount, afterCount);
+    }
+
+    public void testParseExtrasIgnoreUnhandled() {
+        final ContactsSource source = getSource();
+        final EntityDelta state = getEntity(TEST_ID);
+
+        // We should silently ignore types unsupported by source
+        final Bundle extras = new Bundle();
+        extras.putString(Insert.POSTAL, TEST_POSTAL);
+        EntityModifier.parseExtras(mContext, source, state, extras);
+
+        assertNull("Broke source rules", state.getMimeEntries(StructuredPostal.CONTENT_ITEM_TYPE));
+    }
+
+    public void testParseExtrasJobTitle() {
+        final ContactsSource source = getSource();
+        final EntityDelta state = getEntity(TEST_ID);
+
+        // Make sure that we create partial Organizations
+        final Bundle extras = new Bundle();
+        extras.putString(Insert.JOB_TITLE, TEST_NAME);
+        EntityModifier.parseExtras(mContext, source, state, extras);
+
+        final int count = state.getMimeEntries(Organization.CONTENT_ITEM_TYPE).size();
+        assertEquals("Expected to create organization", 1, count);
+    }
 }
diff --git a/tests/src/com/android/contacts/GroupingListAdapterTests.java b/tests/src/com/android/contacts/GroupingListAdapterTests.java
new file mode 100644
index 0000000..1877fac
--- /dev/null
+++ b/tests/src/com/android/contacts/GroupingListAdapterTests.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts;
+
+import android.content.Context;
+import android.database.CharArrayBuffer;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.provider.CallLog.Calls;
+import android.test.AndroidTestCase;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.ViewGroup;
+
+import static com.android.contacts.GroupingListAdapter.ITEM_TYPE_STANDALONE;
+import static com.android.contacts.GroupingListAdapter.ITEM_TYPE_IN_GROUP;
+import static com.android.contacts.GroupingListAdapter.ITEM_TYPE_GROUP_HEADER;
+
+/**
+ * Tests for the contact call list adapter.
+ *
+ * Running all tests:
+ *
+ *   runtest contacts
+ * or
+ *   adb shell am instrument \
+ *     -w com.android.contacts.tests/android.test.InstrumentationTestRunner
+ */
+public class GroupingListAdapterTests extends AndroidTestCase {
+
+    static private final String[] CALL_LOG_PROJECTION = new String[] {
+        Calls._ID,
+        Calls.NUMBER,
+        Calls.DATE,
+    };
+
+    private static final int CALLS_NUMBER_COLUMN_INDEX = 1;
+
+    private MatrixCursor mCursor;
+    private long mNextCall;
+
+    private GroupingListAdapter mAdapter = new GroupingListAdapter(null) {
+
+        @Override
+        protected void addGroups(Cursor cursor) {
+            int count = cursor.getCount();
+            int groupItemCount = 1;
+            cursor.moveToFirst();
+            String currentValue = cursor.getString(CALLS_NUMBER_COLUMN_INDEX);
+            for (int i = 1; i < count; i++) {
+                cursor.moveToNext();
+                String value = cursor.getString(CALLS_NUMBER_COLUMN_INDEX);
+                if (TextUtils.equals(value, currentValue)) {
+                    groupItemCount++;
+                } else {
+                    if (groupItemCount > 1) {
+                        addGroup(i - groupItemCount, groupItemCount, false);
+                    }
+
+                    groupItemCount = 1;
+                    currentValue = value;
+                }
+            }
+            if (groupItemCount > 1) {
+                addGroup(count - groupItemCount, groupItemCount, false);
+            }
+        }
+
+        @Override
+        protected void bindChildView(View view, Context context, Cursor cursor) {
+        }
+
+        @Override
+        protected void bindGroupView(View view, Context context, Cursor cursor, int groupSize,
+                boolean expanded) {
+        }
+
+        @Override
+        protected void bindStandAloneView(View view, Context context, Cursor cursor) {
+        }
+
+        @Override
+        protected View newChildView(Context context, ViewGroup parent) {
+            return null;
+        }
+
+        @Override
+        protected View newGroupView(Context context, ViewGroup parent) {
+            return null;
+        }
+
+        @Override
+        protected View newStandAloneView(Context context, ViewGroup parent) {
+            return null;
+        }
+    };
+
+    private void buildCursor(String... numbers) {
+        mCursor = new MatrixCursor(CALL_LOG_PROJECTION);
+        mNextCall = 1;
+        for (String number : numbers) {
+            mCursor.addRow(new Object[]{mNextCall, number, 1000 - mNextCall});
+            mNextCall++;
+        }
+    }
+
+    public void testGroupingWithoutGroups() {
+        buildCursor("1", "2", "3");
+        mAdapter.changeCursor(mCursor);
+
+        assertEquals(3, mAdapter.getCount());
+        assertPositionMetadata(0, ITEM_TYPE_STANDALONE, false, 0);
+        assertPositionMetadata(1, ITEM_TYPE_STANDALONE, false, 1);
+        assertPositionMetadata(2, ITEM_TYPE_STANDALONE, false, 2);
+    }
+
+    public void testGroupingWithCollapsedGroupAtTheBeginning() {
+        buildCursor("1", "1", "2");
+        mAdapter.changeCursor(mCursor);
+
+        assertEquals(2, mAdapter.getCount());
+        assertPositionMetadata(0, ITEM_TYPE_GROUP_HEADER, false, 0);
+        assertPositionMetadata(1, ITEM_TYPE_STANDALONE, false, 2);
+    }
+
+    public void testGroupingWithExpandedGroupAtTheBeginning() {
+        buildCursor("1", "1", "2");
+        mAdapter.changeCursor(mCursor);
+        mAdapter.toggleGroup(0);
+
+        assertEquals(4, mAdapter.getCount());
+        assertPositionMetadata(0, ITEM_TYPE_GROUP_HEADER, true, 0);
+        assertPositionMetadata(1, ITEM_TYPE_IN_GROUP, false, 0);
+        assertPositionMetadata(2, ITEM_TYPE_IN_GROUP, false, 1);
+        assertPositionMetadata(3, ITEM_TYPE_STANDALONE, false, 2);
+    }
+
+    public void testGroupingWithExpandCollapseCycleAtTheBeginning() {
+        buildCursor("1", "1", "2");
+        mAdapter.changeCursor(mCursor);
+        mAdapter.toggleGroup(0);
+        mAdapter.toggleGroup(0);
+
+        assertEquals(2, mAdapter.getCount());
+        assertPositionMetadata(0, ITEM_TYPE_GROUP_HEADER, false, 0);
+        assertPositionMetadata(1, ITEM_TYPE_STANDALONE, false, 2);
+    }
+
+    public void testGroupingWithCollapsedGroupInTheMiddle() {
+        buildCursor("1", "2", "2", "2", "3");
+        mAdapter.changeCursor(mCursor);
+
+        assertEquals(3, mAdapter.getCount());
+        assertPositionMetadata(0, ITEM_TYPE_STANDALONE, false, 0);
+        assertPositionMetadata(1, ITEM_TYPE_GROUP_HEADER, false, 1);
+        assertPositionMetadata(2, ITEM_TYPE_STANDALONE, false, 4);
+    }
+
+    public void testGroupingWithExpandedGroupInTheMiddle() {
+        buildCursor("1", "2", "2", "2", "3");
+        mAdapter.changeCursor(mCursor);
+        mAdapter.toggleGroup(1);
+
+        assertEquals(6, mAdapter.getCount());
+        assertPositionMetadata(0, ITEM_TYPE_STANDALONE, false, 0);
+        assertPositionMetadata(1, ITEM_TYPE_GROUP_HEADER, true, 1);
+        assertPositionMetadata(2, ITEM_TYPE_IN_GROUP, false, 1);
+        assertPositionMetadata(3, ITEM_TYPE_IN_GROUP, false, 2);
+        assertPositionMetadata(4, ITEM_TYPE_IN_GROUP, false, 3);
+        assertPositionMetadata(5, ITEM_TYPE_STANDALONE, false, 4);
+    }
+
+    public void testGroupingWithCollapsedGroupAtTheEnd() {
+        buildCursor("1", "2", "3", "3", "3");
+        mAdapter.changeCursor(mCursor);
+
+        assertEquals(3, mAdapter.getCount());
+        assertPositionMetadata(0, ITEM_TYPE_STANDALONE, false, 0);
+        assertPositionMetadata(1, ITEM_TYPE_STANDALONE, false, 1);
+        assertPositionMetadata(2, ITEM_TYPE_GROUP_HEADER, false, 2);
+    }
+
+    public void testGroupingWithExpandedGroupAtTheEnd() {
+        buildCursor("1", "2", "3", "3", "3");
+        mAdapter.changeCursor(mCursor);
+        mAdapter.toggleGroup(2);
+
+        assertEquals(6, mAdapter.getCount());
+        assertPositionMetadata(0, ITEM_TYPE_STANDALONE, false, 0);
+        assertPositionMetadata(1, ITEM_TYPE_STANDALONE, false, 1);
+        assertPositionMetadata(2, ITEM_TYPE_GROUP_HEADER, true, 2);
+        assertPositionMetadata(3, ITEM_TYPE_IN_GROUP, false, 2);
+        assertPositionMetadata(4, ITEM_TYPE_IN_GROUP, false, 3);
+        assertPositionMetadata(5, ITEM_TYPE_IN_GROUP, false, 4);
+    }
+
+    public void testGroupingWithMultipleCollapsedGroups() {
+        buildCursor("1", "2", "2", "3", "4", "4", "5", "5", "6");
+        mAdapter.changeCursor(mCursor);
+
+        assertEquals(6, mAdapter.getCount());
+        assertPositionMetadata(0, ITEM_TYPE_STANDALONE, false, 0);
+        assertPositionMetadata(1, ITEM_TYPE_GROUP_HEADER, false, 1);
+        assertPositionMetadata(2, ITEM_TYPE_STANDALONE, false, 3);
+        assertPositionMetadata(3, ITEM_TYPE_GROUP_HEADER, false, 4);
+        assertPositionMetadata(4, ITEM_TYPE_GROUP_HEADER, false, 6);
+        assertPositionMetadata(5, ITEM_TYPE_STANDALONE, false, 8);
+    }
+
+    public void testGroupingWithMultipleExpandedGroups() {
+        buildCursor("1", "2", "2", "3", "4", "4", "5", "5", "6");
+        mAdapter.changeCursor(mCursor);
+        mAdapter.toggleGroup(1);
+
+        // Note that expanding the group of 2's shifted the group of 5's down from the
+        // 4th to the 6th position
+        mAdapter.toggleGroup(6);
+
+        assertEquals(10, mAdapter.getCount());
+        assertPositionMetadata(0, ITEM_TYPE_STANDALONE, false, 0);
+        assertPositionMetadata(1, ITEM_TYPE_GROUP_HEADER, true, 1);
+        assertPositionMetadata(2, ITEM_TYPE_IN_GROUP, false, 1);
+        assertPositionMetadata(3, ITEM_TYPE_IN_GROUP, false, 2);
+        assertPositionMetadata(4, ITEM_TYPE_STANDALONE, false, 3);
+        assertPositionMetadata(5, ITEM_TYPE_GROUP_HEADER, false, 4);
+        assertPositionMetadata(6, ITEM_TYPE_GROUP_HEADER, true, 6);
+        assertPositionMetadata(7, ITEM_TYPE_IN_GROUP, false, 6);
+        assertPositionMetadata(8, ITEM_TYPE_IN_GROUP, false, 7);
+        assertPositionMetadata(9, ITEM_TYPE_STANDALONE, false, 8);
+    }
+
+    public void testPositionCache() {
+        buildCursor("1", "2", "2", "3", "4", "4", "5", "5", "6");
+        mAdapter.changeCursor(mCursor);
+
+        // First pass - building up cache
+        assertEquals(6, mAdapter.getCount());
+        assertPositionMetadata(0, ITEM_TYPE_STANDALONE, false, 0);
+        assertPositionMetadata(1, ITEM_TYPE_GROUP_HEADER, false, 1);
+        assertPositionMetadata(2, ITEM_TYPE_STANDALONE, false, 3);
+        assertPositionMetadata(3, ITEM_TYPE_GROUP_HEADER, false, 4);
+        assertPositionMetadata(4, ITEM_TYPE_GROUP_HEADER, false, 6);
+        assertPositionMetadata(5, ITEM_TYPE_STANDALONE, false, 8);
+
+        // Second pass - using cache
+        assertEquals(6, mAdapter.getCount());
+        assertPositionMetadata(0, ITEM_TYPE_STANDALONE, false, 0);
+        assertPositionMetadata(1, ITEM_TYPE_GROUP_HEADER, false, 1);
+        assertPositionMetadata(2, ITEM_TYPE_STANDALONE, false, 3);
+        assertPositionMetadata(3, ITEM_TYPE_GROUP_HEADER, false, 4);
+        assertPositionMetadata(4, ITEM_TYPE_GROUP_HEADER, false, 6);
+        assertPositionMetadata(5, ITEM_TYPE_STANDALONE, false, 8);
+
+        // Invalidate cache by expanding a group
+        mAdapter.toggleGroup(1);
+
+        // First pass - building up cache
+        assertPositionMetadata(0, ITEM_TYPE_STANDALONE, false, 0);
+        assertPositionMetadata(1, ITEM_TYPE_GROUP_HEADER, true, 1);
+        assertPositionMetadata(2, ITEM_TYPE_IN_GROUP, false, 1);
+        assertPositionMetadata(3, ITEM_TYPE_IN_GROUP, false, 2);
+        assertPositionMetadata(4, ITEM_TYPE_STANDALONE, false, 3);
+        assertPositionMetadata(5, ITEM_TYPE_GROUP_HEADER, false, 4);
+        assertPositionMetadata(6, ITEM_TYPE_GROUP_HEADER, false, 6);
+        assertPositionMetadata(7, ITEM_TYPE_STANDALONE, false, 8);
+
+        // Second pass - using cache
+        assertPositionMetadata(0, ITEM_TYPE_STANDALONE, false, 0);
+        assertPositionMetadata(1, ITEM_TYPE_GROUP_HEADER, true, 1);
+        assertPositionMetadata(2, ITEM_TYPE_IN_GROUP, false, 1);
+        assertPositionMetadata(3, ITEM_TYPE_IN_GROUP, false, 2);
+        assertPositionMetadata(4, ITEM_TYPE_STANDALONE, false, 3);
+        assertPositionMetadata(5, ITEM_TYPE_GROUP_HEADER, false, 4);
+        assertPositionMetadata(6, ITEM_TYPE_GROUP_HEADER, false, 6);
+        assertPositionMetadata(7, ITEM_TYPE_STANDALONE, false, 8);
+    }
+
+    public void testGroupDescriptorArrayGrowth() {
+        String[] numbers = new String[500];
+        for (int i = 0; i < numbers.length; i++) {
+
+            // Make groups of 2
+            numbers[i] = String.valueOf((i / 2) * 2);
+        }
+
+        buildCursor(numbers);
+        mAdapter.changeCursor(mCursor);
+
+        assertEquals(250, mAdapter.getCount());
+    }
+
+    private void assertPositionMetadata(int position, int itemType, boolean isExpanded,
+            int cursorPosition) {
+        GroupingListAdapter.PositionMetadata metadata = new GroupingListAdapter.PositionMetadata();
+        mAdapter.obtainPositionMetadata(metadata, position);
+        assertEquals(itemType, metadata.itemType);
+        if (metadata.itemType == ITEM_TYPE_GROUP_HEADER) {
+            assertEquals(isExpanded, metadata.isExpanded);
+        }
+        assertEquals(cursorPosition, metadata.cursorPosition);
+    }
+}
diff --git a/tests/src/com/android/contacts/RecentCallsListActivityTests.java b/tests/src/com/android/contacts/RecentCallsListActivityTests.java
index 2cdd8d7..d00854f 100644
--- a/tests/src/com/android/contacts/RecentCallsListActivityTests.java
+++ b/tests/src/com/android/contacts/RecentCallsListActivityTests.java
@@ -97,7 +97,7 @@
 
     @Override
     public void setUp() {
-        mActivity = (RecentCallsListActivity) getActivity();
+        mActivity = getActivity();
         mVoicemail = mActivity.mVoiceMailNumber;
         mAdapter = mActivity.mAdapter;
         mParentView = new FrameLayout(mActivity);
@@ -224,9 +224,9 @@
         mCursor.moveToLast();
         while(!mCursor.isBeforeFirst()) {
             if (null == mList[i]) {
-                mList[i] = mAdapter.newView(mActivity, mCursor, mParentView);
+                mList[i] = mAdapter.newStandAloneView(mActivity, mParentView);
             }
-            mAdapter.bindView(mList[i], mActivity, mCursor);
+            mAdapter.bindStandAloneView(mList[i], mActivity, mCursor);
             mCursor.moveToPrevious();
             i++;
         }
@@ -276,7 +276,7 @@
             duration = mRnd.nextInt(10 * 60);  // 0 - 10 minutes random.
         }
         row.add(duration);  // duration
-        if (mVoicemail.equals(number)) {
+        if (mVoicemail != null && mVoicemail.equals(number)) {
             assertEquals(Calls.OUTGOING_TYPE, type);
         }
         row.add(type);  // type
@@ -309,7 +309,10 @@
      * @param duration In seconds of the call. Use RAND_DURATION to pick a random one.
      */
     private void insertVoicemail(long date, int duration) {
-        insert(mVoicemail, date, duration, Calls.OUTGOING_TYPE);
+        // mVoicemail may be null
+        if (mVoicemail != null) {
+            insert(mVoicemail, date, duration, Calls.OUTGOING_TYPE);
+        }
     }
 
     /**