OSDN Git Service

auto import from //depot/cupcake/@136620
[android-x86/packages-apps-Contacts.git] / src / com / android / contacts / EditContactActivity.java
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.android.contacts;
18
19 import static com.android.contacts.ContactEntryAdapter.CONTACT_CUSTOM_RINGTONE_COLUMN;
20 import static com.android.contacts.ContactEntryAdapter.CONTACT_NAME_COLUMN;
21 import static com.android.contacts.ContactEntryAdapter.CONTACT_NOTES_COLUMN;
22 import static com.android.contacts.ContactEntryAdapter.CONTACT_PROJECTION;
23 import static com.android.contacts.ContactEntryAdapter.CONTACT_SEND_TO_VOICEMAIL_COLUMN;
24 import static com.android.contacts.ContactEntryAdapter.CONTACT_PHONETIC_NAME_COLUMN;
25 import static com.android.contacts.ContactEntryAdapter.METHODS_AUX_DATA_COLUMN;
26 import static com.android.contacts.ContactEntryAdapter.METHODS_DATA_COLUMN;
27 import static com.android.contacts.ContactEntryAdapter.METHODS_ID_COLUMN;
28 import static com.android.contacts.ContactEntryAdapter.METHODS_ISPRIMARY_COLUMN;
29 import static com.android.contacts.ContactEntryAdapter.METHODS_KIND_COLUMN;
30 import static com.android.contacts.ContactEntryAdapter.METHODS_LABEL_COLUMN;
31 import static com.android.contacts.ContactEntryAdapter.METHODS_PROJECTION;
32 import static com.android.contacts.ContactEntryAdapter.METHODS_TYPE_COLUMN;
33 import static com.android.contacts.ContactEntryAdapter.ORGANIZATIONS_COMPANY_COLUMN;
34 import static com.android.contacts.ContactEntryAdapter.ORGANIZATIONS_ID_COLUMN;
35 import static com.android.contacts.ContactEntryAdapter.ORGANIZATIONS_ISPRIMARY_COLUMN;
36 import static com.android.contacts.ContactEntryAdapter.ORGANIZATIONS_LABEL_COLUMN;
37 import static com.android.contacts.ContactEntryAdapter.ORGANIZATIONS_PROJECTION;
38 import static com.android.contacts.ContactEntryAdapter.ORGANIZATIONS_TITLE_COLUMN;
39 import static com.android.contacts.ContactEntryAdapter.ORGANIZATIONS_TYPE_COLUMN;
40 import static com.android.contacts.ContactEntryAdapter.PHONES_ID_COLUMN;
41 import static com.android.contacts.ContactEntryAdapter.PHONES_ISPRIMARY_COLUMN;
42 import static com.android.contacts.ContactEntryAdapter.PHONES_LABEL_COLUMN;
43 import static com.android.contacts.ContactEntryAdapter.PHONES_NUMBER_COLUMN;
44 import static com.android.contacts.ContactEntryAdapter.PHONES_PROJECTION;
45 import static com.android.contacts.ContactEntryAdapter.PHONES_TYPE_COLUMN;
46
47 import android.app.Activity;
48 import android.app.AlertDialog;
49 import android.app.Dialog;
50 import android.content.ActivityNotFoundException;
51 import android.content.ContentResolver;
52 import android.content.ContentUris;
53 import android.content.ContentValues;
54 import android.content.Context;
55 import android.content.DialogInterface;
56 import android.content.Intent;
57 import android.content.SharedPreferences;
58 import android.content.res.ColorStateList;
59 import android.content.res.Resources;
60 import android.database.Cursor;
61 import android.graphics.Bitmap;
62 import android.media.Ringtone;
63 import android.media.RingtoneManager;
64 import android.net.Uri;
65 import android.os.Bundle;
66 import android.os.Parcel;
67 import android.os.Parcelable;
68 import android.preference.PreferenceManager;
69 import android.provider.Contacts;
70 import android.provider.Contacts.ContactMethods;
71 import android.provider.Contacts.Intents.Insert;
72 import android.provider.Contacts.Groups;
73 import android.provider.Contacts.Organizations;
74 import android.provider.Contacts.People;
75 import android.provider.Contacts.Phones;
76 import android.telephony.PhoneNumberFormattingTextWatcher;
77 import android.text.Editable;
78 import android.text.TextUtils;
79 import android.text.TextWatcher;
80 import android.text.method.TextKeyListener;
81 import android.text.method.TextKeyListener.Capitalize;
82 import android.util.Log;
83 import android.util.SparseBooleanArray;
84 import android.view.KeyEvent;
85 import android.view.LayoutInflater;
86 import android.view.Menu;
87 import android.view.MenuItem;
88 import android.view.View;
89 import android.view.ViewGroup;
90 import android.view.ViewParent;
91 import android.view.inputmethod.EditorInfo;
92 import android.widget.Button;
93 import android.widget.CheckBox;
94 import android.widget.EditText;
95 import android.widget.ImageView;
96 import android.widget.LinearLayout;
97 import android.widget.TextView;
98 import android.widget.Toast;
99
100 import java.io.ByteArrayOutputStream;
101 import java.util.ArrayList;
102
103 /**
104  * Activity for editing or inserting a contact. Note that if the contact data changes in the
105  * background while this activity is running, the updates will be overwritten.
106  */
107 public final class EditContactActivity extends Activity implements View.OnClickListener,
108         TextWatcher, View.OnFocusChangeListener {
109     private static final String TAG = "EditContactActivity";
110
111     private static final int STATE_UNKNOWN = 0;
112     /** Editing an existing contact */
113     private static final int STATE_EDIT = 1;
114     /** The full insert mode */
115     private static final int STATE_INSERT = 2;
116
117     /** The launch code when picking a photo and the raw data is returned */
118     private static final int PHOTO_PICKED_WITH_DATA = 3021;
119
120     /** The launch code when picking a ringtone */
121     private static final int RINGTONE_PICKED = 3023;
122     
123     // These correspond to the string array in resources for picker "other" items
124     final static int OTHER_ORGANIZATION = 0;
125     final static int OTHER_NOTE = 1;
126     
127     // Dialog IDs
128     final static int DELETE_CONFIRMATION_DIALOG = 2;
129     
130     // Section IDs
131     final static int SECTION_PHONES = 3;
132     final static int SECTION_EMAIL = 4;
133     final static int SECTION_IM = 5;
134     final static int SECTION_POSTAL = 6;
135     final static int SECTION_ORG = 7;
136     final static int SECTION_NOTE = 8;
137
138     // Menu item IDs
139     public static final int MENU_ITEM_SAVE = 1;
140     public static final int MENU_ITEM_DONT_SAVE = 2;
141     public static final int MENU_ITEM_DELETE = 3;
142     public static final int MENU_ITEM_PHOTO = 6;
143     
144     /** Used to represent an invalid type for a contact entry */
145     private static final int INVALID_TYPE = -1;
146     
147     /** The default type for a phone that is added via an intent */
148     private static final int DEFAULT_PHONE_TYPE = Phones.TYPE_MOBILE;
149
150     /** The default type for an email that is added via an intent */
151     private static final int DEFAULT_EMAIL_TYPE = ContactMethods.TYPE_HOME;
152
153     /** The default type for a postal address that is added via an intent */
154     private static final int DEFAULT_POSTAL_TYPE = ContactMethods.TYPE_HOME;
155
156     private int mState; // saved across instances
157     private boolean mInsert; // saved across instances
158     private Uri mUri; // saved across instances
159     /** In insert mode this is the photo */
160     private Bitmap mPhoto; // saved across instances
161     private boolean mPhotoChanged = false; // saved across instances
162     
163     private EditText mNameView;
164     private ImageView mPhotoImageView;
165     private ViewGroup mContentView;
166     private LinearLayout mLayout;
167     private LayoutInflater mInflater;
168     private MenuItem mPhotoMenuItem;
169     private boolean mPhotoPresent = false;
170     private EditText mPhoneticNameView;  // invisible in some locales, but always present
171
172     /** Flag marking this contact as changed, meaning we should write changes back. */
173     private boolean mContactChanged = false;
174
175     // These are accessed by inner classes. They're package scoped to make access more efficient.
176     /* package */ ContentResolver mResolver;
177     /* package */ ArrayList<EditEntry> mPhoneEntries = new ArrayList<EditEntry>();
178     /* package */ ArrayList<EditEntry> mEmailEntries = new ArrayList<EditEntry>();
179     /* package */ ArrayList<EditEntry> mImEntries = new ArrayList<EditEntry>();
180     /* package */ ArrayList<EditEntry> mPostalEntries = new ArrayList<EditEntry>();
181     /* package */ ArrayList<EditEntry> mOrgEntries = new ArrayList<EditEntry>();
182     /* package */ ArrayList<EditEntry> mNoteEntries = new ArrayList<EditEntry>();
183     /* package */ ArrayList<EditEntry> mOtherEntries = new ArrayList<EditEntry>();
184     /* package */ ArrayList<ArrayList<EditEntry>> mSections = new ArrayList<ArrayList<EditEntry>>();
185     
186     /* package */ static final int MSG_DELETE = 1;
187     /* package */ static final int MSG_CHANGE_LABEL = 2;
188     /* package */ static final int MSG_ADD_PHONE = 3;
189     /* package */ static final int MSG_ADD_EMAIL = 4;
190     /* package */ static final int MSG_ADD_POSTAL = 5;
191
192     private static final int[] TYPE_PRECEDENCE_PHONES = new int[] {
193             Phones.TYPE_MOBILE, Phones.TYPE_HOME, Phones.TYPE_WORK, Phones.TYPE_OTHER
194     };
195     private static final int[] TYPE_PRECEDENCE_METHODS = new int[] {
196             ContactMethods.TYPE_HOME, ContactMethods.TYPE_WORK, ContactMethods.TYPE_OTHER
197     };
198     private static final int[] TYPE_PRECEDENCE_IM = new int[] {
199             ContactMethods.PROTOCOL_GOOGLE_TALK, ContactMethods.PROTOCOL_AIM,
200             ContactMethods.PROTOCOL_MSN, ContactMethods.PROTOCOL_YAHOO,
201             ContactMethods.PROTOCOL_JABBER
202     };
203     private static final int[] TYPE_PRECEDENCE_ORG = new int[] {
204             Organizations.TYPE_WORK, Organizations.TYPE_OTHER
205     };
206
207     public void onClick(View v) {
208         switch (v.getId()) {
209             case R.id.photoImage: {
210                 doPickPhotoAction();
211                 break;
212             }
213             
214             case R.id.checkable: {
215                 CheckBox checkBox = (CheckBox) v.findViewById(R.id.checkbox);
216                 checkBox.toggle();
217                 
218                 EditEntry entry = findEntryForView(v);
219                 entry.data = checkBox.isChecked() ? "1" : "0";
220                 
221                 mContactChanged = true;
222                 break;
223             }
224             
225             case R.id.entry_ringtone: {
226                 EditEntry entry = findEntryForView(v);
227                 doPickRingtone(entry);
228                 break;
229             }
230             
231             case R.id.separator: {
232                 // Someone clicked on a section header, so handle add action
233                 int sectionType = (Integer) v.getTag();
234                 doAddAction(sectionType);
235                 break;
236             }
237
238             case R.id.saveButton:
239                 doSaveAction();
240                 break;
241
242             case R.id.discardButton:
243                 doRevertAction();
244                 break;
245
246             case R.id.delete: {
247                 EditEntry entry = findEntryForView(v);
248                 if (entry != null) {
249                     // Clear the text and hide the view so it gets saved properly
250                     ((TextView) entry.view.findViewById(R.id.data)).setText(null);
251                     entry.view.setVisibility(View.GONE);
252                     entry.isDeleted = true;
253                 }
254                 
255                 // Force rebuild of views because section headers might need to change
256                 buildViews();
257                 break;
258             }
259
260             case R.id.label: {
261                 EditEntry entry = findEntryForView(v);
262                 if (entry != null) {
263                     String[] labels = getLabelsForKind(this, entry.kind);
264                     LabelPickedListener listener = new LabelPickedListener(entry, labels);
265                     new AlertDialog.Builder(EditContactActivity.this)
266                             .setItems(labels, listener)
267                             .setTitle(R.string.selectLabel)
268                             .show();
269                 }
270                 break;
271             }
272         }
273     }
274
275     private void setPhotoPresent(boolean present) {
276         mPhotoPresent = present;
277         
278         // Correctly scale the contact photo if present, otherwise just center
279         // the photo placeholder icon.
280         if (mPhotoPresent) {
281             mPhotoImageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
282         } else {
283             mPhotoImageView.setImageResource(R.drawable.ic_menu_add_picture);
284             mPhotoImageView.setScaleType(ImageView.ScaleType.CENTER);
285         }
286         
287         if (mPhotoMenuItem != null) {
288             if (present) {
289                 mPhotoMenuItem.setTitle(R.string.removePicture);
290                 mPhotoMenuItem.setIcon(android.R.drawable.ic_menu_delete);
291             } else {
292                 mPhotoMenuItem.setTitle(R.string.addPicture);
293                 mPhotoMenuItem.setIcon(R.drawable.ic_menu_add_picture);
294             }
295         }
296     }
297     
298     private EditEntry findEntryForView(View v) {
299         // Try to find the entry for this view
300         EditEntry entry = null;
301         do {
302             Object tag = v.getTag();
303             if (tag != null && tag instanceof EditEntry) {
304                 entry = (EditEntry) tag;
305                 break;
306             } else {
307                 ViewParent parent = v.getParent();
308                 if (parent != null && parent instanceof View) {
309                     v = (View) parent;
310                 } else {
311                     v = null;
312                 }
313             }
314         } while (v != null);
315         return entry;
316     }
317
318     private DialogInterface.OnClickListener mDeleteContactDialogListener =
319             new DialogInterface.OnClickListener() {
320         public void onClick(DialogInterface dialog, int button) {
321             mResolver.delete(mUri, null, null);
322             finish();
323         }
324     };
325
326     private boolean mMobilePhoneAdded = false;
327     private boolean mPrimaryEmailAdded = false;
328
329     @Override
330     protected void onCreate(Bundle icicle) {
331         super.onCreate(icicle);
332
333         mResolver = getContentResolver();
334
335         // Build the list of sections
336         setupSections();
337
338         // Load the UI
339         mInflater = getLayoutInflater();
340         mContentView = (ViewGroup)mInflater.inflate(R.layout.edit_contact, null);
341         setContentView(mContentView);
342         
343         mLayout = (LinearLayout) findViewById(R.id.list);
344         mNameView = (EditText) findViewById(R.id.name);
345         mPhotoImageView = (ImageView) findViewById(R.id.photoImage);
346         mPhotoImageView.setOnClickListener(this);
347         mPhoneticNameView = (EditText) findViewById(R.id.phonetic_name);
348         
349         // Setup the bottom buttons
350         View view = findViewById(R.id.saveButton);
351         view.setOnClickListener(this);
352         view = findViewById(R.id.discardButton);
353         view.setOnClickListener(this);
354
355         // Resolve the intent
356         mState = STATE_UNKNOWN;
357         Intent intent = getIntent();
358         String action = intent.getAction();
359         mUri = intent.getData();
360         if (mUri != null) {
361             if (action.equals(Intent.ACTION_EDIT)) {
362                 if (icicle == null) {
363                     // Build the entries & views
364                     buildEntriesForEdit(getIntent().getExtras());
365                     buildViews();
366                 }
367                 setTitle(R.string.editContact_title_edit);
368                 mState = STATE_EDIT;
369             } else if (action.equals(Intent.ACTION_INSERT)) {
370                 if (icicle == null) {
371                     // Build the entries & views
372                     buildEntriesForInsert(getIntent().getExtras());
373                     buildViews();
374                 }
375                 setTitle(R.string.editContact_title_insert);
376                 mState = STATE_INSERT;
377                 mInsert = true;
378             }
379         }
380
381         if (mState == STATE_UNKNOWN) {
382             Log.e(TAG, "Cannot resolve intent: " + intent);
383             finish();
384             return;
385         }
386
387         if (mState == STATE_EDIT) {
388             setTitle(getResources().getText(R.string.editContact_title_edit));
389         } else {
390             setTitle(getResources().getText(R.string.editContact_title_insert));
391         }
392     }
393
394     private void setupSections() {
395         mSections.add(mPhoneEntries);
396         mSections.add(mEmailEntries);
397         mSections.add(mImEntries);
398         mSections.add(mPostalEntries);
399         mSections.add(mOrgEntries);
400         mSections.add(mNoteEntries);
401         mSections.add(mOtherEntries);
402     }
403     
404     @Override
405     protected void onSaveInstanceState(Bundle outState) {
406         
407         // To store current focus between config changes, follow focus down the
408         // view tree, keeping track of any parents with EditEntry tags
409         View focusedChild = mContentView.getFocusedChild();
410         EditEntry focusedEntry = null;
411         while (focusedChild != null) {
412             Object tag = focusedChild.getTag();
413             if (tag instanceof EditEntry) {
414                 focusedEntry = (EditEntry) tag;
415             }
416             
417             // Keep going deeper until child isn't a group
418             if (focusedChild instanceof ViewGroup) {
419                 View deeperFocus = ((ViewGroup) focusedChild).getFocusedChild();
420                 if (deeperFocus != null) {
421                     focusedChild = deeperFocus;
422                 } else {
423                     break;
424                 }
425             } else {
426                 break;
427             }
428         }
429         
430         if (focusedChild != null) {
431             int requestFocusId = focusedChild.getId();
432             int requestCursor = 0;
433             if (focusedChild instanceof EditText) {
434                 requestCursor = ((EditText) focusedChild).getSelectionStart();
435             }
436             
437             // Store focus values in EditEntry if found, otherwise store as
438             // generic values
439             if (focusedEntry != null) {
440                 focusedEntry.requestFocusId = requestFocusId;
441                 focusedEntry.requestCursor = requestCursor;
442             } else {
443                 outState.putInt("requestFocusId", requestFocusId);
444                 outState.putInt("requestCursor", requestCursor);
445             }
446         }
447         
448         outState.putParcelableArrayList("phoneEntries", mPhoneEntries);
449         outState.putParcelableArrayList("emailEntries", mEmailEntries);
450         outState.putParcelableArrayList("imEntries", mImEntries);
451         outState.putParcelableArrayList("postalEntries", mPostalEntries);
452         outState.putParcelableArrayList("orgEntries", mOrgEntries);
453         outState.putParcelableArrayList("noteEntries", mNoteEntries);
454         outState.putParcelableArrayList("otherEntries", mOtherEntries);
455         outState.putInt("state", mState);
456         outState.putBoolean("insert", mInsert);
457         outState.putParcelable("uri", mUri);
458         outState.putString("name", mNameView.getText().toString());
459         outState.putParcelable("photo", mPhoto);
460         outState.putBoolean("photoChanged", mPhotoChanged);
461         outState.putString("phoneticName", mPhoneticNameView.getText().toString());
462         outState.putBoolean("contactChanged", mContactChanged);
463     }
464
465     @Override
466     protected void onRestoreInstanceState(Bundle inState) {
467         mPhoneEntries = inState.getParcelableArrayList("phoneEntries");
468         mEmailEntries = inState.getParcelableArrayList("emailEntries");
469         mImEntries = inState.getParcelableArrayList("imEntries");
470         mPostalEntries = inState.getParcelableArrayList("postalEntries");
471         mOrgEntries = inState.getParcelableArrayList("orgEntries");
472         mNoteEntries = inState.getParcelableArrayList("noteEntries");
473         mOtherEntries = inState.getParcelableArrayList("otherEntries");
474         setupSections();
475
476         mState = inState.getInt("state");
477         mInsert = inState.getBoolean("insert");
478         mUri = inState.getParcelable("uri");
479         mNameView.setText(inState.getString("name"));
480         mPhoto = inState.getParcelable("photo");
481         if (mPhoto != null) {
482             mPhotoImageView.setImageBitmap(mPhoto);
483             setPhotoPresent(true);
484         } else {
485             mPhotoImageView.setImageResource(R.drawable.ic_contact_picture);
486             setPhotoPresent(false);
487         }
488         mPhotoChanged = inState.getBoolean("photoChanged");
489         mPhoneticNameView.setText(inState.getString("phoneticName"));
490         mContactChanged = inState.getBoolean("contactChanged");
491
492         // Now that everything is restored, build the view
493         buildViews();
494         
495         // Try restoring any generally requested focus
496         int requestFocusId = inState.getInt("requestFocusId", View.NO_ID);
497         View focusedChild = mContentView.findViewById(requestFocusId);
498         if (focusedChild != null) {
499             focusedChild.requestFocus();
500             if (focusedChild instanceof EditText) {
501                 int requestCursor = inState.getInt("requestCursor", 0);
502                 ((EditText) focusedChild).setSelection(requestCursor);
503             }
504         }
505     }
506
507     @Override
508     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
509         if (resultCode != RESULT_OK) {
510             return;
511         }
512
513         switch (requestCode) {
514             case PHOTO_PICKED_WITH_DATA: {
515                 final Bundle extras = data.getExtras();
516                 if (extras != null) {
517                     Bitmap photo = extras.getParcelable("data");
518                     mPhoto = photo;
519                     mPhotoChanged = true;
520                     mPhotoImageView.setImageBitmap(photo);
521                     setPhotoPresent(true);
522                 }
523                 break;
524             }
525
526             case RINGTONE_PICKED: {
527                 Uri pickedUri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
528                 handleRingtonePicked(pickedUri);
529                 mContactChanged = true;
530                 break;
531             }
532         }
533     }
534     
535     @Override
536     public boolean onKeyDown(int keyCode, KeyEvent event) {
537         switch (keyCode) {
538             case KeyEvent.KEYCODE_BACK: {
539                 doSaveAction();
540                 return true;
541             }
542         }
543         return super.onKeyDown(keyCode, event);
544     }
545     
546     @Override
547     public boolean onCreateOptionsMenu(Menu menu) {
548         super.onCreateOptionsMenu(menu);
549         menu.add(0, MENU_ITEM_SAVE, 0, R.string.menu_done)
550                 .setIcon(android.R.drawable.ic_menu_save)
551                 .setAlphabeticShortcut('\n');
552         menu.add(0, MENU_ITEM_DONT_SAVE, 0, R.string.menu_doNotSave)
553                 .setIcon(android.R.drawable.ic_menu_close_clear_cancel)
554                 .setAlphabeticShortcut('q');
555         if (!mInsert) {
556             menu.add(0, MENU_ITEM_DELETE, 0, R.string.menu_deleteContact)
557                     .setIcon(android.R.drawable.ic_menu_delete);
558         }
559
560         mPhotoMenuItem = menu.add(0, MENU_ITEM_PHOTO, 0, null);
561         // Updates the state of the menu item
562         setPhotoPresent(mPhotoPresent);
563
564         return true;
565     }
566
567     @Override
568     public boolean onOptionsItemSelected(MenuItem item) {
569         switch (item.getItemId()) {
570             case MENU_ITEM_SAVE:
571                 doSaveAction();
572                 return true;
573     
574             case MENU_ITEM_DONT_SAVE:
575                 doRevertAction();
576                 return true;
577     
578             case MENU_ITEM_DELETE:
579                 // Get confirmation
580                 showDialog(DELETE_CONFIRMATION_DIALOG);
581                 return true;
582     
583             case MENU_ITEM_PHOTO:
584                 if (!mPhotoPresent) {
585                     doPickPhotoAction();
586                 } else {
587                     doRemovePhotoAction();
588                 }
589                 return true;
590         }
591
592         return false;
593     }
594     
595     /**
596      * Try guessing the next-best type of {@link EditEntry} to insert into the
597      * given list. We walk down the precedence list until we find a type that
598      * doesn't exist yet, or default to the lowest ranking type.
599      */
600     private int guessNextType(ArrayList<EditEntry> entries, int[] precedenceList) {
601         // Keep track of the types we've seen already
602         SparseBooleanArray existAlready = new SparseBooleanArray(entries.size());
603         for (int i = entries.size() - 1; i >= 0; i--) {
604             EditEntry entry = entries.get(i);
605             if (!entry.isDeleted) {
606                 existAlready.put(entry.type, true);
607             }
608         }
609         
610         // Pick the first item we haven't seen
611         for (int type : precedenceList) {
612             if (!existAlready.get(type, false)) {
613                 return type;
614             }
615         }
616         
617         // Otherwise default to last item
618         return precedenceList[precedenceList.length - 1];
619     }
620
621     private void doAddAction(int sectionType) {
622         EditEntry entry = null;
623         switch (sectionType) {
624             case SECTION_PHONES: {
625                 // Try figuring out which type to insert next
626                 int nextType = guessNextType(mPhoneEntries, TYPE_PRECEDENCE_PHONES);
627                 entry = EditEntry.newPhoneEntry(EditContactActivity.this,
628                         Uri.withAppendedPath(mUri, People.Phones.CONTENT_DIRECTORY),
629                         nextType);
630                 mPhoneEntries.add(entry);
631                 break;
632             }
633             case SECTION_EMAIL: {
634                 // Try figuring out which type to insert next
635                 int nextType = guessNextType(mEmailEntries, TYPE_PRECEDENCE_METHODS);
636                 entry = EditEntry.newEmailEntry(EditContactActivity.this,
637                         Uri.withAppendedPath(mUri, People.ContactMethods.CONTENT_DIRECTORY),
638                         nextType);
639                 mEmailEntries.add(entry);
640                 break;
641             }
642             case SECTION_IM: {
643                 // Try figuring out which type to insert next
644                 int nextType = guessNextType(mImEntries, TYPE_PRECEDENCE_IM);
645                 entry = EditEntry.newImEntry(EditContactActivity.this,
646                         Uri.withAppendedPath(mUri, People.ContactMethods.CONTENT_DIRECTORY),
647                         nextType);
648                 mImEntries.add(entry);
649                 break;
650             }
651             case SECTION_POSTAL: {
652                 int nextType = guessNextType(mPostalEntries, TYPE_PRECEDENCE_METHODS);
653                 entry = EditEntry.newPostalEntry(EditContactActivity.this,
654                         Uri.withAppendedPath(mUri, People.ContactMethods.CONTENT_DIRECTORY),
655                         nextType);
656                 mPostalEntries.add(entry);
657                 break;
658             }
659             case SECTION_ORG: {
660                 int nextType = guessNextType(mOrgEntries, TYPE_PRECEDENCE_ORG);
661                 entry = EditEntry.newOrganizationEntry(EditContactActivity.this,
662                         Uri.withAppendedPath(mUri, Organizations.CONTENT_DIRECTORY),
663                         nextType);
664                 mOrgEntries.add(entry);
665                 break;
666             }
667             case SECTION_NOTE: {
668                 entry = EditEntry.newNotesEntry(EditContactActivity.this, null, mUri);
669                 mNoteEntries.add(entry);
670                 break;
671             }
672         }
673         
674         // Rebuild the views if needed
675         if (entry != null) {
676             buildViews();
677             mContactChanged = true;
678
679             View dataView = entry.view.findViewById(R.id.data);
680             if (dataView == null) {
681                 entry.view.requestFocus();
682             } else {
683                 dataView.requestFocus();
684             }
685         }
686     }
687
688     private void doRevertAction() {
689         finish();
690     }
691
692     private void doPickPhotoAction() {
693         Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
694         // TODO: get these values from constants somewhere
695         intent.setType("image/*");
696         intent.putExtra("crop", "true");
697         intent.putExtra("aspectX", 1);
698         intent.putExtra("aspectY", 1);
699         intent.putExtra("outputX", 96);
700         intent.putExtra("outputY", 96);
701         try {
702             intent.putExtra("return-data", true);
703             startActivityForResult(intent, PHOTO_PICKED_WITH_DATA);
704         } catch (ActivityNotFoundException e) {
705             new AlertDialog.Builder(EditContactActivity.this)
706                 .setTitle(R.string.errorDialogTitle)
707                 .setMessage(R.string.photoPickerNotFoundText)
708                 .setPositiveButton(android.R.string.ok, null)
709                 .show();
710         }
711     }
712
713     private void doRemovePhotoAction() {
714         mPhoto = null;
715         mPhotoChanged = true;
716         setPhotoPresent(false);
717     }
718     
719     private void doPickRingtone(EditEntry entry) {
720         Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
721         // Allow user to pick 'Default'
722         intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
723         // Show only ringtones
724         intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_RINGTONE);
725         // Don't show 'Silent'
726         intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false);
727         
728         Uri ringtoneUri;
729         if (entry.data != null) {
730             ringtoneUri = Uri.parse(entry.data);
731         } else {
732             // Otherwise pick default ringtone Uri so that something is selected.
733             ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
734         }
735         
736         // Put checkmark next to the current ringtone for this contact
737         intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, ringtoneUri);
738         // Launch!
739         startActivityForResult(intent, RINGTONE_PICKED);
740     }
741     
742     private void handleRingtonePicked(Uri pickedUri) {
743         EditEntry entry = getOtherEntry(People.CUSTOM_RINGTONE);
744         if (entry == null) {
745             Log.w(TAG, "Ringtone picked but could not find ringtone entry");
746             return;
747         }
748         
749         if (pickedUri == null || RingtoneManager.isDefault(pickedUri)) {
750             entry.data = null;
751         } else {
752             entry.data = pickedUri.toString();
753         }
754         
755         updateRingtoneView(entry);
756     }
757
758     private void updateRingtoneView(EditEntry entry) {
759         String ringtoneName;
760         if (entry.data == null) {
761             ringtoneName = getString(R.string.default_ringtone);
762         } else {
763             Uri ringtoneUri = Uri.parse(entry.data);
764             Ringtone ringtone = RingtoneManager.getRingtone(this, ringtoneUri);
765             if (ringtone == null) {
766                 Log.w(TAG, "ringtone's URI doesn't resolve to a Ringtone");
767                 return;
768             }
769             ringtoneName = ringtone.getTitle(this);
770         }
771         
772         updateDataView(entry, ringtoneName);
773     }
774     
775     private void updateDataView(EditEntry entry, String text) {
776         TextView dataView = (TextView) entry.view.findViewById(R.id.data);
777         dataView.setText(text);
778     }
779     
780     @Override
781     protected Dialog onCreateDialog(int id) {
782         switch (id) {
783             case DELETE_CONFIRMATION_DIALOG:
784                 return new AlertDialog.Builder(EditContactActivity.this)
785                         .setTitle(R.string.deleteConfirmation_title)
786                         .setIcon(android.R.drawable.ic_dialog_alert)
787                         .setMessage(R.string.deleteConfirmation)
788                         .setNegativeButton(android.R.string.cancel, null)
789                         .setPositiveButton(android.R.string.ok, mDeleteContactDialogListener)
790                         .setCancelable(false)
791                         .create();
792         }
793         return super.onCreateDialog(id);
794     }
795     
796     static String[] getLabelsForKind(Context context, int kind) {
797         final Resources resources = context.getResources();
798         switch (kind) {
799             case Contacts.KIND_PHONE:
800                 return resources.getStringArray(android.R.array.phoneTypes);
801             case Contacts.KIND_EMAIL:
802                 return resources.getStringArray(android.R.array.emailAddressTypes);
803             case Contacts.KIND_POSTAL:
804                 return resources.getStringArray(android.R.array.postalAddressTypes);
805             case Contacts.KIND_IM:
806                 return resources.getStringArray(android.R.array.imProtocols);
807             case Contacts.KIND_ORGANIZATION:
808                 return resources.getStringArray(android.R.array.organizationTypes);
809             case EditEntry.KIND_CONTACT:
810                 return resources.getStringArray(R.array.otherLabels);
811         }
812         return null;
813     }
814
815     int getTypeFromLabelPosition(CharSequence[] labels, int labelPosition) {
816         // In the UI Custom... comes last, but it is uses the constant 0
817         // so it is in the same location across the various kinds. Fix up the
818         // position to a valid type here.
819         if (labelPosition == labels.length - 1) {
820             return ContactMethods.TYPE_CUSTOM;
821         } else {
822             return labelPosition + 1;
823         }
824     }
825     
826     private EditEntry getOtherEntry(String column) {
827         for (int i = mOtherEntries.size() - 1; i >= 0; i--) {
828             EditEntry entry = mOtherEntries.get(i);
829             if (isOtherEntry(entry, column)) {
830                 return entry;
831             }
832         }
833         return null;
834     }
835     
836     private static boolean isOtherEntry(EditEntry entry, String column) {
837         return entry != null && entry.column != null && entry.column.equals(column);
838     }
839     
840     private void createCustomPicker(final EditEntry entry, final ArrayList<EditEntry> addTo) {
841         final EditText label = new EditText(this);
842         label.setKeyListener(TextKeyListener.getInstance(false, Capitalize.WORDS));
843         label.requestFocus();
844         new AlertDialog.Builder(this)
845                 .setView(label)
846                 .setTitle(R.string.customLabelPickerTitle)
847                 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
848                     public void onClick(DialogInterface dialog, int which) {
849                         entry.setLabel(EditContactActivity.this, ContactMethods.TYPE_CUSTOM,
850                                 label.getText().toString());
851                         mContactChanged = true;
852
853                         if (addTo != null) {
854                             addTo.add(entry);
855                             buildViews();
856                             entry.view.requestFocus(View.FOCUS_DOWN);
857                         }
858                     }
859                 })
860                 .setNegativeButton(android.R.string.cancel, null)
861                 .show();
862     }
863     
864     /**
865      * Saves or creates the contact based on the mode, and if sucessful finishes the activity.
866      */
867     private void doSaveAction() {
868         // Save or create the contact if needed
869         switch (mState) {
870             case STATE_EDIT:
871                 save();
872                 break;
873
874             case STATE_INSERT:
875                 create();
876                 break;
877
878             default:
879                 Log.e(TAG, "Unknown state in doSaveOrCreate: " + mState);
880                 break;
881         }
882         finish();
883     }
884     
885     /**
886      * Save the various fields to the existing contact.
887      */
888     private void save() {
889         ContentValues values = new ContentValues();
890         String data;
891         int numValues = 0;
892
893         // Handle the name and send to voicemail specially
894         final String name = mNameView.getText().toString();
895         if (name != null && TextUtils.isGraphic(name)) {
896             numValues++;
897         }
898         values.put(People.NAME, name);
899         values.put(People.PHONETIC_NAME, mPhoneticNameView.getText().toString());
900         mResolver.update(mUri, values, null, null);
901
902         if (mPhotoChanged) {
903             // Only write the photo if it's changed, since we don't initially load mPhoto
904             if (mPhoto != null) {
905                 ByteArrayOutputStream stream = new ByteArrayOutputStream();
906                 mPhoto.compress(Bitmap.CompressFormat.JPEG, 75, stream);
907                 Contacts.People.setPhotoData(mResolver, mUri, stream.toByteArray());
908             } else {
909                 Contacts.People.setPhotoData(mResolver, mUri, null);
910             }
911         }
912
913         int entryCount = ContactEntryAdapter.countEntries(mSections, false);
914         for (int i = 0; i < entryCount; i++) {
915             EditEntry entry = ContactEntryAdapter.getEntry(mSections, i, false);
916             int kind = entry.kind;
917             data = entry.getData();
918             boolean empty = data == null || !TextUtils.isGraphic(data);
919             if (kind == EditEntry.KIND_CONTACT) {
920                 values.clear();
921                 if (!empty) {
922                     values.put(entry.column, data);
923                     mResolver.update(entry.uri, values, null, null);
924                     numValues++;
925                 } else {
926                     values.put(entry.column, (String) null);
927                     mResolver.update(entry.uri, values, null, null);
928                 }
929             } else {
930                 if (!empty) {
931                     values.clear();
932                     entry.toValues(values);
933                     if (entry.id != 0) {
934                         mResolver.update(entry.uri, values, null, null);
935                     } else {
936                         mResolver.insert(entry.uri, values);
937                     }
938                     numValues++;
939                 } else if (entry.id != 0) {
940                     mResolver.delete(entry.uri, null, null);
941                 }
942             }
943         }
944
945         if (numValues == 0) {
946             // The contact is completely empty, delete it
947             mResolver.delete(mUri, null, null);
948             mUri = null;
949             setResult(RESULT_CANCELED);
950         } else {
951             // Add the entry to the my contacts group if it isn't there already
952             People.addToMyContactsGroup(mResolver, ContentUris.parseId(mUri));
953             setResult(RESULT_OK, new Intent().setData(mUri));
954
955             // Only notify user if we actually changed contact
956             if (mContactChanged || mPhotoChanged) {
957                 Toast.makeText(this, R.string.contactSavedToast, Toast.LENGTH_SHORT).show();
958             }
959         }
960     }
961
962     /**
963      * Takes the entered data and saves it to a new contact.
964      */
965     private void create() {
966         ContentValues values = new ContentValues();
967         String data;
968         int numValues = 0;
969
970         // Create the contact itself
971         final String name = mNameView.getText().toString();
972         if (name != null && TextUtils.isGraphic(name)) {
973             numValues++;
974         }
975         values.put(People.NAME, name);
976         values.put(People.PHONETIC_NAME, mPhoneticNameView.getText().toString());
977
978         // Add the contact to the My Contacts group
979         Uri contactUri = People.createPersonInMyContactsGroup(mResolver, values);
980
981         // Add the contact to the group that is being displayed in the contact list
982         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
983         int displayType = prefs.getInt(ContactsListActivity.PREF_DISPLAY_TYPE,
984                 ContactsListActivity.DISPLAY_TYPE_UNKNOWN);
985         if (displayType == ContactsListActivity.DISPLAY_TYPE_USER_GROUP) {
986             String displayGroup = prefs.getString(ContactsListActivity.PREF_DISPLAY_INFO,
987                     null);
988             if (!TextUtils.isEmpty(displayGroup)) {
989                 People.addToGroup(mResolver, ContentUris.parseId(contactUri), displayGroup);
990             }
991         } else {
992             // Check to see if we're not syncing everything and if so if My Contacts is synced.
993             // If it isn't then the created contact can end up not in any groups that are
994             // currently synced and end up getting removed from the phone, which is really bad.
995             boolean syncingEverything = !"0".equals(Contacts.Settings.getSetting(mResolver, null,
996                     Contacts.Settings.SYNC_EVERYTHING));
997             if (!syncingEverything) {
998                 boolean syncingMyContacts = false;
999                 Cursor c = mResolver.query(Groups.CONTENT_URI, new String[] { Groups.SHOULD_SYNC },
1000                         Groups.SYSTEM_ID + "=?", new String[] { Groups.GROUP_MY_CONTACTS }, null);
1001                 if (c != null) {
1002                     try {
1003                         if (c.moveToFirst()) {
1004                             syncingMyContacts = !"0".equals(c.getString(0));
1005                         }
1006                     } finally {
1007                         c.close();
1008                     }
1009                 }
1010
1011                 if (!syncingMyContacts) {
1012                     // Not syncing My Contacts, so find a group that is being synced and stick
1013                     // the contact in there. We sort the list so at least all contacts
1014                     // will appear in the same group.
1015                     c = mResolver.query(Groups.CONTENT_URI, new String[] { Groups._ID },
1016                             Groups.SHOULD_SYNC + "!=0", null, Groups.DEFAULT_SORT_ORDER);
1017                     if (c != null) {
1018                         try {
1019                             if (c.moveToFirst()) {
1020                                 People.addToGroup(mResolver, ContentUris.parseId(contactUri),
1021                                         c.getLong(0));
1022                             }
1023                         } finally {
1024                             c.close();
1025                         }
1026                     }
1027                 }
1028             }
1029         }
1030
1031         // Handle the photo
1032         if (mPhoto != null) {
1033             ByteArrayOutputStream stream = new ByteArrayOutputStream();
1034             mPhoto.compress(Bitmap.CompressFormat.JPEG, 75, stream);
1035             Contacts.People.setPhotoData(getContentResolver(), contactUri, stream.toByteArray());
1036         }
1037
1038         // Create the contact methods
1039         int entryCount = ContactEntryAdapter.countEntries(mSections, false);
1040         for (int i = 0; i < entryCount; i++) {
1041             EditEntry entry = ContactEntryAdapter.getEntry(mSections, i, false);
1042             if (entry.kind != EditEntry.KIND_CONTACT) {
1043                 values.clear();
1044                 if (entry.toValues(values)) {
1045                     // Only create the entry if there is data
1046                     entry.uri = mResolver.insert(
1047                             Uri.withAppendedPath(contactUri, entry.contentDirectory), values);
1048                     entry.id = ContentUris.parseId(entry.uri);
1049                     numValues++;
1050                 }
1051             } else {
1052                 // Update the contact with any straggling data, like notes
1053                 data = entry.getData();
1054                 values.clear();
1055                 if (data != null && TextUtils.isGraphic(data)) {
1056                     values.put(entry.column, data);
1057                     mResolver.update(contactUri, values, null, null);
1058                     numValues++;
1059                 }
1060             }
1061         }
1062
1063         if (numValues == 0) {
1064             mResolver.delete(contactUri, null, null);
1065             setResult(RESULT_CANCELED);
1066         } else {
1067             mUri = contactUri;
1068             Intent resultIntent = new Intent()
1069                     .setData(mUri)
1070                     .putExtra(Intent.EXTRA_SHORTCUT_NAME, name);
1071             setResult(RESULT_OK, resultIntent);
1072             Toast.makeText(this, R.string.contactCreatedToast, Toast.LENGTH_SHORT).show();
1073         }
1074     }
1075
1076     /**
1077      * Build up the entries to display on the screen.
1078      *
1079      * @param extras the extras used to start this activity, may be null
1080      */
1081     private void buildEntriesForEdit(Bundle extras) {
1082         Cursor personCursor = mResolver.query(mUri, CONTACT_PROJECTION, null, null, null);
1083         if (personCursor == null) {
1084             Log.e(TAG, "invalid contact uri: " + mUri);
1085             finish();
1086             return;
1087         } else if (!personCursor.moveToFirst()) {
1088             Log.e(TAG, "invalid contact uri: " + mUri);
1089             finish();
1090             personCursor.close();
1091             return;
1092         }
1093
1094         // Clear out the old entries
1095         int numSections = mSections.size();
1096         for (int i = 0; i < numSections; i++) {
1097             mSections.get(i).clear();
1098         }
1099
1100         EditEntry entry;
1101
1102         // Name
1103         mNameView.setText(personCursor.getString(CONTACT_NAME_COLUMN));
1104         mNameView.addTextChangedListener(this);
1105
1106         // Photo
1107         mPhoto = People.loadContactPhoto(this, mUri, 0, null);
1108         if (mPhoto == null) {
1109             setPhotoPresent(false);
1110         } else {
1111             setPhotoPresent(true);
1112             mPhotoImageView.setImageBitmap(mPhoto);
1113         }
1114         
1115         // Organizations
1116         Uri organizationsUri = Uri.withAppendedPath(mUri, Organizations.CONTENT_DIRECTORY);
1117         Cursor organizationsCursor = mResolver.query(organizationsUri, ORGANIZATIONS_PROJECTION,
1118                 null, null, null);
1119
1120         if (organizationsCursor != null) {
1121             while (organizationsCursor.moveToNext()) {
1122                 int type = organizationsCursor.getInt(ORGANIZATIONS_TYPE_COLUMN);
1123                 String label = organizationsCursor.getString(ORGANIZATIONS_LABEL_COLUMN);
1124                 String company = organizationsCursor.getString(ORGANIZATIONS_COMPANY_COLUMN);
1125                 String title = organizationsCursor.getString(ORGANIZATIONS_TITLE_COLUMN);
1126                 long id = organizationsCursor.getLong(ORGANIZATIONS_ID_COLUMN);
1127                 Uri uri = ContentUris.withAppendedId(Organizations.CONTENT_URI, id);
1128
1129                 // Add an organization entry
1130                 entry = EditEntry.newOrganizationEntry(this, label, type, company, title, uri, id);
1131                 entry.isPrimary = organizationsCursor.getLong(ORGANIZATIONS_ISPRIMARY_COLUMN) != 0;
1132                 mOrgEntries.add(entry);
1133             }
1134             organizationsCursor.close();
1135         }
1136
1137         // Notes
1138         if (!personCursor.isNull(CONTACT_NOTES_COLUMN)) {
1139             entry = EditEntry.newNotesEntry(this, personCursor.getString(CONTACT_NOTES_COLUMN),
1140                     mUri);
1141             mNoteEntries.add(entry);
1142         }
1143
1144         // Ringtone
1145         entry = EditEntry.newRingtoneEntry(this,
1146                 personCursor.getString(CONTACT_CUSTOM_RINGTONE_COLUMN), mUri);
1147         mOtherEntries.add(entry);
1148         
1149         // Send to voicemail
1150         entry = EditEntry.newSendToVoicemailEntry(this,
1151                 personCursor.getString(CONTACT_SEND_TO_VOICEMAIL_COLUMN), mUri);
1152         mOtherEntries.add(entry);
1153
1154         // Phonetic name
1155         mPhoneticNameView.setText(personCursor.getString(CONTACT_PHONETIC_NAME_COLUMN));
1156         mPhoneticNameView.addTextChangedListener(this);
1157
1158         personCursor.close();
1159
1160         // Build up the phone entries
1161         Uri phonesUri = Uri.withAppendedPath(mUri, People.Phones.CONTENT_DIRECTORY);
1162         Cursor phonesCursor = mResolver.query(phonesUri, PHONES_PROJECTION,
1163                 null, null, null);
1164
1165         if (phonesCursor != null) {
1166             while (phonesCursor.moveToNext()) {
1167                 int type = phonesCursor.getInt(PHONES_TYPE_COLUMN);
1168                 String label = phonesCursor.getString(PHONES_LABEL_COLUMN);
1169                 String number = phonesCursor.getString(PHONES_NUMBER_COLUMN);
1170                 long id = phonesCursor.getLong(PHONES_ID_COLUMN);
1171                 boolean isPrimary = phonesCursor.getLong(PHONES_ISPRIMARY_COLUMN) != 0;
1172                 Uri uri = ContentUris.withAppendedId(phonesUri, id);
1173
1174                 // Add a phone number entry
1175                 entry = EditEntry.newPhoneEntry(this, label, type, number, uri, id);
1176                 entry.isPrimary = isPrimary;
1177                 mPhoneEntries.add(entry);
1178
1179                 // Keep track of which primary types have been added
1180                 if (type == Phones.TYPE_MOBILE) {
1181                     mMobilePhoneAdded = true;
1182                 }
1183             }
1184
1185             phonesCursor.close();
1186         }
1187
1188         // Build the contact method entries
1189         Uri methodsUri = Uri.withAppendedPath(mUri, People.ContactMethods.CONTENT_DIRECTORY);
1190         Cursor methodsCursor = mResolver.query(methodsUri, METHODS_PROJECTION, null, null, null);
1191
1192         if (methodsCursor != null) {
1193             while (methodsCursor.moveToNext()) {
1194                 int kind = methodsCursor.getInt(METHODS_KIND_COLUMN);
1195                 String label = methodsCursor.getString(METHODS_LABEL_COLUMN);
1196                 String data = methodsCursor.getString(METHODS_DATA_COLUMN);
1197                 String auxData = methodsCursor.getString(METHODS_AUX_DATA_COLUMN);
1198                 int type = methodsCursor.getInt(METHODS_TYPE_COLUMN);
1199                 long id = methodsCursor.getLong(METHODS_ID_COLUMN);
1200                 boolean isPrimary = methodsCursor.getLong(METHODS_ISPRIMARY_COLUMN) != 0;
1201                 Uri uri = ContentUris.withAppendedId(methodsUri, id);
1202
1203                 switch (kind) {
1204                     case Contacts.KIND_EMAIL: {
1205                         entry = EditEntry.newEmailEntry(this, label, type, data, uri, id);
1206                         entry.isPrimary = isPrimary;
1207                         mEmailEntries.add(entry);
1208     
1209                         if (isPrimary) {
1210                             mPrimaryEmailAdded = true;
1211                         }
1212                         break;
1213                     }
1214
1215                     case Contacts.KIND_POSTAL: {
1216                         entry = EditEntry.newPostalEntry(this, label, type, data, uri, id);
1217                         entry.isPrimary = isPrimary;
1218                         mPostalEntries.add(entry);
1219                         break;
1220                     }
1221
1222                     case Contacts.KIND_IM: {
1223                         Object protocolObj = ContactMethods.decodeImProtocol(auxData);
1224                         if (protocolObj == null) {
1225                             // Invalid IM protocol, log it then ignore.
1226                             Log.e(TAG, "Couldn't decode IM protocol: " + auxData);
1227                             continue;
1228                         } else {
1229                             if (protocolObj instanceof Number) {
1230                                 int protocol = ((Number) protocolObj).intValue();
1231                                 entry = EditEntry.newImEntry(this,
1232                                         getLabelsForKind(this, Contacts.KIND_IM)[protocol], protocol, 
1233                                         data, uri, id);
1234                             } else {
1235                                 entry = EditEntry.newImEntry(this, protocolObj.toString(), -1, data,
1236                                         uri, id);
1237                             }
1238                             mImEntries.add(entry);
1239                         }
1240                         break;
1241                     }
1242                 }
1243             }
1244
1245             methodsCursor.close();
1246         }
1247
1248         // Add values from the extras, if there are any
1249         if (extras != null) {
1250             addFromExtras(extras, phonesUri, methodsUri);
1251         }
1252
1253         // Add the base types if needed
1254         if (!mMobilePhoneAdded) {
1255             entry = EditEntry.newPhoneEntry(this,
1256                     Uri.withAppendedPath(mUri, People.Phones.CONTENT_DIRECTORY),
1257                     DEFAULT_PHONE_TYPE);
1258             mPhoneEntries.add(entry);
1259         }
1260
1261         if (!mPrimaryEmailAdded) {
1262             entry = EditEntry.newEmailEntry(this,
1263                     Uri.withAppendedPath(mUri, People.ContactMethods.CONTENT_DIRECTORY),
1264                     DEFAULT_EMAIL_TYPE);
1265             entry.isPrimary = true;
1266             mEmailEntries.add(entry);
1267         }
1268
1269         mContactChanged = false;
1270     }
1271
1272     /**
1273      * Build the list of EditEntries for full mode insertions.
1274      * 
1275      * @param extras the extras used to start this activity, may be null
1276      */
1277     private void buildEntriesForInsert(Bundle extras) {
1278         // Clear out the old entries
1279         int numSections = mSections.size();
1280         for (int i = 0; i < numSections; i++) {
1281             mSections.get(i).clear();
1282         }
1283
1284         EditEntry entry;
1285
1286         // Check the intent extras
1287         if (extras != null) {
1288             addFromExtras(extras, null, null);
1289         }
1290
1291         // Photo
1292         mPhotoImageView.setImageResource(R.drawable.ic_contact_picture);
1293
1294         // Add the base entries if they're not already present
1295         if (!mMobilePhoneAdded) {
1296             entry = EditEntry.newPhoneEntry(this, null, Phones.TYPE_MOBILE);
1297             entry.isPrimary = true;
1298             mPhoneEntries.add(entry);
1299         }
1300
1301         if (!mPrimaryEmailAdded) {
1302             entry = EditEntry.newEmailEntry(this, null, DEFAULT_EMAIL_TYPE);
1303             entry.isPrimary = true;
1304             mEmailEntries.add(entry);
1305         }
1306
1307         // Ringtone
1308         entry = EditEntry.newRingtoneEntry(this, null, mUri);
1309         mOtherEntries.add(entry);
1310         
1311         // Send to voicemail
1312         entry = EditEntry.newSendToVoicemailEntry(this, "0", mUri);
1313         mOtherEntries.add(entry);
1314     }
1315
1316     private void addFromExtras(Bundle extras, Uri phonesUri, Uri methodsUri) {
1317         EditEntry entry;
1318
1319         // Read the name from the bundle
1320         CharSequence name = extras.getCharSequence(Insert.NAME);
1321         if (name != null && TextUtils.isGraphic(name)) {
1322             mNameView.setText(name);
1323         }
1324
1325         // Read the phonetic name from the bundle
1326         CharSequence phoneticName = extras.getCharSequence(Insert.PHONETIC_NAME);
1327         if (!TextUtils.isEmpty(phoneticName)) {
1328             mPhoneticNameView.setText(phoneticName);
1329         }
1330
1331         // Postal entries from extras
1332         CharSequence postal = extras.getCharSequence(Insert.POSTAL);
1333         int postalType = extras.getInt(Insert.POSTAL_TYPE, INVALID_TYPE);
1334         if (!TextUtils.isEmpty(postal) && postalType == INVALID_TYPE) {
1335             postalType = DEFAULT_POSTAL_TYPE;
1336         }
1337
1338         if (postalType != INVALID_TYPE) {
1339             entry = EditEntry.newPostalEntry(this, null, postalType, postal.toString(),
1340                     methodsUri, 0);
1341             entry.isPrimary = extras.getBoolean(Insert.POSTAL_ISPRIMARY);
1342             mPostalEntries.add(entry);
1343         }
1344
1345         // Email entries from extras
1346         addEmailFromExtras(extras, methodsUri, Insert.EMAIL, Insert.EMAIL_TYPE,
1347                 Insert.EMAIL_ISPRIMARY);
1348         addEmailFromExtras(extras, methodsUri, Insert.SECONDARY_EMAIL, Insert.SECONDARY_EMAIL_TYPE,
1349                 null);
1350         addEmailFromExtras(extras, methodsUri, Insert.TERTIARY_EMAIL, Insert.TERTIARY_EMAIL_TYPE,
1351                 null);
1352    
1353         // Phone entries from extras
1354         addPhoneFromExtras(extras, phonesUri, Insert.PHONE, Insert.PHONE_TYPE,
1355                 Insert.PHONE_ISPRIMARY);
1356         addPhoneFromExtras(extras, phonesUri, Insert.SECONDARY_PHONE, Insert.SECONDARY_PHONE_TYPE,
1357                 null);
1358         addPhoneFromExtras(extras, phonesUri, Insert.TERTIARY_PHONE, Insert.TERTIARY_PHONE_TYPE,
1359                 null);
1360
1361         // IM entries from extras
1362         CharSequence imHandle = extras.getCharSequence(Insert.IM_HANDLE);
1363         CharSequence imProtocol = extras.getCharSequence(Insert.IM_PROTOCOL);
1364    
1365         if (imHandle != null && imProtocol != null) {
1366             Object protocolObj = ContactMethods.decodeImProtocol(imProtocol.toString());
1367             if (protocolObj instanceof Number) {
1368                 int protocol = ((Number) protocolObj).intValue();
1369                 entry = EditEntry.newImEntry(this,
1370                         getLabelsForKind(this, Contacts.KIND_IM)[protocol], protocol, 
1371                         imHandle.toString(), methodsUri, 0);
1372             } else {
1373                 entry = EditEntry.newImEntry(this, protocolObj.toString(), -1, imHandle.toString(),
1374                         methodsUri, 0);
1375             }
1376             entry.isPrimary = extras.getBoolean(Insert.IM_ISPRIMARY);
1377             mImEntries.add(entry);
1378         }
1379     }
1380
1381     private void addEmailFromExtras(Bundle extras, Uri methodsUri, String emailField,
1382             String typeField, String primaryField) {
1383         CharSequence email = extras.getCharSequence(emailField);
1384         
1385         // Correctly handle String in typeField as TYPE_CUSTOM 
1386         int emailType = INVALID_TYPE;
1387         String customLabel = null;
1388         if(extras.get(typeField) instanceof String) {
1389             emailType = ContactMethods.TYPE_CUSTOM;
1390             customLabel = extras.getString(typeField);
1391         } else {
1392             emailType = extras.getInt(typeField, INVALID_TYPE);
1393         }
1394
1395         if (!TextUtils.isEmpty(email) && emailType == INVALID_TYPE) {
1396             emailType = DEFAULT_EMAIL_TYPE;
1397             mPrimaryEmailAdded = true;
1398         }
1399
1400         if (emailType != INVALID_TYPE) {
1401             EditEntry entry = EditEntry.newEmailEntry(this, customLabel, emailType, email.toString(),
1402                     methodsUri, 0);
1403             entry.isPrimary = (primaryField == null) ? false : extras.getBoolean(primaryField);
1404             mEmailEntries.add(entry);
1405
1406             // Keep track of which primary types have been added
1407             if (entry.isPrimary) {
1408                 mPrimaryEmailAdded = true;
1409             }
1410         }
1411     }
1412
1413     private void addPhoneFromExtras(Bundle extras, Uri phonesUri, String phoneField,
1414             String typeField, String primaryField) {
1415         CharSequence phoneNumber = extras.getCharSequence(phoneField);
1416         
1417         // Correctly handle String in typeField as TYPE_CUSTOM 
1418         int phoneType = INVALID_TYPE;
1419         String customLabel = null;
1420         if(extras.get(typeField) instanceof String) {
1421             phoneType = Phones.TYPE_CUSTOM;
1422             customLabel = extras.getString(typeField);
1423         } else {
1424             phoneType = extras.getInt(typeField, INVALID_TYPE);
1425         }
1426         
1427         if (!TextUtils.isEmpty(phoneNumber) && phoneType == INVALID_TYPE) {
1428             phoneType = DEFAULT_PHONE_TYPE;
1429         }
1430
1431         if (phoneType != INVALID_TYPE) {
1432             EditEntry entry = EditEntry.newPhoneEntry(this, customLabel, phoneType,
1433                     phoneNumber.toString(), phonesUri, 0);
1434             entry.isPrimary = (primaryField == null) ? false : extras.getBoolean(primaryField);
1435             mPhoneEntries.add(entry);
1436
1437             // Keep track of which primary types have been added
1438             if (phoneType == Phones.TYPE_MOBILE) {
1439                 mMobilePhoneAdded = true;
1440             }
1441         }
1442     }
1443
1444     /**
1445      * Removes all existing views, builds new ones for all the entries, and adds them.
1446      */
1447     private void buildViews() {
1448         // Remove existing views
1449         final LinearLayout layout = mLayout;
1450         layout.removeAllViews();
1451         
1452         buildViewsForSection(layout, mPhoneEntries,
1453                 R.string.listSeparatorCallNumber_edit, SECTION_PHONES);
1454         buildViewsForSection(layout, mEmailEntries,
1455                 R.string.listSeparatorSendEmail_edit, SECTION_EMAIL);
1456         buildViewsForSection(layout, mImEntries,
1457                 R.string.listSeparatorSendIm_edit, SECTION_IM);
1458         buildViewsForSection(layout, mPostalEntries,
1459                 R.string.listSeparatorMapAddress_edit, SECTION_POSTAL);
1460         buildViewsForSection(layout, mOrgEntries,
1461                 R.string.listSeparatorOrganizations, SECTION_ORG);
1462         buildViewsForSection(layout, mNoteEntries,
1463                 R.string.label_notes, SECTION_NOTE);
1464         
1465         buildOtherViews(layout, mOtherEntries);
1466     }
1467
1468
1469     /**
1470      * Builds the views for a specific section.
1471      * 
1472      * @param layout the container
1473      * @param section the section to build the views for
1474      */
1475     private void buildViewsForSection(final LinearLayout layout, ArrayList<EditEntry> section,
1476             int separatorResource, int sectionType) {
1477         
1478         View divider = mInflater.inflate(R.layout.edit_divider, layout, false);
1479         layout.addView(divider);
1480         
1481         // Count up undeleted children
1482         int activeChildren = 0;
1483         for (int i = section.size() - 1; i >= 0; i--) {
1484             EditEntry entry = section.get(i);
1485             if (!entry.isDeleted) {
1486                 activeChildren++;
1487             }
1488         }
1489         
1490         // Build the correct group header based on undeleted children
1491         ViewGroup header;
1492         if (activeChildren == 0) {
1493             header = (ViewGroup) mInflater.inflate(R.layout.edit_separator_alone, layout, false);
1494         } else {
1495             header = (ViewGroup) mInflater.inflate(R.layout.edit_separator, layout, false);
1496         }
1497
1498         // Because we're emulating a ListView, we need to handle focus changes
1499         // with some additional logic.
1500         header.setOnFocusChangeListener(this);
1501         
1502         TextView text = (TextView) header.findViewById(R.id.text);
1503         text.setText(getText(separatorResource));
1504         
1505         // Force TextView to always default color if we have children.  This makes sure
1506         // we don't change color when parent is pressed.
1507         if (activeChildren > 0) {
1508             ColorStateList stateList = text.getTextColors();
1509             text.setTextColor(stateList.getDefaultColor());
1510         }
1511
1512         View addView = header.findViewById(R.id.separator);
1513         addView.setTag(Integer.valueOf(sectionType));
1514         addView.setOnClickListener(this);
1515         
1516         // Build views for the current section
1517         for (EditEntry entry : section) {
1518             entry.activity = this; // this could be null from when the state is restored
1519             if (!entry.isDeleted) {
1520                 View view = buildViewForEntry(entry);
1521                 header.addView(view);
1522             }
1523         }
1524         
1525         layout.addView(header);
1526     }
1527     
1528     private void buildOtherViews(final LinearLayout layout, ArrayList<EditEntry> section) {
1529         // Build views for the current section, putting a divider between each one
1530         for (EditEntry entry : section) {
1531             View divider = mInflater.inflate(R.layout.edit_divider, layout, false);
1532             layout.addView(divider);
1533
1534             entry.activity = this; // this could be null from when the state is restored
1535             View view = buildViewForEntry(entry);
1536             view.setOnClickListener(this);
1537             layout.addView(view);
1538         }
1539         
1540         View divider = mInflater.inflate(R.layout.edit_divider, layout, false);
1541         layout.addView(divider);
1542     }
1543     
1544     /**
1545      * Builds a view to display an EditEntry.
1546      * 
1547      * @param entry the entry to display
1548      * @return a view that will display the given entry
1549      */
1550     /* package */ View buildViewForEntry(final EditEntry entry) {
1551         // Look for any existing entered text, and save it if found
1552         if (entry.view != null && entry.syncDataWithView) {
1553             String enteredText = ((TextView) entry.view.findViewById(R.id.data))
1554                     .getText().toString();
1555             if (!TextUtils.isEmpty(enteredText)) {
1556                 entry.data = enteredText;
1557             }
1558         }
1559
1560         // Build a new view
1561         final ViewGroup parent = mLayout;
1562         View view;
1563
1564         // Because we're emulating a ListView, we might need to handle focus changes
1565         // with some additional logic.
1566         if (entry.kind == Contacts.KIND_ORGANIZATION) {
1567             view = mInflater.inflate(R.layout.edit_contact_entry_org, parent, false);
1568         } else if (isOtherEntry(entry, People.CUSTOM_RINGTONE)) {
1569             view = mInflater.inflate(R.layout.edit_contact_entry_ringtone, parent, false);
1570             view.setOnFocusChangeListener(this);
1571         } else if (isOtherEntry(entry, People.SEND_TO_VOICEMAIL)) {
1572             view = mInflater.inflate(R.layout.edit_contact_entry_voicemail, parent, false);
1573             view.setOnFocusChangeListener(this);
1574         } else if (!entry.isStaticLabel) {
1575             view = mInflater.inflate(R.layout.edit_contact_entry, parent, false);
1576         } else {
1577             view = mInflater.inflate(R.layout.edit_contact_entry_static_label, parent, false);
1578         }
1579         entry.view = view;
1580         
1581         // Set the entry as the tag so we can find it again later given just the view
1582         view.setTag(entry);
1583
1584         // Bind the label
1585         entry.bindLabel(this);
1586
1587         // Bind data
1588         TextView data = (TextView) view.findViewById(R.id.data);
1589         TextView data2 = (TextView) view.findViewById(R.id.data2);
1590         
1591         if (data instanceof Button) {
1592             data.setOnClickListener(this);
1593         }
1594         if (data.length() == 0) {
1595             if (entry.syncDataWithView) {
1596                 // If there is already data entered don't overwrite it
1597                 data.setText(entry.data);
1598             } else {
1599                 fillViewData(entry);
1600             }
1601         }
1602         if (data2 != null && data2.length() == 0) {
1603             // If there is already data entered don't overwrite it
1604             data2.setText(entry.data2);
1605         }
1606         data.setHint(entry.hint);
1607         if (data2 != null) data2.setHint(entry.hint2);
1608         if (entry.lines > 1) {
1609             data.setLines(entry.lines);
1610             data.setMaxLines(entry.maxLines);
1611             if (data2 != null) {
1612                 data2.setLines(entry.lines);
1613                 data2.setMaxLines(entry.maxLines);
1614             }
1615         }
1616         int contentType = entry.contentType;
1617         if (contentType != EditorInfo.TYPE_NULL) {
1618             data.setInputType(contentType);
1619             if (data2 != null) {
1620                 data2.setInputType(contentType);
1621             }
1622             if ((contentType&EditorInfo.TYPE_MASK_CLASS)
1623                     == EditorInfo.TYPE_CLASS_PHONE) {
1624                 data.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
1625                 if (data2 != null) {
1626                     data2.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
1627                 }
1628             }
1629         }
1630         
1631         // Give focus to children as requested, possibly after a configuration change
1632         View focusChild = view.findViewById(entry.requestFocusId);
1633         if (focusChild != null) {
1634             focusChild.requestFocus();
1635             if (focusChild instanceof EditText) {
1636                 ((EditText) focusChild).setSelection(entry.requestCursor);
1637             }
1638         }
1639         
1640         // Reset requested focus values
1641         entry.requestFocusId = View.NO_ID;
1642         entry.requestCursor = 0;
1643
1644         // Connect listeners up to watch for changed values.
1645         if (data instanceof EditText) {
1646             data.addTextChangedListener(this);
1647         }
1648         if (data2 instanceof EditText) {
1649             data2.addTextChangedListener(this);
1650         }
1651
1652         // Hook up the delete button
1653         View delete = view.findViewById(R.id.delete);
1654         if (delete != null) delete.setOnClickListener(this);
1655         
1656         return view;
1657     }
1658
1659     private void fillViewData(final EditEntry entry) {
1660         if (isOtherEntry(entry, People.CUSTOM_RINGTONE)) {
1661             updateRingtoneView(entry);
1662         } else if (isOtherEntry(entry, People.SEND_TO_VOICEMAIL)) {
1663             CheckBox checkBox = (CheckBox) entry.view.findViewById(R.id.checkbox);
1664             boolean sendToVoicemail = false;
1665             if (entry.data != null) {
1666                 sendToVoicemail = (Integer.valueOf(entry.data) == 1);
1667             }
1668             checkBox.setChecked(sendToVoicemail);
1669         }
1670     }
1671     
1672     /**
1673      * Handles the results from the label change picker.
1674      */
1675     private final class LabelPickedListener implements DialogInterface.OnClickListener {
1676         EditEntry mEntry;
1677         String[] mLabels;
1678
1679         public LabelPickedListener(EditEntry entry, String[] labels) {
1680             mEntry = entry;
1681             mLabels = labels;
1682         }
1683
1684         public void onClick(DialogInterface dialog, int which) {
1685             // TODO: Use a managed dialog
1686             if (mEntry.kind != Contacts.KIND_IM) {
1687                 final int type = getTypeFromLabelPosition(mLabels, which);
1688                 if (type == ContactMethods.TYPE_CUSTOM) {
1689                     createCustomPicker(mEntry, null);
1690                 } else {
1691                     mEntry.setLabel(EditContactActivity.this, type, mLabels[which]);
1692                     mContactChanged = true;
1693                 }
1694             } else {
1695                 mEntry.setLabel(EditContactActivity.this, which, mLabels[which]);
1696                 mContactChanged = true;
1697             }
1698         }
1699     }
1700
1701     /**
1702      * A basic structure with the data for a contact entry in the list.
1703      */
1704     private static final class EditEntry extends ContactEntryAdapter.Entry implements Parcelable {
1705         // These aren't stuffed into the parcel
1706         public EditContactActivity activity;
1707         public View view;
1708
1709         // These are stuffed into the parcel
1710         public String hint;
1711         public String hint2;
1712         public String column;
1713         public String contentDirectory;
1714         public String data2;
1715         public int contentType;
1716         public int type;
1717         /**
1718          * If 0 or 1, setSingleLine will be called. If negative, setSingleLine
1719          * will not be called.
1720          */
1721         public int lines = 1;
1722         public boolean isPrimary;
1723         public boolean isDeleted = false;
1724         public boolean isStaticLabel = false;
1725         public boolean syncDataWithView = true;
1726
1727         /**
1728          * Request focus on the child of this {@link EditEntry} found using
1729          * {@link View#findViewById(int)}. This value should be reset to
1730          * {@link View#NO_ID} after each use.
1731          */
1732         public int requestFocusId = View.NO_ID;
1733
1734         /**
1735          * If the {@link #requestFocusId} is an {@link EditText}, this value
1736          * indicates the requested cursor position placement.
1737          */
1738         public int requestCursor = 0;
1739
1740         private EditEntry() {
1741             // only used by CREATOR
1742         }
1743
1744         public EditEntry(EditContactActivity activity) {
1745             this.activity = activity;
1746         }
1747
1748         public EditEntry(EditContactActivity activity, String label,
1749                 int type, String data, Uri uri, long id) {
1750             this.activity = activity;
1751             this.isPrimary = false;
1752             this.label = label;
1753             this.type = type;
1754             this.data = data;
1755             this.uri = uri;
1756             this.id = id;
1757         }
1758
1759         public int describeContents() {
1760             return 0;
1761         }
1762
1763         public void writeToParcel(Parcel parcel, int flags) {
1764             // Make sure to read data from the input field, if anything is entered
1765             data = getData();
1766
1767             // Write in our own fields.
1768             parcel.writeString(hint);
1769             parcel.writeString(hint2);
1770             parcel.writeString(column);
1771             parcel.writeString(contentDirectory);
1772             parcel.writeString(data2);
1773             parcel.writeInt(contentType);
1774             parcel.writeInt(type);
1775             parcel.writeInt(lines);
1776             parcel.writeInt(isPrimary ? 1 : 0);
1777             parcel.writeInt(isDeleted ? 1 : 0);
1778             parcel.writeInt(isStaticLabel ? 1 : 0);
1779             parcel.writeInt(syncDataWithView ? 1 : 0);
1780
1781             // Write in the fields from Entry
1782             super.writeToParcel(parcel);
1783         }
1784
1785         public static final Parcelable.Creator<EditEntry> CREATOR =
1786             new Parcelable.Creator<EditEntry>() {
1787             public EditEntry createFromParcel(Parcel in) {
1788                 EditEntry entry = new EditEntry();
1789
1790                 // Read out our own fields
1791                 entry.hint = in.readString();
1792                 entry.hint2 = in.readString();
1793                 entry.column = in.readString();
1794                 entry.contentDirectory = in.readString();
1795                 entry.data2 = in.readString();
1796                 entry.contentType = in.readInt();
1797                 entry.type = in.readInt();
1798                 entry.lines = in.readInt();
1799                 entry.isPrimary = in.readInt() == 1;
1800                 entry.isDeleted = in.readInt() == 1;
1801                 entry.isStaticLabel = in.readInt() == 1;
1802                 entry.syncDataWithView = in.readInt() == 1;
1803                 
1804                 // Read out the fields from Entry
1805                 entry.readFromParcel(in);
1806
1807                 return entry;
1808             }
1809             
1810             public EditEntry[] newArray(int size) {
1811                 return new EditEntry[size];
1812             }
1813         };
1814
1815         public void setLabel(Context context, int typeIn, String labelIn) {
1816             type = typeIn;
1817             label = labelIn;
1818             if (view != null) {
1819                 bindLabel(context);
1820             }
1821         }
1822         
1823         public void bindLabel(Context context) {
1824             TextView v = (TextView) view.findViewById(R.id.label);
1825             if (isStaticLabel) {
1826                 v.setText(label);
1827                 return;
1828             }
1829
1830             switch (kind) {
1831                 case Contacts.KIND_PHONE: {
1832                     v.setText(Phones.getDisplayLabel(context, type, label));
1833                     break;
1834                 }
1835
1836                 case Contacts.KIND_IM: {
1837                     v.setText(getLabelsForKind(activity, kind)[type]);
1838                     break;
1839                 }
1840                 
1841                 case Contacts.KIND_ORGANIZATION: {
1842                     v.setText(Organizations.getDisplayLabel(activity, type, label));
1843                     break;
1844                 }
1845
1846                 default: {
1847                     v.setText(Contacts.ContactMethods.getDisplayLabel(context, kind, type, label));
1848                     if (kind == Contacts.KIND_POSTAL) {
1849                         v.setMaxLines(3);
1850                     }
1851                     break;
1852                 }
1853             }
1854             v.setOnClickListener(activity);
1855         }
1856
1857         /**
1858          * Returns the data for the entry
1859          * @return the data for the entry
1860          */
1861         public String getData() {
1862             if (view != null && syncDataWithView) {
1863                 CharSequence text = ((TextView) view.findViewById(R.id.data)).getText();
1864                 if (text != null) {
1865                     return text.toString();
1866                 }
1867             }
1868
1869             if (data != null) {
1870                 return data.toString();
1871             }
1872
1873             return null;
1874         }
1875
1876         /**
1877          * Dumps the entry into a HashMap suitable for passing to the database.
1878          * 
1879          * @param values the HashMap to fill in.
1880          * @return true if the value should be saved, false otherwise
1881          */
1882         public boolean toValues(ContentValues values) {
1883             boolean success = false;
1884             String labelString = null;
1885             // Save the type and label
1886             if (view != null) {
1887                 // Read the possibly updated label from the text field
1888                 labelString = ((TextView) view.findViewById(R.id.label)).getText().toString();
1889             }
1890             switch (kind) {
1891                 case Contacts.KIND_PHONE:
1892                     if (type != Phones.TYPE_CUSTOM) {
1893                         labelString = null;
1894                     }
1895                     values.put(Phones.LABEL, labelString);
1896                     values.put(Phones.TYPE, type);
1897                     break;
1898
1899                 case Contacts.KIND_EMAIL:
1900                     if (type != ContactMethods.TYPE_CUSTOM) {
1901                         labelString = null;
1902                     }
1903                     values.put(ContactMethods.LABEL, labelString);
1904                     values.put(ContactMethods.KIND, kind);
1905                     values.put(ContactMethods.TYPE, type);
1906                     break;
1907
1908                 case Contacts.KIND_IM:
1909                     values.put(ContactMethods.KIND, kind);
1910                     values.put(ContactMethods.TYPE, ContactMethods.TYPE_OTHER);
1911                     values.putNull(ContactMethods.LABEL);
1912                     if (type != -1) {
1913                         values.put(ContactMethods.AUX_DATA,
1914                                 ContactMethods.encodePredefinedImProtocol(type));
1915                     } else {
1916                         values.put(ContactMethods.AUX_DATA,
1917                                 ContactMethods.encodeCustomImProtocol(label.toString()));
1918                     }
1919                     break;
1920
1921                 case Contacts.KIND_POSTAL:
1922                     if (type != ContactMethods.TYPE_CUSTOM) {
1923                         labelString = null;
1924                     }
1925                     values.put(ContactMethods.LABEL, labelString);
1926                     values.put(ContactMethods.KIND, kind);
1927                     values.put(ContactMethods.TYPE, type);
1928                     break;
1929
1930                 case Contacts.KIND_ORGANIZATION:
1931                     if (type != ContactMethods.TYPE_CUSTOM) {
1932                         labelString = null;
1933                     }
1934                     values.put(ContactMethods.LABEL, labelString);
1935                     values.put(ContactMethods.TYPE, type);
1936                     // Save the title
1937                     if (view != null) {
1938                         // Read the possibly updated data from the text field
1939                         data2 = ((TextView) view.findViewById(R.id.data2)).getText().toString();
1940                     }
1941                     if (!TextUtils.isGraphic(data2)) {
1942                         values.putNull(Organizations.TITLE);
1943                     } else {
1944                         values.put(Organizations.TITLE, data2.toString());
1945                         success = true;
1946                     }
1947                     break;
1948
1949                 default:
1950                     Log.w(TAG, "unknown kind " + kind);
1951                     values.put(ContactMethods.LABEL, labelString);
1952                     values.put(ContactMethods.KIND, kind);
1953                     values.put(ContactMethods.TYPE, type);
1954                     break;
1955             }
1956
1957             // Only set the ISPRIMARY flag if part of the incoming data.  This is because the
1958             // ContentProvider will try finding a new primary when setting to false, meaning
1959             // it's possible to lose primary altogether as we walk down the list.  If this editor
1960             // implements editing of primaries in the future, this will need to be revisited.
1961             if (isPrimary) {
1962                 values.put(ContactMethods.ISPRIMARY, 1);
1963             }
1964
1965             // Save the data
1966             if (view != null && syncDataWithView) {
1967                 // Read the possibly updated data from the text field
1968                 data = ((TextView) view.findViewById(R.id.data)).getText().toString();
1969             }
1970             if (!TextUtils.isGraphic(data)) {
1971                 values.putNull(column);
1972                 return success;
1973             } else {
1974                 values.put(column, data.toString());
1975                 return true;
1976             }
1977         }
1978
1979         /**
1980          * Create a new empty organization entry
1981          */
1982         public static final EditEntry newOrganizationEntry(EditContactActivity activity,
1983                 Uri uri, int type) {
1984             return newOrganizationEntry(activity, null, type, null, null, uri, 0);
1985         }
1986
1987         /**
1988          * Create a new company entry with the given data.
1989          */
1990         public static final EditEntry newOrganizationEntry(EditContactActivity activity,
1991                 String label, int type, String company, String title, Uri uri, long id) {
1992             EditEntry entry = new EditEntry(activity, label, type, company, uri, id);
1993             entry.hint = activity.getString(R.string.ghostData_company);
1994             entry.hint2 = activity.getString(R.string.ghostData_title);
1995             entry.data2 = title;
1996             entry.column = Organizations.COMPANY;
1997             entry.contentDirectory = Organizations.CONTENT_DIRECTORY;
1998             entry.kind = Contacts.KIND_ORGANIZATION;
1999             entry.contentType = EditorInfo.TYPE_CLASS_TEXT
2000                     | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
2001             return entry;
2002         }
2003
2004         /**
2005          * Create a new notes entry with the given data.
2006          */
2007         public static final EditEntry newNotesEntry(EditContactActivity activity,
2008                 String data, Uri uri) {
2009             EditEntry entry = new EditEntry(activity);
2010             entry.label = activity.getString(R.string.label_notes);
2011             entry.hint = activity.getString(R.string.ghostData_notes);
2012             entry.data = data;
2013             entry.uri = uri;
2014             entry.column = People.NOTES;
2015             entry.maxLines = 10;
2016             entry.lines = 2;
2017             entry.id = 0;
2018             entry.kind = KIND_CONTACT;
2019             entry.contentType = EditorInfo.TYPE_CLASS_TEXT
2020                     | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES
2021                     | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
2022             entry.isStaticLabel = true;
2023             return entry;
2024         }
2025
2026         /**
2027          * Create a new ringtone entry with the given data.
2028          */
2029         public static final EditEntry newRingtoneEntry(EditContactActivity activity,
2030                 String data, Uri uri) {
2031             EditEntry entry = new EditEntry(activity);
2032             entry.label = activity.getString(R.string.label_ringtone);
2033             entry.data = data;
2034             entry.uri = uri;
2035             entry.column = People.CUSTOM_RINGTONE;
2036             entry.kind = KIND_CONTACT;
2037             entry.isStaticLabel = true;
2038             entry.syncDataWithView = false;
2039             entry.lines = -1;
2040             return entry;
2041         }
2042
2043         /**
2044          * Create a new send-to-voicemail entry with the given data.
2045          */
2046         public static final EditEntry newSendToVoicemailEntry(EditContactActivity activity,
2047                 String data, Uri uri) {
2048             EditEntry entry = new EditEntry(activity);
2049             entry.label = activity.getString(R.string.actionIncomingCall);
2050             entry.data = data;
2051             entry.uri = uri;
2052             entry.column = People.SEND_TO_VOICEMAIL;
2053             entry.kind = KIND_CONTACT;
2054             entry.isStaticLabel = true;
2055             entry.syncDataWithView = false;
2056             entry.lines = -1;
2057             return entry;
2058         }
2059
2060         /**
2061          * Create a new empty email entry
2062          */
2063         public static final EditEntry newPhoneEntry(EditContactActivity activity,
2064                 Uri uri, int type) {
2065             return newPhoneEntry(activity, null, type, null, uri, 0);
2066         }
2067
2068         /**
2069          * Create a new phone entry with the given data.
2070          */
2071         public static final EditEntry newPhoneEntry(EditContactActivity activity,
2072                 String label, int type, String data, Uri uri,
2073                 long id) {
2074             EditEntry entry = new EditEntry(activity, label, type, data, uri, id);
2075             entry.hint = activity.getString(R.string.ghostData_phone);
2076             entry.column = People.Phones.NUMBER;
2077             entry.contentDirectory = People.Phones.CONTENT_DIRECTORY;
2078             entry.kind = Contacts.KIND_PHONE;
2079             entry.contentType = EditorInfo.TYPE_CLASS_PHONE;
2080             return entry;
2081         }
2082
2083         /**
2084          * Create a new empty email entry
2085          */
2086         public static final EditEntry newEmailEntry(EditContactActivity activity,
2087                 Uri uri, int type) {
2088             return newEmailEntry(activity, null, type, null, uri, 0);
2089         }
2090
2091         /**
2092          * Create a new email entry with the given data.
2093          */
2094         public static final EditEntry newEmailEntry(EditContactActivity activity,
2095                 String label, int type, String data, Uri uri,
2096                 long id) {
2097             EditEntry entry = new EditEntry(activity, label, type, data, uri, id);
2098             entry.hint = activity.getString(R.string.ghostData_email);
2099             entry.column = ContactMethods.DATA;
2100             entry.contentDirectory = People.ContactMethods.CONTENT_DIRECTORY;
2101             entry.kind = Contacts.KIND_EMAIL;
2102             entry.contentType = EditorInfo.TYPE_CLASS_TEXT
2103                     | EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
2104             return entry;
2105         }
2106
2107         /**
2108          * Create a new empty postal address entry
2109          */
2110         public static final EditEntry newPostalEntry(EditContactActivity activity,
2111                 Uri uri, int type) {
2112             return newPostalEntry(activity, null, type, null, uri, 0);
2113         }
2114
2115         /**
2116          * Create a new postal address entry with the given data.
2117          *
2118          * @param label label for the item, from the db not the display label
2119          * @param type the type of postal address
2120          * @param data the starting data for the entry, may be null
2121          * @param uri the uri for the entry if it already exists, may be null
2122          * @param id the id for the entry if it already exists, 0 it it doesn't
2123          * @return the new EditEntry
2124          */
2125         public static final EditEntry newPostalEntry(EditContactActivity activity,
2126                 String label, int type, String data, Uri uri, long id) {
2127             EditEntry entry = new EditEntry(activity, label, type, data, uri, id);
2128             entry.hint = activity.getString(R.string.ghostData_postal);
2129             entry.column = ContactMethods.DATA;
2130             entry.contentDirectory = People.ContactMethods.CONTENT_DIRECTORY;
2131             entry.kind = Contacts.KIND_POSTAL;
2132             entry.contentType = EditorInfo.TYPE_CLASS_TEXT
2133                     | EditorInfo.TYPE_TEXT_VARIATION_POSTAL_ADDRESS
2134                     | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS
2135                     | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
2136             entry.maxLines = 4;
2137             entry.lines = 2;
2138             return entry;
2139         }
2140
2141         /**
2142          * Create a new IM address entry
2143          */
2144         public static final EditEntry newImEntry(EditContactActivity activity,
2145                 Uri uri, int type) {
2146             return newImEntry(activity, null, type, null, uri, 0);
2147         }
2148
2149         /**
2150          * Create a new IM address entry with the given data.
2151          *
2152          * @param label label for the item, from the db not the display label
2153          * @param protocol the type used
2154          * @param data the starting data for the entry, may be null
2155          * @param uri the uri for the entry if it already exists, may be null
2156          * @param id the id for the entry if it already exists, 0 it it doesn't
2157          * @return the new EditEntry
2158          */
2159         public static final EditEntry newImEntry(EditContactActivity activity,
2160                 String label, int protocol, String data, Uri uri, long id) {
2161             EditEntry entry = new EditEntry(activity, label, protocol, data, uri, id);
2162             entry.hint = activity.getString(R.string.ghostData_im);
2163             entry.column = ContactMethods.DATA;
2164             entry.contentDirectory = People.ContactMethods.CONTENT_DIRECTORY;
2165             entry.kind = Contacts.KIND_IM;
2166             entry.contentType = EditorInfo.TYPE_CLASS_TEXT
2167                     | EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
2168             return entry;
2169         }
2170     }
2171
2172     public void afterTextChanged(Editable s) {
2173         // Someone edited a text field, so assume this contact is changed
2174         mContactChanged = true;
2175     }
2176
2177     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
2178         // Do nothing; editing handled by afterTextChanged()
2179     }
2180
2181     public void onTextChanged(CharSequence s, int start, int before, int count) {
2182         // Do nothing; editing handled by afterTextChanged()
2183     }
2184     
2185     public void onFocusChange(View v, boolean hasFocus) {
2186         // Because we're emulating a ListView, we need to setSelected() for
2187         // views as they are focused.
2188         v.setSelected(hasFocus);
2189     }
2190 }