Merge "Small tweaks to contacts layout."
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index e6336bc..01a6f95 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -493,6 +493,14 @@
             </intent-filter>
         </activity>
 
+        <!-- Confirm that a single detail will be added to an existing contact -->
+        <activity
+            android:name=".activities.ConfirmAddDetailActivity"
+            android:label="@string/activity_title_confirm_add_detail"
+            android:theme="@style/ConfirmAddDetailDialogTheme"
+            android:windowSoftInputMode="adjustResize"
+            android:exported="false"/>
+
         <!-- Create a new or edit an existing contact -->
         <activity
             android:name=".activities.ContactEditorActivity"
@@ -589,7 +597,7 @@
         <!-- vCard related -->
         <activity android:name=".vcard.ImportVCardActivity"
             android:configChanges="orientation|screenSize|keyboardHidden"
-            android:theme="@style/BackgroundOnly">
+            android:theme="@style/BackgroundOnlyTheme">
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
                 <data android:mimeType="text/directory" />
@@ -602,7 +610,7 @@
 
         <activity android:name=".vcard.NfcImportVCardActivity"
             android:configChanges="orientation|screenSize|keyboardHidden"
-            android:theme="@style/BackgroundOnly">
+            android:theme="@style/BackgroundOnlyTheme">
             <intent-filter>
                 <action android:name="android.nfc.action.NDEF_DISCOVERED" />
                 <data android:mimeType="text/vcard" />
@@ -613,13 +621,13 @@
         </activity>
 
         <activity android:name=".vcard.CancelActivity"
-            android:theme="@style/BackgroundOnly" />
+            android:theme="@style/BackgroundOnlyTheme" />
 
         <activity android:name=".vcard.SelectAccountActivity"
-            android:theme="@style/BackgroundOnly" />
+            android:theme="@style/BackgroundOnlyTheme" />
 
         <activity android:name=".vcard.ExportVCardActivity"
-            android:theme="@style/BackgroundOnly" />
+            android:theme="@style/BackgroundOnlyTheme" />
 
         <service
             android:name=".vcard.VCardService"
diff --git a/res/drawable-hdpi/change_photo_box_normal_holo_light.9.png b/res/drawable-hdpi/change_photo_box_normal_holo_light.9.png
deleted file mode 100644
index f026cc8..0000000
--- a/res/drawable-hdpi/change_photo_box_normal_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/change_photo_box_pressed_holo_light.9.png b/res/drawable-hdpi/change_photo_box_pressed_holo_light.9.png
deleted file mode 100644
index a0770ea..0000000
--- a/res/drawable-hdpi/change_photo_box_pressed_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/directory_bg.9.png b/res/drawable-hdpi/directory_bg.9.png
deleted file mode 100644
index f0a92d4..0000000
--- a/res/drawable-hdpi/directory_bg.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/directory_bg_holo.9.png b/res/drawable-hdpi/directory_bg_holo.9.png
deleted file mode 100644
index 7f7209d..0000000
--- a/res/drawable-hdpi/directory_bg_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_menu_compose_holo_light.png b/res/drawable-hdpi/ic_menu_compose_holo_light.png
deleted file mode 100644
index 07ab787..0000000
--- a/res/drawable-hdpi/ic_menu_compose_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_menu_done_holo_light.png b/res/drawable-hdpi/ic_menu_done_holo_light.png
deleted file mode 100644
index 89c6e04..0000000
--- a/res/drawable-hdpi/ic_menu_done_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/infobar_dark.9.png b/res/drawable-hdpi/infobar_dark.9.png
deleted file mode 100644
index 5a08290..0000000
--- a/res/drawable-hdpi/infobar_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/section_header.9.png b/res/drawable-hdpi/section_header.9.png
deleted file mode 100644
index 8cd231b..0000000
--- a/res/drawable-hdpi/section_header.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/sym_action_audiochat_holo_dark.png b/res/drawable-hdpi/sym_action_audiochat_holo_dark.png
deleted file mode 100644
index d4e3329..0000000
--- a/res/drawable-hdpi/sym_action_audiochat_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/sym_action_sms.png b/res/drawable-hdpi/sym_action_sms.png
deleted file mode 100644
index 9f18105..0000000
--- a/res/drawable-hdpi/sym_action_sms.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/sym_action_videochat_holo_dark.png b/res/drawable-hdpi/sym_action_videochat_holo_dark.png
deleted file mode 100644
index 821940a..0000000
--- a/res/drawable-hdpi/sym_action_videochat_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/change_photo_box_normal_holo_light.9.png b/res/drawable-mdpi/change_photo_box_normal_holo_light.9.png
deleted file mode 100644
index 591e6d5..0000000
--- a/res/drawable-mdpi/change_photo_box_normal_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/change_photo_box_pressed_holo_light.9.png b/res/drawable-mdpi/change_photo_box_pressed_holo_light.9.png
deleted file mode 100644
index 5d5eee2..0000000
--- a/res/drawable-mdpi/change_photo_box_pressed_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/directory_bg.9.png b/res/drawable-mdpi/directory_bg.9.png
deleted file mode 100644
index 80578cd..0000000
--- a/res/drawable-mdpi/directory_bg.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/directory_bg_holo.9.png b/res/drawable-mdpi/directory_bg_holo.9.png
deleted file mode 100644
index 7f7209d..0000000
--- a/res/drawable-mdpi/directory_bg_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_menu_compose_holo_light.png b/res/drawable-mdpi/ic_menu_compose_holo_light.png
deleted file mode 100644
index 5236c1c..0000000
--- a/res/drawable-mdpi/ic_menu_compose_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_menu_done_holo_light.png b/res/drawable-mdpi/ic_menu_done_holo_light.png
deleted file mode 100644
index 3468bbd..0000000
--- a/res/drawable-mdpi/ic_menu_done_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/infobar_dark.9.png b/res/drawable-mdpi/infobar_dark.9.png
deleted file mode 100644
index ae3caf7..0000000
--- a/res/drawable-mdpi/infobar_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/section_header.9.png b/res/drawable-mdpi/section_header.9.png
deleted file mode 100644
index ac906cd..0000000
--- a/res/drawable-mdpi/section_header.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/sym_action_audiochat_holo_dark.png b/res/drawable-mdpi/sym_action_audiochat_holo_dark.png
deleted file mode 100644
index 848404d..0000000
--- a/res/drawable-mdpi/sym_action_audiochat_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/sym_action_sms.png b/res/drawable-mdpi/sym_action_sms.png
deleted file mode 100644
index f098f4a..0000000
--- a/res/drawable-mdpi/sym_action_sms.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/sym_action_videochat_holo_dark.png b/res/drawable-mdpi/sym_action_videochat_holo_dark.png
deleted file mode 100644
index 9112e87..0000000
--- a/res/drawable-mdpi/sym_action_videochat_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/change_photo_box_normal_holo_light.9.png b/res/drawable-xhdpi/change_photo_box_normal_holo_light.9.png
deleted file mode 100644
index 7e1e97f..0000000
--- a/res/drawable-xhdpi/change_photo_box_normal_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/change_photo_box_pressed_holo_light.9.png b/res/drawable-xhdpi/change_photo_box_pressed_holo_light.9.png
deleted file mode 100644
index e98266f..0000000
--- a/res/drawable-xhdpi/change_photo_box_pressed_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_menu_compose_holo_light.png b/res/drawable-xhdpi/ic_menu_compose_holo_light.png
deleted file mode 100644
index 9561ce7..0000000
--- a/res/drawable-xhdpi/ic_menu_compose_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_menu_done_holo_light.png b/res/drawable-xhdpi/ic_menu_done_holo_light.png
deleted file mode 100644
index a8a0972..0000000
--- a/res/drawable-xhdpi/ic_menu_done_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/infobar_dark.9.png b/res/drawable-xhdpi/infobar_dark.9.png
deleted file mode 100644
index 24ed9a2..0000000
--- a/res/drawable-xhdpi/infobar_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/quickactions_icon_activated.9.png b/res/drawable-xhdpi/quickactions_icon_activated.9.png
deleted file mode 100644
index b3aaf7d..0000000
--- a/res/drawable-xhdpi/quickactions_icon_activated.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/section_header.9.png b/res/drawable-xhdpi/section_header.9.png
deleted file mode 100644
index 5026ea6..0000000
--- a/res/drawable-xhdpi/section_header.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/sym_action_audiochat_holo_dark.png b/res/drawable-xhdpi/sym_action_audiochat_holo_dark.png
deleted file mode 100644
index 216d34f..0000000
--- a/res/drawable-xhdpi/sym_action_audiochat_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/sym_action_sms.png b/res/drawable-xhdpi/sym_action_sms.png
deleted file mode 100644
index 5003be1..0000000
--- a/res/drawable-xhdpi/sym_action_sms.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/sym_action_videochat_holo_dark.png b/res/drawable-xhdpi/sym_action_videochat_holo_dark.png
deleted file mode 100644
index 5011489..0000000
--- a/res/drawable-xhdpi/sym_action_videochat_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/res/layout/confirm_add_detail_activity.xml b/res/layout/confirm_add_detail_activity.xml
new file mode 100644
index 0000000..6bbaac5
--- /dev/null
+++ b/res/layout/confirm_add_detail_activity.xml
@@ -0,0 +1,153 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- Layout for confirming the addition of a piece of information to an existing contact. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:ex="http://schemas.android.com/apk/res/com.android.contacts"
+    android:id="@+id/root_view"
+    android:orientation="vertical"
+    android:visibility="invisible"
+    style="@style/ConfirmAddDetailViewStyle">
+
+    <!--
+      The header contains the contact photo, name, a link to the contact card, and
+      possibly an extra data field to disambiguate contacts with the same name.
+    -->
+    <RelativeLayout
+        style="@style/ConfirmAddDetailHeaderViewStyle">
+
+        <ImageView
+            android:id="@+id/photo"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:scaleType="centerCrop"
+            android:src="@drawable/ic_contact_picture" />
+
+        <View
+            android:id="@+id/photo_text_bar"
+            android:layout_width="0dip"
+            android:layout_height="42dip"
+            android:layout_alignBottom="@id/photo"
+            android:layout_alignLeft="@id/photo"
+            android:layout_alignRight="@id/photo"
+            android:background="#7F000000" />
+
+        <ImageButton
+            android:id="@+id/open_details_button"
+            android:src="@drawable/ic_contacts_holo_dark"
+            android:background="?android:attr/selectableItemBackground"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_marginRight="16dip"
+            android:layout_marginBottom="5dip"
+            android:layout_alignBottom="@id/photo_text_bar"
+            android:layout_alignRight="@id/photo_text_bar" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="42dip"
+            android:orientation="vertical"
+            android:layout_alignBottom="@id/photo"
+            android:layout_alignLeft="@id/photo"
+            android:layout_toLeftOf="@id/open_details_button"
+            android:paddingRight="8dip"
+            android:paddingLeft="8dip">
+
+            <TextView
+                android:id="@+id/name"
+                android:layout_width="wrap_content"
+                android:layout_height="0dip"
+                android:layout_weight="1"
+                android:paddingLeft="8dip"
+                android:gravity="center_vertical"
+                android:textColor="@android:color/white"
+                android:textSize="16sp"
+                android:singleLine="true" />
+
+            <TextView
+                android:id="@+id/extra_info"
+                android:layout_width="wrap_content"
+                android:layout_height="0dip"
+                android:layout_weight="1"
+                android:paddingLeft="8dip"
+                android:gravity="center_vertical"
+                android:textAppearance="?android:attr/textAppearanceSmall"
+                android:textColor="@android:color/white"
+                android:singleLine="true"
+                android:paddingBottom="4dip"
+                android:visibility="gone" />
+
+        </LinearLayout>
+
+        <View
+            android:id="@+id/open_details_push_layer"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:background="?android:attr/selectableItemBackground" />
+
+    </RelativeLayout>
+
+    <!-- Message that gets displayed if the contact is read-only (instead of showing the editor) -->
+    <TextView android:id="@+id/read_only_warning"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="60dip"
+        android:visibility="gone"
+        android:padding="15dip"
+        android:textAppearance="?android:attr/textAppearanceSmall"/>
+
+    <!-- Container for a single detail field editor when the contact is not read-only -->
+    <FrameLayout
+        android:id="@+id/editor_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="60dip"
+        android:layout_marginTop="4dip"
+        android:layout_marginRight="15dip"/>
+
+    <View
+        android:id="@+id/divider"
+        android:layout_width="match_parent"
+        android:layout_height="1dip"
+        android:background="?android:attr/listDivider"/>
+
+    <!-- Action buttons -->
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        style="?android:attr/buttonBarStyle">
+
+        <Button
+            android:id="@+id/btn_cancel"
+            style="?android:attr/buttonBarButtonStyle"
+            android:layout_width="0dip"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="@android:string/cancel" />
+
+        <Button
+            android:id="@+id/btn_done"
+            style="?android:attr/buttonBarButtonStyle"
+            android:layout_width="0dip"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="@android:string/ok" />
+
+    </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/dialpad_activity.xml b/res/layout/dialpad_activity.xml
deleted file mode 100644
index 93e9523..0000000
--- a/res/layout/dialpad_activity.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
->
-    <fragment class="com.android.contacts.dialpad.DialpadFragment"
-            android:id="@+id/dialpad_fragment"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent" />
-</FrameLayout>
diff --git a/res/values-sw580dp/styles.xml b/res/values-sw580dp/styles.xml
index 5f18d22..52eab18 100644
--- a/res/values-sw580dp/styles.xml
+++ b/res/values-sw580dp/styles.xml
@@ -113,7 +113,17 @@
         <item name="android:windowContentOverlay">@null</item>
     </style>
 
-    <style name="BackgroundOnly" parent="@android:Theme.Holo.Light">
+    <style name="ConfirmAddDetailViewStyle">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+    </style>
+
+    <style name="ConfirmAddDetailHeaderViewStyle">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">200dip</item>
+    </style>
+
+    <style name="BackgroundOnlyTheme" parent="@android:Theme.Holo.Light">
         <item name="android:windowBackground">@null</item>
         <item name="android:windowContentOverlay">@null</item>
         <item name="android:windowAnimationStyle">@null</item>
diff --git a/res/values-w470dp/styles.xml b/res/values-w470dp/styles.xml
new file mode 100644
index 0000000..dab7802
--- /dev/null
+++ b/res/values-w470dp/styles.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources>
+    <style name="ConfirmAddDetailViewStyle">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">match_parent</item>
+    </style>
+
+    <style name="ConfirmAddDetailHeaderViewStyle">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">0dip</item>
+        <item name="android:layout_weight">1</item>
+    </style>
+</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 90cb071..9bdf669 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -92,7 +92,7 @@
         <item name="android:textColorSecondary">@color/secondary_text_color</item>
     </style>
 
-    <style name="BackgroundOnly">
+    <style name="BackgroundOnlyTheme" parent="@android:style/Theme.Holo.Light">
         <item name="android:windowBackground">@null</item>
         <item name="android:windowContentOverlay">@null</item>
         <item name="android:windowAnimationStyle">@null</item>
@@ -205,10 +205,14 @@
         <item name="android:background">@android:color/transparent</item>
     </style>
 
-    <style name="NonPhoneActivityTheme" parent="@android:Theme.Translucent">
+    <style name="NonPhoneActivityTheme" parent="@android:Theme.Translucent.NoTitleBar">
     </style>
 
-    <style name="NonPhoneDialogTheme" parent="@android:Theme.Dialog">
+    <style name="NonPhoneDialogTheme" parent="@android:Theme.Holo.Light.Dialog">
+    </style>
+
+    <style name="ConfirmAddDetailDialogTheme" parent="@android:style/Theme.Holo.Light.Dialog.MinWidth">
+        <item name="android:windowCloseOnTouchOutside">true</item>
     </style>
 
     <style name="SectionDivider">
@@ -259,6 +263,16 @@
         <item name="android:layout_weight">1</item>
     </style>
 
+    <style name="ConfirmAddDetailViewStyle">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+    </style>
+
+    <style name="ConfirmAddDetailHeaderViewStyle">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">150dip</item>
+    </style>
+
     <style name="DialtactsActionBarStyle" parent="android:Widget.Holo.ActionBar">
         <item name="android:backgroundSplit">@drawable/ab_bottom_opaque_dark_holo</item>
         <item name="android:backgroundStacked">@drawable/ab_stacked_opaque_dark_holo</item>
diff --git a/src/com/android/contacts/ContactsApplication.java b/src/com/android/contacts/ContactsApplication.java
index 1c8c080..4007916 100644
--- a/src/com/android/contacts/ContactsApplication.java
+++ b/src/com/android/contacts/ContactsApplication.java
@@ -18,6 +18,7 @@
 
 import com.android.contacts.model.AccountTypeManager;
 import com.android.contacts.test.InjectedServices;
+import com.android.contacts.util.Constants;
 import com.google.common.annotations.VisibleForTesting;
 
 import android.app.Application;
@@ -27,9 +28,9 @@
 import android.content.SharedPreferences;
 import android.os.StrictMode;
 import android.preference.PreferenceManager;
+import android.util.Log;
 
 public final class ContactsApplication extends Application {
-
     private static InjectedServices sInjectedServices;
     private AccountTypeManager mAccountTypeManager;
     private ContactPhotoManager mContactPhotoManager;
@@ -100,6 +101,10 @@
     public void onCreate() {
         super.onCreate();
 
+        if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
+            Log.d(Constants.PERFORMANCE_TAG, "ContactsApplication.onCreate start");
+        }
+
         // Priming caches to placate the StrictMode police
         Context context = getApplicationContext();
         PreferenceManager.getDefaultSharedPreferences(context);
@@ -108,5 +113,9 @@
 
         StrictMode.setThreadPolicy(
                 new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
+
+        if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
+            Log.d(Constants.PERFORMANCE_TAG, "ContactsApplication.onCreate finish");
+        }
     }
 }
diff --git a/src/com/android/contacts/activities/ConfirmAddDetailActivity.java b/src/com/android/contacts/activities/ConfirmAddDetailActivity.java
new file mode 100644
index 0000000..e97a718
--- /dev/null
+++ b/src/com/android/contacts/activities/ConfirmAddDetailActivity.java
@@ -0,0 +1,786 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.contacts.activities;
+
+import com.android.contacts.R;
+import com.android.contacts.editor.Editor;
+import com.android.contacts.editor.ViewIdGenerator;
+import com.android.contacts.model.AccountType;
+import com.android.contacts.model.AccountTypeManager;
+import com.android.contacts.model.DataKind;
+import com.android.contacts.model.EntityDelta;
+import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.android.contacts.model.EntityDeltaList;
+import com.android.contacts.model.EntityModifier;
+import com.android.contacts.util.DialogManager;
+import com.android.contacts.util.EmptyService;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.AsyncQueryHandler;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.Intent;
+import android.content.OperationApplicationException;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.net.Uri.Builder;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * This is a dialog-themed activity for confirming the addition of a detail to an existing contact
+ * (once the user has selected this contact from a list of all contacts). The incoming intent
+ * must have an extra with max 1 phone or email specified, using
+ * {@link ContactsContract.Intents.Insert.PHONE} with type
+ * {@link ContactsContract.Intents.Insert.PHONE_TYPE} or
+ * {@link ContactsContract.Intents.Insert.EMAIL} with type
+ * {@link ContactsContract.Intents.Insert.EMAIL_TYPE} intent keys.
+ */
+public class ConfirmAddDetailActivity extends Activity implements
+        DialogManager.DialogShowingViewActivity {
+
+    private static final String TAG = ConfirmAddDetailActivity.class.getSimpleName();
+
+    private static final String LEGACY_CONTACTS_AUTHORITY = "contacts";
+
+    private LayoutInflater mInflater;
+    private View mRootView;
+    private TextView mDisplayNameView;
+    private TextView mReadOnlyWarningView;
+    private ImageView mPhotoView;
+    private ViewGroup mEditorContainerView;
+
+    private AccountTypeManager mAccountTypeManager;
+    private ContentResolver mContentResolver;
+
+    private AccountType mEditableAccountType;
+    private EntityDelta mState;
+    private Uri mContactUri;
+    private long mContactId;
+    private String mDisplayName;
+    private boolean mIsReadyOnly;
+
+    private QueryHandler mQueryHandler;
+    private EntityDeltaList mEntityDeltaList;
+
+    private String mMimetype = Phone.CONTENT_ITEM_TYPE;
+
+    /**
+     * DialogManager may be needed if the user wants to apply a "custom" label to the contact detail
+     */
+    private final DialogManager mDialogManager = new DialogManager(this);
+
+    /**
+     * PhotoQuery contains the projection used for retrieving the name and photo
+     * ID of a contact.
+     */
+    private interface ContactQuery {
+        final String[] COLUMNS = new String[] {
+            Contacts._ID,
+            Contacts.LOOKUP_KEY,
+            Contacts.PHOTO_ID,
+            Contacts.DISPLAY_NAME,
+        };
+        final int _ID = 0;
+        final int LOOKUP_KEY = 1;
+        final int PHOTO_ID = 2;
+        final int DISPLAY_NAME = 3;
+    }
+
+    /**
+     * PhotoQuery contains the projection used for retrieving the raw bytes of
+     * the contact photo.
+     */
+    private interface PhotoQuery {
+        final String[] COLUMNS = new String[] {
+            Photo.PHOTO
+        };
+
+        final int PHOTO = 0;
+    }
+
+    /**
+     * ExtraInfoQuery contains the projection used for retrieving the extra info
+     * on a contact (only needed if someone else exists with the same name as
+     * this contact).
+     */
+    private interface ExtraInfoQuery {
+        final String[] COLUMNS = new String[] {
+            RawContacts.CONTACT_ID,
+            Data.MIMETYPE,
+            Data.DATA1,
+        };
+        final int CONTACT_ID = 0;
+        final int MIMETYPE = 1;
+        final int DATA1 = 2;
+    }
+
+    /**
+     * List of mimetypes to use in order of priority to display for a contact in
+     * a disambiguation case. For example, if the contact does not have a
+     * nickname, use the email field, and etc.
+     */
+    private static final String[] sMimeTypePriorityList = new String[] { Nickname.CONTENT_ITEM_TYPE,
+            Email.CONTENT_ITEM_TYPE, Im.CONTENT_ITEM_TYPE, StructuredPostal.CONTENT_ITEM_TYPE,
+            Phone.CONTENT_ITEM_TYPE };
+
+    private static final int TOKEN_CONTACT_INFO = 0;
+    private static final int TOKEN_PHOTO_QUERY = 1;
+    private static final int TOKEN_DISAMBIGUATION_QUERY = 2;
+    private static final int TOKEN_EXTRA_INFO_QUERY = 3;
+
+    private final OnClickListener mDetailsButtonClickListener = new OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            if (mIsReadyOnly) {
+                onSaveCompleted(true);
+            } else {
+                doSaveAction();
+            }
+        }
+    };
+
+    private final OnClickListener mDoneButtonClickListener = new OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            doSaveAction();
+        }
+    };
+
+    private final OnClickListener mCancelButtonClickListener = new OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            setResult(RESULT_CANCELED);
+            finish();
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        mContentResolver = getContentResolver();
+
+        final Intent intent = getIntent();
+        mContactUri = intent.getData();
+
+        if (mContactUri == null) {
+            setResult(RESULT_CANCELED);
+            finish();
+        }
+
+        Bundle extras = intent.getExtras();
+        if (extras != null) {
+            if (extras.containsKey(ContactsContract.Intents.Insert.PHONE)) {
+                mMimetype = Phone.CONTENT_ITEM_TYPE;
+            } else if (extras.containsKey(ContactsContract.Intents.Insert.EMAIL)) {
+                mMimetype = Email.CONTENT_ITEM_TYPE;
+            } else {
+                throw new IllegalStateException("Error: No valid mimetype found in intent extras");
+            }
+        }
+
+        mAccountTypeManager = AccountTypeManager.getInstance(this);
+
+        setContentView(R.layout.confirm_add_detail_activity);
+
+        mRootView = findViewById(R.id.root_view);
+        mReadOnlyWarningView = (TextView) findViewById(R.id.read_only_warning);
+
+        // Setup "header" (containing contact info) to save the detail and then go to the editor
+        findViewById(R.id.open_details_push_layer).setOnClickListener(mDetailsButtonClickListener);
+
+        // Setup "done" button to save the detail to the contact and exit.
+        findViewById(R.id.btn_done).setOnClickListener(mDoneButtonClickListener);
+
+        // Setup "cancel" button to return to previous activity.
+        findViewById(R.id.btn_cancel).setOnClickListener(mCancelButtonClickListener);
+
+        // Retrieve references to all the Views in the dialog activity.
+        mDisplayNameView = (TextView) findViewById(R.id.name);
+        mPhotoView = (ImageView) findViewById(R.id.photo);
+        mEditorContainerView = (ViewGroup) findViewById(R.id.editor_container);
+
+        startContactQuery(mContactUri, true);
+
+        new QueryEntitiesTask(this).execute(intent);
+    }
+
+    @Override
+    public DialogManager getDialogManager() {
+        return mDialogManager;
+    }
+
+    @Override
+    protected Dialog onCreateDialog(int id, Bundle args) {
+        if (DialogManager.isManagedId(id)) return mDialogManager.onCreateDialog(id, args);
+
+        // Nobody knows about the Dialog
+        Log.w(TAG, "Unknown dialog requested, id: " + id + ", args: " + args);
+        return null;
+    }
+
+    /**
+     * Reset the query handler by creating a new QueryHandler instance.
+     */
+    private void resetAsyncQueryHandler() {
+        // the api AsyncQueryHandler.cancelOperation() doesn't really work. Since we really
+        // need the old async queries to be cancelled, let's do it the hard way.
+        mQueryHandler = new QueryHandler(mContentResolver);
+    }
+
+    /**
+     * Internal method to query contact by Uri.
+     *
+     * @param contactUri the contact uri
+     * @param resetQueryHandler whether to use a new AsyncQueryHandler or not
+     */
+    private void startContactQuery(Uri contactUri, boolean resetQueryHandler) {
+        if (resetQueryHandler) {
+            resetAsyncQueryHandler();
+        }
+
+        mQueryHandler.startQuery(TOKEN_CONTACT_INFO, contactUri, contactUri, ContactQuery.COLUMNS,
+                null, null, null);
+    }
+
+    /**
+     * Internal method to query contact photo by photo id and uri.
+     *
+     * @param photoId the photo id.
+     * @param lookupKey the lookup uri.
+     * @param resetQueryHandler whether to use a new AsyncQueryHandler or not.
+     */
+    private void startPhotoQuery(long photoId, Uri lookupKey, boolean resetQueryHandler) {
+        if (resetQueryHandler) {
+            resetAsyncQueryHandler();
+        }
+
+        mQueryHandler.startQuery(TOKEN_PHOTO_QUERY, lookupKey,
+                ContentUris.withAppendedId(Data.CONTENT_URI, photoId),
+                PhotoQuery.COLUMNS, null, null, null);
+    }
+
+    /**
+     * Internal method to query for contacts with a given display name.
+     *
+     * @param contactDisplayName the display name to look for.
+     */
+    private void startDisambiguationQuery(String contactDisplayName) {
+        // Apply a limit of 1 result to the query because we only need to
+        // determine whether or not at least one other contact has the same
+        // name. We don't need to find ALL other contacts with the same name.
+        Builder builder = Contacts.CONTENT_URI.buildUpon();
+        builder.appendQueryParameter("limit", String.valueOf(1));
+        Uri uri = builder.build();
+
+        mQueryHandler.startQuery(TOKEN_DISAMBIGUATION_QUERY, null, uri,
+                new String[] { Contacts._ID } /* unused projection but a valid one was needed */,
+                Contacts.DISPLAY_NAME_PRIMARY + " = ? and " + Contacts.PHOTO_ID + " is null and "
+                + Contacts._ID + " <> ?",
+                new String[] { contactDisplayName, String.valueOf(mContactId) }, null);
+    }
+
+    /**
+     * Internal method to query for extra data fields for this contact.
+     */
+    private void startExtraInfoQuery() {
+        mQueryHandler.startQuery(TOKEN_EXTRA_INFO_QUERY, null, Data.CONTENT_URI,
+                ExtraInfoQuery.COLUMNS, RawContacts.CONTACT_ID + " = ?",
+                new String[] { String.valueOf(mContactId) }, null);
+    }
+
+    private static class QueryEntitiesTask extends AsyncTask<Intent, Void, EntityDeltaList> {
+
+        private ConfirmAddDetailActivity activityTarget;
+        private String mSelection;
+
+        public QueryEntitiesTask(ConfirmAddDetailActivity target) {
+            activityTarget = target;
+        }
+
+        @Override
+        protected EntityDeltaList doInBackground(Intent... params) {
+
+            final Intent intent = params[0];
+
+            final ContentResolver resolver = activityTarget.getContentResolver();
+
+            // Handle both legacy and new authorities
+            final Uri data = intent.getData();
+            final String authority = data.getAuthority();
+            final String mimeType = intent.resolveType(resolver);
+
+            mSelection = "0";
+            String selectionArg = null;
+            if (ContactsContract.AUTHORITY.equals(authority)) {
+                if (Contacts.CONTENT_ITEM_TYPE.equals(mimeType)) {
+                    // Handle selected aggregate
+                    final long contactId = ContentUris.parseId(data);
+                    selectionArg = String.valueOf(contactId);
+                    mSelection = RawContacts.CONTACT_ID + "=?";
+                } else if (RawContacts.CONTENT_ITEM_TYPE.equals(mimeType)) {
+                    final long rawContactId = ContentUris.parseId(data);
+                    final long contactId = queryForContactId(resolver, rawContactId);
+                    selectionArg = String.valueOf(contactId);
+                    mSelection = RawContacts.CONTACT_ID + "=?";
+                }
+            } else if (android.provider.Contacts.AUTHORITY.equals(authority)) {
+                final long rawContactId = ContentUris.parseId(data);
+                selectionArg = String.valueOf(rawContactId);
+                mSelection = Data.RAW_CONTACT_ID + "=?";
+            }
+
+            return EntityDeltaList.fromQuery(activityTarget.getContentResolver(), mSelection,
+                    new String[] { selectionArg }, null);
+        }
+
+        private static long queryForContactId(ContentResolver resolver, long rawContactId) {
+            Cursor contactIdCursor = null;
+            long contactId = -1;
+            try {
+                contactIdCursor = resolver.query(RawContacts.CONTENT_URI,
+                        new String[] { RawContacts.CONTACT_ID },
+                        RawContacts._ID + "=?", new String[] { String.valueOf(rawContactId) },
+                        null);
+                if (contactIdCursor != null && contactIdCursor.moveToFirst()) {
+                    contactId = contactIdCursor.getLong(0);
+                }
+            } finally {
+                if (contactIdCursor != null) {
+                    contactIdCursor.close();
+                }
+            }
+            return contactId;
+        }
+
+        @Override
+        protected void onPostExecute(EntityDeltaList entityList) {
+            if (activityTarget.isFinishing()) {
+                return;
+            }
+            activityTarget.setEntityDeltaList(entityList);
+            activityTarget.findEditableRawContact();
+            activityTarget.parseExtras();
+            activityTarget.bindEditor();
+        }
+    }
+
+    private class QueryHandler extends AsyncQueryHandler {
+
+        public QueryHandler(ContentResolver cr) {
+            super(cr);
+        }
+
+        @Override
+        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+            try {
+                if (this != mQueryHandler) {
+                    Log.d(TAG, "onQueryComplete: discard result, the query handler is reset!");
+                    return;
+                }
+                if (ConfirmAddDetailActivity.this.isFinishing()) {
+                    return;
+                }
+
+                switch (token) {
+                    case TOKEN_PHOTO_QUERY: {
+                        // Set the photo
+                        Bitmap photoBitmap = null;
+                        if (cursor != null && cursor.moveToFirst()
+                                && !cursor.isNull(PhotoQuery.PHOTO)) {
+                            byte[] photoData = cursor.getBlob(PhotoQuery.PHOTO);
+                            photoBitmap = BitmapFactory.decodeByteArray(photoData, 0,
+                                    photoData.length, null);
+                        }
+
+                        if (photoBitmap != null) {
+                            mPhotoView.setImageBitmap(photoBitmap);
+                        }
+
+                        break;
+                    }
+                    case TOKEN_CONTACT_INFO: {
+                        // Set the contact's name
+                        if (cursor != null && cursor.moveToFirst()) {
+                            // Get the cursor values
+                            mDisplayName = cursor.getString(ContactQuery.DISPLAY_NAME);
+                            final long photoId = cursor.getLong(ContactQuery.PHOTO_ID);
+
+                            // If there is no photo ID, then do a disambiguation
+                            // query because other contacts could have the same
+                            // name as this contact.
+                            if (photoId == 0) {
+                                mContactId = cursor.getLong(ContactQuery._ID);
+                                startDisambiguationQuery(mDisplayName);
+                            } else {
+                                // Otherwise do the photo query.
+                                Uri lookupUri = Contacts.getLookupUri(mContactId,
+                                        cursor.getString(ContactQuery.LOOKUP_KEY));
+                                startPhotoQuery(photoId, lookupUri,
+                                        false /* don't reset query handler */);
+                                // Display the name because there is no
+                                // disambiguation query.
+                                setDisplayName();
+                                onLoadDataFinished();
+                            }
+                        }
+                        break;
+                    }
+                    case TOKEN_DISAMBIGUATION_QUERY: {
+                        // If a cursor was returned with more than 0 results,
+                        // then at least one other contact exists with the same
+                        // name as this contact. Extra info on this contact must
+                        // be displayed to disambiguate the contact, so retrieve
+                        // those additional fields. Otherwise, no other contacts
+                        // with this name exists, so do nothing further.
+                        if (cursor != null && cursor.getCount() > 0) {
+                            startExtraInfoQuery();
+                        } else {
+                            // If there are no other contacts with this name,
+                            // then display the name.
+                            setDisplayName();
+                            onLoadDataFinished();
+                        }
+                        break;
+                    }
+                    case TOKEN_EXTRA_INFO_QUERY: {
+                        // This case should only occur if there are one or more
+                        // other contacts with the same contact name.
+                        if (cursor != null && cursor.moveToFirst()) {
+                            HashMap<String, String> hashMapCursorData = new
+                                    HashMap<String, String>();
+
+                            // Convert the cursor data into a hashmap of
+                            // (mimetype, data value) pairs. If a contact has
+                            // multiple values with the same mimetype, it's fine
+                            // to override that hashmap entry because we only
+                            // need one value of that type.
+                            while (!cursor.isAfterLast()) {
+                                final String mimeType = cursor.getString(ExtraInfoQuery.MIMETYPE);
+                                if (!TextUtils.isEmpty(mimeType)) {
+                                    String value = cursor.getString(ExtraInfoQuery.DATA1);
+                                    if (!TextUtils.isEmpty(value)) {
+                                        // As a special case, phone numbers
+                                        // should be formatted in a specific way.
+                                        if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)) {
+                                            value = PhoneNumberUtils.formatNumber(value);
+                                        }
+                                        hashMapCursorData.put(mimeType, value);
+                                    }
+                                }
+                                cursor.moveToNext();
+                            }
+
+                            // Find the first non-empty field according to the
+                            // mimetype priority list and display this under the
+                            // contact's display name to disambiguate the contact.
+                            for (String mimeType : sMimeTypePriorityList) {
+                                if (hashMapCursorData.containsKey(mimeType)) {
+                                    setDisplayName();
+                                    setExtraInfoField(hashMapCursorData.get(mimeType));
+                                    break;
+                                }
+                            }
+                            onLoadDataFinished();
+                        }
+                        break;
+                    }
+                }
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
+            }
+        }
+    }
+
+    public void setEntityDeltaList(EntityDeltaList entityList) {
+        mEntityDeltaList = entityList;
+    }
+
+    public void findEditableRawContact() {
+        if (mEntityDeltaList == null) {
+            return;
+        }
+        for (EntityDelta state : mEntityDeltaList) {
+            final String accountType = state.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
+            final String dataSet = state.getValues().getAsString(RawContacts.DATA_SET);
+            final AccountType type = mAccountTypeManager.getAccountType(accountType, dataSet);
+
+            // Raw contacts that are not from external sources should be editable.
+            if (!type.isExternal()) {
+                mEditableAccountType = type;
+                mState = state;
+                return;
+            }
+        }
+    }
+
+    public void parseExtras() {
+        if (mEditableAccountType == null || mState == null) {
+            return;
+        }
+        // Handle any incoming values that should be inserted
+        final Bundle extras = getIntent().getExtras();
+        if (extras != null && extras.size() > 0) {
+            // If there are any intent extras, add them as additional fields in the EntityDelta.
+            EntityModifier.parseExtras(this, mEditableAccountType, mState, extras);
+        }
+    }
+
+    /**
+     * Rebuild the editor to match our underlying {@link #mEntityDeltaList} object.
+     */
+    private void bindEditor() {
+        if (mEntityDeltaList == null) {
+            return;
+        }
+
+        // If no valid raw contact (to insert the data) was found, we won't have an editable
+        // account type to use. In this case, display an error message and hide the "OK" button.
+        if (mEditableAccountType == null) {
+            mIsReadyOnly = true;
+            mReadOnlyWarningView.setText(getString(R.string.contact_read_only));
+            mReadOnlyWarningView.setVisibility(View.VISIBLE);
+            mEditorContainerView.setVisibility(View.GONE);
+            findViewById(R.id.btn_done).setVisibility(View.GONE);
+            // Nothing more to be done, just show the UI
+            onLoadDataFinished();
+            return;
+        }
+
+        // Otherwise display an editor that allows the user to add the data to this raw contact.
+        for (DataKind kind : mEditableAccountType.getSortedDataKinds()) {
+            // Skip kind that are not editable
+            if (!kind.editable) continue;
+            if (mMimetype.equals(kind.mimeType)) {
+                for (ValuesDelta valuesDelta : mState.getMimeEntries(mMimetype)) {
+                    // Skip entries that aren't visible
+                    if (!valuesDelta.isVisible()) continue;
+                    if (valuesDelta.isInsert()) {
+                        inflateEditorView(kind, valuesDelta, mState);
+                        return;
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Creates an EditorView for the given entry. This function must be used while constructing
+     * the views corresponding to the the object-model. The resulting EditorView is also added
+     * to the end of mEditors
+     */
+    private void inflateEditorView(DataKind dataKind, ValuesDelta valuesDelta, EntityDelta state) {
+        final View view = mInflater.inflate(dataKind.editorLayoutResourceId, mEditorContainerView,
+                false);
+
+        if (view instanceof Editor) {
+            Editor editor = (Editor) view;
+            // Don't allow deletion of the field because there is only 1 detail in this editor.
+            editor.setDeletable(false);
+            editor.setValues(dataKind, valuesDelta, state, false, new ViewIdGenerator());
+        }
+
+        mEditorContainerView.addView(view);
+    }
+
+    /**
+     * Set the display name to the correct TextView. Don't do this until it is
+     * certain there is no need for a disambiguation field (otherwise the screen
+     * will flicker because the name will be centered and then moved upwards).
+     */
+    private void setDisplayName() {
+        mDisplayNameView.setText(mDisplayName);
+    }
+
+    /**
+     * Set the TextView (for extra contact info) with the given value and make the
+     * TextView visible.
+     */
+    private void setExtraInfoField(String value) {
+        TextView extraTextView = (TextView) findViewById(R.id.extra_info);
+        extraTextView.setVisibility(View.VISIBLE);
+        extraTextView.setText(value);
+    }
+
+    /**
+     * Shows all the contents of the dialog to the user at one time. This should only be called
+     * once all the queries have completed, otherwise the screen will flash as additional data
+     * comes in.
+     */
+    private void onLoadDataFinished() {
+        mRootView.setVisibility(View.VISIBLE);
+    }
+
+    /**
+     * Saves or creates the contact based on the mode, and if successful
+     * finishes the activity.
+     */
+    private void doSaveAction() {
+        final PersistTask task = new PersistTask(this, mAccountTypeManager);
+        task.execute(mEntityDeltaList);
+    }
+
+
+    /**
+     * Background task for persisting edited contact data, using the changes
+     * defined by a set of {@link EntityDelta}. This task starts
+     * {@link EmptyService} to make sure the background thread can finish
+     * persisting in cases where the system wants to reclaim our process.
+     */
+    public static class PersistTask extends AsyncTask<EntityDeltaList, Void, Integer> {
+        // In the future, use ContactSaver instead of WeakAsyncTask because of
+        // the danger of the activity being null during a save action
+        private static final int PERSIST_TRIES = 3;
+
+        private static final int RESULT_UNCHANGED = 0;
+        private static final int RESULT_SUCCESS = 1;
+        private static final int RESULT_FAILURE = 2;
+
+        private ConfirmAddDetailActivity activityTarget;
+        private WeakReference<ProgressDialog> mProgress;
+
+        private AccountTypeManager mAccountTypeManager;
+
+        public PersistTask(ConfirmAddDetailActivity target, AccountTypeManager accountTypeManager) {
+            activityTarget = target;
+            mAccountTypeManager = accountTypeManager;
+        }
+
+        @Override
+        protected void onPreExecute() {
+            mProgress = new WeakReference<ProgressDialog>(ProgressDialog.show(activityTarget, null,
+                    activityTarget.getText(R.string.savingContact)));
+
+            // Before starting this task, start an empty service to protect our
+            // process from being reclaimed by the system.
+            final Context context = activityTarget;
+            context.startService(new Intent(context, EmptyService.class));
+        }
+
+        @Override
+        protected Integer doInBackground(EntityDeltaList... params) {
+            final Context context = activityTarget;
+            final ContentResolver resolver = context.getContentResolver();
+
+            EntityDeltaList state = params[0];
+
+            // Trim any empty fields, and RawContacts, before persisting
+            EntityModifier.trimEmpty(state, mAccountTypeManager);
+
+            // Attempt to persist changes
+            int tries = 0;
+            Integer result = RESULT_FAILURE;
+            while (tries++ < PERSIST_TRIES) {
+                try {
+                    // Build operations and try applying
+                    final ArrayList<ContentProviderOperation> diff = state.buildDiff();
+                    ContentProviderResult[] results = null;
+                    if (!diff.isEmpty()) {
+                         results = resolver.applyBatch(ContactsContract.AUTHORITY, diff);
+                    }
+
+                    result = (diff.size() > 0) ? RESULT_SUCCESS : RESULT_UNCHANGED;
+                    break;
+
+                } catch (RemoteException e) {
+                    // Something went wrong, bail without success
+                    Log.e(TAG, "Problem persisting user edits", e);
+                    break;
+
+                } catch (OperationApplicationException e) {
+                    // Version consistency failed, bail without success
+                    Log.e(TAG, "Version consistency failed", e);
+                    break;
+                }
+            }
+
+            return result;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        protected void onPostExecute(Integer result) {
+            final Context context = activityTarget;
+
+            // Dismiss the progress dialog
+            mProgress.get().dismiss();
+
+            // Show a toast message based on the success or failure of the save action.
+            if (result == RESULT_SUCCESS) {
+                Toast.makeText(context, R.string.contactSavedToast, Toast.LENGTH_SHORT).show();
+            } else if (result == RESULT_FAILURE) {
+                Toast.makeText(context, R.string.contactSavedErrorToast, Toast.LENGTH_LONG).show();
+            }
+
+            // Stop the service that was protecting us
+            context.stopService(new Intent(context, EmptyService.class));
+            activityTarget.onSaveCompleted(result != RESULT_FAILURE);
+        }
+    }
+
+    /**
+     * This method is intended to be executed after the background task for saving edited info has
+     * finished. The method sets the activity result (and intent if applicable) and finishes the
+     * activity.
+     * @param success is true if the save task completed successfully, or false otherwise.
+     */
+    private void onSaveCompleted(boolean success) {
+        if (success) {
+            Intent intent = new Intent(Intent.ACTION_VIEW, mContactUri);
+            setResult(RESULT_OK, intent);
+        } else {
+            setResult(RESULT_CANCELED);
+        }
+        finish();
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/activities/ContactSelectionActivity.java b/src/com/android/contacts/activities/ContactSelectionActivity.java
index fbb9b66..0ab5881 100644
--- a/src/com/android/contacts/activities/ContactSelectionActivity.java
+++ b/src/com/android/contacts/activities/ContactSelectionActivity.java
@@ -32,11 +32,13 @@
 import com.android.contacts.list.PostalAddressPickerFragment;
 import com.android.contacts.widget.ContextMenuAdapter;
 
+import android.app.Activity;
 import android.app.Fragment;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
 import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Intents.Insert;
 import android.text.TextUtils;
 import android.view.MenuItem;
 import android.view.View;
@@ -45,6 +47,8 @@
 import android.widget.SearchView;
 import android.widget.SearchView.OnQueryTextListener;
 
+import java.util.Set;
+
 /**
  * Displays a list of contacts (or phone numbers or postal addresses) for the
  * purposes of selecting one.
@@ -53,6 +57,8 @@
         implements View.OnCreateContextMenuListener, OnQueryTextListener, OnClickListener {
     private static final String TAG = "ContactSelectionActivity";
 
+    private static final int SUBACTIVITY_ADD_TO_EXISTING_CONTACT = 0;
+
     private static final String KEY_ACTION_CODE = "actionCode";
     private static final int DEFAULT_DIRECTORY_RESULT_LIMIT = 20;
 
@@ -314,8 +320,22 @@
 
         @Override
         public void onEditContactAction(Uri contactLookupUri) {
-            Intent intent = new Intent(Intent.ACTION_EDIT, contactLookupUri);
-            startActivityAndForwardResult(intent);
+            Bundle extras = getIntent().getExtras();
+            if (launchAddToContactDialog(extras)) {
+                // Show a confirmation dialog to add the value(s) to the existing contact.
+                Intent intent = new Intent(ContactSelectionActivity.this,
+                        ConfirmAddDetailActivity.class);
+                intent.setData(contactLookupUri);
+                if (extras != null) {
+                    intent.putExtras(extras);
+                }
+                // Wait for the activity result because we want to keep the picker open (in case the
+                // user cancels adding the info to a contact and wants to pick someone else).
+                startActivityForResult(intent, SUBACTIVITY_ADD_TO_EXISTING_CONTACT);
+            } else {
+                // Otherwise launch the full contact editor.
+                startActivityAndForwardResult(new Intent(Intent.ACTION_EDIT, contactLookupUri));
+            }
         }
 
         @Override
@@ -327,6 +347,33 @@
         public void onShortcutIntentCreated(Intent intent) {
             returnPickerResult(intent);
         }
+
+        /**
+         * Returns true if is a single email or single phone number provided in the {@link Intent}
+         * extras bundle so that a pop-up confirmation dialog can be used to add the data to
+         * a contact. Otherwise return false if there are other intent extras that require launching
+         * the full contact editor.
+         */
+        private boolean launchAddToContactDialog(Bundle extras) {
+            if (extras == null) {
+                return false;
+            }
+            Set<String> intentExtraKeys = extras.keySet();
+            int numIntentExtraKeys = intentExtraKeys.size();
+            if (numIntentExtraKeys == 2) {
+                boolean hasPhone = intentExtraKeys.contains(Insert.PHONE) &&
+                        intentExtraKeys.contains(Insert.PHONE_TYPE);
+                boolean hasEmail = intentExtraKeys.contains(Insert.EMAIL) &&
+                        intentExtraKeys.contains(Insert.EMAIL_TYPE);
+                return hasPhone || hasEmail;
+            } else if (numIntentExtraKeys == 1) {
+                return intentExtraKeys.contains(Insert.PHONE) ||
+                        intentExtraKeys.contains(Insert.EMAIL);
+            }
+            // Having 0 or more than 2 intent extra keys means that we should launch
+            // the full contact editor to properly handle the intent extras.
+            return false;
+        }
     }
 
     private final class PhoneNumberPickerActionListener implements
@@ -415,4 +462,17 @@
             finish();
         }
     }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if (requestCode == SUBACTIVITY_ADD_TO_EXISTING_CONTACT) {
+            if (resultCode == Activity.RESULT_OK) {
+                if (data != null) {
+                    startActivity(data);
+                }
+                finish();
+            }
+        }
+    }
 }
diff --git a/src/com/android/contacts/activities/NonPhoneActivity.java b/src/com/android/contacts/activities/NonPhoneActivity.java
index 922be47..3a54292 100644
--- a/src/com/android/contacts/activities/NonPhoneActivity.java
+++ b/src/com/android/contacts/activities/NonPhoneActivity.java
@@ -82,8 +82,6 @@
                 final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
                 intent.setType(Contacts.CONTENT_ITEM_TYPE);
                 intent.putExtra(Insert.PHONE, getArgumentPhoneNumber());
-                intent.setFlags(
-                        Intent.FLAG_ACTIVITY_FORWARD_RESULT | Intent.FLAG_ACTIVITY_NO_HISTORY);
                 startActivity(intent);
             }
             dismiss();
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index b68bfc9..ebf1dbd 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -55,6 +55,7 @@
 import com.android.contacts.preference.DisplayOptionsPreferenceFragment;
 import com.android.contacts.util.AccountSelectionUtil;
 import com.android.contacts.util.AccountsListAdapter;
+import com.android.contacts.util.Constants;
 import com.android.contacts.util.DialogManager;
 import com.android.contacts.util.PhoneCapabilityTester;
 
@@ -233,6 +234,9 @@
 
     @Override
     protected void onCreate(Bundle savedState) {
+        if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
+            Log.d(Constants.PERFORMANCE_TAG, "PeopleActivity.onCreate start");
+        }
         super.onCreate(savedState);
 
         if (!processIntent(false)) {
@@ -242,6 +246,9 @@
 
         mIsRecreatedInstance = (savedState != null);
         createViewsAndFragments(savedState);
+        if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
+            Log.d(Constants.PERFORMANCE_TAG, "PeopleActivity.onCreate finish");
+        }
     }
 
     @Override
diff --git a/src/com/android/contacts/editor/LabeledEditorView.java b/src/com/android/contacts/editor/LabeledEditorView.java
index 6452c80..1bea060 100644
--- a/src/com/android/contacts/editor/LabeledEditorView.java
+++ b/src/com/android/contacts/editor/LabeledEditorView.java
@@ -293,10 +293,14 @@
         boolean isEmpty = isEmpty();
         if (mWasEmpty != isEmpty) {
             if (isEmpty) {
-                mListener.onRequest(EditorListener.FIELD_TURNED_EMPTY);
+                if (mListener != null) {
+                    mListener.onRequest(EditorListener.FIELD_TURNED_EMPTY);
+                }
                 if (mIsDeletable) mDeleteContainer.setVisibility(View.INVISIBLE);
             } else {
-                mListener.onRequest(EditorListener.FIELD_TURNED_NON_EMPTY);
+                if (mListener != null) {
+                    mListener.onRequest(EditorListener.FIELD_TURNED_NON_EMPTY);
+                }
                 if (mIsDeletable) mDeleteContainer.setVisibility(View.VISIBLE);
             }
             mWasEmpty = isEmpty;
diff --git a/src/com/android/contacts/model/AccountTypeManager.java b/src/com/android/contacts/model/AccountTypeManager.java
index d60f355..65af3ee 100644
--- a/src/com/android/contacts/model/AccountTypeManager.java
+++ b/src/com/android/contacts/model/AccountTypeManager.java
@@ -16,6 +16,7 @@
 
 package com.android.contacts.model;
 
+import com.android.contacts.util.Constants;
 import com.android.i18n.phonenumbers.PhoneNumberUtil;
 import com.android.internal.util.Objects;
 import com.google.android.collect.Lists;
@@ -271,6 +272,9 @@
      * called on a background thread.
      */
     protected void loadAccountsInBackground() {
+        if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
+            Log.d(Constants.PERFORMANCE_TAG, "AccountTypeManager.loadAccountsInBackground start");
+        }
         long startTime = SystemClock.currentThreadTimeMillis();
 
         // Account types, keyed off the account type and data set concatenation.
@@ -419,6 +423,9 @@
             mInitializationLatch.countDown();
             mInitializationLatch = null;
         }
+        if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
+            Log.d(Constants.PERFORMANCE_TAG, "AccountTypeManager.loadAccountsInBackground finish");
+        }
     }
 
     // Bookkeeping method for tracking the known account types in the given maps.
diff --git a/src/com/android/contacts/model/BaseAccountType.java b/src/com/android/contacts/model/BaseAccountType.java
index aafa3ea..517f487 100644
--- a/src/com/android/contacts/model/BaseAccountType.java
+++ b/src/com/android/contacts/model/BaseAccountType.java
@@ -65,7 +65,7 @@
     public BaseAccountType() {
         this.accountType = null;
         this.dataSet = null;
-        this.titleRes = R.string.account_phone;
+        this.titleRes = R.string.local_profile_title;
         this.iconRes = R.mipmap.ic_launcher_contacts;
     }
 
diff --git a/src/com/android/contacts/model/FallbackAccountType.java b/src/com/android/contacts/model/FallbackAccountType.java
index 8bb3992..a40828e 100644
--- a/src/com/android/contacts/model/FallbackAccountType.java
+++ b/src/com/android/contacts/model/FallbackAccountType.java
@@ -25,7 +25,7 @@
     public FallbackAccountType(Context context) {
         this.accountType = null;
         this.dataSet = null;
-        this.titleRes = R.string.account_phone;
+        this.titleRes = R.string.local_profile_title;
         this.iconRes = R.mipmap.ic_launcher_contacts;
 
         this.resPackageName = null;
diff --git a/src/com/android/contacts/quickcontact/DataAction.java b/src/com/android/contacts/quickcontact/DataAction.java
index 109e8b0..f7bf6ed 100644
--- a/src/com/android/contacts/quickcontact/DataAction.java
+++ b/src/com/android/contacts/quickcontact/DataAction.java
@@ -3,7 +3,6 @@
 import com.android.contacts.ContactsUtils;
 import com.android.contacts.R;
 import com.android.contacts.model.AccountType.EditType;
-import com.android.contacts.model.AccountTypeManager;
 import com.android.contacts.model.DataKind;
 import com.android.contacts.util.Constants;
 import com.android.contacts.util.PhoneCapabilityTester;
@@ -179,14 +178,11 @@
                     final boolean isAudioChatCapable =
                             (chatCapability & Im.CAPABILITY_HAS_VOICE) != 0;
                     if (isVideoChatCapable || isAudioChatCapable) {
-                        final AccountTypeManager accountTypes = AccountTypeManager.getInstance(
-                                context.getApplicationContext());
                         mAlternateIntent = new Intent(
                                 Intent.ACTION_SENDTO, Uri.parse("xmpp:" + data + "?call"));
-                        // Use Holo dark theme since the background is darker than usual.
                         mAlternateIconRes = (isVideoChatCapable
-                                ? R.drawable.sym_action_videochat_holo_dark
-                                : R.drawable.sym_action_audiochat_holo_dark);
+                                ? R.drawable.sym_action_videochat_holo_light
+                                : R.drawable.sym_action_audiochat_holo_light);
                     }
                 }
             }
diff --git a/src/com/android/contacts/util/Constants.java b/src/com/android/contacts/util/Constants.java
index a8ba059..d79f029 100644
--- a/src/com/android/contacts/util/Constants.java
+++ b/src/com/android/contacts/util/Constants.java
@@ -24,4 +24,8 @@
     public static final String SCHEME_MAILTO = "mailto";
     public static final String SCHEME_IMTO = "imto";
     public static final String SCHEME_SIP = "sip";
+
+    // Log tag for performance measurement.
+    // To enable: adb shell setprop log.tag.ContactsPerf VERBOSE
+    public static final String PERFORMANCE_TAG = "ContactsPerf";
 }