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