OSDN Git Service

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