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

EditContactActivity

public final class EditContactActivity extends android.app.Activity implements View.OnFocusChangeListener, android.text.TextWatcher, View.OnClickListener
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.

Fields Summary
private static final String
TAG
private static final int
STATE_UNKNOWN
private static final int
STATE_EDIT
Editing an existing contact
private static final int
STATE_INSERT
The full insert mode
private static final int
PHOTO_PICKED_WITH_DATA
The launch code when picking a photo and the raw data is returned
private static final int
RINGTONE_PICKED
The launch code when picking a ringtone
static final int
OTHER_ORGANIZATION
static final int
OTHER_NOTE
static final int
DELETE_CONFIRMATION_DIALOG
static final int
SECTION_PHONES
static final int
SECTION_EMAIL
static final int
SECTION_IM
static final int
SECTION_POSTAL
static final int
SECTION_ORG
static final int
SECTION_NOTE
public static final int
MENU_ITEM_SAVE
public static final int
MENU_ITEM_DONT_SAVE
public static final int
MENU_ITEM_DELETE
public static final int
MENU_ITEM_PHOTO
private static final int
INVALID_TYPE
Used to represent an invalid type for a contact entry
private static final int
DEFAULT_PHONE_TYPE
The default type for a phone that is added via an intent
private static final int
DEFAULT_EMAIL_TYPE
The default type for an email that is added via an intent
private static final int
DEFAULT_POSTAL_TYPE
The default type for a postal address that is added via an intent
private int
mState
private boolean
mInsert
private android.net.Uri
mUri
private android.graphics.Bitmap
mPhoto
In insert mode this is the photo
private boolean
mPhotoChanged
private android.widget.EditText
mNameView
private android.widget.ImageView
mPhotoImageView
private android.view.ViewGroup
mContentView
private android.widget.LinearLayout
mLayout
private android.view.LayoutInflater
mInflater
private android.view.MenuItem
mPhotoMenuItem
private boolean
mPhotoPresent
private android.widget.EditText
mPhoneticNameView
private boolean
mContactChanged
Flag marking this contact as changed, meaning we should write changes back.
android.content.ContentResolver
mResolver
ArrayList
mPhoneEntries
ArrayList
mEmailEntries
ArrayList
mImEntries
ArrayList
mPostalEntries
ArrayList
mOrgEntries
ArrayList
mNoteEntries
ArrayList
mOtherEntries
ArrayList
mSections
static final int
MSG_DELETE
static final int
MSG_CHANGE_LABEL
static final int
MSG_ADD_PHONE
static final int
MSG_ADD_EMAIL
static final int
MSG_ADD_POSTAL
private static final int[]
TYPE_PRECEDENCE_PHONES
private static final int[]
TYPE_PRECEDENCE_METHODS
private static final int[]
TYPE_PRECEDENCE_IM
private static final int[]
TYPE_PRECEDENCE_ORG
private DialogInterface.OnClickListener
mDeleteContactDialogListener
private boolean
mMobilePhoneAdded
private boolean
mPrimaryEmailAdded
Constructors Summary
Methods Summary
private voidaddEmailFromExtras(android.os.Bundle extras, android.net.Uri methodsUri, java.lang.String emailField, java.lang.String typeField, java.lang.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 voidaddFromExtras(android.os.Bundle extras, android.net.Uri phonesUri, android.net.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 voidaddPhoneFromExtras(android.os.Bundle extras, android.net.Uri phonesUri, java.lang.String phoneField, java.lang.String typeField, java.lang.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;
            }
        }
    
public voidafterTextChanged(android.text.Editable s)

        // Someone edited a text field, so assume this contact is changed
        mContactChanged = true;
    
public voidbeforeTextChanged(java.lang.CharSequence s, int start, int count, int after)

        // Do nothing; editing handled by afterTextChanged()
    
private voidbuildEntriesForEdit(android.os.Bundle extras)
Build up the entries to display on the screen.

param
extras the extras used to start this activity, may be null

        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;
    
private voidbuildEntriesForInsert(android.os.Bundle extras)
Build the list of EditEntries for full mode insertions.

param
extras the extras used to start this activity, may be null

        // 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 voidbuildOtherViews(android.widget.LinearLayout layout, java.util.ArrayList 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);
    
android.view.ViewbuildViewForEntry(com.android.contacts.EditContactActivity$EditEntry entry)
Builds a view to display an EditEntry.

param
entry the entry to display
return
a view that will display the given 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 voidbuildViews()
Removes all existing views, builds new ones for all the entries, and adds them.

        // 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);
    
private voidbuildViewsForSection(android.widget.LinearLayout layout, java.util.ArrayList section, int separatorResource, int sectionType)
Builds the views for a specific section.

param
layout the container
param
section the section to build the views for

        
        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 voidcreate()
Takes the entered data and saves it to a new contact.

        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();
        }
    
private voidcreateCustomPicker(com.android.contacts.EditContactActivity$EditEntry entry, java.util.ArrayList 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();
    
private voiddoAddAction(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 voiddoPickPhotoAction()

        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 voiddoPickRingtone(com.android.contacts.EditContactActivity$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 voiddoRemovePhotoAction()

        mPhoto = null;
        mPhotoChanged = true;
        setPhotoPresent(false);
    
private voiddoRevertAction()

        finish();
    
private voiddoSaveAction()
Saves or creates the contact based on the mode, and if sucessful finishes the activity.

        // 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();
    
private voidfillViewData(com.android.contacts.EditContactActivity$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);
        }
    
private com.android.contacts.EditContactActivity$EditEntryfindEntryForView(android.view.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;
    
static java.lang.String[]getLabelsForKind(android.content.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;
    
private com.android.contacts.EditContactActivity$EditEntrygetOtherEntry(java.lang.String column)

        for (int i = mOtherEntries.size() - 1; i >= 0; i--) {
            EditEntry entry = mOtherEntries.get(i);
            if (isOtherEntry(entry, column)) {
                return entry;
            }
        }
        return null;
    
intgetTypeFromLabelPosition(java.lang.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 intguessNextType(java.util.ArrayList entries, int[] precedenceList)
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.

        // 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 voidhandleRingtonePicked(android.net.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 static booleanisOtherEntry(com.android.contacts.EditContactActivity$EditEntry entry, java.lang.String column)

        return entry != null && entry.column != null && entry.column.equals(column);
    
protected voidonActivityResult(int requestCode, int resultCode, android.content.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;
            }
        }
    
public voidonClick(android.view.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;
            }
        }
    
protected voidonCreate(android.os.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));
        }
    
protected android.app.DialogonCreateDialog(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);
    
public booleanonCreateOptionsMenu(android.view.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;
    
public voidonFocusChange(android.view.View v, boolean hasFocus)

        // Because we're emulating a ListView, we need to setSelected() for
        // views as they are focused.
        v.setSelected(hasFocus);
    
public booleanonKeyDown(int keyCode, android.view.KeyEvent event)

        switch (keyCode) {
            case KeyEvent.KEYCODE_BACK: {
                doSaveAction();
                return true;
            }
        }
        return super.onKeyDown(keyCode, event);
    
public booleanonOptionsItemSelected(android.view.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;
    
protected voidonRestoreInstanceState(android.os.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);
            }
        }
    
protected voidonSaveInstanceState(android.os.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);
    
public voidonTextChanged(java.lang.CharSequence s, int start, int before, int count)

        // Do nothing; editing handled by afterTextChanged()
    
private voidsave()
Save the various fields to the existing contact.

        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();
            }
        }
    
private voidsetPhotoPresent(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 voidsetupSections()

        mSections.add(mPhoneEntries);
        mSections.add(mEmailEntries);
        mSections.add(mImEntries);
        mSections.add(mPostalEntries);
        mSections.add(mOrgEntries);
        mSections.add(mNoteEntries);
        mSections.add(mOtherEntries);
    
private voidupdateDataView(com.android.contacts.EditContactActivity$EditEntry entry, java.lang.String text)

        TextView dataView = (TextView) entry.view.findViewById(R.id.data);
        dataView.setText(text);
    
private voidupdateRingtoneView(com.android.contacts.EditContactActivity$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);