FileDocCategorySizeDatePackage
EditContactActivity.javaAPI DocAndroid 1.5 API88334Wed May 06 22:42:44 BST 2009com.android.contacts

EditContactActivity.java

/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.contacts;

import static com.android.contacts.ContactEntryAdapter.CONTACT_CUSTOM_RINGTONE_COLUMN;
import static com.android.contacts.ContactEntryAdapter.CONTACT_NAME_COLUMN;
import static com.android.contacts.ContactEntryAdapter.CONTACT_NOTES_COLUMN;
import static com.android.contacts.ContactEntryAdapter.CONTACT_PROJECTION;
import static com.android.contacts.ContactEntryAdapter.CONTACT_SEND_TO_VOICEMAIL_COLUMN;
import static com.android.contacts.ContactEntryAdapter.CONTACT_PHONETIC_NAME_COLUMN;
import static com.android.contacts.ContactEntryAdapter.METHODS_AUX_DATA_COLUMN;
import static com.android.contacts.ContactEntryAdapter.METHODS_DATA_COLUMN;
import static com.android.contacts.ContactEntryAdapter.METHODS_ID_COLUMN;
import static com.android.contacts.ContactEntryAdapter.METHODS_ISPRIMARY_COLUMN;
import static com.android.contacts.ContactEntryAdapter.METHODS_KIND_COLUMN;
import static com.android.contacts.ContactEntryAdapter.METHODS_LABEL_COLUMN;
import static com.android.contacts.ContactEntryAdapter.METHODS_PROJECTION;
import static com.android.contacts.ContactEntryAdapter.METHODS_TYPE_COLUMN;
import static com.android.contacts.ContactEntryAdapter.ORGANIZATIONS_COMPANY_COLUMN;
import static com.android.contacts.ContactEntryAdapter.ORGANIZATIONS_ID_COLUMN;
import static com.android.contacts.ContactEntryAdapter.ORGANIZATIONS_ISPRIMARY_COLUMN;
import static com.android.contacts.ContactEntryAdapter.ORGANIZATIONS_LABEL_COLUMN;
import static com.android.contacts.ContactEntryAdapter.ORGANIZATIONS_PROJECTION;
import static com.android.contacts.ContactEntryAdapter.ORGANIZATIONS_TITLE_COLUMN;
import static com.android.contacts.ContactEntryAdapter.ORGANIZATIONS_TYPE_COLUMN;
import static com.android.contacts.ContactEntryAdapter.PHONES_ID_COLUMN;
import static com.android.contacts.ContactEntryAdapter.PHONES_ISPRIMARY_COLUMN;
import static com.android.contacts.ContactEntryAdapter.PHONES_LABEL_COLUMN;
import static com.android.contacts.ContactEntryAdapter.PHONES_NUMBER_COLUMN;
import static com.android.contacts.ContactEntryAdapter.PHONES_PROJECTION;
import static com.android.contacts.ContactEntryAdapter.PHONES_TYPE_COLUMN;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.preference.PreferenceManager;
import android.provider.Contacts;
import android.provider.Contacts.ContactMethods;
import android.provider.Contacts.Intents.Insert;
import android.provider.Contacts.Groups;
import android.provider.Contacts.Organizations;
import android.provider.Contacts.People;
import android.provider.Contacts.Phones;
import android.telephony.PhoneNumberFormattingTextWatcher;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.method.TextKeyListener;
import android.text.method.TextKeyListener.Capitalize;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
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;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

import java.io.ByteArrayOutputStream;
import java.util.ArrayList;

/**
 * Activity for editing or inserting a contact. Note that if the contact data changes in the
 * background while this activity is running, the updates will be overwritten.
 */
public final class EditContactActivity extends Activity implements View.OnClickListener,
        TextWatcher, View.OnFocusChangeListener {
    private static final String TAG = "EditContactActivity";

    private static final int STATE_UNKNOWN = 0;
    /** Editing an existing contact */
    private static final int STATE_EDIT = 1;
    /** The full insert mode */
    private static final int STATE_INSERT = 2;

    /** The launch code when picking a photo and the raw data is returned */
    private static final int PHOTO_PICKED_WITH_DATA = 3021;

    /** The launch code when picking a ringtone */
    private static final int RINGTONE_PICKED = 3023;
    
    // These correspond to the string array in resources for picker "other" items
    final static int OTHER_ORGANIZATION = 0;
    final static int OTHER_NOTE = 1;
    
    // Dialog IDs
    final static int DELETE_CONFIRMATION_DIALOG = 2;
    
    // Section IDs
    final static int SECTION_PHONES = 3;
    final static int SECTION_EMAIL = 4;
    final static int SECTION_IM = 5;
    final static int SECTION_POSTAL = 6;
    final static int SECTION_ORG = 7;
    final static int SECTION_NOTE = 8;

    // Menu item IDs
    public static final int MENU_ITEM_SAVE = 1;
    public static final int MENU_ITEM_DONT_SAVE = 2;
    public static final int MENU_ITEM_DELETE = 3;
    public static final int MENU_ITEM_PHOTO = 6;
    
    /** Used to represent an invalid type for a contact entry */
    private static final int INVALID_TYPE = -1;
    
    /** The default type for a phone that is added via an intent */
    private static final int DEFAULT_PHONE_TYPE = Phones.TYPE_MOBILE;

    /** The default type for an email that is added via an intent */
    private static final int DEFAULT_EMAIL_TYPE = ContactMethods.TYPE_HOME;

    /** The default type for a postal address that is added via an intent */
    private static final int DEFAULT_POSTAL_TYPE = ContactMethods.TYPE_HOME;

    private int mState; // saved across instances
    private boolean mInsert; // saved across instances
    private Uri mUri; // saved across instances
    /** In insert mode this is the photo */
    private Bitmap mPhoto; // saved across instances
    private boolean mPhotoChanged = false; // saved across instances
    
    private EditText mNameView;
    private ImageView mPhotoImageView;
    private ViewGroup mContentView;
    private LinearLayout mLayout;
    private LayoutInflater mInflater;
    private MenuItem mPhotoMenuItem;
    private boolean mPhotoPresent = false;
    private EditText mPhoneticNameView;  // invisible in some locales, but always present

    /** Flag marking this contact as changed, meaning we should write changes back. */
    private boolean mContactChanged = false;

    // These are accessed by inner classes. They're package scoped to make access more efficient.
    /* package */ ContentResolver mResolver;
    /* package */ ArrayList<EditEntry> mPhoneEntries = new ArrayList<EditEntry>();
    /* package */ ArrayList<EditEntry> mEmailEntries = new ArrayList<EditEntry>();
    /* package */ ArrayList<EditEntry> mImEntries = new ArrayList<EditEntry>();
    /* package */ ArrayList<EditEntry> mPostalEntries = new ArrayList<EditEntry>();
    /* package */ ArrayList<EditEntry> mOrgEntries = new ArrayList<EditEntry>();
    /* package */ ArrayList<EditEntry> mNoteEntries = new ArrayList<EditEntry>();
    /* package */ ArrayList<EditEntry> mOtherEntries = new ArrayList<EditEntry>();
    /* package */ ArrayList<ArrayList<EditEntry>> mSections = new ArrayList<ArrayList<EditEntry>>();
    
    /* package */ static final int MSG_DELETE = 1;
    /* package */ static final int MSG_CHANGE_LABEL = 2;
    /* package */ static final int MSG_ADD_PHONE = 3;
    /* package */ static final int MSG_ADD_EMAIL = 4;
    /* package */ static final int MSG_ADD_POSTAL = 5;

    private static final int[] TYPE_PRECEDENCE_PHONES = new int[] {
            Phones.TYPE_MOBILE, Phones.TYPE_HOME, Phones.TYPE_WORK, Phones.TYPE_OTHER
    };
    private static final int[] TYPE_PRECEDENCE_METHODS = new int[] {
            ContactMethods.TYPE_HOME, ContactMethods.TYPE_WORK, ContactMethods.TYPE_OTHER
    };
    private static final int[] TYPE_PRECEDENCE_IM = new int[] {
            ContactMethods.PROTOCOL_GOOGLE_TALK, ContactMethods.PROTOCOL_AIM,
            ContactMethods.PROTOCOL_MSN, ContactMethods.PROTOCOL_YAHOO,
            ContactMethods.PROTOCOL_JABBER
    };
    private static final int[] TYPE_PRECEDENCE_ORG = new int[] {
            Organizations.TYPE_WORK, Organizations.TYPE_OTHER
    };

    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.photoImage: {
                doPickPhotoAction();
                break;
            }
            
            case R.id.checkable: {
                CheckBox checkBox = (CheckBox) v.findViewById(R.id.checkbox);
                checkBox.toggle();
                
                EditEntry entry = findEntryForView(v);
                entry.data = checkBox.isChecked() ? "1" : "0";
                
                mContactChanged = true;
                break;
            }
            
            case R.id.entry_ringtone: {
                EditEntry entry = findEntryForView(v);
                doPickRingtone(entry);
                break;
            }
            
            case R.id.separator: {
                // Someone clicked on a section header, so handle add action
                int sectionType = (Integer) v.getTag();
                doAddAction(sectionType);
                break;
            }

            case R.id.saveButton:
                doSaveAction();
                break;

            case R.id.discardButton:
                doRevertAction();
                break;

            case R.id.delete: {
                EditEntry entry = findEntryForView(v);
                if (entry != null) {
                    // Clear the text and hide the view so it gets saved properly
                    ((TextView) entry.view.findViewById(R.id.data)).setText(null);
                    entry.view.setVisibility(View.GONE);
                    entry.isDeleted = true;
                }
                
                // Force rebuild of views because section headers might need to change
                buildViews();
                break;
            }

            case R.id.label: {
                EditEntry entry = findEntryForView(v);
                if (entry != null) {
                    String[] labels = getLabelsForKind(this, entry.kind);
                    LabelPickedListener listener = new LabelPickedListener(entry, labels);
                    new AlertDialog.Builder(EditContactActivity.this)
                            .setItems(labels, listener)
                            .setTitle(R.string.selectLabel)
                            .show();
                }
                break;
            }
        }
    }

    private void setPhotoPresent(boolean present) {
        mPhotoPresent = present;
        
        // Correctly scale the contact photo if present, otherwise just center
        // the photo placeholder icon.
        if (mPhotoPresent) {
            mPhotoImageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
        } else {
            mPhotoImageView.setImageResource(R.drawable.ic_menu_add_picture);
            mPhotoImageView.setScaleType(ImageView.ScaleType.CENTER);
        }
        
        if (mPhotoMenuItem != null) {
            if (present) {
                mPhotoMenuItem.setTitle(R.string.removePicture);
                mPhotoMenuItem.setIcon(android.R.drawable.ic_menu_delete);
            } else {
                mPhotoMenuItem.setTitle(R.string.addPicture);
                mPhotoMenuItem.setIcon(R.drawable.ic_menu_add_picture);
            }
        }
    }
    
    private EditEntry findEntryForView(View v) {
        // Try to find the entry for this view
        EditEntry entry = null;
        do {
            Object tag = v.getTag();
            if (tag != null && tag instanceof EditEntry) {
                entry = (EditEntry) tag;
                break;
            } else {
                ViewParent parent = v.getParent();
                if (parent != null && parent instanceof View) {
                    v = (View) parent;
                } else {
                    v = null;
                }
            }
        } while (v != null);
        return entry;
    }

    private DialogInterface.OnClickListener mDeleteContactDialogListener =
            new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int button) {
            mResolver.delete(mUri, null, null);
            finish();
        }
    };

    private boolean mMobilePhoneAdded = false;
    private boolean mPrimaryEmailAdded = false;

    @Override
    protected void onCreate(Bundle icicle) {
        super.onCreate(icicle);

        mResolver = getContentResolver();

        // Build the list of sections
        setupSections();

        // Load the UI
        mInflater = getLayoutInflater();
        mContentView = (ViewGroup)mInflater.inflate(R.layout.edit_contact, null);
        setContentView(mContentView);
        
        mLayout = (LinearLayout) findViewById(R.id.list);
        mNameView = (EditText) findViewById(R.id.name);
        mPhotoImageView = (ImageView) findViewById(R.id.photoImage);
        mPhotoImageView.setOnClickListener(this);
        mPhoneticNameView = (EditText) findViewById(R.id.phonetic_name);
        
        // Setup the bottom buttons
        View view = findViewById(R.id.saveButton);
        view.setOnClickListener(this);
        view = findViewById(R.id.discardButton);
        view.setOnClickListener(this);

        // Resolve the intent
        mState = STATE_UNKNOWN;
        Intent intent = getIntent();
        String action = intent.getAction();
        mUri = intent.getData();
        if (mUri != null) {
            if (action.equals(Intent.ACTION_EDIT)) {
                if (icicle == null) {
                    // Build the entries & views
                    buildEntriesForEdit(getIntent().getExtras());
                    buildViews();
                }
                setTitle(R.string.editContact_title_edit);
                mState = STATE_EDIT;
            } else if (action.equals(Intent.ACTION_INSERT)) {
                if (icicle == null) {
                    // Build the entries & views
                    buildEntriesForInsert(getIntent().getExtras());
                    buildViews();
                }
                setTitle(R.string.editContact_title_insert);
                mState = STATE_INSERT;
                mInsert = true;
            }
        }

        if (mState == STATE_UNKNOWN) {
            Log.e(TAG, "Cannot resolve intent: " + intent);
            finish();
            return;
        }

        if (mState == STATE_EDIT) {
            setTitle(getResources().getText(R.string.editContact_title_edit));
        } else {
            setTitle(getResources().getText(R.string.editContact_title_insert));
        }
    }

    private void setupSections() {
        mSections.add(mPhoneEntries);
        mSections.add(mEmailEntries);
        mSections.add(mImEntries);
        mSections.add(mPostalEntries);
        mSections.add(mOrgEntries);
        mSections.add(mNoteEntries);
        mSections.add(mOtherEntries);
    }
    
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        
        // To store current focus between config changes, follow focus down the
        // view tree, keeping track of any parents with EditEntry tags
        View focusedChild = mContentView.getFocusedChild();
        EditEntry focusedEntry = null;
        while (focusedChild != null) {
            Object tag = focusedChild.getTag();
            if (tag instanceof EditEntry) {
                focusedEntry = (EditEntry) tag;
            }
            
            // Keep going deeper until child isn't a group
            if (focusedChild instanceof ViewGroup) {
                View deeperFocus = ((ViewGroup) focusedChild).getFocusedChild();
                if (deeperFocus != null) {
                    focusedChild = deeperFocus;
                } else {
                    break;
                }
            } else {
                break;
            }
        }
        
        if (focusedChild != null) {
            int requestFocusId = focusedChild.getId();
            int requestCursor = 0;
            if (focusedChild instanceof EditText) {
                requestCursor = ((EditText) focusedChild).getSelectionStart();
            }
            
            // Store focus values in EditEntry if found, otherwise store as
            // generic values
            if (focusedEntry != null) {
                focusedEntry.requestFocusId = requestFocusId;
                focusedEntry.requestCursor = requestCursor;
            } else {
                outState.putInt("requestFocusId", requestFocusId);
                outState.putInt("requestCursor", requestCursor);
            }
        }
        
        outState.putParcelableArrayList("phoneEntries", mPhoneEntries);
        outState.putParcelableArrayList("emailEntries", mEmailEntries);
        outState.putParcelableArrayList("imEntries", mImEntries);
        outState.putParcelableArrayList("postalEntries", mPostalEntries);
        outState.putParcelableArrayList("orgEntries", mOrgEntries);
        outState.putParcelableArrayList("noteEntries", mNoteEntries);
        outState.putParcelableArrayList("otherEntries", mOtherEntries);
        outState.putInt("state", mState);
        outState.putBoolean("insert", mInsert);
        outState.putParcelable("uri", mUri);
        outState.putString("name", mNameView.getText().toString());
        outState.putParcelable("photo", mPhoto);
        outState.putBoolean("photoChanged", mPhotoChanged);
        outState.putString("phoneticName", mPhoneticNameView.getText().toString());
        outState.putBoolean("contactChanged", mContactChanged);
    }

    @Override
    protected void onRestoreInstanceState(Bundle inState) {
        mPhoneEntries = inState.getParcelableArrayList("phoneEntries");
        mEmailEntries = inState.getParcelableArrayList("emailEntries");
        mImEntries = inState.getParcelableArrayList("imEntries");
        mPostalEntries = inState.getParcelableArrayList("postalEntries");
        mOrgEntries = inState.getParcelableArrayList("orgEntries");
        mNoteEntries = inState.getParcelableArrayList("noteEntries");
        mOtherEntries = inState.getParcelableArrayList("otherEntries");
        setupSections();

        mState = inState.getInt("state");
        mInsert = inState.getBoolean("insert");
        mUri = inState.getParcelable("uri");
        mNameView.setText(inState.getString("name"));
        mPhoto = inState.getParcelable("photo");
        if (mPhoto != null) {
            mPhotoImageView.setImageBitmap(mPhoto);
            setPhotoPresent(true);
        } else {
            mPhotoImageView.setImageResource(R.drawable.ic_contact_picture);
            setPhotoPresent(false);
        }
        mPhotoChanged = inState.getBoolean("photoChanged");
        mPhoneticNameView.setText(inState.getString("phoneticName"));
        mContactChanged = inState.getBoolean("contactChanged");

        // Now that everything is restored, build the view
        buildViews();
        
        // Try restoring any generally requested focus
        int requestFocusId = inState.getInt("requestFocusId", View.NO_ID);
        View focusedChild = mContentView.findViewById(requestFocusId);
        if (focusedChild != null) {
            focusedChild.requestFocus();
            if (focusedChild instanceof EditText) {
                int requestCursor = inState.getInt("requestCursor", 0);
                ((EditText) focusedChild).setSelection(requestCursor);
            }
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode != RESULT_OK) {
            return;
        }

        switch (requestCode) {
            case PHOTO_PICKED_WITH_DATA: {
                final Bundle extras = data.getExtras();
                if (extras != null) {
                    Bitmap photo = extras.getParcelable("data");
                    mPhoto = photo;
                    mPhotoChanged = true;
                    mPhotoImageView.setImageBitmap(photo);
                    setPhotoPresent(true);
                }
                break;
            }

            case RINGTONE_PICKED: {
                Uri pickedUri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
                handleRingtonePicked(pickedUri);
                mContactChanged = true;
                break;
            }
        }
    }
    
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        switch (keyCode) {
            case KeyEvent.KEYCODE_BACK: {
                doSaveAction();
                return true;
            }
        }
        return super.onKeyDown(keyCode, event);
    }
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        menu.add(0, MENU_ITEM_SAVE, 0, R.string.menu_done)
                .setIcon(android.R.drawable.ic_menu_save)
                .setAlphabeticShortcut('\n');
        menu.add(0, MENU_ITEM_DONT_SAVE, 0, R.string.menu_doNotSave)
                .setIcon(android.R.drawable.ic_menu_close_clear_cancel)
                .setAlphabeticShortcut('q');
        if (!mInsert) {
            menu.add(0, MENU_ITEM_DELETE, 0, R.string.menu_deleteContact)
                    .setIcon(android.R.drawable.ic_menu_delete);
        }

        mPhotoMenuItem = menu.add(0, MENU_ITEM_PHOTO, 0, null);
        // Updates the state of the menu item
        setPhotoPresent(mPhotoPresent);

        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case MENU_ITEM_SAVE:
                doSaveAction();
                return true;
    
            case MENU_ITEM_DONT_SAVE:
                doRevertAction();
                return true;
    
            case MENU_ITEM_DELETE:
                // Get confirmation
                showDialog(DELETE_CONFIRMATION_DIALOG);
                return true;
    
            case MENU_ITEM_PHOTO:
                if (!mPhotoPresent) {
                    doPickPhotoAction();
                } else {
                    doRemovePhotoAction();
                }
                return true;
        }

        return false;
    }
    
    /**
     * Try guessing the next-best type of {@link EditEntry} to insert into the
     * given list. We walk down the precedence list until we find a type that
     * doesn't exist yet, or default to the lowest ranking type.
     */
    private int guessNextType(ArrayList<EditEntry> entries, int[] precedenceList) {
        // Keep track of the types we've seen already
        SparseBooleanArray existAlready = new SparseBooleanArray(entries.size());
        for (int i = entries.size() - 1; i >= 0; i--) {
            EditEntry entry = entries.get(i);
            if (!entry.isDeleted) {
                existAlready.put(entry.type, true);
            }
        }
        
        // Pick the first item we haven't seen
        for (int type : precedenceList) {
            if (!existAlready.get(type, false)) {
                return type;
            }
        }
        
        // Otherwise default to last item
        return precedenceList[precedenceList.length - 1];
    }

    private void doAddAction(int sectionType) {
        EditEntry entry = null;
        switch (sectionType) {
            case SECTION_PHONES: {
                // Try figuring out which type to insert next
                int nextType = guessNextType(mPhoneEntries, TYPE_PRECEDENCE_PHONES);
                entry = EditEntry.newPhoneEntry(EditContactActivity.this,
                        Uri.withAppendedPath(mUri, People.Phones.CONTENT_DIRECTORY),
                        nextType);
                mPhoneEntries.add(entry);
                break;
            }
            case SECTION_EMAIL: {
                // Try figuring out which type to insert next
                int nextType = guessNextType(mEmailEntries, TYPE_PRECEDENCE_METHODS);
                entry = EditEntry.newEmailEntry(EditContactActivity.this,
                        Uri.withAppendedPath(mUri, People.ContactMethods.CONTENT_DIRECTORY),
                        nextType);
                mEmailEntries.add(entry);
                break;
            }
            case SECTION_IM: {
                // Try figuring out which type to insert next
                int nextType = guessNextType(mImEntries, TYPE_PRECEDENCE_IM);
                entry = EditEntry.newImEntry(EditContactActivity.this,
                        Uri.withAppendedPath(mUri, People.ContactMethods.CONTENT_DIRECTORY),
                        nextType);
                mImEntries.add(entry);
                break;
            }
            case SECTION_POSTAL: {
                int nextType = guessNextType(mPostalEntries, TYPE_PRECEDENCE_METHODS);
                entry = EditEntry.newPostalEntry(EditContactActivity.this,
                        Uri.withAppendedPath(mUri, People.ContactMethods.CONTENT_DIRECTORY),
                        nextType);
                mPostalEntries.add(entry);
                break;
            }
            case SECTION_ORG: {
                int nextType = guessNextType(mOrgEntries, TYPE_PRECEDENCE_ORG);
                entry = EditEntry.newOrganizationEntry(EditContactActivity.this,
                        Uri.withAppendedPath(mUri, Organizations.CONTENT_DIRECTORY),
                        nextType);
                mOrgEntries.add(entry);
                break;
            }
            case SECTION_NOTE: {
                entry = EditEntry.newNotesEntry(EditContactActivity.this, null, mUri);
                mNoteEntries.add(entry);
                break;
            }
        }
        
        // Rebuild the views if needed
        if (entry != null) {
            buildViews();
            mContactChanged = true;

            View dataView = entry.view.findViewById(R.id.data);
            if (dataView == null) {
                entry.view.requestFocus();
            } else {
                dataView.requestFocus();
            }
        }
    }

    private void doRevertAction() {
        finish();
    }

    private void doPickPhotoAction() {
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
        // TODO: get these values from constants somewhere
        intent.setType("image/*");
        intent.putExtra("crop", "true");
        intent.putExtra("aspectX", 1);
        intent.putExtra("aspectY", 1);
        intent.putExtra("outputX", 96);
        intent.putExtra("outputY", 96);
        try {
            intent.putExtra("return-data", true);
            startActivityForResult(intent, PHOTO_PICKED_WITH_DATA);
        } catch (ActivityNotFoundException e) {
            new AlertDialog.Builder(EditContactActivity.this)
                .setTitle(R.string.errorDialogTitle)
                .setMessage(R.string.photoPickerNotFoundText)
                .setPositiveButton(android.R.string.ok, null)
                .show();
        }
    }

    private void doRemovePhotoAction() {
        mPhoto = null;
        mPhotoChanged = true;
        setPhotoPresent(false);
    }
    
    private void doPickRingtone(EditEntry entry) {
        Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
        // Allow user to pick 'Default'
        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
        // Show only ringtones
        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_RINGTONE);
        // Don't show 'Silent'
        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false);
        
        Uri ringtoneUri;
        if (entry.data != null) {
            ringtoneUri = Uri.parse(entry.data);
        } else {
            // Otherwise pick default ringtone Uri so that something is selected.
            ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
        }
        
        // Put checkmark next to the current ringtone for this contact
        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, ringtoneUri);
        // Launch!
        startActivityForResult(intent, RINGTONE_PICKED);
    }
    
    private void handleRingtonePicked(Uri pickedUri) {
        EditEntry entry = getOtherEntry(People.CUSTOM_RINGTONE);
        if (entry == null) {
            Log.w(TAG, "Ringtone picked but could not find ringtone entry");
            return;
        }
        
        if (pickedUri == null || RingtoneManager.isDefault(pickedUri)) {
            entry.data = null;
        } else {
            entry.data = pickedUri.toString();
        }
        
        updateRingtoneView(entry);
    }

    private void updateRingtoneView(EditEntry entry) {
        String ringtoneName;
        if (entry.data == null) {
            ringtoneName = getString(R.string.default_ringtone);
        } else {
            Uri ringtoneUri = Uri.parse(entry.data);
            Ringtone ringtone = RingtoneManager.getRingtone(this, ringtoneUri);
            if (ringtone == null) {
                Log.w(TAG, "ringtone's URI doesn't resolve to a Ringtone");
                return;
            }
            ringtoneName = ringtone.getTitle(this);
        }
        
        updateDataView(entry, ringtoneName);
    }
    
    private void updateDataView(EditEntry entry, String text) {
        TextView dataView = (TextView) entry.view.findViewById(R.id.data);
        dataView.setText(text);
    }
    
    @Override
    protected Dialog onCreateDialog(int id) {
        switch (id) {
            case DELETE_CONFIRMATION_DIALOG:
                return new AlertDialog.Builder(EditContactActivity.this)
                        .setTitle(R.string.deleteConfirmation_title)
                        .setIcon(android.R.drawable.ic_dialog_alert)
                        .setMessage(R.string.deleteConfirmation)
                        .setNegativeButton(android.R.string.cancel, null)
                        .setPositiveButton(android.R.string.ok, mDeleteContactDialogListener)
                        .setCancelable(false)
                        .create();
        }
        return super.onCreateDialog(id);
    }
    
    static String[] getLabelsForKind(Context context, int kind) {
        final Resources resources = context.getResources();
        switch (kind) {
            case Contacts.KIND_PHONE:
                return resources.getStringArray(android.R.array.phoneTypes);
            case Contacts.KIND_EMAIL:
                return resources.getStringArray(android.R.array.emailAddressTypes);
            case Contacts.KIND_POSTAL:
                return resources.getStringArray(android.R.array.postalAddressTypes);
            case Contacts.KIND_IM:
                return resources.getStringArray(android.R.array.imProtocols);
            case Contacts.KIND_ORGANIZATION:
                return resources.getStringArray(android.R.array.organizationTypes);
            case EditEntry.KIND_CONTACT:
                return resources.getStringArray(R.array.otherLabels);
        }
        return null;
    }

    int getTypeFromLabelPosition(CharSequence[] labels, int labelPosition) {
        // In the UI Custom... comes last, but it is uses the constant 0
        // so it is in the same location across the various kinds. Fix up the
        // position to a valid type here.
        if (labelPosition == labels.length - 1) {
            return ContactMethods.TYPE_CUSTOM;
        } else {
            return labelPosition + 1;
        }
    }
    
    private EditEntry getOtherEntry(String column) {
        for (int i = mOtherEntries.size() - 1; i >= 0; i--) {
            EditEntry entry = mOtherEntries.get(i);
            if (isOtherEntry(entry, column)) {
                return entry;
            }
        }
        return null;
    }
    
    private static boolean isOtherEntry(EditEntry entry, String column) {
        return entry != null && entry.column != null && entry.column.equals(column);
    }
    
    private void createCustomPicker(final EditEntry entry, final ArrayList<EditEntry> addTo) {
        final EditText label = new EditText(this);
        label.setKeyListener(TextKeyListener.getInstance(false, Capitalize.WORDS));
        label.requestFocus();
        new AlertDialog.Builder(this)
                .setView(label)
                .setTitle(R.string.customLabelPickerTitle)
                .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());
                        mContactChanged = true;

                        if (addTo != null) {
                            addTo.add(entry);
                            buildViews();
                            entry.view.requestFocus(View.FOCUS_DOWN);
                        }
                    }
                })
                .setNegativeButton(android.R.string.cancel, null)
                .show();
    }
    
    /**
     * Saves or creates the contact based on the mode, and if sucessful finishes the activity.
     */
    private void doSaveAction() {
        // Save or create the contact if needed
        switch (mState) {
            case STATE_EDIT:
                save();
                break;

            case STATE_INSERT:
                create();
                break;

            default:
                Log.e(TAG, "Unknown state in doSaveOrCreate: " + mState);
                break;
        }
        finish();
    }
    
    /**
     * Save the various fields to the existing contact.
     */
    private void save() {
        ContentValues values = new ContentValues();
        String data;
        int numValues = 0;

        // Handle the name and send to voicemail specially
        final String name = mNameView.getText().toString();
        if (name != null && TextUtils.isGraphic(name)) {
            numValues++;
        }
        values.put(People.NAME, name);
        values.put(People.PHONETIC_NAME, mPhoneticNameView.getText().toString());
        mResolver.update(mUri, values, null, null);

        if (mPhotoChanged) {
            // Only write the photo if it's changed, since we don't initially load mPhoto
            if (mPhoto != null) {
                ByteArrayOutputStream stream = new ByteArrayOutputStream();
                mPhoto.compress(Bitmap.CompressFormat.JPEG, 75, stream);
                Contacts.People.setPhotoData(mResolver, mUri, stream.toByteArray());
            } else {
                Contacts.People.setPhotoData(mResolver, mUri, null);
            }
        }

        int entryCount = ContactEntryAdapter.countEntries(mSections, false);
        for (int i = 0; i < entryCount; i++) {
            EditEntry entry = ContactEntryAdapter.getEntry(mSections, i, false);
            int kind = entry.kind;
            data = entry.getData();
            boolean empty = data == null || !TextUtils.isGraphic(data);
            if (kind == EditEntry.KIND_CONTACT) {
                values.clear();
                if (!empty) {
                    values.put(entry.column, data);
                    mResolver.update(entry.uri, values, null, null);
                    if (!People.CUSTOM_RINGTONE.equals(entry.column) &&
                            !People.SEND_TO_VOICEMAIL.equals(entry.column)) {
                        numValues++;
                    }
                } else {
                    values.put(entry.column, (String) null);
                    mResolver.update(entry.uri, values, null, null);
                }
            } else {
                if (!empty) {
                    values.clear();
                    entry.toValues(values);
                    if (entry.id != 0) {
                        mResolver.update(entry.uri, values, null, null);
                    } else {
                        mResolver.insert(entry.uri, values);
                    }
                    if (!People.CUSTOM_RINGTONE.equals(entry.column) &&
                            !People.SEND_TO_VOICEMAIL.equals(entry.column)) {
                        numValues++;
                    }
                } else if (entry.id != 0) {
                    mResolver.delete(entry.uri, null, null);
                }
            }
        }

        if (numValues == 0) {
            // The contact is completely empty, delete it
            mResolver.delete(mUri, null, null);
            mUri = null;
            setResult(RESULT_CANCELED);
        } else {
            // Add the entry to the my contacts group if it isn't there already
            People.addToMyContactsGroup(mResolver, ContentUris.parseId(mUri));
            setResult(RESULT_OK, new Intent().setData(mUri));

            // Only notify user if we actually changed contact
            if (mContactChanged || mPhotoChanged) {
                Toast.makeText(this, R.string.contactSavedToast, Toast.LENGTH_SHORT).show();
            }
        }
    }

    /**
     * Takes the entered data and saves it to a new contact.
     */
    private void create() {
        ContentValues values = new ContentValues();
        String data;
        int numValues = 0;

        // Create the contact itself
        final String name = mNameView.getText().toString();
        if (name != null && TextUtils.isGraphic(name)) {
            numValues++;
        }
        values.put(People.NAME, name);
        values.put(People.PHONETIC_NAME, mPhoneticNameView.getText().toString());

        // Add the contact to the My Contacts group
        Uri contactUri = People.createPersonInMyContactsGroup(mResolver, values);

        // Add the contact to the group that is being displayed in the contact list
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
        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);
            }
        } else {
            // Check to see if we're not syncing everything and if so if My Contacts is synced.
            // If it isn't then the created contact can end up not in any groups that are
            // currently synced and end up getting removed from the phone, which is really bad.
            boolean syncingEverything = !"0".equals(Contacts.Settings.getSetting(mResolver, null,
                    Contacts.Settings.SYNC_EVERYTHING));
            if (!syncingEverything) {
                boolean syncingMyContacts = false;
                Cursor c = mResolver.query(Groups.CONTENT_URI, new String[] { Groups.SHOULD_SYNC },
                        Groups.SYSTEM_ID + "=?", new String[] { Groups.GROUP_MY_CONTACTS }, null);
                if (c != null) {
                    try {
                        if (c.moveToFirst()) {
                            syncingMyContacts = !"0".equals(c.getString(0));
                        }
                    } finally {
                        c.close();
                    }
                }

                if (!syncingMyContacts) {
                    // Not syncing My Contacts, so find a group that is being synced and stick
                    // the contact in there. We sort the list so at least all contacts
                    // will appear in the same group.
                    c = mResolver.query(Groups.CONTENT_URI, new String[] { Groups._ID },
                            Groups.SHOULD_SYNC + "!=0", null, Groups.DEFAULT_SORT_ORDER);
                    if (c != null) {
                        try {
                            if (c.moveToFirst()) {
                                People.addToGroup(mResolver, ContentUris.parseId(contactUri),
                                        c.getLong(0));
                            }
                        } finally {
                            c.close();
                        }
                    }
                }
            }
        }

        // Handle the photo
        if (mPhoto != null) {
            ByteArrayOutputStream stream = new ByteArrayOutputStream();
            mPhoto.compress(Bitmap.CompressFormat.JPEG, 75, stream);
            Contacts.People.setPhotoData(getContentResolver(), contactUri, stream.toByteArray());
        }

        // Create the contact methods
        int entryCount = ContactEntryAdapter.countEntries(mSections, false);
        for (int i = 0; i < entryCount; i++) {
            EditEntry entry = ContactEntryAdapter.getEntry(mSections, i, false);
            if (entry.kind != EditEntry.KIND_CONTACT) {
                values.clear();
                if (entry.toValues(values)) {
                    // Only create the entry if there is data
                    entry.uri = mResolver.insert(
                            Uri.withAppendedPath(contactUri, entry.contentDirectory), values);
                    entry.id = ContentUris.parseId(entry.uri);
                    if (!People.CUSTOM_RINGTONE.equals(entry.column) &&
                            !People.SEND_TO_VOICEMAIL.equals(entry.column)) {
                        numValues++;
                    }
                }
            } else {
                // Update the contact with any straggling data, like notes
                data = entry.getData();
                values.clear();
                if (data != null && TextUtils.isGraphic(data)) {
                    values.put(entry.column, data);
                    mResolver.update(contactUri, values, null, null);
                    if (!People.CUSTOM_RINGTONE.equals(entry.column) &&
                            !People.SEND_TO_VOICEMAIL.equals(entry.column)) {
                        numValues++;
                    }
                }
            }
        }

        if (numValues == 0) {
            mResolver.delete(contactUri, null, null);
            setResult(RESULT_CANCELED);
        } else {
            mUri = contactUri;
            Intent resultIntent = new Intent()
                    .setData(mUri)
                    .putExtra(Intent.EXTRA_SHORTCUT_NAME, name);
            setResult(RESULT_OK, resultIntent);
            Toast.makeText(this, R.string.contactCreatedToast, Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * Build up the entries to display on the screen.
     *
     * @param extras the extras used to start this activity, may be null
     */
    private void buildEntriesForEdit(Bundle extras) {
        Cursor personCursor = mResolver.query(mUri, CONTACT_PROJECTION, null, null, null);
        if (personCursor == null) {
            Log.e(TAG, "invalid contact uri: " + mUri);
            finish();
            return;
        } else if (!personCursor.moveToFirst()) {
            Log.e(TAG, "invalid contact uri: " + mUri);
            finish();
            personCursor.close();
            return;
        }

        // Clear out the old entries
        int numSections = mSections.size();
        for (int i = 0; i < numSections; i++) {
            mSections.get(i).clear();
        }

        EditEntry entry;

        // Name
        mNameView.setText(personCursor.getString(CONTACT_NAME_COLUMN));
        mNameView.addTextChangedListener(this);

        // Photo
        mPhoto = People.loadContactPhoto(this, mUri, 0, null);
        if (mPhoto == null) {
            setPhotoPresent(false);
        } else {
            setPhotoPresent(true);
            mPhotoImageView.setImageBitmap(mPhoto);
        }
        
        // Organizations
        Uri organizationsUri = Uri.withAppendedPath(mUri, Organizations.CONTENT_DIRECTORY);
        Cursor organizationsCursor = mResolver.query(organizationsUri, ORGANIZATIONS_PROJECTION,
                null, null, null);

        if (organizationsCursor != null) {
            while (organizationsCursor.moveToNext()) {
                int type = organizationsCursor.getInt(ORGANIZATIONS_TYPE_COLUMN);
                String label = organizationsCursor.getString(ORGANIZATIONS_LABEL_COLUMN);
                String company = organizationsCursor.getString(ORGANIZATIONS_COMPANY_COLUMN);
                String title = organizationsCursor.getString(ORGANIZATIONS_TITLE_COLUMN);
                long id = organizationsCursor.getLong(ORGANIZATIONS_ID_COLUMN);
                Uri uri = ContentUris.withAppendedId(Organizations.CONTENT_URI, id);

                // Add an organization entry
                entry = EditEntry.newOrganizationEntry(this, label, type, company, title, uri, id);
                entry.isPrimary = organizationsCursor.getLong(ORGANIZATIONS_ISPRIMARY_COLUMN) != 0;
                mOrgEntries.add(entry);
            }
            organizationsCursor.close();
        }

        // Notes
        if (!personCursor.isNull(CONTACT_NOTES_COLUMN)) {
            entry = EditEntry.newNotesEntry(this, personCursor.getString(CONTACT_NOTES_COLUMN),
                    mUri);
            mNoteEntries.add(entry);
        }

        // Ringtone
        entry = EditEntry.newRingtoneEntry(this,
                personCursor.getString(CONTACT_CUSTOM_RINGTONE_COLUMN), mUri);
        mOtherEntries.add(entry);
        
        // Send to voicemail
        entry = EditEntry.newSendToVoicemailEntry(this,
                personCursor.getString(CONTACT_SEND_TO_VOICEMAIL_COLUMN), mUri);
        mOtherEntries.add(entry);

        // Phonetic name
        mPhoneticNameView.setText(personCursor.getString(CONTACT_PHONETIC_NAME_COLUMN));
        mPhoneticNameView.addTextChangedListener(this);

        personCursor.close();

        // Build up the phone entries
        Uri phonesUri = Uri.withAppendedPath(mUri, People.Phones.CONTENT_DIRECTORY);
        Cursor phonesCursor = mResolver.query(phonesUri, PHONES_PROJECTION,
                null, null, null);

        if (phonesCursor != null) {
            while (phonesCursor.moveToNext()) {
                int type = phonesCursor.getInt(PHONES_TYPE_COLUMN);
                String label = phonesCursor.getString(PHONES_LABEL_COLUMN);
                String number = phonesCursor.getString(PHONES_NUMBER_COLUMN);
                long id = phonesCursor.getLong(PHONES_ID_COLUMN);
                boolean isPrimary = phonesCursor.getLong(PHONES_ISPRIMARY_COLUMN) != 0;
                Uri uri = ContentUris.withAppendedId(phonesUri, id);

                // Add a phone number entry
                entry = EditEntry.newPhoneEntry(this, label, type, number, uri, id);
                entry.isPrimary = isPrimary;
                mPhoneEntries.add(entry);

                // Keep track of which primary types have been added
                if (type == Phones.TYPE_MOBILE) {
                    mMobilePhoneAdded = true;
                }
            }

            phonesCursor.close();
        }

        // Build the contact method entries
        Uri methodsUri = Uri.withAppendedPath(mUri, People.ContactMethods.CONTENT_DIRECTORY);
        Cursor methodsCursor = mResolver.query(methodsUri, METHODS_PROJECTION, null, null, null);

        if (methodsCursor != null) {
            while (methodsCursor.moveToNext()) {
                int kind = methodsCursor.getInt(METHODS_KIND_COLUMN);
                String label = methodsCursor.getString(METHODS_LABEL_COLUMN);
                String data = methodsCursor.getString(METHODS_DATA_COLUMN);
                String auxData = methodsCursor.getString(METHODS_AUX_DATA_COLUMN);
                int type = methodsCursor.getInt(METHODS_TYPE_COLUMN);
                long id = methodsCursor.getLong(METHODS_ID_COLUMN);
                boolean isPrimary = methodsCursor.getLong(METHODS_ISPRIMARY_COLUMN) != 0;
                Uri uri = ContentUris.withAppendedId(methodsUri, id);

                switch (kind) {
                    case Contacts.KIND_EMAIL: {
                        entry = EditEntry.newEmailEntry(this, label, type, data, uri, id);
                        entry.isPrimary = isPrimary;
                        mEmailEntries.add(entry);
    
                        if (isPrimary) {
                            mPrimaryEmailAdded = true;
                        }
                        break;
                    }

                    case Contacts.KIND_POSTAL: {
                        entry = EditEntry.newPostalEntry(this, label, type, data, uri, id);
                        entry.isPrimary = isPrimary;
                        mPostalEntries.add(entry);
                        break;
                    }

                    case Contacts.KIND_IM: {
                        Object protocolObj = ContactMethods.decodeImProtocol(auxData);
                        if (protocolObj == null) {
                            // Invalid IM protocol, log it then ignore.
                            Log.e(TAG, "Couldn't decode IM protocol: " + auxData);
                            continue;
                        } else {
                            if (protocolObj instanceof Number) {
                                int protocol = ((Number) protocolObj).intValue();
                                entry = EditEntry.newImEntry(this,
                                        getLabelsForKind(this, Contacts.KIND_IM)[protocol], protocol, 
                                        data, uri, id);
                            } else {
                                entry = EditEntry.newImEntry(this, protocolObj.toString(), -1, data,
                                        uri, id);
                            }
                            mImEntries.add(entry);
                        }
                        break;
                    }
                }
            }

            methodsCursor.close();
        }

        // Add values from the extras, if there are any
        if (extras != null) {
            addFromExtras(extras, phonesUri, methodsUri);
        }

        // Add the base types if needed
        if (!mMobilePhoneAdded) {
            entry = EditEntry.newPhoneEntry(this,
                    Uri.withAppendedPath(mUri, People.Phones.CONTENT_DIRECTORY),
                    DEFAULT_PHONE_TYPE);
            mPhoneEntries.add(entry);
        }

        if (!mPrimaryEmailAdded) {
            entry = EditEntry.newEmailEntry(this,
                    Uri.withAppendedPath(mUri, People.ContactMethods.CONTENT_DIRECTORY),
                    DEFAULT_EMAIL_TYPE);
            entry.isPrimary = true;
            mEmailEntries.add(entry);
        }

        mContactChanged = false;
    }

    /**
     * Build the list of EditEntries for full mode insertions.
     * 
     * @param extras the extras used to start this activity, may be null
     */
    private void buildEntriesForInsert(Bundle extras) {
        // Clear out the old entries
        int numSections = mSections.size();
        for (int i = 0; i < numSections; i++) {
            mSections.get(i).clear();
        }

        EditEntry entry;

        // Check the intent extras
        if (extras != null) {
            addFromExtras(extras, null, null);
        }

        // Photo
        mPhotoImageView.setImageResource(R.drawable.ic_contact_picture);

        // Add the base entries if they're not already present
        if (!mMobilePhoneAdded) {
            entry = EditEntry.newPhoneEntry(this, null, Phones.TYPE_MOBILE);
            entry.isPrimary = true;
            mPhoneEntries.add(entry);
        }

        if (!mPrimaryEmailAdded) {
            entry = EditEntry.newEmailEntry(this, null, DEFAULT_EMAIL_TYPE);
            entry.isPrimary = true;
            mEmailEntries.add(entry);
        }

        // Ringtone
        entry = EditEntry.newRingtoneEntry(this, null, mUri);
        mOtherEntries.add(entry);
        
        // Send to voicemail
        entry = EditEntry.newSendToVoicemailEntry(this, "0", mUri);
        mOtherEntries.add(entry);
    }

    private void addFromExtras(Bundle extras, Uri phonesUri, Uri methodsUri) {
        EditEntry entry;

        // Read the name from the bundle
        CharSequence name = extras.getCharSequence(Insert.NAME);
        if (name != null && TextUtils.isGraphic(name)) {
            mNameView.setText(name);
        }

        // Read the phonetic name from the bundle
        CharSequence phoneticName = extras.getCharSequence(Insert.PHONETIC_NAME);
        if (!TextUtils.isEmpty(phoneticName)) {
            mPhoneticNameView.setText(phoneticName);
        }

        // Postal entries from extras
        CharSequence postal = extras.getCharSequence(Insert.POSTAL);
        int postalType = extras.getInt(Insert.POSTAL_TYPE, INVALID_TYPE);
        if (!TextUtils.isEmpty(postal) && postalType == INVALID_TYPE) {
            postalType = DEFAULT_POSTAL_TYPE;
        }

        if (postalType != INVALID_TYPE) {
            entry = EditEntry.newPostalEntry(this, null, postalType, postal.toString(),
                    methodsUri, 0);
            entry.isPrimary = extras.getBoolean(Insert.POSTAL_ISPRIMARY);
            mPostalEntries.add(entry);
        }

        // Email entries from extras
        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);
   
        // 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);
        CharSequence imProtocol = extras.getCharSequence(Insert.IM_PROTOCOL);
   
        if (imHandle != null && imProtocol != null) {
            Object protocolObj = ContactMethods.decodeImProtocol(imProtocol.toString());
            if (protocolObj instanceof Number) {
                int protocol = ((Number) protocolObj).intValue();
                entry = EditEntry.newImEntry(this,
                        getLabelsForKind(this, Contacts.KIND_IM)[protocol], protocol, 
                        imHandle.toString(), methodsUri, 0);
            } else {
                entry = EditEntry.newImEntry(this, protocolObj.toString(), -1, imHandle.toString(),
                        methodsUri, 0);
            }
            entry.isPrimary = extras.getBoolean(Insert.IM_ISPRIMARY);
            mImEntries.add(entry);
        }
    }

    private void addEmailFromExtras(Bundle extras, Uri methodsUri, String emailField,
            String typeField, String primaryField) {
        CharSequence email = extras.getCharSequence(emailField);
        
        // Correctly handle String in typeField as TYPE_CUSTOM 
        int emailType = INVALID_TYPE;
        String customLabel = null;
        if(extras.get(typeField) instanceof String) {
            emailType = ContactMethods.TYPE_CUSTOM;
            customLabel = extras.getString(typeField);
        } else {
            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, customLabel, 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);
        
        // Correctly handle String in typeField as TYPE_CUSTOM 
        int phoneType = INVALID_TYPE;
        String customLabel = null;
        if(extras.get(typeField) instanceof String) {
            phoneType = Phones.TYPE_CUSTOM;
            customLabel = extras.getString(typeField);
        } else {
            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, customLabel, 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.
     */
    private void buildViews() {
        // Remove existing views
        final LinearLayout layout = mLayout;
        layout.removeAllViews();
        
        buildViewsForSection(layout, mPhoneEntries,
                R.string.listSeparatorCallNumber_edit, SECTION_PHONES);
        buildViewsForSection(layout, mEmailEntries,
                R.string.listSeparatorSendEmail_edit, SECTION_EMAIL);
        buildViewsForSection(layout, mImEntries,
                R.string.listSeparatorSendIm_edit, SECTION_IM);
        buildViewsForSection(layout, mPostalEntries,
                R.string.listSeparatorMapAddress_edit, SECTION_POSTAL);
        buildViewsForSection(layout, mOrgEntries,
                R.string.listSeparatorOrganizations, SECTION_ORG);
        buildViewsForSection(layout, mNoteEntries,
                R.string.label_notes, SECTION_NOTE);
        
        buildOtherViews(layout, mOtherEntries);
    }


    /**
     * Builds the views for a specific section.
     * 
     * @param layout the container
     * @param section the section to build the views for
     */
    private void buildViewsForSection(final LinearLayout layout, ArrayList<EditEntry> section,
            int separatorResource, int sectionType) {
        
        View divider = mInflater.inflate(R.layout.edit_divider, layout, false);
        layout.addView(divider);
        
        // Count up undeleted children
        int activeChildren = 0;
        for (int i = section.size() - 1; i >= 0; i--) {
            EditEntry entry = section.get(i);
            if (!entry.isDeleted) {
                activeChildren++;
            }
        }
        
        // Build the correct group header based on undeleted children
        ViewGroup header;
        if (activeChildren == 0) {
            header = (ViewGroup) mInflater.inflate(R.layout.edit_separator_alone, layout, false);
        } else {
            header = (ViewGroup) mInflater.inflate(R.layout.edit_separator, layout, false);
        }

        // Because we're emulating a ListView, we need to handle focus changes
        // with some additional logic.
        header.setOnFocusChangeListener(this);
        
        TextView text = (TextView) header.findViewById(R.id.text);
        text.setText(getText(separatorResource));
        
        // Force TextView to always default color if we have children.  This makes sure
        // we don't change color when parent is pressed.
        if (activeChildren > 0) {
            ColorStateList stateList = text.getTextColors();
            text.setTextColor(stateList.getDefaultColor());
        }

        View addView = header.findViewById(R.id.separator);
        addView.setTag(Integer.valueOf(sectionType));
        addView.setOnClickListener(this);
        
        // Build views for the current section
        for (EditEntry entry : section) {
            entry.activity = this; // this could be null from when the state is restored
            if (!entry.isDeleted) {
                View view = buildViewForEntry(entry);
                header.addView(view);
            }
        }
        
        layout.addView(header);
    }
    
    private void buildOtherViews(final LinearLayout layout, ArrayList<EditEntry> section) {
        // Build views for the current section, putting a divider between each one
        for (EditEntry entry : section) {
            View divider = mInflater.inflate(R.layout.edit_divider, layout, false);
            layout.addView(divider);

            entry.activity = this; // this could be null from when the state is restored
            View view = buildViewForEntry(entry);
            view.setOnClickListener(this);
            layout.addView(view);
        }
        
        View divider = mInflater.inflate(R.layout.edit_divider, layout, false);
        layout.addView(divider);
    }
    
    /**
     * Builds a view to display an EditEntry.
     * 
     * @param entry the entry to display
     * @return a view that will display the given entry
     */
    /* package */ View buildViewForEntry(final EditEntry entry) {
        // Look for any existing entered text, and save it if found
        if (entry.view != null && entry.syncDataWithView) {
            String enteredText = ((TextView) entry.view.findViewById(R.id.data))
                    .getText().toString();
            if (!TextUtils.isEmpty(enteredText)) {
                entry.data = enteredText;
            }
        }

        // Build a new view
        final ViewGroup parent = mLayout;
        View view;

        // Because we're emulating a ListView, we might need to handle focus changes
        // with some additional logic.
        if (entry.kind == Contacts.KIND_ORGANIZATION) {
            view = mInflater.inflate(R.layout.edit_contact_entry_org, parent, false);
        } else if (isOtherEntry(entry, People.CUSTOM_RINGTONE)) {
            view = mInflater.inflate(R.layout.edit_contact_entry_ringtone, parent, false);
            view.setOnFocusChangeListener(this);
        } else if (isOtherEntry(entry, People.SEND_TO_VOICEMAIL)) {
            view = mInflater.inflate(R.layout.edit_contact_entry_voicemail, parent, false);
            view.setOnFocusChangeListener(this);
        } else if (!entry.isStaticLabel) {
            view = mInflater.inflate(R.layout.edit_contact_entry, parent, false);
        } else {
            view = mInflater.inflate(R.layout.edit_contact_entry_static_label, parent, false);
        }
        entry.view = view;
        
        // Set the entry as the tag so we can find it again later given just the view
        view.setTag(entry);

        // Bind the label
        entry.bindLabel(this);

        // Bind data
        TextView data = (TextView) view.findViewById(R.id.data);
        TextView data2 = (TextView) view.findViewById(R.id.data2);
        
        if (data instanceof Button) {
            data.setOnClickListener(this);
        }
        if (data.length() == 0) {
            if (entry.syncDataWithView) {
                // If there is already data entered don't overwrite it
                data.setText(entry.data);
            } else {
                fillViewData(entry);
            }
        }
        if (data2 != null && data2.length() == 0) {
            // If there is already data entered don't overwrite it
            data2.setText(entry.data2);
        }
        data.setHint(entry.hint);
        if (data2 != null) data2.setHint(entry.hint2);
        if (entry.lines > 1) {
            data.setLines(entry.lines);
            data.setMaxLines(entry.maxLines);
            if (data2 != null) {
                data2.setLines(entry.lines);
                data2.setMaxLines(entry.maxLines);
            }
        }
        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.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
                }
            }
        }
        
        // Give focus to children as requested, possibly after a configuration change
        View focusChild = view.findViewById(entry.requestFocusId);
        if (focusChild != null) {
            focusChild.requestFocus();
            if (focusChild instanceof EditText) {
                ((EditText) focusChild).setSelection(entry.requestCursor);
            }
        }
        
        // Reset requested focus values
        entry.requestFocusId = View.NO_ID;
        entry.requestCursor = 0;

        // Connect listeners up to watch for changed values.
        if (data instanceof EditText) {
            data.addTextChangedListener(this);
        }
        if (data2 instanceof EditText) {
            data2.addTextChangedListener(this);
        }

        // Hook up the delete button
        View delete = view.findViewById(R.id.delete);
        if (delete != null) delete.setOnClickListener(this);
        
        return view;
    }

    private void fillViewData(final EditEntry entry) {
        if (isOtherEntry(entry, People.CUSTOM_RINGTONE)) {
            updateRingtoneView(entry);
        } else if (isOtherEntry(entry, People.SEND_TO_VOICEMAIL)) {
            CheckBox checkBox = (CheckBox) entry.view.findViewById(R.id.checkbox);
            boolean sendToVoicemail = false;
            if (entry.data != null) {
                sendToVoicemail = (Integer.valueOf(entry.data) == 1);
            }
            checkBox.setChecked(sendToVoicemail);
        }
    }
    
    /**
     * Handles the results from the label change picker.
     */
    private final class LabelPickedListener implements DialogInterface.OnClickListener {
        EditEntry mEntry;
        String[] mLabels;

        public LabelPickedListener(EditEntry entry, String[] labels) {
            mEntry = entry;
            mLabels = labels;
        }

        public void onClick(DialogInterface dialog, int which) {
            // TODO: Use a managed dialog
            if (mEntry.kind != Contacts.KIND_IM) {
                final int type = getTypeFromLabelPosition(mLabels, which);
                if (type == ContactMethods.TYPE_CUSTOM) {
                    createCustomPicker(mEntry, null);
                } else {
                    mEntry.setLabel(EditContactActivity.this, type, mLabels[which]);
                    mContactChanged = true;
                }
            } else {
                mEntry.setLabel(EditContactActivity.this, which, mLabels[which]);
                mContactChanged = true;
            }
        }
    }

    /**
     * A basic structure with the data for a contact entry in the list.
     */
    private static final class EditEntry extends ContactEntryAdapter.Entry implements Parcelable {
        // These aren't stuffed into the parcel
        public EditContactActivity activity;
        public View view;

        // These are stuffed into the parcel
        public String hint;
        public String hint2;
        public String column;
        public String contentDirectory;
        public String data2;
        public int contentType;
        public int type;
        /**
         * If 0 or 1, setSingleLine will be called. If negative, setSingleLine
         * will not be called.
         */
        public int lines = 1;
        public boolean isPrimary;
        public boolean isDeleted = false;
        public boolean isStaticLabel = false;
        public boolean syncDataWithView = true;

        /**
         * Request focus on the child of this {@link EditEntry} found using
         * {@link View#findViewById(int)}. This value should be reset to
         * {@link View#NO_ID} after each use.
         */
        public int requestFocusId = View.NO_ID;

        /**
         * If the {@link #requestFocusId} is an {@link EditText}, this value
         * indicates the requested cursor position placement.
         */
        public int requestCursor = 0;

        private EditEntry() {
            // only used by CREATOR
        }

        public EditEntry(EditContactActivity activity) {
            this.activity = activity;
        }

        public EditEntry(EditContactActivity activity, String label,
                int type, String data, Uri uri, long id) {
            this.activity = activity;
            this.isPrimary = false;
            this.label = label;
            this.type = type;
            this.data = data;
            this.uri = uri;
            this.id = id;
        }

        public int describeContents() {
            return 0;
        }

        public void writeToParcel(Parcel parcel, int flags) {
            // Make sure to read data from the input field, if anything is entered
            data = getData();

            // Write in our own fields.
            parcel.writeString(hint);
            parcel.writeString(hint2);
            parcel.writeString(column);
            parcel.writeString(contentDirectory);
            parcel.writeString(data2);
            parcel.writeInt(contentType);
            parcel.writeInt(type);
            parcel.writeInt(lines);
            parcel.writeInt(isPrimary ? 1 : 0);
            parcel.writeInt(isDeleted ? 1 : 0);
            parcel.writeInt(isStaticLabel ? 1 : 0);
            parcel.writeInt(syncDataWithView ? 1 : 0);

            // Write in the fields from Entry
            super.writeToParcel(parcel);
        }

        public static final Parcelable.Creator<EditEntry> CREATOR =
            new Parcelable.Creator<EditEntry>() {
            public EditEntry createFromParcel(Parcel in) {
                EditEntry entry = new EditEntry();

                // Read out our own fields
                entry.hint = in.readString();
                entry.hint2 = in.readString();
                entry.column = in.readString();
                entry.contentDirectory = in.readString();
                entry.data2 = in.readString();
                entry.contentType = in.readInt();
                entry.type = in.readInt();
                entry.lines = in.readInt();
                entry.isPrimary = in.readInt() == 1;
                entry.isDeleted = in.readInt() == 1;
                entry.isStaticLabel = in.readInt() == 1;
                entry.syncDataWithView = in.readInt() == 1;
                
                // Read out the fields from Entry
                entry.readFromParcel(in);

                return entry;
            }
            
            public EditEntry[] newArray(int size) {
                return new EditEntry[size];
            }
        };

        public void setLabel(Context context, int typeIn, String labelIn) {
            type = typeIn;
            label = labelIn;
            if (view != null) {
                bindLabel(context);
            }
        }
        
        public void bindLabel(Context context) {
            TextView v = (TextView) view.findViewById(R.id.label);
            if (isStaticLabel) {
                v.setText(label);
                return;
            }

            switch (kind) {
                case Contacts.KIND_PHONE: {
                    v.setText(Phones.getDisplayLabel(context, type, label));
                    break;
                }

                case Contacts.KIND_IM: {
                    v.setText(getLabelsForKind(activity, kind)[type]);
                    break;
                }
                
                case Contacts.KIND_ORGANIZATION: {
                    v.setText(Organizations.getDisplayLabel(activity, type, label));
                    break;
                }

                default: {
                    v.setText(Contacts.ContactMethods.getDisplayLabel(context, kind, type, label));
                    if (kind == Contacts.KIND_POSTAL) {
                        v.setMaxLines(3);
                    }
                    break;
                }
            }
            v.setOnClickListener(activity);
        }

        /**
         * Returns the data for the entry
         * @return the data for the entry
         */
        public String getData() {
            if (view != null && syncDataWithView) {
                CharSequence text = ((TextView) view.findViewById(R.id.data)).getText();
                if (text != null) {
                    return text.toString();
                }
            }

            if (data != null) {
                return data.toString();
            }

            return null;
        }

        /**
         * Dumps the entry into a HashMap suitable for passing to the database.
         * 
         * @param values the HashMap to fill in.
         * @return true if the value should be saved, false otherwise
         */
        public boolean toValues(ContentValues values) {
            boolean success = false;
            String labelString = null;
            // Save the type and label
            if (view != null) {
                // Read the possibly updated label from the text field
                labelString = ((TextView) view.findViewById(R.id.label)).getText().toString();
            }
            switch (kind) {
                case Contacts.KIND_PHONE:
                    if (type != Phones.TYPE_CUSTOM) {
                        labelString = null;
                    }
                    values.put(Phones.LABEL, labelString);
                    values.put(Phones.TYPE, type);
                    break;

                case Contacts.KIND_EMAIL:
                    if (type != ContactMethods.TYPE_CUSTOM) {
                        labelString = null;
                    }
                    values.put(ContactMethods.LABEL, labelString);
                    values.put(ContactMethods.KIND, kind);
                    values.put(ContactMethods.TYPE, type);
                    break;

                case Contacts.KIND_IM:
                    values.put(ContactMethods.KIND, kind);
                    values.put(ContactMethods.TYPE, ContactMethods.TYPE_OTHER);
                    values.putNull(ContactMethods.LABEL);
                    if (type != -1) {
                        values.put(ContactMethods.AUX_DATA,
                                ContactMethods.encodePredefinedImProtocol(type));
                    } else {
                        values.put(ContactMethods.AUX_DATA,
                                ContactMethods.encodeCustomImProtocol(label.toString()));
                    }
                    break;

                case Contacts.KIND_POSTAL:
                    if (type != ContactMethods.TYPE_CUSTOM) {
                        labelString = null;
                    }
                    values.put(ContactMethods.LABEL, labelString);
                    values.put(ContactMethods.KIND, kind);
                    values.put(ContactMethods.TYPE, type);
                    break;

                case Contacts.KIND_ORGANIZATION:
                    if (type != ContactMethods.TYPE_CUSTOM) {
                        labelString = null;
                    }
                    values.put(ContactMethods.LABEL, labelString);
                    values.put(ContactMethods.TYPE, type);
                    // Save the title
                    if (view != null) {
                        // Read the possibly updated data from the text field
                        data2 = ((TextView) view.findViewById(R.id.data2)).getText().toString();
                    }
                    if (!TextUtils.isGraphic(data2)) {
                        values.putNull(Organizations.TITLE);
                    } else {
                        values.put(Organizations.TITLE, data2.toString());
                        success = true;
                    }
                    break;

                default:
                    Log.w(TAG, "unknown kind " + kind);
                    values.put(ContactMethods.LABEL, labelString);
                    values.put(ContactMethods.KIND, kind);
                    values.put(ContactMethods.TYPE, type);
                    break;
            }

            // Only set the ISPRIMARY flag if part of the incoming data.  This is because the
            // ContentProvider will try finding a new primary when setting to false, meaning
            // it's possible to lose primary altogether as we walk down the list.  If this editor
            // implements editing of primaries in the future, this will need to be revisited.
            if (isPrimary) {
                values.put(ContactMethods.ISPRIMARY, 1);
            }

            // Save the data
            if (view != null && syncDataWithView) {
                // Read the possibly updated data from the text field
                data = ((TextView) view.findViewById(R.id.data)).getText().toString();
            }
            if (!TextUtils.isGraphic(data)) {
                values.putNull(column);
                return success;
            } else {
                values.put(column, data.toString());
                return true;
            }
        }

        /**
         * Create a new empty organization entry
         */
        public static final EditEntry newOrganizationEntry(EditContactActivity activity,
                Uri uri, int type) {
            return newOrganizationEntry(activity, null, type, null, null, uri, 0);
        }

        /**
         * Create a new company entry with the given data.
         */
        public static final EditEntry newOrganizationEntry(EditContactActivity activity,
                String label, int type, String company, String title, Uri uri, long id) {
            EditEntry entry = new EditEntry(activity, label, type, company, uri, id);
            entry.hint = activity.getString(R.string.ghostData_company);
            entry.hint2 = activity.getString(R.string.ghostData_title);
            entry.data2 = title;
            entry.column = Organizations.COMPANY;
            entry.contentDirectory = Organizations.CONTENT_DIRECTORY;
            entry.kind = Contacts.KIND_ORGANIZATION;
            entry.contentType = EditorInfo.TYPE_CLASS_TEXT
                    | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
            return entry;
        }

        /**
         * Create a new notes entry with the given data.
         */
        public static final EditEntry newNotesEntry(EditContactActivity activity,
                String data, Uri uri) {
            EditEntry entry = new EditEntry(activity);
            entry.label = activity.getString(R.string.label_notes);
            entry.hint = activity.getString(R.string.ghostData_notes);
            entry.data = data;
            entry.uri = uri;
            entry.column = People.NOTES;
            entry.maxLines = 10;
            entry.lines = 2;
            entry.id = 0;
            entry.kind = KIND_CONTACT;
            entry.contentType = EditorInfo.TYPE_CLASS_TEXT
                    | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES
                    | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
            entry.isStaticLabel = true;
            return entry;
        }

        /**
         * Create a new ringtone entry with the given data.
         */
        public static final EditEntry newRingtoneEntry(EditContactActivity activity,
                String data, Uri uri) {
            EditEntry entry = new EditEntry(activity);
            entry.label = activity.getString(R.string.label_ringtone);
            entry.data = data;
            entry.uri = uri;
            entry.column = People.CUSTOM_RINGTONE;
            entry.kind = KIND_CONTACT;
            entry.isStaticLabel = true;
            entry.syncDataWithView = false;
            entry.lines = -1;
            return entry;
        }

        /**
         * Create a new send-to-voicemail entry with the given data.
         */
        public static final EditEntry newSendToVoicemailEntry(EditContactActivity activity,
                String data, Uri uri) {
            EditEntry entry = new EditEntry(activity);
            entry.label = activity.getString(R.string.actionIncomingCall);
            entry.data = data;
            entry.uri = uri;
            entry.column = People.SEND_TO_VOICEMAIL;
            entry.kind = KIND_CONTACT;
            entry.isStaticLabel = true;
            entry.syncDataWithView = false;
            entry.lines = -1;
            return entry;
        }

        /**
         * Create a new empty email entry
         */
        public static final EditEntry newPhoneEntry(EditContactActivity activity,
                Uri uri, int type) {
            return newPhoneEntry(activity, null, type, null, uri, 0);
        }

        /**
         * Create a new phone entry with the given data.
         */
        public static final EditEntry newPhoneEntry(EditContactActivity activity,
                String label, int type, String data, Uri uri,
                long id) {
            EditEntry entry = new EditEntry(activity, label, type, data, uri, id);
            entry.hint = activity.getString(R.string.ghostData_phone);
            entry.column = People.Phones.NUMBER;
            entry.contentDirectory = People.Phones.CONTENT_DIRECTORY;
            entry.kind = Contacts.KIND_PHONE;
            entry.contentType = EditorInfo.TYPE_CLASS_PHONE;
            return entry;
        }

        /**
         * Create a new empty email entry
         */
        public static final EditEntry newEmailEntry(EditContactActivity activity,
                Uri uri, int type) {
            return newEmailEntry(activity, null, type, null, uri, 0);
        }

        /**
         * Create a new email entry with the given data.
         */
        public static final EditEntry newEmailEntry(EditContactActivity activity,
                String label, int type, String data, Uri uri,
                long id) {
            EditEntry entry = new EditEntry(activity, label, type, data, uri, id);
            entry.hint = activity.getString(R.string.ghostData_email);
            entry.column = ContactMethods.DATA;
            entry.contentDirectory = People.ContactMethods.CONTENT_DIRECTORY;
            entry.kind = Contacts.KIND_EMAIL;
            entry.contentType = EditorInfo.TYPE_CLASS_TEXT
                    | EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
            return entry;
        }

        /**
         * Create a new empty postal address entry
         */
        public static final EditEntry newPostalEntry(EditContactActivity activity,
                Uri uri, int type) {
            return newPostalEntry(activity, null, type, null, uri, 0);
        }

        /**
         * Create a new postal address entry with the given data.
         *
         * @param label label for the item, from the db not the display label
         * @param type the type of postal address
         * @param data the starting data for the entry, may be null
         * @param uri the uri for the entry if it already exists, may be null
         * @param id the id for the entry if it already exists, 0 it it doesn't
         * @return the new EditEntry
         */
        public static final EditEntry newPostalEntry(EditContactActivity activity,
                String label, int type, String data, Uri uri, long id) {
            EditEntry entry = new EditEntry(activity, label, type, data, uri, id);
            entry.hint = activity.getString(R.string.ghostData_postal);
            entry.column = ContactMethods.DATA;
            entry.contentDirectory = People.ContactMethods.CONTENT_DIRECTORY;
            entry.kind = Contacts.KIND_POSTAL;
            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;
        }

        /**
         * Create a new IM address entry
         */
        public static final EditEntry newImEntry(EditContactActivity activity,
                Uri uri, int type) {
            return newImEntry(activity, null, type, null, uri, 0);
        }

        /**
         * Create a new IM address entry with the given data.
         *
         * @param label label for the item, from the db not the display label
         * @param protocol the type used
         * @param data the starting data for the entry, may be null
         * @param uri the uri for the entry if it already exists, may be null
         * @param id the id for the entry if it already exists, 0 it it doesn't
         * @return the new EditEntry
         */
        public static final EditEntry newImEntry(EditContactActivity activity,
                String label, int protocol, String data, Uri uri, long id) {
            EditEntry entry = new EditEntry(activity, label, protocol, data, uri, id);
            entry.hint = activity.getString(R.string.ghostData_im);
            entry.column = ContactMethods.DATA;
            entry.contentDirectory = People.ContactMethods.CONTENT_DIRECTORY;
            entry.kind = Contacts.KIND_IM;
            entry.contentType = EditorInfo.TYPE_CLASS_TEXT
                    | EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
            return entry;
        }
    }

    public void afterTextChanged(Editable s) {
        // Someone edited a text field, so assume this contact is changed
        mContactChanged = true;
    }

    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        // Do nothing; editing handled by afterTextChanged()
    }

    public void onTextChanged(CharSequence s, int start, int before, int count) {
        // Do nothing; editing handled by afterTextChanged()
    }
    
    public void onFocusChange(View v, boolean hasFocus) {
        // Because we're emulating a ListView, we need to setSelected() for
        // views as they are focused.
        v.setSelected(hasFocus);
    }
}