OSDN Git Service

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