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