Code drop from //branches/cupcake/...@124589
diff --git a/src/com/android/contacts/AlphabetIndexer.java b/src/com/android/contacts/AlphabetIndexer.java
deleted file mode 100644
index 4e5fb0f..0000000
--- a/src/com/android/contacts/AlphabetIndexer.java
+++ /dev/null
@@ -1,197 +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.database.Cursor;
-import android.database.DataSetObserver;
-import android.util.SparseIntArray;
-
-/**
- * This class essentially helps in building an index of section boundaries of a
- * sorted column of a cursor. For instance, if a cursor contains a data set 
- * sorted by first name of a person or the title of a song, this class will 
- * perform a binary search to identify the first row that begins with a 
- * particular letter. The search is case-insensitive. The class caches the index 
- * such that subsequent queries for the same letter will return right away.
- */
-public class AlphabetIndexer extends DataSetObserver {
-
-    protected Cursor mDataCursor;
-    protected int mColumnIndex;
-    protected Object[] mAlphabetArray;
-    private SparseIntArray mAlphaMap;
-    private java.text.Collator mCollator;
-
-    /**
-     * Constructs the indexer.
-     * @param cursor the cursor containing the data set
-     * @param columnIndex the column number in the cursor that is sorted 
-     *        alphabetically
-     * @param sections the array of objects that represent the sections. The
-     * toString() method of each item is called and the first letter of the 
-     * String is used as the letter to search for.
-     */
-    public AlphabetIndexer(Cursor cursor, int columnIndex, Object[] sections) {
-        mDataCursor = cursor;
-        mColumnIndex = columnIndex;
-        mAlphabetArray = sections;
-        mAlphaMap = new SparseIntArray(26 /* Optimize for English */);
-        if (cursor != null) {
-            cursor.registerDataSetObserver(this);
-        }
-        // Get a Collator for the current locale for string comparisons.
-        mCollator = java.text.Collator.getInstance();
-        mCollator.setStrength(java.text.Collator.PRIMARY);
-    }
-
-    /**
-     * Sets a new cursor as the data set and resets the cache of indices.
-     * @param cursor the new cursor to use as the data set
-     */
-    public void setCursor(Cursor cursor) {
-        if (mDataCursor != null) {
-            mDataCursor.unregisterDataSetObserver(this);
-        }
-        mDataCursor = cursor;
-        if (cursor != null) {
-            mDataCursor.registerDataSetObserver(this);
-        }
-        mAlphaMap.clear();
-    }
-
-    /**
-     * Performs a binary search or cache lookup to find the first row that
-     * matches a given section's starting letter.
-     * @param sectionIndex the section to search for
-     * @return the row index of the first occurrence, or the nearest next letter.
-     * For instance, if searching for "T" and no "T" is found, then the first
-     * row starting with "U" or any higher letter is returned. If there is no
-     * data following "T" at all, then the list size is returned.
-     */
-    public int indexOf(int sectionIndex) {
-        final SparseIntArray alphaMap = mAlphaMap;
-        final Cursor cursor = mDataCursor;
-
-        if (cursor == null || mAlphabetArray == null) {
-            return 0;
-        }
-        
-        // Check bounds
-        if (sectionIndex <= 0) {
-            return 0;
-        }
-        if (sectionIndex >= mAlphabetArray.length) {
-            sectionIndex = mAlphabetArray.length - 1;
-        }
-
-        int savedCursorPos = cursor.getPosition();
-
-        int count = cursor.getCount();
-        int start = 0;
-        int end = count;
-        int pos;
-
-        String letter = mAlphabetArray[sectionIndex].toString();
-        letter = letter.toUpperCase();
-        int key = letter.charAt(0);
-        // Check map
-        if (Integer.MIN_VALUE != (pos = alphaMap.get(key, Integer.MIN_VALUE))) {
-            // Is it approximate? Using negative value to indicate that it's 
-            // an approximation and positive value when it is the accurate
-            // position.
-            if (pos < 0) {
-                pos = -pos;
-                end = pos;
-            } else {
-                // Not approximate, this is the confirmed start of section, return it
-                return pos;
-            }
-        }
-
-        // Do we have the position of the previous section?
-        if (sectionIndex > 0) {
-            int prevLetter =
-                    mAlphabetArray[sectionIndex - 1].toString().charAt(0);
-            int prevLetterPos = alphaMap.get(prevLetter, Integer.MIN_VALUE);
-            if (prevLetterPos != Integer.MIN_VALUE) {
-                start = Math.abs(prevLetterPos);
-            }
-        }
-
-        // Now that we have a possibly optimized start and end, let's binary search
-
-        pos = (end + start) / 2;
-
-        while (pos < end) {
-            // Get letter at pos
-            cursor.moveToPosition(pos);
-            String curName = cursor.getString(mColumnIndex);
-            if (curName == null) {
-                if (pos == 0) {
-                    break;
-                } else {
-                    pos--;
-                    continue;
-                }
-            }
-            int curLetter = Character.toUpperCase(curName.charAt(0));
-
-            if (curLetter != key) {
-                // Enter approximation in hash if a better solution doesn't exist
-                int curPos = alphaMap.get(curLetter, Integer.MIN_VALUE);
-                if (curPos == Integer.MIN_VALUE || Math.abs(curPos) > pos) {
-                    // Negative pos indicates that it is an approximation
-                    alphaMap.put(curLetter, -pos);
-                }
-                if (mCollator.compare(curName, letter) < 0) {
-                    start = pos + 1;
-                    if (start >= count) {
-                        pos = count;
-                        break;
-                    }
-                } else {
-                    end = pos;
-                }
-            } else {
-                // They're the same, but that doesn't mean it's the start
-                if (start == pos) {
-                    // This is it
-                    break;
-                } else {
-                    // Need to go further lower to find the starting row
-                    end = pos;
-                }
-            }
-            pos = (start + end) / 2;
-        }
-        alphaMap.put(key, pos);
-        cursor.moveToPosition(savedCursorPos);
-        return pos;
-    }
-
-    @Override
-    public void onChanged() {
-        super.onChanged();
-        mAlphaMap.clear();
-    }
-
-    @Override
-    public void onInvalidated() {
-        super.onInvalidated();
-        mAlphaMap.clear();
-    }
-}
diff --git a/src/com/android/contacts/AttachImage.java b/src/com/android/contacts/AttachImage.java
index 6346031..8c91722 100644
--- a/src/com/android/contacts/AttachImage.java
+++ b/src/com/android/contacts/AttachImage.java
@@ -28,22 +28,22 @@
 
 /**
  * Provides an external interface for other applications to attach images
- * to contacts. It will first present a contact picker and then run the 
+ * to contacts. It will first present a contact picker and then run the
  * image that is handed to it through the cropper to make the image the proper
  * size and give the user a chance to use the face detector.
  */
-class AttachImage extends Activity {
+public class AttachImage extends Activity {
     private static final int REQUEST_PICK_CONTACT = 1;
     private static final int REQUEST_CROP_PHOTO = 2;
 
     private static final String CONTACT_URI_KEY = "contact_uri";
 
     public AttachImage() {
-        
+
     }
 
     Uri mContactUri;
-    
+
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
diff --git a/src/com/android/contacts/ContactsGroupSyncSelector.java b/src/com/android/contacts/ContactsGroupSyncSelector.java
index 1043299..6081c79 100644
--- a/src/com/android/contacts/ContactsGroupSyncSelector.java
+++ b/src/com/android/contacts/ContactsGroupSyncSelector.java
@@ -16,28 +16,27 @@
 
 package com.android.contacts;
 
+import com.google.android.googlelogin.GoogleLoginServiceConstants;
+import com.google.android.googlelogin.GoogleLoginServiceHelper;
+
+import android.app.ListActivity;
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
-import android.content.DialogInterface;
+import android.content.Intent;
 import android.database.Cursor;
 import android.os.Bundle;
 import android.provider.Contacts;
+import android.provider.Gmail;
 import android.provider.Contacts.Groups;
 import android.provider.Contacts.Settings;
 import android.text.TextUtils;
-import android.view.ContextThemeWrapper;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.AdapterView;
 import android.widget.ArrayAdapter;
 import android.widget.ListView;
 
-import com.android.internal.app.AlertActivity;
-import com.android.internal.app.AlertController;
-
-public final class ContactsGroupSyncSelector extends AlertActivity implements 
-        ListView.OnItemClickListener, DialogInterface.OnClickListener {
+public final class ContactsGroupSyncSelector extends ListActivity implements View.OnClickListener {
 
     private static final String[] PROJECTION = new String[] {
             Groups._ID, // 0
@@ -50,9 +49,7 @@
     private static final int COLUMN_INDEX_SHOULD_SYNC = 2;
     private static final int COLUMN_INDEX_SYSTEM_ID = 3;
 
-    private ContentResolver mResolver;
-    private ListView mListView;
-    private GroupsAdapter mAdapter;
+    private static final int SUBACTIVITY_GET_ACCOUNT = 1;
 
     boolean[] mChecked;
     boolean mSyncAllGroups;
@@ -60,8 +57,7 @@
     
     private final class GroupsAdapter extends ArrayAdapter<CharSequence> {
         public GroupsAdapter(CharSequence[] items) {
-            super(new ContextThemeWrapper(ContactsGroupSyncSelector.this,
-                    android.R.style.Theme_Light),
+            super(ContactsGroupSyncSelector.this,
                     android.R.layout.simple_list_item_checked,
                     android.R.id.text1, items);
         }
@@ -95,8 +91,9 @@
     /**
      * Handles clicks on the list items
      */
-    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-        boolean isChecked = mListView.isItemChecked(position);
+    @Override
+    protected void onListItemClick(ListView list, View view, int position, long id) {
+        boolean isChecked = list.isItemChecked(position);
         mChecked[position] = isChecked;
         if (position == 0) {
             mSyncAllGroups = isChecked;
@@ -105,58 +102,85 @@
     }
 
     /**
-     * Handles clicks on the OK button
+     * Handles clicks on the OK and cancel buttons
      */
-    public void onClick(DialogInterface dialog, int which) {
-        if (mSyncAllGroups) {
-            // For now we only support a single account and the UI doesn't know what
-            // the account name is, so we're using a global setting for SYNC_EVERYTHING.
-            // Some day when we add multiple accounts to the UI this should use the per
-            // account setting.
-            Settings.setSetting(mResolver, null, Settings.SYNC_EVERYTHING, "1");
-        } else {
-            final ContentResolver resolver = mResolver;
-            ContentValues values = new ContentValues();
-            int count = mChecked.length;
-            for (int i = 1; i < count; i++) {
-                values.clear();
-                values.put(Groups.SHOULD_SYNC, mChecked[i]);
-                resolver.update(ContentUris.withAppendedId(Groups.CONTENT_URI, mGroupIds[i]),
-                        values, null, null);
+    public void onClick(View view) {
+        switch (view.getId()) {
+            case R.id.cancel: {
+                finish();
+                break;
             }
-            // For now we only support a single account and the UI doesn't know what
-            // the account name is, so we're using a global setting for SYNC_EVERYTHING.
-            // Some day when we add multiple accounts to the UI this should use the per
-            // account setting.
-            Settings.setSetting(resolver, null, Settings.SYNC_EVERYTHING, "0");
+            
+            case R.id.ok: {
+                final ContentResolver resolver = getContentResolver();
+                if (mSyncAllGroups) {
+                    // For now we only support a single account and the UI doesn't know what
+                    // the account name is, so we're using a global setting for SYNC_EVERYTHING.
+                    // Some day when we add multiple accounts to the UI this should use the per
+                    // account setting.
+                    Settings.setSetting(resolver, null, Settings.SYNC_EVERYTHING, "1");
+                } else {
+                    ContentValues values = new ContentValues();
+                    int count = mChecked.length;
+                    for (int i = 1; i < count; i++) {
+                        values.clear();
+                        values.put(Groups.SHOULD_SYNC, mChecked[i]);
+                        resolver.update(ContentUris.withAppendedId(Groups.CONTENT_URI, mGroupIds[i]),
+                                values, null, null);
+                    }
+                    // For now we only support a single account and the UI doesn't know what
+                    // the account name is, so we're using a global setting for SYNC_EVERYTHING.
+                    // Some day when we add multiple accounts to the UI this should use the per
+                    // account setting.
+                    Settings.setSetting(resolver, null, Settings.SYNC_EVERYTHING, "0");
+                }
+                finish();
+                break;
+            }
         }
     }
 
     @Override
     protected void onCreate(Bundle savedState) {
         super.onCreate(savedState);
-        mResolver = getContentResolver();
 
-        // Set the alert parameters
-        AlertController.AlertParams params = mAlertParams;
-        params.mTitle = getText(R.string.syncGroupChooserTitle);
-        params.mIcon = getResources().getDrawable(R.drawable.ic_tab_unselected_contacts);
-        params.mPositiveButtonText = getText(R.string.okButtonText);
-        params.mPositiveButtonListener = this;
-        params.mNegativeButtonText = getText(R.string.cancelButtonText);
-        buildItems(params);
+        // Only look for an account on first run.
+        if (savedState == null) {
+            // This will request a Gmail account and if none are present, it will
+            // invoke SetupWizard to login or create one. The result is returned
+            // through onActivityResult().
+            Bundle bundle = new Bundle();
+            bundle.putCharSequence("optional_message", getText(R.string.contactsSyncPlug));
+            GoogleLoginServiceHelper.getCredentials(this, SUBACTIVITY_GET_ACCOUNT,
+                    bundle, GoogleLoginServiceConstants.PREFER_HOSTED, Gmail.GMAIL_AUTH_SERVICE,
+                    true);
+        }
 
-        // Takes the info in mAlertParams and creates the layout
-        setupAlert();
+        setContentView(R.layout.sync_settings);
 
-        mListView = mAlert.getListView();
-        mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
-        mListView.setOnItemClickListener(this);
-        adjustChecks();
+        findViewById(R.id.ok).setOnClickListener(this);
+        findViewById(R.id.cancel).setOnClickListener(this);
+        
+        getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
     }
 
-    private void buildItems(AlertController.AlertParams params) {
-        Cursor cursor = mResolver.query(Groups.CONTENT_URI, PROJECTION, null, null, Groups.NAME);
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
+        super.onActivityResult(requestCode, resultCode, intent);
+        if (requestCode == SUBACTIVITY_GET_ACCOUNT) {
+            if (resultCode == RESULT_OK) {
+                // There is an account setup, build the group list
+                buildItems();
+                adjustChecks();
+            } else {
+                finish();
+            }
+        }
+    }
+
+    private void buildItems() {
+        final ContentResolver resolver = getContentResolver();
+        Cursor cursor = resolver.query(Groups.CONTENT_URI, PROJECTION, null, null, Groups.NAME);
         if (cursor != null) {
             try {
                 int count = cursor.getCount() + 1;
@@ -183,13 +207,12 @@
                     }
                 }
                 mChecked = checked;
-                mSyncAllGroups = getShouldSyncEverything(mResolver);
+                mSyncAllGroups = getShouldSyncEverything(resolver);
                 checked[0] = mSyncAllGroups;
                 mGroupIds = groupIds;
     
                 // Setup the adapter
-                mAdapter = new GroupsAdapter(items);
-                params.mAdapter = mAdapter;
+                setListAdapter(new GroupsAdapter(items));
             } finally {
                 cursor.close();
             }
@@ -197,7 +220,7 @@
     }
 
     private void adjustChecks() {
-        ListView list = mListView;
+        final ListView list = getListView();
         if (mSyncAllGroups) {
             int count = list.getCount();
             for (int i = 0; i < count; i++) {
diff --git a/src/com/android/contacts/ContactsListActivity.java b/src/com/android/contacts/ContactsListActivity.java
index 0ffa786..5e1fd63 100644
--- a/src/com/android/contacts/ContactsListActivity.java
+++ b/src/com/android/contacts/ContactsListActivity.java
@@ -28,8 +28,8 @@
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.SharedPreferences;
-import android.database.Cursor;
 import android.database.CharArrayBuffer;
+import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.net.Uri;
 import android.os.Bundle;
@@ -43,10 +43,10 @@
 import android.provider.Contacts.Phones;
 import android.provider.Contacts.Presence;
 import android.provider.Contacts.Intents.UI;
-import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.ContextMenu;
+import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.Menu;
@@ -55,11 +55,14 @@
 import android.view.ViewGroup;
 import android.view.ContextMenu.ContextMenuInfo;
 import android.widget.AdapterView;
+import android.widget.AlphabetIndexer;
 import android.widget.ListView;
 import android.widget.ResourceCursorAdapter;
+import android.widget.SectionIndexer;
 import android.widget.TextView;
 
 import java.util.ArrayList;
+import java.lang.ref.WeakReference;
 
 /**
  * Displays a list of contacts. Usually is embedded into the ContactsActivity.
@@ -125,6 +128,31 @@
 
     static final int DEFAULT_MODE = MODE_ALL_CONTACTS;
 
+    /**
+     * The type of data to display in the main contacts list. 
+     */
+    static final String PREF_DISPLAY_TYPE = "display_system_group";
+
+    /** Unknown display type. */
+    static final int DISPLAY_TYPE_UNKNOWN = -1;
+    /** Display all contacts */
+    static final int DISPLAY_TYPE_ALL = 0;
+    /** Display all contacts that have phone numbers */
+    static final int DISPLAY_TYPE_ALL_WITH_PHONES = 1;
+    /** Display a system group */
+    static final int DISPLAY_TYPE_SYSTEM_GROUP = 2;
+    /** Display a user group */
+    static final int DISPLAY_TYPE_USER_GROUP = 3;
+
+    /**
+     * Info about what to display. If {@link #PREF_DISPLAY_TYPE}
+     * is {@link #DISPLAY_TYPE_SYSTEM_GROUP} then this will be the system id.
+     * If {@link #PREF_DISPLAY_TYPE} is {@link #DISPLAY_TYPE_USER_GROUP} then this will
+     * be the group name.
+     */ 
+    static final String PREF_DISPLAY_INFO = "display_group";
+
+    
     static final String NAME_COLUMN = People.DISPLAY_NAME;
     
     static final String[] CONTACTS_PROJECTION = new String[] {
@@ -244,9 +272,6 @@
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
 
-        // Make sure the preferences are setup
-        PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
-
         // Resolve the intent
         final Intent intent = getIntent();
 
@@ -255,7 +280,7 @@
         if (title != null) {
             setTitle(title);
         }
-
+        
         final String action = intent.getAction();
         mMode = MODE_UNKNOWN;
         
@@ -355,12 +380,6 @@
             mMode = DEFAULT_MODE;
         }
 
-/*
-        if (!mDefaultMode) {
-            findViewById(R.id.contact_group).banner.setVisibility(View.GONE);
-        }
-*/
-
         // Setup the UI
         final ListView list = getListView();
         list.setFocusable(true);
@@ -392,12 +411,14 @@
     }
 
     private void setEmptyText() {
-        TextView empty = (TextView) findViewById(android.R.id.empty);
+        TextView empty = (TextView) findViewById(R.id.emptyText);
+        // Center the text by default
+        int gravity = Gravity.CENTER;
         switch (mMode) {
             case MODE_GROUP:
                 if (Groups.GROUP_MY_CONTACTS.equals(mDisplayInfo)) {
-                    empty.setText(getString(R.string.groupEmpty,
-                            getText(R.string.groupNameMyContacts)));
+                    empty.setText(getText(R.string.noContactsHelpText));
+                    gravity = Gravity.NO_GRAVITY;
                 } else {
                     empty.setText(getString(R.string.groupEmpty, mDisplayInfo));
                 }
@@ -417,6 +438,7 @@
                 empty.setText(getText(R.string.noContacts));
                 break;
         }
+        empty.setGravity(gravity);
     }
 
     /**
@@ -449,18 +471,17 @@
         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
         
         // Lookup the group to display
-        mDisplayType = prefs.getInt(ContactsPreferenceActivity.PREF_DISPLAY_TYPE,
-                ContactsPreferenceActivity.DISPLAY_TYPE_UNKNOWN);
+        mDisplayType = prefs.getInt(PREF_DISPLAY_TYPE, DISPLAY_TYPE_UNKNOWN);
         switch (mDisplayType) {
-            case ContactsPreferenceActivity.DISPLAY_TYPE_ALL_WITH_PHONES: {
+            case DISPLAY_TYPE_ALL_WITH_PHONES: {
                 mMode = MODE_WITH_PHONES;
                 mDisplayInfo = null;
                 break;
             }
 
-            case ContactsPreferenceActivity.DISPLAY_TYPE_SYSTEM_GROUP: {
+            case DISPLAY_TYPE_SYSTEM_GROUP: {
                 String systemId = prefs.getString(
-                        ContactsPreferenceActivity.PREF_DISPLAY_INFO, null);
+                        PREF_DISPLAY_INFO, null);
                 if (!TextUtils.isEmpty(systemId)) {
                     // Display the selected system group
                     mMode = MODE_GROUP;
@@ -470,14 +491,14 @@
                     // No valid group is present, display everything
                     mMode = MODE_WITH_PHONES;
                     mDisplayInfo = null;
-                    mDisplayType = ContactsPreferenceActivity.DISPLAY_TYPE_ALL;
+                    mDisplayType = DISPLAY_TYPE_ALL;
                 }
                 break;
             }
 
-            case ContactsPreferenceActivity.DISPLAY_TYPE_USER_GROUP: {
+            case DISPLAY_TYPE_USER_GROUP: {
                 String displayGroup = prefs.getString(
-                        ContactsPreferenceActivity.PREF_DISPLAY_INFO, null);
+                        PREF_DISPLAY_INFO, null);
                 if (!TextUtils.isEmpty(displayGroup)) {
                     // Display the selected user group
                     mMode = MODE_GROUP;
@@ -487,12 +508,12 @@
                     // No valid group is present, display everything
                     mMode = MODE_WITH_PHONES;
                     mDisplayInfo = null;
-                    mDisplayType = ContactsPreferenceActivity.DISPLAY_TYPE_ALL;
+                    mDisplayType = DISPLAY_TYPE_ALL;
                 }
                 break;
             }
 
-            case ContactsPreferenceActivity.DISPLAY_TYPE_ALL: {
+            case DISPLAY_TYPE_ALL: {
                 mMode = MODE_ALL_CONTACTS;
                 mDisplayInfo = null;
                 break;
@@ -501,7 +522,7 @@
             default: {
                 // We don't know what to display, default to My Contacts
                 mMode = MODE_GROUP;
-                mDisplayType = ContactsPreferenceActivity.DISPLAY_TYPE_SYSTEM_GROUP;
+                mDisplayType = DISPLAY_TYPE_SYSTEM_GROUP;
                 buildSystemGroupUris(Groups.GROUP_MY_CONTACTS);
                 mDisplayInfo = Groups.GROUP_MY_CONTACTS;
                 break;
@@ -596,19 +617,33 @@
             return false;
         }
 
+        // New contact
         menu.add(0, MENU_NEW_CONTACT, 0, R.string.menu_newContact)
                 .setIcon(android.R.drawable.ic_menu_add)
                 .setIntent(new Intent(Intents.Insert.ACTION, People.CONTENT_URI))
                 .setAlphabeticShortcut('n');
-        if (isChild()) {
-            menu.add(0, 0, 0, R.string.menu_preferences)
-                    .setIcon(android.R.drawable.ic_menu_preferences)
-                    .setIntent(new Intent(this, ContactsPreferenceActivity.class));
-        }
+
+        // Display group
         if (mDefaultMode) {
             menu.add(0, MENU_DISPLAY_GROUP, 0, R.string.menu_displayGroup)
                     .setIcon(R.drawable.ic_menu_allfriends);
         }
+
+        // Sync settings
+        Intent syncIntent = new Intent(Intent.ACTION_VIEW);
+        syncIntent.setClass(this, ContactsGroupSyncSelector.class);
+        menu.add(0, 0, 0, R.string.syncGroupPreference)
+                .setIcon(R.drawable.ic_menu_refresh)
+                .setIntent(syncIntent);
+        
+        // SIM import
+        Intent importIntent = new Intent(Intent.ACTION_VIEW);
+        importIntent.setType("vnd.android.cursor.item/sim-contact");
+        importIntent.setClassName("com.android.phone", "com.android.phone.SimContacts");
+        menu.add(0, 0, 0, R.string.importFromSim)
+                .setIcon(R.drawable.ic_menu_import_contact)
+                .setIntent(importIntent);
+
         return super.onCreateOptionsMenu(menu);
     }
 
@@ -622,27 +657,27 @@
                 // Set the group to display
                 if (mDisplayGroupCurrentSelection == DISPLAY_GROUP_INDEX_ALL_CONTACTS) {
                     // Display all
-                    mDisplayType = ContactsPreferenceActivity.DISPLAY_TYPE_ALL;
+                    mDisplayType = DISPLAY_TYPE_ALL;
                     mDisplayInfo = null;
                 } else if (mDisplayGroupCurrentSelection
                         == DISPLAY_GROUP_INDEX_ALL_CONTACTS_WITH_PHONES) {
                     // Display all with phone numbers
-                    mDisplayType = ContactsPreferenceActivity.DISPLAY_TYPE_ALL_WITH_PHONES;
+                    mDisplayType = DISPLAY_TYPE_ALL_WITH_PHONES;
                     mDisplayInfo = null;
                 } else if (mDisplayGroupsIncludesMyContacts &&
                         mDisplayGroupCurrentSelection == DISPLAY_GROUP_INDEX_MY_CONTACTS) {
-                    mDisplayType = ContactsPreferenceActivity.DISPLAY_TYPE_SYSTEM_GROUP;
+                    mDisplayType = DISPLAY_TYPE_SYSTEM_GROUP;
                     mDisplayInfo = Groups.GROUP_MY_CONTACTS;
                 } else {
-                    mDisplayType = ContactsPreferenceActivity.DISPLAY_TYPE_USER_GROUP;
+                    mDisplayType = DISPLAY_TYPE_USER_GROUP;
                     mDisplayInfo = mDisplayGroups[mDisplayGroupCurrentSelection].toString();
                 }
 
                 // Save the changes to the preferences
                 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
                 prefs.edit()
-                        .putInt(ContactsPreferenceActivity.PREF_DISPLAY_TYPE, mDisplayType)
-                        .putString(ContactsPreferenceActivity.PREF_DISPLAY_INFO, mDisplayInfo)
+                        .putInt(PREF_DISPLAY_TYPE, mDisplayType)
+                        .putString(PREF_DISPLAY_INFO, mDisplayInfo)
                         .commit();
 
                 // Update the display state
@@ -778,8 +813,8 @@
                     .setTitle(R.string.deleteConfirmation_title)
                     .setIcon(android.R.drawable.ic_dialog_alert)
                     .setMessage(R.string.deleteConfirmation)
-                    .setNegativeButton(R.string.noButton, null)
-                    .setPositiveButton(R.string.yesButton, new DeleteClickListener(uri))
+                    .setNegativeButton(android.R.string.cancel, null)
+                    .setPositiveButton(android.R.string.ok, new DeleteClickListener(uri))
                     .setCancelable(false)
                     .show();
                 return true;
@@ -810,8 +845,8 @@
                         .setTitle(R.string.deleteConfirmation_title)
                         .setIcon(android.R.drawable.ic_dialog_alert)
                         .setMessage(R.string.deleteConfirmation)
-                        .setNegativeButton(R.string.noButton, null)
-                        .setPositiveButton(R.string.yesButton, new DeleteClickListener(uri))
+                        .setNegativeButton(android.R.string.cancel, null)
+                        .setPositiveButton(android.R.string.ok, new DeleteClickListener(uri))
                         .setCancelable(false)
                         .show();
                     return true;
@@ -1159,7 +1194,7 @@
                     // The My Contacts group
                     groups.add(DISPLAY_GROUP_INDEX_MY_CONTACTS,
                             getString(R.string.groupNameMyContacts));
-                    if (mDisplayType == ContactsPreferenceActivity.DISPLAY_TYPE_SYSTEM_GROUP
+                    if (mDisplayType == DISPLAY_TYPE_SYSTEM_GROUP
                             && Groups.GROUP_MY_CONTACTS.equals(mDisplayInfo)) {
                         currentIndex = DISPLAY_GROUP_INDEX_MY_CONTACTS;
                     }
@@ -1180,25 +1215,29 @@
         }
     }
 
-    private final class QueryHandler extends AsyncQueryHandler {
+    private static final class QueryHandler extends AsyncQueryHandler {
+        private final WeakReference<ContactsListActivity> mActivity;
+
         public QueryHandler(Context context) {
             super(context.getContentResolver());
+            mActivity = new WeakReference<ContactsListActivity>((ContactsListActivity) context);
         }
 
         @Override
         protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
-            if (!isFinishing()) {
-                mAdapter.setLoading(false);
-                mAdapter.changeCursor(cursor);
+            final ContactsListActivity activity = mActivity.get();
+            if (activity != null && !activity.isFinishing()) {
+                activity.mAdapter.setLoading(false);
+                activity.mAdapter.changeCursor(cursor);
     
                 // Now that the cursor is populated again, it's possible to restore the list state
-                if (mListState != null) {
-                    mList.onRestoreInstanceState(mListState);
-                    if (mListHasFocus) {
-                        mList.requestFocus();
+                if (activity.mListState != null) {
+                    activity.mList.onRestoreInstanceState(activity.mListState);
+                    if (activity.mListHasFocus) {
+                        activity.mList.requestFocus();
                     }
-                    mListHasFocus = false;
-                    mListState = null;
+                    activity.mListHasFocus = false;
+                    activity.mListState = null;
                 }
             } else {
                 cursor.close();
@@ -1216,20 +1255,22 @@
     }
 
     private final class ContactItemListAdapter extends ResourceCursorAdapter 
-            implements FastScrollView.SectionIndexer {
+            implements SectionIndexer {
         
-        private String [] mAlphabet;
         private AlphabetIndexer mIndexer;
+        private String mAlphabet;
         private boolean mLoading = true;
         private CharSequence mUnknownNameText;
         private CharSequence[] mLocalizedLabels;
 
         public ContactItemListAdapter(Context context, int resource, Cursor cursor) {
             super(context, resource, cursor);
-            getAlphabet(context);
+            
+            mAlphabet = context.getString(com.android.internal.R.string.fast_scroll_alphabet);
             if (cursor != null) {
                 mIndexer = new AlphabetIndexer(cursor, NAME_COLUMN_INDEX, mAlphabet);
             }
+            
             mUnknownNameText = context.getText(android.R.string.unknownName);
             switch (mMode) {
                 case MODE_PICK_POSTAL:
@@ -1249,22 +1290,20 @@
 
         @Override
         public boolean isEmpty() {
-            if (mLoading) {
-                // We don't want the empty state to show when loading.
+            if ((mMode & MODE_MASK_CREATE_NEW) == MODE_MASK_CREATE_NEW) {
+                // This mode mask adds a header and we always want it to show up, even
+                // if the list is empty, so always claim the list is not empty.
                 return false;
             } else {
-                return super.isEmpty();
+                if (mLoading) {
+                    // We don't want the empty state to show when loading.
+                    return false;
+                } else {
+                    return super.isEmpty();
+                }
             }
         }
         
-        private void getAlphabet(Context context) {
-            String alphabetString = context.getResources().getString(R.string.alphabet);
-            mAlphabet = new String[alphabetString.length()];
-            for (int i = 0; i < mAlphabet.length; i++) {
-                mAlphabet[i] = String.valueOf(alphabetString.charAt(i));
-            }
-        }
-
         @Override
         public View newView(Context context, Cursor cursor, ViewGroup parent) {
             final View view = super.newView(context, cursor, parent);
@@ -1377,8 +1416,8 @@
             if (mMode == MODE_STREQUENT) {
                 return new String[] { " " };
             } else {
-                return mAlphabet;
-            }
+                return mIndexer.getSections();
+           }
         }
         
         public int getPositionForSection(int sectionIndex) {
@@ -1395,7 +1434,7 @@
                 mIndexer = new AlphabetIndexer(cursor, NAME_COLUMN_INDEX, mAlphabet);
             }
 
-            return mIndexer.indexOf(sectionIndex);
+            return mIndexer.getPositionForSection(sectionIndex);
         }
         
         public int getSectionForPosition(int position) {
diff --git a/src/com/android/contacts/ContactsLiveFolders.java b/src/com/android/contacts/ContactsLiveFolders.java
new file mode 100644
index 0000000..8ca199a
--- /dev/null
+++ b/src/com/android/contacts/ContactsLiveFolders.java
@@ -0,0 +1,112 @@
+/*
+ * 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.Intent;
+import android.content.Context;
+import android.net.Uri;
+import android.app.Activity;
+import android.os.Bundle;
+import android.provider.LiveFolders;
+import android.provider.Contacts;
+
+public class ContactsLiveFolders {
+    public static class StarredContacts extends Activity {
+        public static final Uri CONTENT_URI =
+                Uri.parse("content://contacts/live_folders/favorites");
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+
+            final Intent intent = getIntent();
+            final String action = intent.getAction();
+
+            if (LiveFolders.ACTION_CREATE_LIVE_FOLDER.equals(action)) {
+                setResult(RESULT_OK, createLiveFolder(this, CONTENT_URI,
+                        getString(R.string.liveFolder_favorites_label),
+                        R.drawable.ic_launcher_contacts_starred));
+            } else {
+                setResult(RESULT_CANCELED);
+            }
+
+            finish();
+        }
+    }
+
+    public static class PhoneContacts extends Activity {
+        public static final Uri CONTENT_URI =
+                Uri.parse("content://contacts/live_folders/people_with_phones");
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+
+            final Intent intent = getIntent();
+            final String action = intent.getAction();
+
+            if (LiveFolders.ACTION_CREATE_LIVE_FOLDER.equals(action)) {
+                setResult(RESULT_OK, createLiveFolder(this, CONTENT_URI,
+                        getString(R.string.liveFolder_phones_label),
+                        R.drawable.ic_launcher_contacts_phones));
+            } else {
+                setResult(RESULT_CANCELED);
+            }
+
+            finish();
+        }
+    }
+
+    public static class AllContacts extends Activity {
+        public static final Uri CONTENT_URI =
+                Uri.parse("content://contacts/live_folders/people");
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+
+            final Intent intent = getIntent();
+            final String action = intent.getAction();
+
+            if (LiveFolders.ACTION_CREATE_LIVE_FOLDER.equals(action)) {
+                setResult(RESULT_OK, createLiveFolder(this, CONTENT_URI,
+                        getString(R.string.liveFolder_all_label),
+                        R.drawable.ic_launcher_contacts));
+            } else {
+                setResult(RESULT_CANCELED);
+            }
+
+            finish();
+        }
+    }
+
+    private static Intent createLiveFolder(Context context, Uri uri, String name,
+            int icon) {
+
+        final Intent intent = new Intent();
+
+        intent.setData(uri);
+        intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_BASE_INTENT, new Intent(Intent.ACTION_VIEW,
+                Contacts.People.CONTENT_URI));
+        intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_NAME, name);
+        intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_ICON,
+                Intent.ShortcutIconResource.fromContext(context, icon));
+        intent.putExtra(LiveFolders.EXTRA_LIVE_FOLDER_DISPLAY_MODE, LiveFolders.DISPLAY_MODE_LIST);
+
+        return intent;
+    }
+}
diff --git a/src/com/android/contacts/ContactsPreferenceActivity.java b/src/com/android/contacts/ContactsPreferenceActivity.java
deleted file mode 100644
index b625fe5..0000000
--- a/src/com/android/contacts/ContactsPreferenceActivity.java
+++ /dev/null
@@ -1,55 +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.os.Bundle;
-import android.preference.PreferenceActivity;
-
-public final class ContactsPreferenceActivity extends PreferenceActivity {
-    /**
-     * The type of data to display in the main contacts list. 
-     */
-    static final String PREF_DISPLAY_TYPE = "display_system_group";
-
-    /** Unknown display type. */
-    static final int DISPLAY_TYPE_UNKNOWN = -1;
-    /** Display all contacts */
-    static final int DISPLAY_TYPE_ALL = 0;
-    /** Display all contacts that have phone numbers */
-    static final int DISPLAY_TYPE_ALL_WITH_PHONES = 1;
-    /** Display a system group */
-    static final int DISPLAY_TYPE_SYSTEM_GROUP = 2;
-    /** Display a user group */
-    static final int DISPLAY_TYPE_USER_GROUP = 3;
-
-    /**
-     * Info about what to display. If {@link #PREF_DISPLAY_TYPE}
-     * is {@link #DISPLAY_TYPE_SYSTEM_GROUP} then this will be the system id.
-     * If {@link #PREF_DISPLAY_TYPE} is {@link #DISPLAY_TYPE_USER_GROUP} then this will
-     * be the group name.
-     */ 
-    static final String PREF_DISPLAY_INFO = "display_group";
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        
-        // Load the preferences from an XML resource
-        addPreferencesFromResource(R.xml.preferences);
-    }
-
-}
diff --git a/src/com/android/contacts/EditContactActivity.java b/src/com/android/contacts/EditContactActivity.java
index c301473..3a7610d 100644
--- a/src/com/android/contacts/EditContactActivity.java
+++ b/src/com/android/contacts/EditContactActivity.java
@@ -86,6 +86,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewParent;
+import android.view.inputmethod.EditorInfo;
 import android.widget.Button;
 import android.widget.CheckBox;
 import android.widget.EditText;
@@ -144,12 +145,6 @@
     public static final int MENU_ITEM_ADD = 5;
     public static final int MENU_ITEM_PHOTO = 6;
     
-    // Key listener types
-    final static int INPUT_TEXT = 1;
-    final static int INPUT_TEXT_WORDS = 2;
-    final static int INPUT_TEXT_SENTENCES = 3;
-    final static int INPUT_DIALER = 4;
-
     /** Used to represent an invalid type for a contact entry */
     private static final int INVALID_TYPE = -1;
     
@@ -171,7 +166,7 @@
     
     private EditText mNameView;
     private ImageView mPhotoImageView;
-    private Button mPhotoButton;
+    private View mPhotoButton;
     private CheckBox mSendToVoicemailCheckBox;
     private LinearLayout mLayout;
     private LayoutInflater mInflater;
@@ -258,7 +253,7 @@
                 mPhotoMenuItem.setIcon(android.R.drawable.ic_menu_delete);
             } else {
                 mPhotoMenuItem.setTitle(R.string.addPicture);
-                mPhotoMenuItem.setIcon(android.R.drawable.ic_menu_add);
+                mPhotoMenuItem.setIcon(R.drawable.ic_menu_add_picture);
             }
         }
     }
@@ -310,7 +305,7 @@
         mPhotoImageView = (ImageView) findViewById(R.id.photoImage);
         mPhotoImageView.setOnClickListener(this);
         mPhotoImageView.setVisibility(View.GONE);
-        mPhotoButton = (Button) findViewById(R.id.photoButton);
+        mPhotoButton = findViewById(R.id.photoButton);
         mPhotoButton.setOnClickListener(this);
         mSendToVoicemailCheckBox = (CheckBox) findViewById(R.id.send_to_voicemail);
 
@@ -532,7 +527,7 @@
             new AlertDialog.Builder(EditContactActivity.this)
                 .setTitle(R.string.errorDialogTitle)
                 .setMessage(R.string.photoPickerNotFoundText)
-                .setPositiveButton(R.string.okButtonText, null)
+                .setPositiveButton(android.R.string.ok, null)
                 .show();
         }
     }
@@ -607,8 +602,8 @@
                         .setTitle(R.string.deleteConfirmation_title)
                         .setIcon(android.R.drawable.ic_dialog_alert)
                         .setMessage(R.string.deleteConfirmation)
-                        .setNegativeButton(R.string.noButton, null)
-                        .setPositiveButton(R.string.yesButton, mDeleteContactDialogListener)
+                        .setNegativeButton(android.R.string.cancel, null)
+                        .setPositiveButton(android.R.string.ok, mDeleteContactDialogListener)
                         .setCancelable(false)
                         .create();
         }
@@ -713,7 +708,7 @@
                     case OTHER_ORGANIZATION:
                         entry = EditEntry.newOrganizationEntry(EditContactActivity.this,
                                 Uri.withAppendedPath(mUri, Organizations.CONTENT_DIRECTORY),
-                                ContactMethods.TYPE_WORK);
+                                Organizations.TYPE_WORK);
                         mOtherEntries.add(entry);
                         break;
 
@@ -874,7 +869,7 @@
         new AlertDialog.Builder(this)
                 .setView(label)
                 .setTitle(R.string.customLabelPickerTitle)
-                .setPositiveButton(R.string.okButtonText, new DialogInterface.OnClickListener() {
+                .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                     public void onClick(DialogInterface dialog, int which) {
                         entry.setLabel(EditContactActivity.this, ContactMethods.TYPE_CUSTOM,
                                 label.getText().toString());
@@ -885,7 +880,7 @@
                         }
                     }
                 })
-                .setNegativeButton(R.string.cancelButtonText, null)
+                .setNegativeButton(android.R.string.cancel, null)
                 .show();
     }
     
@@ -1004,10 +999,10 @@
 
         // Add the contact to the group that is being displayed in the contact list
         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
-        int displayType = prefs.getInt(ContactsPreferenceActivity.PREF_DISPLAY_TYPE,
-                ContactsPreferenceActivity.DISPLAY_TYPE_UNKNOWN);
-        if (displayType == ContactsPreferenceActivity.DISPLAY_TYPE_USER_GROUP) {
-            String displayGroup = prefs.getString(ContactsPreferenceActivity.PREF_DISPLAY_INFO,
+        int displayType = prefs.getInt(ContactsListActivity.PREF_DISPLAY_TYPE,
+                ContactsListActivity.DISPLAY_TYPE_UNKNOWN);
+        if (displayType == ContactsListActivity.DISPLAY_TYPE_USER_GROUP) {
+            String displayGroup = prefs.getString(ContactsListActivity.PREF_DISPLAY_INFO,
                     null);
             if (!TextUtils.isEmpty(displayGroup)) {
                 People.addToGroup(mResolver, ContentUris.parseId(contactUri), displayGroup);
@@ -1311,42 +1306,20 @@
         }
 
         // Email entries from extras
-        CharSequence email = extras.getCharSequence(Insert.EMAIL);
-        int emailType = extras.getInt(Insert.EMAIL_TYPE, INVALID_TYPE);
-        if (!TextUtils.isEmpty(email) && emailType == INVALID_TYPE) {
-            emailType = DEFAULT_EMAIL_TYPE;
-            mPrimaryEmailAdded = true;
-        }
+        addEmailFromExtras(extras, methodsUri, Insert.EMAIL, Insert.EMAIL_TYPE,
+                Insert.EMAIL_ISPRIMARY);
+        addEmailFromExtras(extras, methodsUri, Insert.SECONDARY_EMAIL, Insert.SECONDARY_EMAIL_TYPE,
+                null);
+        addEmailFromExtras(extras, methodsUri, Insert.TERTIARY_EMAIL, Insert.TERTIARY_EMAIL_TYPE,
+                null);
    
-        if (emailType != INVALID_TYPE) {
-            entry = EditEntry.newEmailEntry(this, null, emailType, email.toString(), methodsUri, 0);
-            entry.isPrimary = extras.getBoolean(Insert.EMAIL_ISPRIMARY);
-            mEmailEntries.add(entry);
-
-            // Keep track of which primary types have been added
-            if (entry.isPrimary) {
-                mPrimaryEmailAdded = true;
-            }
-        }
-   
-        // Phone entries from extras 
-        CharSequence phoneNumber = extras.getCharSequence(Insert.PHONE);
-        int phoneType = extras.getInt(Insert.PHONE_TYPE, INVALID_TYPE);
-        if (!TextUtils.isEmpty(phoneNumber) && phoneType == INVALID_TYPE) {
-            phoneType = DEFAULT_PHONE_TYPE;
-        }
-   
-        if (phoneType != INVALID_TYPE) {
-            entry = EditEntry.newPhoneEntry(this, null, phoneType,
-                    phoneNumber.toString(), phonesUri, 0);
-            entry.isPrimary = extras.getBoolean(Insert.PHONE_ISPRIMARY);
-            mPhoneEntries.add(entry);
-
-            // Keep track of which primary types have been added
-            if (phoneType == Phones.TYPE_MOBILE) {
-                mMobilePhoneAdded = true;
-            }
-        }
+        // Phone entries from extras
+        addPhoneFromExtras(extras, phonesUri, Insert.PHONE, Insert.PHONE_TYPE,
+                Insert.PHONE_ISPRIMARY);
+        addPhoneFromExtras(extras, phonesUri, Insert.SECONDARY_PHONE, Insert.SECONDARY_PHONE_TYPE,
+                null);
+        addPhoneFromExtras(extras, phonesUri, Insert.TERTIARY_PHONE, Insert.TERTIARY_PHONE_TYPE,
+                null);
 
         // IM entries from extras
         CharSequence imHandle = extras.getCharSequence(Insert.IM_HANDLE);
@@ -1368,6 +1341,49 @@
         }
     }
 
+    private void addEmailFromExtras(Bundle extras, Uri methodsUri, String emailField,
+            String typeField, String primaryField) {
+        CharSequence email = extras.getCharSequence(emailField);
+        int emailType = extras.getInt(typeField, INVALID_TYPE);
+        if (!TextUtils.isEmpty(email) && emailType == INVALID_TYPE) {
+            emailType = DEFAULT_EMAIL_TYPE;
+            mPrimaryEmailAdded = true;
+        }
+
+        if (emailType != INVALID_TYPE) {
+            EditEntry entry = EditEntry.newEmailEntry(this, null, emailType, email.toString(),
+                    methodsUri, 0);
+            entry.isPrimary = (primaryField == null) ? false : extras.getBoolean(primaryField);
+            mEmailEntries.add(entry);
+
+            // Keep track of which primary types have been added
+            if (entry.isPrimary) {
+                mPrimaryEmailAdded = true;
+            }
+        }
+    }
+
+    private void addPhoneFromExtras(Bundle extras, Uri phonesUri, String phoneField,
+            String typeField, String primaryField) {
+        CharSequence phoneNumber = extras.getCharSequence(phoneField);
+        int phoneType = extras.getInt(typeField, INVALID_TYPE);
+        if (!TextUtils.isEmpty(phoneNumber) && phoneType == INVALID_TYPE) {
+            phoneType = DEFAULT_PHONE_TYPE;
+        }
+
+        if (phoneType != INVALID_TYPE) {
+            EditEntry entry = EditEntry.newPhoneEntry(this, null, phoneType,
+                    phoneNumber.toString(), phonesUri, 0);
+            entry.isPrimary = (primaryField == null) ? false : extras.getBoolean(primaryField);
+            mPhoneEntries.add(entry);
+
+            // Keep track of which primary types have been added
+            if (phoneType == Phones.TYPE_MOBILE) {
+                mMobilePhoneAdded = true;
+            }
+        }
+    }
+
     /**
      * Removes all existing views, builds new ones for all the entries, and adds them.
      */
@@ -1475,42 +1491,20 @@
                 data2.setLines(entry.lines);
                 data2.setMaxLines(entry.maxLines);
             }
-        } else if (entry.lines >= 0) {
-            data.setSingleLine();
-            if (data2 != null) {
-                data2.setSingleLine();
-            }
         }
-        switch (entry.keyListener) {
-            case INPUT_TEXT:
-                data.setKeyListener(TextKeyListener.getInstance());
-                if (data2 != null) {
-                    data2.setKeyListener(TextKeyListener.getInstance());
-                }
-                break;
-                
-            case INPUT_TEXT_WORDS:
-                data.setKeyListener(TextKeyListener.getInstance(true, Capitalize.WORDS));
-                if (data2 != null) {
-                    data2.setKeyListener(TextKeyListener.getInstance(true, Capitalize.WORDS));
-                }
-                break;
-                
-            case INPUT_TEXT_SENTENCES:
-                data.setKeyListener(TextKeyListener.getInstance(true, Capitalize.SENTENCES));
-                if (data2 != null) {
-                    data2.setKeyListener(TextKeyListener.getInstance(true, Capitalize.SENTENCES));
-                }
-                break;
-                
-            case INPUT_DIALER:
-                data.setKeyListener(DialerKeyListener.getInstance());
+        int contentType = entry.contentType;
+        if (contentType != EditorInfo.TYPE_NULL) {
+            data.setInputType(contentType);
+            if (data2 != null) {
+                data2.setInputType(contentType);
+            }
+            if ((contentType&EditorInfo.TYPE_MASK_CLASS)
+                    == EditorInfo.TYPE_CLASS_PHONE) {
                 data.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
                 if (data2 != null) {
-                    data2.setKeyListener(DialerKeyListener.getInstance());
                     data2.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
                 }
-                break;
+            }
         }
 
         // Hook up the delete button
@@ -1569,7 +1563,7 @@
         public String column;
         public String contentDirectory;
         public String data2;
-        public int keyListener;
+        public int contentType;
         public int type;
         /**
          * If 0 or 1, setSingleLine will be called. If negative, setSingleLine
@@ -1614,7 +1608,7 @@
             parcel.writeString(column);
             parcel.writeString(contentDirectory);
             parcel.writeString(data2);
-            parcel.writeInt(keyListener);
+            parcel.writeInt(contentType);
             parcel.writeInt(type);
             parcel.writeInt(lines);
             parcel.writeInt(isPrimary ? 1 : 0);
@@ -1637,7 +1631,7 @@
                 entry.column = in.readString();
                 entry.contentDirectory = in.readString();
                 entry.data2 = in.readString();
-                entry.keyListener = in.readInt();
+                entry.contentType = in.readInt();
                 entry.type = in.readInt();
                 entry.lines = in.readInt();
                 entry.isPrimary = in.readInt() == 1;
@@ -1834,7 +1828,8 @@
             entry.column = Organizations.COMPANY;
             entry.contentDirectory = Organizations.CONTENT_DIRECTORY;
             entry.kind = Contacts.KIND_ORGANIZATION;
-            entry.keyListener = INPUT_TEXT_WORDS;
+            entry.contentType = EditorInfo.TYPE_CLASS_TEXT
+                    | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
             return entry;
         }
 
@@ -1853,7 +1848,9 @@
             entry.lines = 2;
             entry.id = 0;
             entry.kind = KIND_CONTACT;
-            entry.keyListener = INPUT_TEXT_SENTENCES;
+            entry.contentType = EditorInfo.TYPE_CLASS_TEXT
+                    | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES
+                    | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
             entry.isStaticLabel = true;
             return entry;
         }
@@ -1894,7 +1891,7 @@
             entry.column = People.Phones.NUMBER;
             entry.contentDirectory = People.Phones.CONTENT_DIRECTORY;
             entry.kind = Contacts.KIND_PHONE;
-            entry.keyListener = INPUT_DIALER;
+            entry.contentType = EditorInfo.TYPE_CLASS_PHONE;
             return entry;
         }
 
@@ -1917,7 +1914,8 @@
             entry.column = ContactMethods.DATA;
             entry.contentDirectory = People.ContactMethods.CONTENT_DIRECTORY;
             entry.kind = Contacts.KIND_EMAIL;
-            entry.keyListener = INPUT_TEXT;
+            entry.contentType = EditorInfo.TYPE_CLASS_TEXT
+                    | EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
             return entry;
         }
 
@@ -1946,7 +1944,10 @@
             entry.column = ContactMethods.DATA;
             entry.contentDirectory = People.ContactMethods.CONTENT_DIRECTORY;
             entry.kind = Contacts.KIND_POSTAL;
-            entry.keyListener = INPUT_TEXT_WORDS;
+            entry.contentType = EditorInfo.TYPE_CLASS_TEXT
+                    | EditorInfo.TYPE_TEXT_VARIATION_POSTAL_ADDRESS
+                    | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS
+                    | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
             entry.maxLines = 4;
             entry.lines = 2;
             return entry;
@@ -1969,7 +1970,7 @@
             entry.column = ContactMethods.DATA;
             entry.contentDirectory = People.ContactMethods.CONTENT_DIRECTORY;
             entry.kind = Contacts.KIND_IM;
-            entry.keyListener = INPUT_TEXT;
+            entry.contentType = EditorInfo.TYPE_CLASS_TEXT;
             return entry;
         }
     }
diff --git a/src/com/android/contacts/FastScrollView.java b/src/com/android/contacts/FastScrollView.java
deleted file mode 100644
index f45e947..0000000
--- a/src/com/android/contacts/FastScrollView.java
+++ /dev/null
@@ -1,450 +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.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup.OnHierarchyChangeListener;
-import android.widget.AbsListView;
-import android.widget.Adapter;
-import android.widget.BaseAdapter;
-import android.widget.FrameLayout;
-import android.widget.HeaderViewListAdapter;
-import android.widget.ListView;
-import android.widget.AbsListView.OnScrollListener;
-
-/**
- * FastScrollView is meant for embedding {@link ListView}s that contain a large number of 
- * items that can be indexed in some fashion. It displays a special scroll bar that allows jumping
- * quickly to indexed sections of the list in touch-mode. Only one child can be added to this 
- * view group and it must be a {@link ListView}, with an adapter that is derived from 
- * {@link BaseAdapter}.
- */
-public class FastScrollView extends FrameLayout 
-        implements OnScrollListener, OnHierarchyChangeListener {
-
-    private Drawable mCurrentThumb;
-    private Drawable mOverlayDrawable;
-
-    private int mThumbH;
-    private int mThumbW;
-    private int mThumbY;
-
-    private RectF mOverlayPos;
-    
-    // Hard coding these for now
-    private int mOverlaySize = 104;
-
-    private boolean mDragging;
-    private ListView mList;
-    private boolean mScrollCompleted;
-    private boolean mThumbVisible;
-    private int mVisibleItem;
-    private Paint mPaint;
-    private int mListOffset;
-    
-    private Object [] mSections;
-    private String mSectionText;
-    private boolean mDrawOverlay;
-    private ScrollFade mScrollFade;
-    
-    private Handler mHandler = new Handler();
-    
-    private BaseAdapter mListAdapter;
-
-    private boolean mChangedBounds;
-
-    interface SectionIndexer {
-        Object[] getSections();
-        
-        int getPositionForSection(int section);
-        
-        int getSectionForPosition(int position);
-    }
-    
-    public FastScrollView(Context context) {
-        super(context);
-
-        init(context);
-    }
-
-
-    public FastScrollView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        
-        init(context);
-    }
-
-    public FastScrollView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-
-        init(context);
-    }
-
-    private void useThumbDrawable(Drawable drawable) {
-        mCurrentThumb = drawable;
-        mThumbW = 64; //mCurrentThumb.getIntrinsicWidth();
-        mThumbH = 52; //mCurrentThumb.getIntrinsicHeight();
-        mChangedBounds = true;
-    }
-
-    private void init(Context context) {
-        // Get both the scrollbar states drawables
-        final Resources res = context.getResources();
-        useThumbDrawable(res.getDrawable(
-                com.android.internal.R.drawable.scrollbar_handle_accelerated_anim2));
-        
-        mOverlayDrawable = res.getDrawable(R.drawable.dialog_full_dark);
-        
-        mScrollCompleted = true;
-        setWillNotDraw(false);
-        
-        // Need to know when the ListView is added
-        setOnHierarchyChangeListener(this);
-        
-        mOverlayPos = new RectF();
-        mScrollFade = new ScrollFade();
-        mPaint = new Paint();
-        mPaint.setAntiAlias(true);
-        mPaint.setTextAlign(Paint.Align.CENTER);
-        mPaint.setTextSize(mOverlaySize / 2);
-        mPaint.setColor(0xFFFFFFFF);
-        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
-    }
-    
-    private void removeThumb() {
-        
-        mThumbVisible = false;
-        // Draw one last time to remove thumb
-        invalidate();
-    }
-    
-    @Override
-    public void draw(Canvas canvas) {
-        super.draw(canvas);
-        
-        if (!mThumbVisible) {
-            // No need to draw the rest
-            return;
-        }
-
-        final int y = mThumbY;
-        final int viewWidth = getWidth();
-        final FastScrollView.ScrollFade scrollFade = mScrollFade;
-
-        int alpha = -1;
-        if (scrollFade.mStarted) {
-            alpha = scrollFade.getAlpha();
-            if (alpha < ScrollFade.ALPHA_MAX / 2) {
-                mCurrentThumb.setAlpha(alpha * 2);
-            }
-            int left = viewWidth - (mThumbW * alpha) / ScrollFade.ALPHA_MAX;
-            mCurrentThumb.setBounds(left, 0, viewWidth, mThumbH);
-            mChangedBounds = true;
-        }
-
-        canvas.translate(0, y);
-        mCurrentThumb.draw(canvas);
-        canvas.translate(0, -y);
-
-        // If user is dragging the scroll bar, draw the alphabet overlay
-        if (mDragging && mDrawOverlay) {
-            mOverlayDrawable.draw(canvas);
-            final Paint paint = mPaint;
-            float descent = paint.descent();
-            final RectF rectF = mOverlayPos;
-            canvas.drawText(mSectionText, (int) (rectF.left + rectF.right) / 2,
-                    (int) (rectF.bottom + rectF.top) / 2 + mOverlaySize / 4 - descent, paint);
-        } else if (alpha == 0) {
-            scrollFade.mStarted = false;
-            removeThumb();
-        } else {
-            invalidate(viewWidth - mThumbW, y, viewWidth, y + mThumbH);            
-        }
-    }
-
-    @Override
-    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
-        super.onSizeChanged(w, h, oldw, oldh);
-        if (mCurrentThumb != null) {
-            mCurrentThumb.setBounds(w - mThumbW, 0, w, mThumbH);
-        }
-        final RectF pos = mOverlayPos;
-        pos.left = (w - mOverlaySize) / 2;
-        pos.right = pos.left + mOverlaySize;
-        pos.top = h / 10; // 10% from top
-        pos.bottom = pos.top + mOverlaySize;
-        mOverlayDrawable.setBounds((int) pos.left, (int) pos.top,
-                (int) pos.right, (int) pos.bottom);
-    }
-    
-    public void onScrollStateChanged(AbsListView view, int scrollState) {
-    }
-    
-    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, 
-            int totalItemCount) {
-        
-        if (totalItemCount - visibleItemCount > 0 && !mDragging) {
-            mThumbY = ((getHeight() - mThumbH) * firstVisibleItem) / (totalItemCount - visibleItemCount);
-            if (mChangedBounds) {
-                final int viewWidth = getWidth();
-                mCurrentThumb.setBounds(viewWidth - mThumbW, 0, viewWidth, mThumbH);
-                mChangedBounds = false;
-            }
-        }
-        mScrollCompleted = true;
-        if (firstVisibleItem == mVisibleItem) {
-            return;
-        }
-        mVisibleItem = firstVisibleItem;
-        if (!mThumbVisible || mScrollFade.mStarted) {
-            mThumbVisible = true;
-            mCurrentThumb.setAlpha(ScrollFade.ALPHA_MAX);
-        }
-        mHandler.removeCallbacks(mScrollFade);
-        mScrollFade.mStarted = false;
-        if (!mDragging) {
-            mHandler.postDelayed(mScrollFade, 1500);
-        }
-    }
-
-    
-    private void getSections() {
-        Adapter adapter = mList.getAdapter();
-        if (adapter instanceof HeaderViewListAdapter) {
-            mListOffset = ((HeaderViewListAdapter)adapter).getHeadersCount();
-            adapter = ((HeaderViewListAdapter)adapter).getWrappedAdapter();
-        }
-        if (adapter instanceof SectionIndexer) {
-            mListAdapter = (BaseAdapter) adapter;
-            mSections = ((SectionIndexer) mListAdapter).getSections();
-        }
-    }
-    
-    public void onChildViewAdded(View parent, View child) {
-        if (child instanceof ListView) {
-            mList = (ListView)child;
-            
-            mList.setOnScrollListener(this);
-            getSections();
-        }
-    }
-
-    public void onChildViewRemoved(View parent, View child) {
-        if (child == mList) {
-            mList = null;
-            mListAdapter = null;
-            mSections = null;
-        }
-    }
-    
-    @Override
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        if (mThumbVisible && ev.getAction() == MotionEvent.ACTION_DOWN) {
-            if (ev.getX() > getWidth() - mThumbW && ev.getY() >= mThumbY &&
-                    ev.getY() <= mThumbY + mThumbH) {
-                mDragging = true;
-                return true;
-            }            
-        }
-        return false;
-    }
-
-    private void scrollTo(float position) {
-        int count = mList.getCount();
-        mScrollCompleted = false;
-        final Object[] sections = mSections;
-        int sectionIndex;
-        if (sections != null && sections.length > 1) {
-            final int nSections = sections.length;
-            int section = (int) (position * nSections);
-            if (section >= nSections) {
-                section = nSections - 1;
-            }
-            sectionIndex = section;
-            final SectionIndexer baseAdapter = (SectionIndexer) mListAdapter;
-            int index = baseAdapter.getPositionForSection(section);
-            
-            // Given the expected section and index, the following code will
-            // try to account for missing sections (no names starting with..)
-            // It will compute the scroll space of surrounding empty sections
-            // and interpolate the currently visible letter's range across the
-            // available space, so that there is always some list movement while
-            // the user moves the thumb.
-            int nextIndex = count;
-            int prevIndex = index;
-            int prevSection = section;
-            int nextSection = section + 1;
-            // Assume the next section is unique
-            if (section < nSections - 1) {
-                nextIndex = baseAdapter.getPositionForSection(section + 1);
-            }
-            
-            // Find the previous index if we're slicing the previous section
-            if (nextIndex == index) {
-                // Non-existent letter
-                while (section > 0) {
-                    section--;
-                     prevIndex = baseAdapter.getPositionForSection(section);
-                     if (prevIndex != index) {
-                         prevSection = section;
-                         sectionIndex = section;
-                         break;
-                     }
-                }
-            }
-            // Find the next index, in case the assumed next index is not
-            // unique. For instance, if there is no P, then request for P's 
-            // position actually returns Q's. So we need to look ahead to make
-            // sure that there is really a Q at Q's position. If not, move 
-            // further down...
-            int nextNextSection = nextSection + 1;
-            while (nextNextSection < nSections &&
-                    baseAdapter.getPositionForSection(nextNextSection) == nextIndex) {
-                nextNextSection++;
-                nextSection++;
-            }
-            // Compute the beginning and ending scroll range percentage of the
-            // currently visible letter. This could be equal to or greater than
-            // (1 / nSections). 
-            float fPrev = (float) prevSection / nSections;
-            float fNext = (float) nextSection / nSections;
-            index = prevIndex + (int) ((nextIndex - prevIndex) * (position - fPrev) 
-                    / (fNext - fPrev));
-            // Don't overflow
-            if (index > count - 1) index = count - 1;
-            
-            mList.setSelectionFromTop(index + mListOffset, 0);
-        } else {
-            int index = (int) (position * count);
-            mList.setSelectionFromTop(index + mListOffset, 0);
-            sectionIndex = -1;
-        }
-
-        if (sectionIndex >= 0) {
-            String text = mSectionText = sections[sectionIndex].toString();
-            mDrawOverlay = (text.length() != 1 || text.charAt(0) != ' ') &&
-                    sectionIndex < sections.length;
-        } else {
-            mDrawOverlay = false;
-        }
-    }
-
-    private void cancelFling() {
-        // Cancel the list fling
-        MotionEvent cancelFling = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0);
-        mList.onTouchEvent(cancelFling);
-        cancelFling.recycle();
-    }
-    
-    @Override
-    public boolean onTouchEvent(MotionEvent me) {
-        if (me.getAction() == MotionEvent.ACTION_DOWN) {
-            if (me.getX() > getWidth() - mThumbW
-                    && me.getY() >= mThumbY 
-                    && me.getY() <= mThumbY + mThumbH) {
-                
-                mDragging = true;
-                if (mListAdapter == null && mList != null) {
-                    getSections();
-                }
-
-                cancelFling();
-                return true;
-            }
-        } else if (me.getAction() == MotionEvent.ACTION_UP) {
-            if (mDragging) {
-                mDragging = false;
-                final Handler handler = mHandler;
-                handler.removeCallbacks(mScrollFade);
-                handler.postDelayed(mScrollFade, 1000);
-                return true;
-            }
-        } else if (me.getAction() == MotionEvent.ACTION_MOVE) {
-            if (mDragging) {
-                final int viewHeight = getHeight();
-                mThumbY = (int) me.getY() - mThumbH + 10;
-                if (mThumbY < 0) {
-                    mThumbY = 0;
-                } else if (mThumbY + mThumbH > viewHeight) {
-                    mThumbY = viewHeight - mThumbH;
-                }
-                // If the previous scrollTo is still pending
-                if (mScrollCompleted) {
-                    scrollTo((float) mThumbY / (viewHeight - mThumbH));
-                }
-                return true;
-            }
-        }
-        
-        return super.onTouchEvent(me);
-    }
-    
-    public class ScrollFade implements Runnable {
-        
-        long mStartTime;
-        long mFadeDuration;
-        boolean mStarted;
-        static final int ALPHA_MAX = 255;
-        static final long FADE_DURATION = 200;
-        
-        void startFade() {
-            mFadeDuration = FADE_DURATION;
-            mStartTime = SystemClock.uptimeMillis();
-            mStarted = true;
-        }
-        
-        int getAlpha() {
-            if (!mStarted) {
-                return ALPHA_MAX;
-            }
-            int alpha;
-            long now = SystemClock.uptimeMillis();
-            if (now > mStartTime + mFadeDuration) {
-                alpha = 0;
-            } else {
-                alpha = (int) (ALPHA_MAX - ((now - mStartTime) * ALPHA_MAX) / mFadeDuration); 
-            }
-            return alpha;
-        }
-        
-        public void run() {
-            if (!mStarted) {
-                startFade();
-                invalidate();
-            }
-            
-            if (getAlpha() > 0) {
-                final int y = mThumbY;
-                final int viewWidth = getWidth();
-                invalidate(viewWidth - mThumbW, y, viewWidth, y + mThumbH);
-            } else {
-                mStarted = false;
-                removeThumb();
-            }
-        }
-    }
-}
diff --git a/src/com/android/contacts/RecentCallsListActivity.java b/src/com/android/contacts/RecentCallsListActivity.java
index 32ecd97..550a385 100644
--- a/src/com/android/contacts/RecentCallsListActivity.java
+++ b/src/com/android/contacts/RecentCallsListActivity.java
@@ -32,13 +32,13 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
-import android.pim.DateUtils;
 import android.provider.CallLog.Calls;
 import android.provider.Contacts.People;
 import android.provider.Contacts.Phones;
 import android.provider.Contacts.Intents.Insert;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
+import android.text.format.DateUtils;
 import android.util.Log;
 import android.view.ContextMenu;
 import android.view.KeyEvent;
@@ -60,14 +60,15 @@
 
 import java.util.HashMap;
 import java.util.LinkedList;
+import java.lang.ref.WeakReference;
 
 /**
  * Displays a list of call log entries.
  */
-public class RecentCallsListActivity extends ListActivity 
+public class RecentCallsListActivity extends ListActivity
         implements View.OnCreateContextMenuListener {
     private static final String TAG = "RecentCallsList";
-    
+
     /** The projection to use when querying the call log table */
     static final String[] CALL_LOG_PROJECTION = new String[] {
             Calls._ID,
@@ -79,7 +80,7 @@
             Calls.CACHED_NUMBER_TYPE,
             Calls.CACHED_NUMBER_LABEL
     };
-    
+
     static final int ID_COLUMN_INDEX = 0;
     static final int NUMBER_COLUMN_INDEX = 1;
     static final int DATE_COLUMN_INDEX = 2;
@@ -97,7 +98,7 @@
             Phones.LABEL,
             Phones.NUMBER
     };
-    
+
     static final int PERSON_ID_COLUMN_INDEX = 0;
     static final int NAME_COLUMN_INDEX = 1;
     static final int PHONE_TYPE_COLUMN_INDEX = 2;
@@ -107,20 +108,20 @@
     private static final int MENU_ITEM_DELETE = 1;
     private static final int MENU_ITEM_DELETE_ALL = 2;
     private static final int MENU_ITEM_VIEW_CONTACTS = 3;
-    
+
     private static final int QUERY_TOKEN = 53;
     private static final int UPDATE_TOKEN = 54;
 
     private RecentCallsAdapter mAdapter;
     private QueryHandler mQueryHandler;
     private String mVoiceMailNumber;
-    
+
     private CharSequence[] mLabelArray;
-    
+
     private Drawable mDrawableIncoming;
     private Drawable mDrawableOutgoing;
-    private Drawable mDrawableMissed;    
-    
+    private Drawable mDrawableMissed;
+
     private static final class ContactInfo {
         public long personId;
         public String name;
@@ -137,8 +138,8 @@
         TextView durationView;
         TextView dateView;
         ImageView iconView;
-    }    
-    
+    }
+
     private static final class CallerInfoQuery {
         String number;
         int position;
@@ -148,25 +149,26 @@
     }
 
     /** Adapter class to fill in data for the Call Log */
-    private final class RecentCallsAdapter extends ResourceCursorAdapter 
+    private final class RecentCallsAdapter extends ResourceCursorAdapter
             implements Runnable, ViewTreeObserver.OnPreDrawListener {
         HashMap<String,ContactInfo> mContactInfo;
-        private LinkedList<CallerInfoQuery> mRequests;
-        private boolean mDone;
+        private final LinkedList<CallerInfoQuery> mRequests;
+        private volatile boolean mDone;
         private boolean mLoading = true;
         ViewTreeObserver.OnPreDrawListener mPreDrawListener;
         private static final int REDRAW = 1;
         private static final int START_THREAD = 2;
         private boolean mFirst;
-        
+        private Thread mCallerIdThread;
+
         public boolean onPreDraw() {
             if (mFirst) {
                 mHandler.sendEmptyMessageDelayed(START_THREAD, 1000);
                 mFirst = false;
             }
-            return true;            
+            return true;
         }
-        
+
         private Handler mHandler = new Handler() {
             @Override
             public void handleMessage(Message msg) {
@@ -180,7 +182,7 @@
                 }
             }
         };
-        
+
         public RecentCallsAdapter() {
             super(RecentCallsListActivity.this, R.layout.recent_calls_list_item, null);
 
@@ -192,7 +194,7 @@
         void setLoading(boolean loading) {
             mLoading = loading;
         }
-        
+
         @Override
         public boolean isEmpty() {
             if (mLoading) {
@@ -209,13 +211,14 @@
 
         public void startRequestProcessing() {
             mDone = false;
-            Thread callerIdThread = new Thread(this);
-            callerIdThread.setPriority(Thread.MIN_PRIORITY);
-            callerIdThread.start();
+            mCallerIdThread = new Thread(this);
+            mCallerIdThread.setPriority(Thread.MIN_PRIORITY);
+            mCallerIdThread.start();
         }
 
         public void stopRequestProcessing() {
             mDone = true;
+            if (mCallerIdThread != null) mCallerIdThread.interrupt();
         }
 
         public void clearCache() {
@@ -226,7 +229,7 @@
 
         private void updateCallLog(CallerInfoQuery ciq, ContactInfo ci) {
             // Check if they are different. If not, don't update.
-            if (TextUtils.equals(ciq.name, ci.name) 
+            if (TextUtils.equals(ciq.name, ci.name)
                     && TextUtils.equals(ciq.numberLabel, ci.label)
                     && ciq.numberType == ci.type) {
                 return;
@@ -236,10 +239,10 @@
             values.put(Calls.CACHED_NUMBER_TYPE, ci.type);
             values.put(Calls.CACHED_NUMBER_LABEL, ci.label);
             RecentCallsListActivity.this.getContentResolver().update(
-                    Calls.CONTENT_URI, 
+                    Calls.CONTENT_URI,
                     values, Calls.NUMBER + "='" + ciq.number + "'", null);
         }
-        
+
         private void enqueueRequest(String number, int position,
                 String name, int numberType, String numberLabel) {
             CallerInfoQuery ciq = new CallerInfoQuery();
@@ -253,7 +256,7 @@
                 mRequests.notifyAll();
             }
         }
-        
+
         private void queryContactInfo(CallerInfoQuery ciq) {
             // First check if there was a prior request for the same number
             // that was already satisfied
@@ -265,9 +268,9 @@
                     }
                 }
             } else {
-                Cursor phonesCursor = 
+                Cursor phonesCursor =
                     RecentCallsListActivity.this.getContentResolver().query(
-                            Uri.withAppendedPath(Phones.CONTENT_FILTER_URL, 
+                            Uri.withAppendedPath(Phones.CONTENT_FILTER_URL,
                                     ciq.number),
                     PHONES_PROJECTION, null, null, null);
                 if (phonesCursor != null) {
@@ -278,7 +281,7 @@
                         info.type = phonesCursor.getInt(PHONE_TYPE_COLUMN_INDEX);
                         info.label = phonesCursor.getString(LABEL_COLUMN_INDEX);
                         info.number = phonesCursor.getString(MATCHED_NUMBER_COLUMN_INDEX);
-                        
+
                         mContactInfo.put(ciq.number, info);
                         // Inform list to update this item, if in view
                         synchronized (mRequests) {
@@ -310,7 +313,7 @@
                             mRequests.wait(1000);
                         } catch (InterruptedException ie) {
                             // Ignore and continue processing requests
-                        }                        
+                        }
                     }
                 }
                 if (ciq != null) {
@@ -318,11 +321,11 @@
                 }
             }
         }
-        
+
         @Override
         public View newView(Context context, Cursor cursor, ViewGroup parent) {
             View view = super.newView(context, cursor, parent);
-            
+
             // Get the views to bind to
             RecentCallsListItemViews views = new RecentCallsListItemViews();
             views.line1View = (TextView) view.findViewById(R.id.line1);
@@ -336,7 +339,7 @@
             return view;
         }
 
-        
+
         @Override
         public void bindView(View view, Context context, Cursor c) {
             final RecentCallsListItemViews views = (RecentCallsListItemViews) view.getTag();
@@ -345,7 +348,7 @@
             String callerName = c.getString(CALLER_NAME_COLUMN_INDEX);
             int callerNumberType = c.getInt(CALLER_NUMBERTYPE_COLUMN_INDEX);
             String callerNumberLabel = c.getString(CALLER_NUMBERLABEL_COLUMN_INDEX);
-            
+
             // Lookup contacts with this number
             ContactInfo info = mContactInfo.get(number);
             if (info == null) {
@@ -359,11 +362,11 @@
                 // Check if any data is different from the data cached in the
                 // calls db. If so, queue the request so that we can update
                 // the calls db.
-                if (!TextUtils.equals(info.name, callerName) 
+                if (!TextUtils.equals(info.name, callerName)
                         || info.type != callerNumberType
                         || !TextUtils.equals(info.label, callerNumberLabel)) {
                     // Something is amiss, so sync up.
-                    enqueueRequest(number, c.getPosition(), 
+                    enqueueRequest(number, c.getPosition(),
                             callerName, callerNumberType, callerNumberLabel);
                 }
             }
@@ -382,7 +385,7 @@
             // Set the text lines
             if (!TextUtils.isEmpty(name)) {
                 views.line1View.setText(name);
-                CharSequence numberLabel = Phones.getDisplayLabel(context, ntype, label, 
+                CharSequence numberLabel = Phones.getDisplayLabel(context, ntype, label,
                         mLabelArray);
                 if (!TextUtils.isEmpty(numberLabel)) {
                     views.line2View.setText(numberLabel);
@@ -447,7 +450,7 @@
                     views.iconView.setImageDrawable(mDrawableMissed);
                     break;
             }
-            // Listen for the first draw 
+            // Listen for the first draw
             if (mPreDrawListener == null) {
                 mFirst = true;
                 mPreDrawListener = this;
@@ -455,17 +458,23 @@
             }
         }
     }
-    
-    private final class QueryHandler extends AsyncQueryHandler {
+
+    private static final class QueryHandler extends AsyncQueryHandler {
+        private final WeakReference<RecentCallsListActivity> mActivity;
+
         public QueryHandler(Context context) {
             super(context.getContentResolver());
+            mActivity = new WeakReference<RecentCallsListActivity>(
+                    (RecentCallsListActivity) context);
         }
 
         @Override
         protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
-            if (!isFinishing()) {
-                mAdapter.setLoading(false);
-                mAdapter.changeCursor(cursor);    
+            final RecentCallsListActivity activity = mActivity.get();
+            if (activity != null && !activity.isFinishing()) {
+                final RecentCallsListActivity.RecentCallsAdapter callsAdapter = activity.mAdapter;
+                callsAdapter.setLoading(false);
+                callsAdapter.changeCursor(cursor);
             } else {
                 cursor.close();
             }
@@ -477,12 +486,12 @@
         super.onCreate(state);
 
         setContentView(R.layout.recent_calls);
-        
+
         mDrawableIncoming = getResources().getDrawable(android.R.drawable.sym_call_incoming);
         mDrawableOutgoing = getResources().getDrawable(android.R.drawable.sym_call_outgoing);
         mDrawableMissed = getResources().getDrawable(android.R.drawable.sym_call_missed);
         mLabelArray = getResources().getTextArray(com.android.internal.R.array.phoneTypes);
-        
+
         // Typing here goes to the dialer
         setDefaultKeyMode(DEFAULT_KEYS_DIALER);
 
@@ -505,21 +514,25 @@
 
         startQuery();
         resetNewCallsFlag();
-        
+
         super.onResume();
         try {
-            ITelephony.Stub.asInterface(ServiceManager.getService("phone"))
-                    .cancelMissedCallsNotification();
+            ITelephony iTelephony = ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
+            if (iTelephony != null) {
+                iTelephony.cancelMissedCallsNotification();
+            } else {
+                Log.w(TAG, "Telephony service is null, can't call cancelMissedCallsNotification");
+            }
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to clear missed calls notification due to remote excetpion");
         }
         mAdapter.mPreDrawListener = null; // Let it restart the thread after next draw
     }
-    
+
     @Override
     protected void onPause() {
         super.onPause();
-        
+
         // Kill the requests thread
         mAdapter.stopRequestProcessing();
     }
@@ -527,6 +540,7 @@
     @Override
     protected void onDestroy() {
         super.onDestroy();
+        mAdapter.stopRequestProcessing();
         Cursor cursor = mAdapter.getCursor();
         if (cursor != null && !cursor.isClosed()) {
             cursor.close();
@@ -541,19 +555,19 @@
 
         ContentValues values = new ContentValues(1);
         values.put(Calls.NEW, "0");
-        mQueryHandler.startUpdate(UPDATE_TOKEN, null, Calls.CONTENT_URI, 
+        mQueryHandler.startUpdate(UPDATE_TOKEN, null, Calls.CONTENT_URI,
                 values, where.toString(), null);
     }
 
     private void startQuery() {
         mAdapter.setLoading(true);
-        
+
         // Cancel any pending queries
         mQueryHandler.cancelOperation(QUERY_TOKEN);
-        mQueryHandler.startQuery(QUERY_TOKEN, null, Calls.CONTENT_URI, 
+        mQueryHandler.startQuery(QUERY_TOKEN, null, Calls.CONTENT_URI,
                 CALL_LOG_PROJECTION, null, null, Calls.DEFAULT_SORT_ORDER);
     }
-    
+
     @Override
     public boolean onCreateOptionsMenu(Menu menu) {
         menu.add(0, MENU_ITEM_DELETE_ALL, 0, R.string.recentCalls_deleteAll)
@@ -589,7 +603,7 @@
         }
 
         ContactInfo info = mAdapter.getContactInfo(number);
-        boolean contactInfoPresent = (info != null && info != ContactInfo.EMPTY); 
+        boolean contactInfoPresent = (info != null && info != ContactInfo.EMPTY);
         if (contactInfoPresent) {
             menu.setHeaderTitle(info.name);
         } else {
@@ -631,7 +645,7 @@
             case MENU_ITEM_DELETE_ALL: {
                 getContentResolver().delete(Calls.CONTENT_URI, null, null);
                 //TODO The change notification should do this automatically, but it isn't working
-                // right now. Remove this when the change notification is working properly. 
+                // right now. Remove this when the change notification is working properly.
                 startQuery();
                 return true;
             }
@@ -704,17 +718,17 @@
                 } catch (RemoteException re) {
                     // Fall through and try to call the contact
                 }
-                
+
                 callEntry(getListView().getSelectedItemPosition());
                 return true;
         }
         return super.onKeyUp(keyCode, event);
     }
-    
+
     /*
      * Get the number from the Contacts, if available, since sometimes
      * the number provided by caller id may not be formatted properly
-     * depending on the carrier (roaming) in use at the time of the 
+     * depending on the carrier (roaming) in use at the time of the
      * incoming call.
      * Logic : If the caller-id number starts with a "+", use it
      *         Else if the number in the contacts starts with a "+", use that one
@@ -728,9 +742,9 @@
             matchingNumber = ci.number;
         } else {
             try {
-                Cursor phonesCursor = 
+                Cursor phonesCursor =
                     RecentCallsListActivity.this.getContentResolver().query(
-                            Uri.withAppendedPath(Phones.CONTENT_FILTER_URL, 
+                            Uri.withAppendedPath(Phones.CONTENT_FILTER_URL,
                                     number),
                     PHONES_PROJECTION, null, null, null);
                 if (phonesCursor != null) {
@@ -743,14 +757,14 @@
                 // Use the number from the call log
             }
         }
-        if (!TextUtils.isEmpty(matchingNumber) && 
-                (matchingNumber.startsWith("+") 
+        if (!TextUtils.isEmpty(matchingNumber) &&
+                (matchingNumber.startsWith("+")
                         || matchingNumber.length() > number.length())) {
             number = matchingNumber;
         }
         return number;
     }
-    
+
     private void callEntry(int position) {
         if (position < 0) {
             // In touch mode you may often not have something selected, so
@@ -769,8 +783,8 @@
             }
 
             int callType = cursor.getInt(CALL_TYPE_COLUMN_INDEX);
-            if (!number.startsWith("+") && 
-                    (callType == Calls.INCOMING_TYPE 
+            if (!number.startsWith("+") &&
+                    (callType == Calls.INCOMING_TYPE
                             || callType == Calls.MISSED_TYPE)) {
                 // If the caller-id matches a contact with a better qualified number, use it
                 number = getBetterNumberFromContacts(number);
diff --git a/src/com/android/contacts/SpecialCharSequenceMgr.java b/src/com/android/contacts/SpecialCharSequenceMgr.java
index e23d460..38bc93d 100644
--- a/src/com/android/contacts/SpecialCharSequenceMgr.java
+++ b/src/com/android/contacts/SpecialCharSequenceMgr.java
@@ -193,7 +193,7 @@
         AlertDialog alert = new AlertDialog.Builder(context)
                 .setTitle(R.string.imei)
                 .setMessage(imeiStr)
-                .setPositiveButton(R.string.ok, null)
+                .setPositiveButton(android.R.string.ok, null)
                 .setCancelable(false)
                 .show();
         alert.getWindow().setType(WindowManager.LayoutParams.TYPE_PRIORITY_PHONE);
diff --git a/src/com/android/contacts/TwelveKeyDialer.java b/src/com/android/contacts/TwelveKeyDialer.java
index caa77a1..41adc5a 100644
--- a/src/com/android/contacts/TwelveKeyDialer.java
+++ b/src/com/android/contacts/TwelveKeyDialer.java
@@ -18,10 +18,12 @@
 
 import android.app.Activity;
 import android.content.ActivityNotFoundException;
-
+import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
 import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 import android.graphics.drawable.Drawable;
 import android.media.AudioManager;
 import android.media.ToneGenerator;
@@ -29,32 +31,45 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
-import android.provider.Settings;
+import android.provider.Contacts.Intents.Insert;
 import android.provider.Contacts.People;
 import android.provider.Contacts.Phones;
 import android.provider.Contacts.PhonesColumns;
-import android.provider.Contacts.Intents.Insert;
+import android.provider.Settings;
 import android.telephony.PhoneNumberFormattingTextWatcher;
 import android.telephony.PhoneNumberUtils;
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
 import android.text.Editable;
-import android.text.Selection;
 import android.text.TextUtils;
 import android.text.TextWatcher;
 import android.text.method.DialerKeyListener;
 import android.util.Log;
 import android.view.KeyEvent;
+import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
 import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.android.internal.telephony.ITelephony;
 
 /**
  * Dialer activity that displays the typical twelve key interface.
  */
 public class TwelveKeyDialer extends Activity implements View.OnClickListener,
-        View.OnLongClickListener, View.OnKeyListener, TextWatcher {
+        View.OnLongClickListener, View.OnKeyListener,
+        AdapterView.OnItemClickListener, TextWatcher {
 
     private static final String TAG = "TwelveKeyDialer";
     
@@ -75,7 +90,11 @@
     private Drawable mDigitsEmptyBackground;
     private Drawable mDeleteBackground;
     private Drawable mDeleteEmptyBackground;
-    
+    private View mDigitsAndBackspace;
+    private View mDialpad;
+    private ListView mDialpadChooser;
+    private DialpadChooserAdapter mDialpadChooserAdapter;
+
     // determines if we want to playback local DTMF tones.
     private boolean mDTMFToneEnabled;
     
@@ -84,6 +103,29 @@
     /** Indicates if we are opening this dialer to add a call from the InCallScreen. */
     private boolean mIsAddCallMode;
 
+    PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+            /**
+             * Listen for phone state changes so that we can take down the
+             * "dialpad chooser" if the phone becomes idle while the
+             * chooser UI is visible.
+             */
+            @Override
+            public void onCallStateChanged(int state, String incomingNumber) {
+                // Log.i(TAG, "PhoneStateListener.onCallStateChanged: "
+                //       + state + ", '" + incomingNumber + "'");
+                if ((state == TelephonyManager.CALL_STATE_IDLE) && dialpadChooserVisible()) {
+                    // Log.i(TAG, "Call ended with dialpad chooser visible!  Taking it down...");
+                    // Note there's a race condition in the UI here: the
+                    // dialpad chooser could conceivably disappear (on its
+                    // own) at the exact moment the user was trying to select
+                    // one of the choices, which would be confusing.  (But at
+                    // least that's better than leaving the dialpad chooser
+                    // onscreen, but useless...)
+                    showDialpadChooser(false);
+                }
+            }
+        };
+
     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
         // Do nothing
     }
@@ -148,6 +190,17 @@
         view.setOnLongClickListener(this);
         mDelete = view;
 
+        mDigitsAndBackspace = (View) findViewById(R.id.digitsAndBackspace);
+        mDialpad = (View) findViewById(R.id.dialpad);  // This is null in landscape mode
+
+        // Set up the "dialpad chooser" UI; see showDialpadChooser().
+        mDialpadChooser = (ListView) findViewById(R.id.dialpadChooser);
+        mDialpadChooser.setOnItemClickListener(this);
+        // Add a dummy "footer" view so that the divider under the bottom
+        // item will be visible.
+        // (We set android:footerDividersEnabled="true" on this ListView in XML.)
+        mDialpadChooser.addFooterView(new View(this), null, false);
+
         if (!resolveIntent() && icicle != null) {
             super.onRestoreInstanceState(icicle);
         }
@@ -206,10 +259,14 @@
         } else {
             intent = getIntent();
         }
+        // Log.i(TAG, "==> resolveIntent(): intent: " + intent);
 
         // by default we are not adding a call.
         mIsAddCallMode = false;
-        
+
+        // By default we don't show the "dialpad chooser" UI.
+        boolean needToShowDialpadChooser = false;
+
         // Resolve the intent
         final String action = intent.getAction();
         if (Intent.ACTION_DIAL.equals(action) || Intent.ACTION_VIEW.equals(action)) {
@@ -238,8 +295,26 @@
                     }
                 }
             }
+        } else if (Intent.ACTION_MAIN.equals(action)) {
+            // The MAIN action means we're bringing up a blank dialer
+            // (e.g. by selecting the Home shortcut, or tabbing over from
+            // Contacts or Call log.)
+            //
+            // At this point, IF there's already an active call, there's a
+            // good chance that the user got here accidentally (but really
+            // wanted the in-call dialpad instead).  So we bring up an
+            // intermediate UI to make the user confirm what they really
+            // want to do.
+            if (phoneIsInUse()) {
+                // Log.i(TAG, "resolveIntent(): phone is in use; showing dialpad chooser!");
+                needToShowDialpadChooser = true;
+            }
         }
 
+        // Bring up the "dialpad chooser" IFF we need to make the user
+        // confirm which dialpad they really want.
+        showDialpadChooser(needToShowDialpadChooser);
+
         return ignoreState;
     }
 
@@ -326,12 +401,42 @@
                 resolveIntent();
             }
         }
+
+        // While we're in the foreground, listen for phone state changes,
+        // purely so that we can take down the "dialpad chooser" if the
+        // phone becomes idle while the chooser UI is visible.
+        TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
+        telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
+
+        // Potentially show hint text in the mDigits field when the user
+        // hasn't typed any digits yet.  (If there's already an active call,
+        // this hint text will remind the user that he's about to add a new
+        // call.)
+        //
+        // TODO: consider adding better UI for the case where *both* lines
+        // are currently in use.  (Right now we let the user try to add
+        // another call, but that call is guaranteed to fail.  Perhaps the
+        // entire dialer UI should be disabled instead.)
+        if (phoneIsInUse()) {
+            mDigits.setHint(R.string.dialerDialpadHintText);
+        } else {
+            // Common case; no hint necessary.
+            mDigits.setHint(null);
+
+            // Also, a sanity-check: the "dialpad chooser" UI should NEVER
+            // be visible if the phone is idle!
+            showDialpadChooser(false);
+        }
     }
 
     @Override
     protected void onPause() {
         super.onPause();
 
+        // Stop listening for phone state changes.
+        TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
+        telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
+
         synchronized(mToneGeneratorLock) {
             if (mToneGenerator != null) {
                 mToneStopper.removeMessages(STOP_TONE);
@@ -351,6 +456,11 @@
 
     @Override
     public boolean onPrepareOptionsMenu(Menu menu) {
+        // We never show a menu if the "choose dialpad" UI is up.
+        if (dialpadChooserVisible()) {
+            return false;
+        }
+
         CharSequence digits = mDigits.getText();
         if (digits == null || !TextUtils.isGraphic(digits)) {
             mAddToContactMenuItem.setVisible(false);
@@ -591,5 +701,217 @@
             mToneStopper.sendEmptyMessageDelayed(STOP_TONE, TONE_LENGTH_MS);
         }
     }
-}
 
+    /**
+     * Brings up the "dialpad chooser" UI in place of the usual Dialer
+     * elements (the textfield/button and the dialpad underneath).
+     *
+     * We show this UI if the user brings up the Dialer while a call is
+     * already in progress, since there's a good chance we got here
+     * accidentally (and the user really wanted the in-call dialpad instead).
+     * So in this situation we display an intermediate UI that lets the user
+     * explicitly choose between the in-call dialpad ("Use touch tone
+     * keypad") and the regular Dialer ("Add call").  (Or, the option "Return
+     * to call in progress" just goes back to the in-call UI with no dialpad
+     * at all.)
+     *
+     * @param enabled If true, show the "dialpad chooser" instead
+     *                of the regular Dialer UI
+     */
+    private void showDialpadChooser(boolean enabled) {
+        if (enabled) {
+            // Log.i(TAG, "Showing dialpad chooser!");
+            mDigitsAndBackspace.setVisibility(View.GONE);
+            if (mDialpad != null) mDialpad.setVisibility(View.GONE);
+            mDialpadChooser.setVisibility(View.VISIBLE);
+
+            // Instantiate the DialpadChooserAdapter and hook it up to the
+            // ListView.  We do this only once.
+            if (mDialpadChooserAdapter == null) {
+                mDialpadChooserAdapter = new DialpadChooserAdapter(this);
+                mDialpadChooser.setAdapter(mDialpadChooserAdapter);
+            }
+        } else {
+            // Log.i(TAG, "Displaying normal Dialer UI.");
+            mDigitsAndBackspace.setVisibility(View.VISIBLE);
+            if (mDialpad != null) mDialpad.setVisibility(View.VISIBLE);
+            mDialpadChooser.setVisibility(View.GONE);
+        }
+    }
+
+    /**
+     * @return true if we're currently showing the "dialpad chooser" UI.
+     */
+    private boolean dialpadChooserVisible() {
+        return mDialpadChooser.getVisibility() == View.VISIBLE;
+    }
+
+    /**
+     * Simple list adapter, binding to an icon + text label
+     * for each item in the "dialpad chooser" list.
+     */
+    private static class DialpadChooserAdapter extends BaseAdapter {
+        private LayoutInflater mInflater;
+
+        // Simple struct for a single "choice" item.
+        static class ChoiceItem {
+            String text;
+            Bitmap icon;
+            int id;
+
+            public ChoiceItem(String s, Bitmap b, int i) {
+                text = s;
+                icon = b;
+                id = i;
+            }
+        }
+
+        // IDs for the possible "choices":
+        static final int DIALPAD_CHOICE_USE_DTMF_DIALPAD = 101;
+        static final int DIALPAD_CHOICE_RETURN_TO_CALL = 102;
+        static final int DIALPAD_CHOICE_ADD_NEW_CALL = 103;
+
+        private static final int NUM_ITEMS = 3;
+        private ChoiceItem mChoiceItems[] = new ChoiceItem[NUM_ITEMS];
+
+        public DialpadChooserAdapter(Context context) {
+            // Cache the LayoutInflate to avoid asking for a new one each time.
+            mInflater = LayoutInflater.from(context);
+
+            // Initialize the possible choices.
+            // TODO: could this be specified entirely in XML?
+
+            // - "Use touch tone keypad"
+            mChoiceItems[0] = new ChoiceItem(
+                    context.getString(R.string.dialer_useDtmfDialpad),
+                    BitmapFactory.decodeResource(context.getResources(),
+                                                 R.drawable.ic_dialer_fork_tt_keypad),
+                    DIALPAD_CHOICE_USE_DTMF_DIALPAD);
+
+            // - "Return to call in progress"
+            mChoiceItems[1] = new ChoiceItem(
+                    context.getString(R.string.dialer_returnToInCallScreen),
+                    BitmapFactory.decodeResource(context.getResources(),
+                                                 R.drawable.ic_dialer_fork_current_call),
+                    DIALPAD_CHOICE_RETURN_TO_CALL);
+
+            // - "Add call"
+            mChoiceItems[2] = new ChoiceItem(
+                    context.getString(R.string.dialer_addAnotherCall),
+                    BitmapFactory.decodeResource(context.getResources(),
+                                                 R.drawable.ic_dialer_fork_add_call),
+                    DIALPAD_CHOICE_ADD_NEW_CALL);
+        }
+
+        public int getCount() {
+            return NUM_ITEMS;
+        }
+
+        /**
+         * Return the ChoiceItem for a given position.
+         */
+        public Object getItem(int position) {
+            return mChoiceItems[position];
+        }
+
+        /**
+         * Return a unique ID for each possible choice.
+         */
+        public long getItemId(int position) {
+            return position;
+        }
+
+        /**
+         * Make a view for each row.
+         */
+        public View getView(int position, View convertView, ViewGroup parent) {
+            // When convertView is non-null, we can reuse it (there's no need
+            // to reinflate it.)
+            if (convertView == null) {
+                convertView = mInflater.inflate(R.layout.dialpad_chooser_list_item, null);
+            }
+
+            TextView text = (TextView) convertView.findViewById(R.id.text);
+            text.setText(mChoiceItems[position].text);
+
+            ImageView icon = (ImageView) convertView.findViewById(R.id.icon);
+            icon.setImageBitmap(mChoiceItems[position].icon);
+
+            return convertView;
+        }
+    }
+
+    /**
+     * Handle clicks from the dialpad chooser.
+     */
+    public void onItemClick(AdapterView parent, View v, int position, long id) {
+        DialpadChooserAdapter.ChoiceItem item =
+                (DialpadChooserAdapter.ChoiceItem) parent.getItemAtPosition(position);
+        int itemId = item.id;
+        switch (itemId) {
+            case DialpadChooserAdapter.DIALPAD_CHOICE_USE_DTMF_DIALPAD:
+                // Log.i(TAG, "DIALPAD_CHOICE_USE_DTMF_DIALPAD");
+                // Fire off an intent to go back to the in-call UI
+                // with the dialpad visible.
+                returnToInCallScreen(true);
+                break;
+
+            case DialpadChooserAdapter.DIALPAD_CHOICE_RETURN_TO_CALL:
+                // Log.i(TAG, "DIALPAD_CHOICE_RETURN_TO_CALL");
+                // Fire off an intent to go back to the in-call UI
+                // (with the dialpad hidden).
+                returnToInCallScreen(false);
+                break;
+
+            case DialpadChooserAdapter.DIALPAD_CHOICE_ADD_NEW_CALL:
+                // Log.i(TAG, "DIALPAD_CHOICE_ADD_NEW_CALL");
+                // Ok, guess the user really did want to be here (in the
+                // regular Dialer) after all.  Bring back the normal Dialer UI.
+                showDialpadChooser(false);
+                break;
+
+            default:
+                Log.w(TAG, "onItemClick: unexpected itemId: " + itemId);
+                break;
+        }
+    }
+
+    /**
+     * Returns to the in-call UI (where there's presumably a call in
+     * progress) in response to the user selecting "use touch tone keypad"
+     * or "return to call" from the dialpad chooser.
+     */
+    private void returnToInCallScreen(boolean showDialpad) {
+        try {
+            ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
+            if (phone != null) phone.showCallScreenWithDialpad(showDialpad);
+        } catch (RemoteException e) {
+            Log.w(TAG, "phone.showCallScreenWithDialpad() failed", e);
+        }
+
+        // Finally, finish() ourselves so that we don't stay on the
+        // activity stack.
+        // Note that we do this whether or not the showCallScreenWithDialpad()
+        // call above had any effect or not!  (That call is a no-op if the
+        // phone is idle, which can happen if the current call ends while
+        // the dialpad chooser is up.  In this case we can't show the
+        // InCallScreen, and there's no point staying here in the Dialer,
+        // so we just take the user back where he came from...)
+        finish();
+    }
+
+    /**
+     * @return true if the phone is "in use", meaning that at least one line
+     *              is active (ie. off hook or ringing or dialing).
+     */
+    private boolean phoneIsInUse() {
+        boolean phoneInUse = false;
+        try {
+            ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
+            if (phone != null) phoneInUse = !phone.isIdle();
+        } catch (RemoteException e) {
+            Log.w(TAG, "phone.isIdle() failed", e);
+        }
+        return phoneInUse;
+    }
+}
diff --git a/src/com/android/contacts/ViewContactActivity.java b/src/com/android/contacts/ViewContactActivity.java
index c1e9bc5..9a11f76 100644
--- a/src/com/android/contacts/ViewContactActivity.java
+++ b/src/com/android/contacts/ViewContactActivity.java
@@ -55,6 +55,8 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.database.Cursor;
@@ -91,6 +93,7 @@
 import android.widget.Toast;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Displays the details of a specific contact.
@@ -99,11 +102,25 @@
         implements View.OnCreateContextMenuListener, View.OnClickListener,
         DialogInterface.OnClickListener {
     private static final String TAG = "ViewContact";
+    private static final String SHOW_BARCODE_INTENT = "com.google.zxing.client.android.ENCODE";
+
+    private static final String[] PHONE_KEYS = {
+        Contacts.Intents.Insert.PHONE,
+        Contacts.Intents.Insert.SECONDARY_PHONE,
+        Contacts.Intents.Insert.TERTIARY_PHONE
+    };
+
+    private static final String[] EMAIL_KEYS = {
+        Contacts.Intents.Insert.EMAIL,
+        Contacts.Intents.Insert.SECONDARY_EMAIL,
+        Contacts.Intents.Insert.TERTIARY_EMAIL
+    };
 
     private static final int DIALOG_CONFIRM_DELETE = 1;
 
     public static final int MENU_ITEM_DELETE = 1;
     public static final int MENU_ITEM_MAKE_DEFAULT = 2;
+    public static final int MENU_ITEM_SHOW_BARCODE = 3;
 
     private Uri mUri;
     private ContentResolver mResolver;
@@ -256,8 +273,8 @@
                         .setTitle(R.string.deleteConfirmation_title)
                         .setIcon(android.R.drawable.ic_dialog_alert)
                         .setMessage(R.string.deleteConfirmation)
-                        .setNegativeButton(R.string.noButton, null)
-                        .setPositiveButton(R.string.yesButton, this)
+                        .setNegativeButton(android.R.string.cancel, null)
+                        .setPositiveButton(android.R.string.ok, this)
                         .setCancelable(false)
                         .create();
         }
@@ -310,6 +327,29 @@
     }
 
     @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        super.onPrepareOptionsMenu(menu);
+        // Perform this check each time the menu is about to be shown, because the Barcode Scanner
+        // could be installed or uninstalled at any time.
+        if (isBarcodeScannerInstalled()) {
+            if (menu.findItem(MENU_ITEM_SHOW_BARCODE) == null) {
+                menu.add(0, MENU_ITEM_SHOW_BARCODE, 0, R.string.menu_showBarcode)
+                        .setIcon(R.drawable.ic_menu_show_barcode);
+            }
+        } else {
+            menu.removeItem(MENU_ITEM_SHOW_BARCODE);
+        }
+        return true;
+    }
+
+    private boolean isBarcodeScannerInstalled() {
+        final Intent intent = new Intent(SHOW_BARCODE_INTENT);
+        List<ResolveInfo> list = getPackageManager().queryIntentActivities(intent,
+                PackageManager.MATCH_DEFAULT_ONLY);
+        return list.size() > 0;
+    }
+
+    @Override
     public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
         AdapterView.AdapterContextMenuInfo info;
         try {
@@ -356,6 +396,44 @@
                 showDialog(DIALOG_CONFIRM_DELETE);
                 return true;
             }
+            case MENU_ITEM_SHOW_BARCODE:
+                if (mCursor.moveToFirst()) {
+                    Intent intent = new Intent(SHOW_BARCODE_INTENT);
+                    intent.putExtra("ENCODE_TYPE", "CONTACT_TYPE");
+                    Bundle bundle = new Bundle();
+                    String name = mCursor.getString(CONTACT_NAME_COLUMN);
+                    if (!TextUtils.isEmpty(name)) {
+                        bundle.putString(Contacts.Intents.Insert.NAME, name);
+                        // The 0th ViewEntry in each ArrayList below is a separator item
+                        int entriesToAdd = Math.min(mPhoneEntries.size() - 1, PHONE_KEYS.length);
+                        for (int x = 0; x < entriesToAdd; x++) {
+                            ViewEntry entry = mPhoneEntries.get(x + 1);
+                            bundle.putString(PHONE_KEYS[x], entry.data);
+                        }
+                        entriesToAdd = Math.min(mEmailEntries.size() - 1, EMAIL_KEYS.length);
+                        for (int x = 0; x < entriesToAdd; x++) {
+                            ViewEntry entry = mEmailEntries.get(x + 1);
+                            bundle.putString(EMAIL_KEYS[x], entry.data);
+                        }
+                        if (mPostalEntries.size() >= 2) {
+                            ViewEntry entry = mPostalEntries.get(1);
+                            bundle.putString(Contacts.Intents.Insert.POSTAL, entry.data);
+                        }
+                        intent.putExtra("ENCODE_DATA", bundle);
+                        try {
+                            startActivity(intent);
+                        } catch (ActivityNotFoundException e) {
+                            // The check in onPrepareOptionsMenu() should make this impossible, but
+                            // for safety I'm catching the exception rather than crashing. Ideally
+                            // I'd call Menu.removeItem() here too, but I don't see a way to get
+                            // the options menu.
+                            Log.e(TAG, "Show barcode menu item was clicked but Barcode Scanner " +
+                                    "was not installed.");
+                        }
+                        return true;
+                    }
+                }
+                break;
         }
         return super.onOptionsItemSelected(item);
     }